Access private properties and methods from event handler - javascript

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);

Related

JavaScript Literal Object In seperate Files

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.

Best practice for obtaining 'this' context

Being a long time C++/C# developer, I find myself moving a lot of of my JS code into "classes" to group functions and data together. As those classes handle event though, I'm finding myself having to write "stub" handlers that serve only to route the calls into a class method to provide the proper this context. So I'm doing things like this:
var Manager = {
foo: 'x',
bar: 1,
onClickStub: function(evt) {
// 'this' refers to HTMLElement event source
Manager.onClick(evt);
},
onClick: function(evt) {
// 'this' now refers to Manager.
// real work goes here.
}
}
Is this the normal way of doing things or is there a better way to structure my event handlers while keeping my class organization?
As Joseph Silber said in the comments above, I think bind would be perfect in this case. If you need to support older browsers, you can always add a shim to Function.prototype.bind (see an example implementation in the MDN docs). Then your code could just be:
var Manager = {
var foo: 'x',
var bar: 1,
// no more stub!
onClick: function(evt) {
// 'this' will refer to Manager.
// real work goes here.
}
}
// And when you bind the event handler:
var el = document.getElementById('something');
el.addEventListener('click', Manager.onClick.bind(Manager));
The best way that I know to do this is to assign this to another variable at the top of your class and then refer to that one throughout the class. But of course this only works if you are not using an anonymous object as your class.
For instance:
var Manager = function(){
var self = this,
var foo = 'x',
var bar = 1;
var onClick = function(evt) {
console.log(self); // refers to the manager
console.log(this); // refers to the element the onclick is assigned to
// If you want this to equal the manager then just do: this = self;
}
}() // edit: to make this an immediate function
In response to a comment below you could attach the onclick like this
element.onclick = Manager.onClick;
Then in this case the this variable in the onclick function is indeed the html element, and the self variable is the Manager function.

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.

jQuery access to 'this' object with events and triggers

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'));
//...

How to refer to object in JavaScript event handler?

Note: This question uses jQuery but the question has nothing to do with jQuery!
Okay so I have this object:
var box = new BigBox();
This object has a method named Serialize():
box.AddToPage();
Here is the method AddToPage():
function AddToPage()
{
$('#some_item').html("<div id='box' onclick='this.OnClick()'></div>");
}
The problem above is the this.OnClick() (which obviously does not work). I need the onclick handler to invoke a member of the BigBox class. How can I do this?
How can an object refer to itself in an event handler?
You should attach the handler using jQuery:
function AddToPage()
{
var self = this;
$('#some_item').empty().append(
$("<div id='box'></div>")
.click(function() { self.OnClick(someParameter); })
);
}
In order to force the event handler to be called on the context of your object (and to pass parameters), you need to add an anonymous function that calls the handler correctly. Otherwise, the this keyword in the handler will refer to the DOM element.
Don't add event handlers with inline code.
function AddToPage()
{
$('#some_item').html("<div id='box'></div>");
$('#box').click(this.OnClick);
}
EDIT:
Another way (avoids the extra select):
function AddToPage()
{
var div = $('<div id="box"></div>'); // probably don't need ID anymore..
div.click(this.OnClick);
$('#some_item').append(div);
}
EDIT (in response to "how to pass parameters");
I'm not sure what params you want to pass, but..
function AddToPage()
{
var self = this, div = $('<div></div>');
div.click(function (eventObj) {
self.OnClick(eventObj, your, params, here);
});
$('#some_item').append(div);
}
In jQuery 1.4 you could use a proxy.
BigBox.prototype.AddToPage= function () {
var div= $('<div>', {id: box});
div.click(jQuery.proxy(this, 'OnClick');
div.appendTo('#some_item');
}
You can also use a manual closure:
var that= this;
div.click(function(event) { that.OnClick(event); });
Or, most simply of all, but requiring some help to implement in browsers that don't yet support it (it's an ECMAScript Fifth Edition feature):
div.click(this.OnClick.bind(this));
If you are using jQuery, then you can separate your code from your markup (the old seperation of concerns thing) like this
$(document).ready(function() {
var box = new BigBox();
$('#box').click(function() {
box.serialize();
});
});
You only need to add the click handler once for all divs with id of box. And because the click is an anonymous function, it gets the scope of the function it is placed in and therefore access to the box instance.

Categories