简体   繁体   中英

How to dispatch Javascript custom events from a custom object

I have 2 scripts (Extremely simplified examples below) - The first controls the display of a specified nav menu on a page and it us used in several places for different kinds of menus.

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'); 

The other is used for 1 specific purpose on a specific page containing a menu that is controlled by the 1st 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}`;
         }
       });
   });
 })();

As you can see, the 2nd script has a click handler that performs an action based on the state of each menu item ("selected" or not). Since that state is set by the 1st script, this only works if 1st script's click handler runs BEFORE the 2nd.

I created a version where I force the 2nd script's click handler to run first(via setTimeout ). Clicking on the items, you can see that the resulting text does not match. https://codepen.io/daveh0/pen/GROXmrq

Because the 1st script is used in many other places, it needs to run independently of any other script, otherwise I would have just combined the 2.

I think a custom event is the answer here. i implemented one here: https://codepen.io/daveh0/pen/xxPaqzJ from an example I found, but I have no idea if this is the correct way to do it. It works, but that doesn't mean a ton...

I'd think the event should be somehow tied to the NavMenu object, but I couldn't get it to dispatch from anything other than window or document or another DOM element. Can anyone point me in the right direction for a way that I can do something like: myMenu.addEventListener('done', e => //do some stuff) rather than having it attached to window ? Or if I'm way off to even begin with, feel free to let me know as much too.

I know this is kind of built into jQuery, but this needs to stay Vanilla JavaScript - thanks!

I ended up using an event emitter class instead that I found (and learned a lot from) in this article . As @Bergi stated in a comment on the OP, events cannot be attached to a plain object. Since Script 1 can be used anywhere in all different situations, this seemed most standard and logical to me.

It's super simple...

  1. Create an instance of the emitter in Script 1:
    This.emitter = new MyEventEmitter();

  2. Emit events wherever necessary with whatever data is appropriate (in Script 1): This.emitter.emit("done", item);

  3. "Listen" for the event and handle appropriately in Script 2:
    NavMenu.emitter.on("done", menuItem => {...});


Here's the whole thing which you can see working in this 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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM