Javascript custom events object for another object? - javascript

I am making a custom events object so that I can inject custom events into another object, so far I have (simplified),
function Game() {
new Events(["pause", "resume"], this);
};
function Events(events, obj) {
// Event object for object
obj.events = {};
// For each event
events.forEach(function(event) {
// Attach event array to store callbacks
this.events[event] = [];
}, obj);
// Fire event
obj.fire = function(event) {
////////////
};
// Add event
obj.on = function(event, callback) {
////////////
};
};
My question is, is this the right way to do this? Is it considered ok to call Event from Game and add to Game from Events? It for some reason seems wrong to me, and I do not know why?
Is there any way that I should be structuring this code that I am not aware of?
(I do not want to add Events to Game's prototype for the sole reason that Game has events and is not an extension of Events)
Thank you for taking the time to read my question.

My question is, is this the right way to do this? Is it considered ok to call Event from Game and add to Game from Events?
Yes, it's perfectly fine. This is called the decorator pattern. I don't see a reason for your feeling that it was wrong.
Is there any way that I should be structuring this code that I am not aware of?
Don't use new. Events is not a constructor. A better and more descriptive signature might be
function makeEventEmitter(obj, events) {

Related

Delegating a function call in Javascript

Is there a way in Javascript to have a delegate like the ones in c# ?
Example in c#
Object.onFunctionCall = delegate (vars v) {
Console.WriteLine("I can do something in this private delegate function");
};
I would like with my Javascript to have my main object do something over a long time, and shot a delegate once in a while to give a little update. All that without having to change the code itself of my class to adjust for the webpage.
function mainObject() {
this.onUpdate = function() { //Potentially the delegate function here
}
}
var a = new mainObject();
a.onUpdate = Delegate {
$(".myText").text("Just got a delegate update");
}
I dunno if it's clear enough.. havent found ressources on this so I suppose there is just no way to do so ?
NOTE: I am not looking into jquery Click delegates event here, but into delegating a function call like how it works in c#
Let me know
Although the original question was ansered by solving the root problem (observer - pattern) there is a way to implement delegates in JavaScript.
The C# delegate pattern is available in native JavaScript using context binding. Context binding in JavaScript is done with the .call method. The function will be called in the context given by the first argument.
Example:
function calledFunc() {
console.log(this.someProp);
}
var myObject = {
someProp : 42,
doSomething : function() {
calledFunc.call(this);
}
}
myObject.doSomething();
// will write 42 to console;
What you are looking for is an "Observer Pattern", as described eg. here.
But as you are interested in jQuery, you don't need to go the trouble of writing an observer pattern for yourself. jQuery already implements an observer in the guise of its .on() method, which can be invoked on a jQuery collection to cause callback function(s) to fire every time a native or custom event is dispatched.
Here's an example :
$(function() {
//attach a custom event handler to the document
$(document).on('valueChange', function (evt) {
$(this).find("#s0").text(evt.type);
$(this).find("#s1").text(evt.value);
$(this).find("#s2").text(evt.change);
$(this).find("#s3").text(evt.timestamp).toLocaleString();
});
//customEvent(): a utility function that returns a jQuery Event, with custom type and data properties
//This is necessary for the dispatch an event with data
function customEvent(type, data) {
return $.extend($.Event(type||''), data||{});
};
//randomUpdate(): fetches data and broadcasts it in the form of a 'changeValue' custom event
//(for demo purposes, the data is randomly generated)
function randomUpdate() {
var event = customEvent('valueChange', {
value: (10 + Math.random() * 20).toFixed(2),
change: (-3 + Math.random() * 6).toFixed(2),
timestamp: new Date()
});
$(document).trigger(event);//broadcast the event to the document
}
});
Here's a demo, complete with "start" and "stop" buttons for a regular "interval" dispatch of the custom event.
Notes
Under some circumstances, it might be more appropriate to broadcast the event to the four data spans individually.
On the web, you will find mention of a more convenient jQuery.event.trigger({...}) syntax. Unfortunately this was an undocumented feature of jQuery, which disappeared at v1.9 or thereabouts.

Utilizing Firefox's default/built-in Event Listeners

I have a context menuitem which is activated if an image is right-clicked, the exact same way that 'context-copyimage' is activated.
Is it possible to tie/pair that menuitem to the 'context-copyimage' therefore eliminating the need to add extra (duplicate) event-listeners and show/hide handlers??!!
(Adding an observer to 'context-copyimage' defeats the purpose)
If not, is it possible to use the event-listener that 'context-copyimage' uses?
Update:
I am trying to reduce listeners. At the moment, script has a popupshowing listeners. On popupshowing, it checks for gContextMenu.onImag and if true, it shows the menuitem. Firefox's context-copyimage does the exact same thing. I was wondering if it was possible to tie these 2 in order to remove/reduce the in-script event listeners.
I was also chatting with Dagger and he said that:
... the state of built-in items isn't set from an event handler, it's
set from the constructor for nsContextMenu, and there are no
mechanisms to hook into it
So it seems, that is not possible
No, there is no sane way of avoiding the event listener that would perform better than another event listener and is compatible with unloading the add-on in session.
Hooking nsContextMenu
As you have been already told, the state is initialized via gContextMenu = new nsContextMenu(...). So you'd need to hook the stuff, which is actually quite easy.
var newProto = Object.create(nsContextMenu.prototype);
newProto.initMenuOriginal = nsContextMenu.prototype.initMenu;
newProto.initMenu = function() {
let rv = this.initMenuOriginal.apply(this, arguments);
console.log("ctx", this.onImage, this); // Or whatever code you'd like to run.
return rv;
};
nsContextMenu.prototype = newProto;
Now, the first question is: Does it actually perform better? After all this just introduced another link in the prototype-chain. Of course, one could avoid Object.create and just override nsContextMenu.prototype.initMenu directly.
But the real question is: How would one remove the hook again? Answer: you really cannot, as other add-ons might have hooked the same thing after you and unhooking would also unhook the other add-ons. But you need to get rid of the reference, or else the add-on will leak memory when disabled/uninstalled. Well, you could fight with Components.utils.makeObjectPropsNormal, but that doesn't really help with closed-over variables. So lets avoid closures... Hmm... You'd need some kind of messaging, e.g. event listeners or observers... and we're back to square one.
Also I wouldn't call this sane compared to
document.getElementById("contentAreaContextMenu").addEventListener(...)
I'd call it "overkill for no measurable benefit".
Overriding onpopupshowing=
One could override the <menupopup onpopupshowing=. Yeah, that might fly... Except that other add-ons might have the same idea, so welcome to compatibility hell. Also this again involves pushing stuff into the window, which causes cross-compartment wrappers, which makes things error-prone again.
Is this a solution? Maybe, but not a sane one.
What else?
Not much, really.
Yes this is absolutely possible.
Morat from mozillazine gave a great solution here: http://forums.mozillazine.org/viewtopic.php?p=13307339&sid=0700480c573017c00f6e99b74854b0b2#p13307339
function handleClick(event) {
window.removeEventListener("click", handleClick, true);
event.preventDefault();
event.stopPropagation();
var node = document.popupNode;
document.popupNode = event.originalTarget;
var menuPopup = document.getElementById("contentAreaContextMenu");
var shiftKey = false;
gContextMenu = new nsContextMenu(menuPopup, shiftKey);
if (gContextMenu.onImage) {
var imgurl = gContextMenu.mediaURL || gContextMenu.imageURL;
}
else if (gContextMenu.hasBGImage && !gContextMenu.isTextSelected) {
var imgurl = gContextMenu.bgImageURL;
}
console.log('imgurl = ', imgurl)
document.popupNode = node;
gContextMenu = null;
}
window.addEventListener("click", handleClick, true);
this gives you access to gContextMenu which has all kinds of properties like if you are over a link, or if you right click on an image, and if you did than gContextMenu.imageURL holds its value. cool stuff
This code here console logs imgurl, if you are not over an image it will log undefined

Can't listen to global event in jQuery

Another question on stackoverflow pointed out that it should be possible to trigger an event on all listning objects using:
$.event.trigger('customEvent');
However this does not seem to work for me in an example like:
$('body').bind('customEvent', function(){ alert('Working!'); });
Am I doing something completely wrong, or has this great functionality been disabled?
It looks like that functionality has been removed. Browsing through the tags I managed to find this TODO in v1.8b1:
// TODO: Stop taunting the data cache; remove global events and always attach to document
And it was removed as of v1.9.0.
There is nothing stopping you from implementing it based on the old source code here (v1.6.2), but it looks like it was doing naughty things talking to jQuery.cache so it's probably best to live without it or come up with another solution.
$('*').trigger('customEvent');
Perhaps? (jsFiddle)
Or a more efficient approach of keeping track of each subscription and calling .trigger() on that.
jsFiddle
var customSubs;
$.fn.subscribeCustom = function (fn) {
this.on('customEvent', fn);
if (!customSubs)
customSubs = this;
else
customSubs = customSubs.add(this);
};
$('span').subscribeCustom(function () {
alert('span!');
});
$('div').subscribeCustom(function () {
alert('div!');
});
customSubs.trigger('customEvent');

Only register a function with an event once?

I have a jQuery plugin that needs to register a click event handler:
$.fn.myPlugin = function (options) {
var settings = {
// snipped
};
$.extend(settings, options || {});
$("body").click(function () {
// Do Something
});
// Rest of the plugin
});
The problem is that multiple invocations register the function more than once. Since the function needs to stay attached, I can't use .one().
Is there a way if a function is already attached? Can I give it a name or so? Or do I have to set some boolean flag using closure magic?
Namespace your events.
$('body').unbind('click.myPlugin').bind('click.myPlugin', function() {
..code...
});
More on Namespaced Events.
A very easy method with good performance would be to set a data element on the body element:
if (!$.data(document.body, 'myPluginRegistered') {
$.data(document.body, 'myPluginRegistered', true);
// do your plugin code here
}
Easiest might be the boolean plus closure magic you suggested. Alternatively, you could get the list of all functions bound to "click" object, and see if the function you're attaching is there already.
This question shows how to get the list.
List all javascript events wired up on a page using jquery
Though, the namespace suggestion that came in after I first responded is probably simpler.

Is it possible to disconnect all event handlers in Dojo?

Some code I am working with replaces some HTML elements that have Dojo event listeners with new HTML coming from an AJAX call (using .innerHTML=). I have read that event listeners should be disconnected using the dojo.disconnect(handle) method before they are replaced to prevent memory leaks.
Is it possible to derive all handles connected to a particular element, so that I can pass each one to .disconnect(handle), or is it up to me to maintain this list in my code?
Actually if you are using widgets they normally should disconnect stuff in tehir destroy() method. If you are handling the nodes yourself, I see two ways you can go.
1) Manage all connects manually, means storing them somewhere.
2) Probably the safer one: store all the connect handlers in the node they connect to, like so:
node._connectHandlers = [];
node._connectHandlers.push(dojo.connect(node, "onclick", ...));
And later you can simply disconnect them all using
dojo.query("*", nodeContainingConnects).forEach(function(node){
if (typeof node._connectHandlers!="undefined"){
dojo.forEach(node._connectHandlers, "dojo.disconnect(item)");
}
});
Actually, this may work well, but there might be a more efficient way to get all connects by nodes. I just didnt find it. hth
Following the answer of Wolfram Kriesing this can be "improved":
dojo._connect_tmp = dojo.connect;
dojo.connect = function (obj, event, context, method, dontFix) {
if(obj._connectHandlers == undefined){ obj._connectHandlers = [];}
var handler = dojo._connect_tmp (obj, event, context, method, dontFix);
obj._connectHandlers.push(handler);
return handler;
};
dojo.iwanttobefree = function (obj) {
if(obj._connectHandlers == undefined) {
} else {
dojo.forEach(obj._connectHandlers, "dojo.disconnect(item)");
}
};
Then you can do this:
dojo.connect(myObj, 'onfocus', function(){alert('weee')});
dojo.iwanttobefree(myObj);
Replacing dojo code can be very very very ugly for multiple reasons, so maybe you want to create your own namespace.

Categories