Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

142
Views
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!

about 4 years ago · Juan Pablo Isaza
1 answers
Answer question

0

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);
about 4 years ago · Juan Pablo Isaza Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!