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'));
//...
Related
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.
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 looking into deferred and custom events. I'm not sure what method would suit my application.
I have a class that calls another class. Inside this class a user can drag files on to the window.
Once the file has been dragged on, I wish to send details about the files to my main class.
I've thought about using deferred, but the user needs to drag files over and over again, and as of my understanding this can only be used once.
So in my main class I:
this.dropBox = new DropBox();
Then in the DropBox class I have:
$(window).on('drop', this.drop);
But what should I put in my 'drop' method. Every time something is dropped I wish to 'alert' my main class about it and act upon it. How can I 'listen' for the event. Should I use deferred, custom event or something else?
There are typically two options for this:
Delegate
A delegate should implement a certain "interface", a set of functions that handle certain events.
function DropBox(delegate)
{
this.delegate = delegate;
$(window).on('drop', $.proxy(this, 'drop'));
}
DropBox.prototype.drop = function(e) {
// do stuff with event
this.delegate.drop(e);
}
// inside main instance
this.dropBox = new DropBox(this);
// delegate interface
this.drop = function(e) {
// handle file drop
};
Callback
If the delegate only needs one function, you can use a callback as well:
function DropBox(dropEventHandler)
{
this.dropEventHandler = dropEventHandler;
$(window).on('drop', this.drop);
}
DropBox.prototype.drop = function(e) {
this.dropEventHandler(e);
};
// inside main instance
var self = this;
this.dropBox = new DropBox(function(e) {
// handle file drop
// use 'self' to reference this instance
});
Why not just give a callback over to the DropBox?
Well, like this code in the main class:
this.dropBox = new DropBox(function(fileInfo) {
// this code can be executed by the DropBox Object multiple times!
});
And the DropBox:
window.DropBox = function(callback) {
this.userHasDroppedFiles = function(fileinfo) {
// do stuff
callback(fileinfo); // give the fileinfo back with the callback!
}
}
Also, there are no classes in JavaScript! You have only Objects, and you can use constructor-functions combined with prototypes to generate a class like behaviour, but you will never actually have classes like in Java, C# or similar languages. Keep this always in mind.
Some JS frameworks build their own class layer on top of the main JS possibilities, then you may have Framework classes, but also never native JavaScript classes, because native JavaScript classes dont exist!
I have an occurence where I want to have a main js-file with one resize function and specific files that can add workload to the main file without changing the mainfile and manually calling functions.
Lets say I have an object literal
var App = {
resize: function(){
// Code should be executed here
},
addResize: function(){
// ?
}
}
and I want a function to add code to the resize function which dynamically adds workload to the resize function (which gets called on window resize):
App.addResize(function(){ ... });
The first thing that came to my mind is to store the anonymous functions from addResize to an array and iterating over it in the resize function, but that doesn't feel like doing a best-practice:
var App = {
resizeFunctions = [];
resize: function(){
// iterate over resizeFunctions and call each one
// here I define throttling/debouncing ONCE
},
addResize: function(fn){
this.resizeFunctions.push(fn);
}
}
window.onresize = App.resize();
App.addResize(fn1);
App.addResize(fn2);
Is there a better way?
as you are referring to one function, ie. a resize function, I assume that you are looking for function overloading:
Function overloading in Javascript - Best practices
http://ejohn.org/blog/javascript-method-overloading/
If you want to extend the functionality of a set of methods that are all related to a single parent-object into different child objects, I would look into prototypal inheritance.
It allows you to define re-define the parent methods for each of the child-objects.
Do you want to overwrite the existing function?
Then you can just do this:
App.addResize = function(){}
App.addResize(function(){ ... });
would pass the function to addResize as an attribute but not add it to it. You could do
App.addResize.newFunction = function(){ ... };
Where newFunction is the name of the function
You can treat your object literal as array.
App["resize"] = function(){
//Code goes here
}
__
Or
App.resize = function(){
//Code here
}
Both are equally good. These will update the definition of resize
If you want to add some new method, then too the same syntax will work.
App["myNewMethod"] = new function(){
//Code here
}
Edit after OP's comment
var oldFun = App["resize"]; // or may be store this in App itself
App["resize"] = function(){
//Pre-processing
// Now call original resize method
oldFun(); //If resize method used method argument then use oldFun.apply( this, arguments );
//Post processing
}
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.