Create JavaScript custom event
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 functionChangeState
is called.
I found some solutions but I didn't manage to make it work... What is the simplest way to do it?
Solution 1:
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);
});
Solution 2:
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 notcreateEvent
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
Solution 3:
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
});
Solution 4:
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();