Tengo 2 scripts (ejemplos extremadamente simplificados a continuación): el primero controla la visualización de un menú de navegación específico en una página y se usa en varios lugares para diferentes tipos de menús.
const NavMenu = function(selector) { this.menuItems = document.querySelectorAll(`${selector} li > a`); let This = this; This.menuItems.forEach(item => { item.addEventListener('click', e => { item.classList.toggle('selected'); }); }); } const myNavMenu = new NavMenu('.menu-wrap');El otro se usa para 1 propósito específico en una página específica que contiene un menú controlado por el primer script.
(function () { const menuItems = document.querySelectorAll('.menu-wrap li > a'); menuItems.forEach(item => { item.addEventListener('click', e => { const selectedItems = document.querySelectorAll('.menu-wrap li > a.selected'); let str = ''; if(selectedItems.length) { selectedItems.forEach((item, i) => { str += `${item.textContent} `; }); document.querySelector('#results').innerText = `Selected: ${str}`; } }); }); })();Como puede ver, la segunda secuencia de comandos tiene un controlador de clics que realiza una acción según el estado de cada elemento del menú ("seleccionado" o no). Dado que ese estado lo establece el primer script, esto solo funciona si el controlador de clics del primer script se ejecuta ANTES del segundo.
Creé una versión en la que fuerzo al controlador de clics del segundo script para que se ejecute primero (a través setTimeout ). Al hacer clic en los elementos, puede ver que el texto resultante no coincide. https://codepen.io/daveh0/pen/GROXmrq
Debido a que el primer script se usa en muchos otros lugares, debe ejecutarse independientemente de cualquier otro script; de lo contrario, simplemente habría combinado el 2.
Creo que un evento personalizado es la respuesta aquí. Implementé uno aquí: https://codepen.io/daveh0/pen/xxPaqzJ de un ejemplo que encontré, pero no tengo idea si esta es la forma correcta de hacerlo. Funciona, pero eso no significa mucho...
Creo que el evento debería estar vinculado de alguna manera al objeto NavMenu , pero no pude hacer que se enviara desde otra cosa que no sea window , un document u otro elemento DOM. ¿Alguien puede indicarme la dirección correcta para saber cómo puedo hacer algo como: myMenu.addEventListener('done', e => //do some stuff) en lugar de tenerlo adjunto a la window ? O si estoy muy lejos para empezar, siéntete libre de hacérmelo saber también.
Sé que esto está integrado en jQuery, pero debe permanecer como Vanilla JavaScript, ¡gracias!
Terminé usando una clase de emisor de eventos que encontré (y de la que aprendí mucho) en este artículo . Como dijo @Bergi en un comentario sobre el OP, los eventos no se pueden adjuntar a un objeto simple. Dado que el Script 1 se puede usar en cualquier lugar y en todas las situaciones diferentes, me pareció lo más estándar y lógico.
Es súper sencillo...
Cree una instancia del emisor en el Script 1:
This.emitter = new MyEventEmitter();
Emita eventos donde sea necesario con cualquier dato que sea apropiado (en el Script 1): This.emitter.emit("done", item);
"Escuche" el evento y manéjelo apropiadamente en el Script 2:
NavMenu.emitter.on("done", menuItem => {...});
Aquí está todo lo que puede ver funcionando en este Codepen .
class MyEventEmitter { constructor() { this._events = {}; } on(name, listener) { if (!this._events[name]) { this._events[name] = []; } this._events[name].push(listener); } removeListener(name, listenerToRemove) { if (!this._events[name]) { throw new Error( `Can't remove a listener. Event "${name}" doesn't exits.` ); } const filterListeners = (listener) => listener !== listenerToRemove; this._events[name] = this._events[name].filter(filterListeners); } emit(name, data) { if (!this._events[name]) { throw new Error(`Can't emit an event. Event "${name}" doesn't exits.`); } const fireCallbacks = (callback) => { callback(data); }; this._events[name].forEach(fireCallbacks); } } const NavMenu = function (selector) { this.menuItems = document.querySelectorAll(`${selector} li > a`); let This = this; This.emitter = new MyEventEmitter(); This.menuItems.forEach((item) => { item.addEventListener("click", (e) => { item.classList.toggle("selected"); This.emitter.emit("done", item); }); }); }; const myNavMenu = new NavMenu(".menu-wrap"); (function (NavMenu) { const menuItems = document.querySelectorAll(".menu-wrap li > a"); menuItems.forEach((item) => { item.addEventListener("click", (e) => { NavMenu.emitter.on("done", (menuItem) => { const selectedItems = document.querySelectorAll( ".menu-wrap li > a.selected" ); let str = "Selected: "; if (selectedItems.length) { selectedItems.forEach((item, i) => { str += `${item.textContent} `; }); } else { str += `none`; } document.querySelector("#results").innerText = `${str}`; }); }); }); })(myNavMenu);