72

I would like to create a custom event in JavaScript.

I have a WPF application with a WebBrowser inside, and a HTML page with JavaScript.

I work with a printer. When the state of the printer changes, it triggers an event in .NET.

Then, I call a JavaScript method OnPrinterStateChanged(state) with the InvokeScript function of the WebBrowser control.

The problem is that I have to implement the method OnPrinterStateChanged(state) in my webpage. I can't change the name of the method or subscribe/unsubscribe to the event...

I would like to move the JavaScript method OnPrinterStateChanged(state) in a separate JavaScript file.

What I want :

  • Subscribe/Unsubscribe to the event in my HTML page and decide what I want to do when the event is triggered (ex. : "function ChangeState")
  • When the .NET event is triggered, it calls the OnPrinterStateChanged(state) of my separate .js file, then the JavaScript event is triggered and the function ChangeState is called.

I found some solutions but I didn't manage to make it work... What is the simplest way to do it?

danwellman
  • 9,068
  • 8
  • 60
  • 88
Gab
  • 1,861
  • 5
  • 26
  • 39
  • What solutions did you find? Please show your attempt so far – musefan Apr 28 '14 at 15:12
  • I found http://www.nczonline.net/blog/2010/03/09/custom-events-in-javascript/ but like I said, I'm a beginner and I don't know exactly if it could do what I want and where use this code... – Gab Apr 28 '14 at 15:16
  • https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events – Rafael Emshoff Oct 16 '15 at 13:26

4 Answers4

110

Perhaps something like this?

function OnPrinterStateChanged(state) {
    var evt = new CustomEvent('printerstatechanged', { detail: state });

    window.dispatchEvent(evt);
}


//Listen to your custom event
window.addEventListener('printerstatechanged', function (e) {
    console.log('printer state changed', e.detail);
});

An alternative solution would be to use function composition, but then it would be hard to remove specific listeners.

function OnPrinterStateChanged(state) {}

function compose(fn1, fn2) {
    return function () {
        fn1.apply(this, arguments);
        fn2.apply(this, arguments);
    };
}

//Add a new listener
OnPrinterStateChanged = compose(OnPrinterStateChanged, function (state) {
    console.log('listener 1');
});

//Add another one
OnPrinterStateChanged = compose(OnPrinterStateChanged, function (state) {
     console.log('listener 2');
});

EDIT:

Here's how you can do it with jQuery.

function OnPrinterStateChanged(state) {
    var evt = $.Event('printerstatechanged');
    evt.state = state;

    $(window).trigger(evt);
}


//Listen to your custom event
$(window).on('printerstatechanged', function (e) {
    console.log('printer state changed', e.state);
});
plalx
  • 42,889
  • 6
  • 74
  • 90
  • Thank you for the answer ! But I got this error : "Object doesn't support property or method 'addEventListener'" Any idea why ? I tride to replace by "AttachEvent" ( http://stackoverflow.com/questions/12216301/object-doesnt-support-property-or-method-webbrowser-control ) but I have another script error without details. – Gab Apr 28 '14 at 15:23
  • @Gab In which browser? – plalx Apr 28 '14 at 15:25
  • It's a WebBrowser from WPF so it's Internet Explorer – Gab Apr 28 '14 at 15:27
  • @Gab, well this embedded WebBrowser seems outdated then and proabably doesn't support custom events. I suggest that you use a micro library for custom event supports and use then same strategy. Here's a few http://microjs.com/#events – plalx Apr 28 '14 at 15:41
  • Ok thank you, tell with if you know one of these libraries or how to do it with Jquery for example... Otherwise, thank you anyway – Gab Apr 28 '14 at 15:51
14

I like using this EventDispatcher constructor method, which, using a dummy element, isolates the events so they will not be fired on any existing DOM element, nor the window or document.

I am using CustomEvent and not createEvent because it's the newer approach. Read more here.

I like to wrap each native method with these names:

on, off, trigger

function eventDispatcher(){
  // Create a dummy DOM element, which is a local variable 
  var dummy = document.createTextNode('')

  // Create custom wrappers with nicer names
  return {
    off(...args){ 
      dummy.removeEventListener(...args) 
      return this
    },
    on(...args){ 
      dummy.addEventListener(...args) 
      return this
    },
    trigger(eventName, data){
      if( !eventName ) return
      
      // if the event should be allowed to bubble, 
      // then "dummy" must be in the DOM
      var e = new CustomEvent(eventName, {detail:data})
      dummy.dispatchEvent(e)
      return this
    }
  }
}

////////////////////////////////////
// initialize the event dispatcher by creating an instance in an isolated way
var myEventDispatcher = eventDispatcher();

////////////////////////////////////
// listen to a "foo" event
myEventDispatcher
  .on('foo', e => console.log(e.type, e.detail) )
  .on('bar', e => console.log(e.type, e.detail) )

////////////////////////////////////
// trigger a "foo" event with some data
myEventDispatcher
  .trigger('foo', 123)
  .trigger('bar', 987);

The above eventDispatcher could be integrated into another code, for example, a Constructor: (for example some component)

function eventDispatcher(){
  var dummy = document.createTextNode('')

  return {
    off(...args){ 
      dummy.removeEventListener(...args) 
      return this
    },
    on(...args){ 
      dummy.addEventListener(...args) 
      return this
    },
    trigger(eventName, data){
      if( !eventName ) return
      var e = new CustomEvent(eventName, {detail:data})
      dummy.dispatchEvent(e)
      return this
    }
  }
}

/////////////////////////////
// Some component: 

var MyComponent = function(){
  // merge `EventDispatcher` instance into "this" instance
  Object.assign(this, eventDispatcher())
  
  // set some default value
  this.value = 1
}

MyComponent.prototype = {
  set value(v){
    this.trigger('change', v)
  }
}

var comp = new MyComponent();

// bind a listener to "comp" (which itself is an instance of "MyComponent")
comp.on('change', e => console.log("Type:", e.type,", Value:", e.detail) )

// set some value
comp.value = 3;

See my related answer, which shows a more complex implementation of the above

vsync
  • 118,978
  • 58
  • 307
  • 400
  • 1
    Will not using `on` and `trigger` create an incompatibility with jQuery? – Mark Kramer May 04 '17 at 03:42
  • 1
    No, since these methods are declared on the `EventDispatcher` Object, which isn't a jQuery object.. it will only conflict if you extend any jQuery object with an instance of `EventDispatcher`, which no person would ever do because it makes no sense. – vsync Aug 15 '17 at 11:28
  • Works perfectly, THANK YOU! – Mkey May 14 '18 at 08:09
2

Ok I found a solution.

I had to change the WebBrowser IE Version to Internet Explorer 11: http://weblog.west-wind.com/posts/2011/May/21/Web-Browser-Control-Specifying-the-IE-Version

And then :

function OnPrinterStateChanged(state) {

    var evt = document.createEvent("Event");
    evt.state = state;
    evt.initEvent("printerstatechanged", true, false);
    window.dispatchEvent(evt);

}


window.addEventListener('printerstatechanged', function (e) {
  // Do something
});
Liam
  • 27,717
  • 28
  • 128
  • 190
Gab
  • 1,861
  • 5
  • 26
  • 39
  • 4
    Keep in mind that `document.createEvent` has been replaced by the `CustomEvent` or `Event` constructors and you should use that instead. I am pretty sure IE 11 supports them. – plalx Apr 29 '14 at 11:53
  • 2
    IE doesn't support either. You have to use the "old fashioned way" to acquire IE support. https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events – Derick Mar 11 '16 at 18:33
  • 2
    IE11 does support `CustomEvent` @Derick – vsync Mar 16 '17 at 20:29
  • Mozilla has a [simple polyfill](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill) for browsers that don't support `CustomEvent` (basically just IE) – Kyle Fox Mar 18 '18 at 23:22
2

Requirement: ES6

let MyClass = (function () {
    let __sym = Symbol('MyClass');

    class MyClass {
        constructor() {
            this[__sym] = {};
        }
        on(event, callback) {
            this[__sym][event] = { callback: callback }
        }
        getError() {
            let event = this[__sym].error;

            if (event && event.callback) {
                event.callback('some parameter');
            }
        }
    }

    return MyClass;
}());

let myClass = new MyClass();

myClass.on('error', function (e) {
    console.log('error:', e);
});

console.log('before getting error:');

myClass.getError();