Say I have this structure:
File1.js:
var myObject = {
bindEvents: function() {
console.log('Root events binding');
}
}
keyboard-object.js:
myObject.bindEvents: function() {
console.log('Keyboard events binding');
}
mouse-object.js:
myObject.bindEvents: function() {
// extends original bindEvents and adds more functionality
// right now this behavior overrides the original bindEvents method
console.log('Mouse events binding');
}
How can I trigger myObject.bindEvents() and make sure it is fired in each file?
My purpose is to split one big object into separate files and make one method that fires all the corresponding method(s) in each file, as in bindEvents should trigger bindEvents (or keyboardEvents in my case) in the object
You are essentially after a custom events handler for objects here. I would take a look at the one that Backbone.js supplies.
Be ware that you are not assigning an anonymous function to (earlier declared) myObject properties with the described syntax (as in):
myObject.bindEvents: function() {
console.log('Keyboard events binding');
}
What you are doing here is actually labeling the anonymous function with the word "myObject.bindEvents" which doesn't do you any good.
I think that - aside from this error - you are trying to do something like this:
myObject.bindEvents = function() {
console.log('Keyboard events binding');
}
And only Now your myObject has a property method of bindEvents which than you can invoke by simply declaring:
myObject.bindEvents(); later on your script.
Ok eventually I decided to use two (or more) seperate objects and trigger methods by firing events which are set on the document.
So my current solution is this:
myObject.js
var myObject = {
bindEvents: function() {
// some code here
$(document).trigger('myObject:bindEvents');
}
}
secondObject.js
var secondObject = {
bindEvents: function() {
// separate code and logic here
}
}
$(document).on('myObject:bindEvents',function(){
secondObject.bindEvents();
});
This gives me the flexibility to add more separate objects and bind their methods to events which are fired by the myObject object.
Related
I've used the jQuery Boilerplate template as starting point for a jQuery plug-in. This template provides a set up where this represents the plug-in instance and gives access to properties and methods:
init: function() {
$(this.element).css({borderColor: "red"});
this.drawMarker([100, 200]);
},
drawMarker: function(coordinates) {
if (this.settings.isAbsolute) {
// ...
}
}
Now I need to handle some mouse clicks and it's all getting really confusing because callback functions redefine the this variable to represent the clicked event so, in order to access the plugin stuff, I came up with this ugly workaround:
this.container.on("click", "." + this.settings.markerClass,
{plugin: this}, this.removeMarker);
... and:
removeMarker: function(event){
var plugin = event.data.plugin;
var marker = $(this);
if (plugin.settings.isAbsolute) {
// ...
}
}
Is this actually what I'm supposed to do or I'm overlooking a most straightforward approach?
One possibility is to use the jQuery.proxy() function (added on 1.4) to force a given context inside event handlers:
this.$container.on("click", "." + this.settings.markerClass,
$.proxy(this.removeMarker, this));
Then, the stuff you need can be reached as follows:
Plugin properties/methods: this
Clicked element: event.target (on delegated events, it's the precise element the user clicked on; the one we normally want)
removeMarker: function(event){
var $marker = $(event.target);
if (this.settings.isAbsolute) {
// ...
}
}
This technique is courtesy of Patrick Evans.
If you need to access privately scoped variables (using functions that are therefore by definition not on the plugins prototype) just create an additional variable that aliases the plugin object:
var plugin = this;
this.container.on('click', function() {
// use plugin here
...
});
If the callback function in question is on the prototype, you can access the object within the callback thus:
var plugin = $(element).data('plugin_' + pluginName);
I'm doing this inside one of my Views:
render: function($options) {
...
this.collection.on('reset', _(function() {
this.render($options);
}).bind(this));
....
}
The problem is, whenever reset as well as the re-rendering has been triggered, a new reset binding will be created, resulting 2x, 4x, 8x, etc. times of re-rendering as it goes on.
It's a bit tricky to move the binding into the initialize section (which should solve this issue), however since it's not an option, is there any other solution available, like having Backbone checking if this event has been bound before, or something?
Moving your binding to initialize would be best but assuming that you have good reasons not to, you could just set a flag:
initialize: function() {
var _this = this;
this._finish_initializing = _.once(function($options) {
_this.collection.on('reset', function() {
_this.render($options);
});
});
//...
},
render: function($options) {
this._finish_initializing($options);
//...
}
There are lots of different ways to implement the flag, _.once just nicely hides the flag checking. You could also trigger an event in render have a listener that unbinds itself:
initialize: function() {
var finish_initializing = function($options) {
/* your binding goes here ... */
this.off('render', finish_initializing);
};
this.on('render', finish_initializing, this);
},
render: function($options) {
this.trigger('render', $options);
//...
}
That's the same logic really, just dressed up in different clothes. You could also use an explicit flag and an if in render or assign a function to this._finish in initialize and that function would delete this._finish.
like having Backbone checking if this event has been bound before, or something?
Sure..
!!this.collection._events["render"]
Backbone doesn't expose most of the API required to make it useful. That's alright, use it anyway.
First, define your event handler function as a named function
var self = this;
var onReset = function() {
self.render($options);
}
Then, defensively unbind the function each time render is called
this.collection.off('reset', onReset);
this.collection.on('reset', onReset);
I recently accomplished this using a javascript variable.
Outside of any functions, I declared:
var boundalready =0
Then, inside the function:
if (boundalready == 0){
boundalready = 1;
bind(this);
};
This worked for me pretty well.
I have two classes orchestrated by a main class and I would like to know how to gain access to the correct 'this' object when events are fired among these classes. Here's what I have:
// My main class that orchestrates the two worker classes
function MainClass()
{
this.workerOne = new ChildWorkerOne();
this.workerOne.bindBehaviors.apply(this.workerOne);
this.workerTwo = new ChildWorkerTwo();
this.workerTwo.bindBehaviors.apply(this.workerTwo);
// a custom event I'm creating and will be triggered by
// a separate event that occurs in workerTwo
$(document).bind("customEvent", this.onCustomAction);
}
MainClass.prototype.onCustomAction = function(event, data)
{
// I want to call a method that belongs to 'workerOne'.
this.workerOne.makeItHappen();
// However, the 'this' object refers to the 'Document' and
// not the 'MainClass' object.
// How would I invoke 'makeItHappen' here?
};
ChildWorkerOne.prototype.makeItHappen = function()
{
// Do a bunch of work here
};
ChildWorkerTwo.prototype.bindBehaviors = function()
{
$(div).click(function(e){
$.post(url, params, function(data)
{
// do a bunch of work with this class and then
// trigger event to update data with ChildWorkerOne
$(document).trigger("customEvent", [data]);
}
});
};
I don't want to merge ChildWorkerOne and ChildWorkerTwo because they are two separate entities that don't belong together and MainClass conceptually should orchestrate the ChildWorkerOne and ChildWorkerTwo. However, I do want to invoke the behavior of one in the other.
What's the best way to go about doing this?
You need to persist the this value, you can do it in many ways, jQuery 1.4+ provides you the $.proxy method, e.g.:
//...
$(document).bind("customEvent", $.proxy(this.onCustomAction, this));
// or
$(document).bind("customEvent", $.proxy(this, 'onCustomAction'));
//...
One of the nice things about MooTools, is that it lets you easily assign/fire events to objects, for example:
var playerSingleton = new (new Class({
Implements: [Events],
initialize: function() {},
setVolume: function() {
// do some stuff..
this.fireEvent('volumeChanged')
}
}));
// Somewhere else...
playerSingleton.addEvent('volumeChanged', function() {
// do something when volume changes
});
playerSingleton.setVolume(75);
// bam our event fires.
How would something like this be done with jQuery?
I know there's .bind and .trigger, but it seems like the only way to do this is to bind/fire events to the window object:
$(window).bind('volumeChanged', fn);
Is there anything better than this, more like the MooTools approach?
jQuery's bind and trigger seem to work on normal objects. Haven't seen the source code to see how it works (if it's part of the public API or not), but it does. See this discussion from last year poking around the same idea.
player is a regular object, with methods to set volume, and add listeners for volume change. an example here.
var player = {
setVolume: function() {
$(this).trigger("volumeChanged");
},
addVolumeChangeHandler: function(fn) {
$(this).bind("volumeChanged", fn);
}
};
// add a listener
player.addVolumeChangeHandler(function() {
alert("volume has been changed");
});
// change volume (should fire the attached listener)
player.setVolume(); // alerts "volume has been changed"
I have the code (inside one object)
onclick: this._addX.bind(this)
and then inside another object
onclick: this._addY.bind(this)
Now, _addX() and _addY are nearly identical, except they both end up calling (on the click event) a function with different argument values, say _addX calls foo('x') and _addY calls foo('y'). So I tried:
onclick: this._add.bind(this,'x') and
onclick: this._add.bind(this,'y') in the two objects. And of course I changed _add to accept an argument.
At runtime, when _add is called, it does not see any incoming arguments! I have fumbled around with different syntaxes but nothing works. Any ideas? The original syntax works fine (no arguments) but forces me to duplicate a large function with only one line different, which pains me. Thanks in advance.
_add: function(which) {
var me = this;
var checkFull = function(abk) {
if (abk.isFull) {
alert("full");
} else {
alert(which); // which is always undefined here!
}
};
getAddressBook(checkFull); //checkFull is a fn called by getAddressBook
},
this works and it keeps the scope within an element click event with the scope set to the class and not the element--there is no point in passing scope to the add method, it already has that:
var foo = new Class({
Implements: [Options],
add: function(what) {
alert(what);
},
initialize: function(options) {
this.setOptions(options);
this.options.element.addEvents({
click: function() {
this.add(this.options.what);
}.bind(this)
});
}
});
window.addEvent("domready", function() {
new foo({
element: $("foo"),
what: "nothin'"
});
});
just make an element with id=foo and click it to test (alerts nothin'). if your onclick is a function / event handler within your class as opposed to a normal element click event, then things are going to differ slightly - post a working skeleton of your work on http://mootools.net/shell/
If you read my previous answer, disregard it. The MooTools .bind method supports passing parameters. So something else isn't working as you expect:
onclick: this._add.bind(this, 'y');
Here is a simple setup on JSBin to show how bind truly does pass parameters.
The only purpose of bind is to "tell" the JS what object you mean when you say this. i.e. you pass as a parameter to bind an instance of the object you wish the this key word will refer to inside the function you used the bind on.