Binding/listening to events - javascript

I made a jQuery plugin that calls its functions at timed events. I want to provide those who use the plugin the ability to listen to these functions when they are called and add code to be performed when the functions are triggered.
This may be a simple question but I can’t wrap my head around it. What is the best method to achieve this? I can only find information on binding handlers to events, not events to events. I want something like the code below:
$( "body" ).bind( "pluginInstance.functionName", function() {
console.log( "triggered" );
});

You could invoke the declared function you passed to the plugin. Example:
$.fn.myPlugin = function(callback){
callback();
}
$('elem').myPlugin(function(){
console.log("triggered");
})
If you wanted to be more sophisticated about it you could set the callback function to an object and refer to the object versus the arguments, which is more specific and easier to read:
$.fn.myPlugin = function(options){
options.callback();
}
$('elem').myPlugin(
var options = {
callback: function(){
console.log('triggered')
}
}
)

I would allow the plugin consumer to pass in an options hash to your plugin's constructor with optional event methods / properties that you have predetermined. I would also have a 'defaults' object defined in your plugin that defines those available properties and events that the user can override. You can use the jQuery extend method to copy any defaults that the user did not pass in within the constructor.
As an example, you may have a 'onSlide' event that the user can add a callback for in your plugin. I would set the plugin up as follows:
$.fn.newPlugin = function(options){
var defaults = {
onSlide: null,
onRightClick: null
};
var settings = $.extend(defaults, options);
//Pretend something in your plugin happens and you want to optionally
//invoke the user's passed in onSlide method if they set it
if(settings.onSlide != null){
settings.onSlide();
}
}
When the user is consuming your plugin they can pass the 'onSlide' handler into the constructor:
$('elem').newPlugin({
onSlide: function(){
alert('slide event triggered');
}
});

You can use custom events http://css-tricks.com/custom-events-are-pretty-cool/. See http://jsfiddle.net/3L45H/1 and https://learn.jquery.com/plugins/basic-plugin-creation/
From your plugin, you can call the following to trigger an event
$(this.element).trigger('pluginInstance.functionName');
And listen for it as you do any other DOM events
$('body').bind( "pluginInstance.functionName", function() {
console.log( "triggered" );
});
$('body').myPlugin();

Related

Subscribers not able listen to startup events when library is being instantiated

I want to allow users to subscribe to events in my codes lifecycle so i've included a pubsub mechanism which is made available to the user as soon as the library has been instantiated. It works well for the most part. The code looks like something this:
var library = function() {
//library code here
//Publish some sort of initialisation event
ev.publish('init');
//return an object that includes the ev pubsub functionality
};
then I imagined a user might be able to interact with it like so:
var test = new library();
//the ev object is returned from the instantiation of the library
test.ev.subscribe('init',function() {});
This works for the most part - the subscribe function can hook in to all post initialisation publishes.
The Problem lies with when the library is instantiated and various initialisation events are fired. There's no way for a user can interact with the publish events that are fired during initialisation because by the time the user is given an opportunity to subscribe to anything those startup events have already been fired.
How can I refactor this code to allow the event system to be captured in full?
There are several approach to achieve this kind of thing. Based on your architecture, requirements, preference you can do either of the following:
Approach 1 :
You can accept a callback function as a init function and there you can handle it.
var library = function(initCallback) {
ev.subscribe('init', initCallback);
//library code here
//Publish some sort of initialisation event
ev.publish('init');
//return an object that includes the ev pubsub functionality
};
Then you can call like
var test = new library(function() {});
Approach 2 :
Separate the init function from instantiate. This architecture used most. You can expose selected method only as public.
var library = function() {
//library code here
//Publish some sort of initialisation event
function init() {
ev.publish('init');
}
//return an object that includes the ev pubsub functionality
return {
init : init,
ev : ev,
// .... expose other function/properties
};
};
Then you can use this like:
var test = new library();
//the ev object is returned from the instantiation of the library
test.ev.subscribe('init',function() {});
//Call the actual initialization
test.init();
Approach 3:
As you stated you do not want to follow neither of above approaches, then there may be only one way to achieve what you want, by moving the event handler from your object. As you haven't gave your event handler code, I am giving an example implementation with jquery event handler.
var library = function() {
//library code here
//Publish some sort of initialisation event
var self = this;
$(window).trigger( "init.library", [ self ] );
//return an object that includes the ev pubsub functionality
};
hen you can use this like:
$(window).on( "init.library", function( event, obj ) {
//Do whatever you want with "obj"
});
var test = new library();
If you do not want to use the last approach, then best of luck :). And if you find any other interesting solution, please post it for others. Actually another thing you can do, if your purpose is to just call the init function, after instantiate of the object(not while instantiating) then you can change ev.publish('init'); line as
setTimeout(function() {
ev.publish('init');
},1);
Then your code will also work!!
Happy coding!!
I settled on an approach that used a queue array. If a publisher is fired and there are no corresponding subscribers then the publish is added to a pending array. Then any newly added subscribers check the pending array and if there's a matching publish entry then fire the subscribers callback.
http://jsfiddle.net/uMCFT/3/
var uid, topics, pending = {};
uid = 0;
topics = {};
subscribe = function (topic, fn) {
if (!topics.hasOwnProperty(topic)) {
topics[topic] = {};
}
topics[topic][++uid] = fn;
for(var key in pending) {
if(pending[key] === topic) {
delete pending[key];
for (key in topics[topic]) {
topics[topic][key](topic);
}
}
}
};
publish = function (topic, obj) {
var key;
if(!topics.topic) {
pending[topic] = topic;
}
if (topics.hasOwnProperty(topic)) {
for (key in topics[topic]) {
topics[topic][key](topic, obj);
}
}
};
publish('that');
subscribe('that',function() {console.log('hi')});

jQuery - using methods for custom events

I have a custom jQuery event like this
jQuery(this).trigger({
type: 'my_own_event',
data1: 'foo',
data2: 'bar'
});
To attach an event handler I have to use the .on() function like this.
jQuery(element).on( 'my_own_event', function(e) {
//Do something
});
How do I use it like any other inbuilt event like .click(), .keyup(). Something like this
jQuery(element).my_own_event(function(e){
//Do Something
});
I tried using jQuery.fn to do this as follows
jQuery.fn.my_own_event = function(callback) {
jQuery(this).on('my_own_event',callback);
}
and it does work on Firefox 27.
Is this the right way to do this?
Is it OK to use underscores in the event name?
That's fine. The naming is okay, merely a matter of convention.
You should tweak the shortcut function, though. The jQuery() is not required, as this is already a jQuery object. You should also return the original object for chaining purposes.
You may also want to handle the empty argument version of the call, as the builtin event shortcuts do, to trigger the event.
Here's how the jQuery source itself does it:
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
Where name is the event such as click or keyup. This also handles the optional data argument, which would set event.data in the callback function.
Then you're completely in line with the existing jQuery event shortcut function conventions.
So your example could be changed to:
jQuery.fn.my_own_event = function(data, callback) {
return arguments.length > 0 ?
this.on('my_own_event', null, data, callback) :
this.trigger('my_own_event');
};
Yes the Custom event you have created is fine and the event name is also as per standard event naming convention
Article from official Jquery site:
the .trigger() method can trigger both standard browser event names and custom event names to call attached handlers. Event names should only contain alphanumerics, underscore, and colon chraracters.
REF:
https://api.jquery.com/trigger/
Happy Coding:)

What is the advantage of custom events in backbone?

I understand how custom events work in Backbone and how to trigger them, but I'm having trouble understanding when exactly to use them and what purpose they serve over just calling the function directly.
e.g.
var MyView = Backbone.View.extend({
tagName: 'div',
className: 'myview',
initialize: function() {
this.model.on("mycustomevent", this.doSomething, this);
},
doSomething: function() {
console.log('you triggered a custom event');
}
});
If I am not mistaken, The doSomething method can be called by using this.model.trigger("mycustomevent") within other methods, but can be also called directly with this.doSomething()
Outside the view, it can be called similarly with
var myview = new MyView({model:somemodel});
myview.model.trigger("customevent");
myview.doSomething();
What I am confused about is why not forgo the the custom event and just call the method directly when you need it? Any example uses would be greatly appreciated!
You might want to add multiple handlers in different places in the code, f.ex:
this.model.on("mycustomevent", this.doSomething, this);
// ... and somewhere else you add an anonymous function
this.model.on("mycustomevent", function() {
console.log('do something');
});
Then when you trigger the event, it will execute all handlers. You can also use off to unbind/manage individual or multiple handlers.
If you are asking about a generic explanation of the event pattern (also called observer pattern, publish/subscribe, etc...), you should probably look for a more in-depth article or book.
With backbone, the perfect example is changing a property of a model. Using a function, you would have to do something like this...
$( '#someUI' ).click( function {
// update the model property
myModel.someProperty = 'somethingDifferent';
// update any ui that depends on this properties value
$( '#uiThatDisplaysModelData' ).find( 'someSelector' ).html( 'somethingDifferent' );
// save the model change to the server
$.ajax( {
url: 'somesaveurl',
data: { someProperty: 'somethingDifferent' }
success: callback
} );
} );
And then repeat those steps all over your code for each property change.
With backbone and a little setup the same things can be accomplished with:
myModel.set( 'property', 'somethingDifferent' );
This is because we have attached handlers to the change and change:property events of this model. These are custom events that are created automatically for models by backbone. So whenever any part of your code manipulates the model, the DOM updates and saving can be done automatically. We can also bind input validation or whatever we want to these custom events.
It's basically just applying the observer pattern to your application, where events belong to an object that is observable, and the handlers belong to its observers.
http://en.wikipedia.org/wiki/Observer_pattern

How to Pass Custom Parameters to Event Handler

Just getting started with Dojo. I want to pass a couple of custom parameters to an event handler. In jQuery, you can do it like this:
$('#button').click({
customData: 'foo'
}, handlerFunction);
And customData can be accessed from handlerFunction like this:
function handlerFunction(event) {
console.log(event.data.customData);
}
I'm migrating a bit of jQuery code over to Dojo. How can I pass those parameters to the Dojo event handler?
Well, generaly, closures allow you to pass "hidden" parameters to a function:
function make_event_handler(customData){
return function(evt){
//customData can be used here
//just like any other normal variable
console.log(customData);
}
}
So when connecting an event in dojo:
dojo.connect(node, 'onclick', make_event_handler(17));
Another possibility that I like a lot is using dojo.partial / dojo.hitch to create the closures for you.
function event_handler(customData, evt){
///
}
dojo.connect(node, 'onclick', dojo.partial(event_handler, 17))
Note that all of these these required your event handlers to be created with passing the extra parameter(s) in mind. I don't know if you can do a more direct translation of the JQuery code since that would require extra massaging of the evt variable and I don't think dojo does that.
Also:
this.connect(other, "onClick", function(e) {
/* other is accesible here still */
});
or :
this.connect(other, "onClick", dojo.hitch(this, "handler", other);
and its event handler:
this.handler = function(other, evt){...}

OO JavaScript: Accessing another method of the same object?

I have a JavaScript object called ShippingUI:
function ShippingUI(){
...
}
It has several methods:
ShippingUI.prototype.UpdateItemQTYs = function(ItemJQOBJ, NewQTY)
{
...
}
ShippingUI.prototype.EH_SortableRecieve = function(event, ui)
{
...
}
The "EH_SortableRecieve()" function is a drop event handler. When it runs, it needs to call "UpdateItemQTYs()", a sister function in the same object. I'm trying to use:
ShippingUI.prototype.EH_SortableRecieve = function(event, ui)
{
this.UpdateItemQTYs('ABCD',123);
}
But keep getting the error:
"this.UpdateItemQTYs is not a function"
I'm guessing that "this" is pointing to something else... so how do I get the 'real' "this"?
Event Binding method:
// Takes a Jquery Object and makes it sortable with our custom options
this.MakeSortable = function(JQOBJ)
{
JQOBJ.sortable({
connectWith: '.connectedSortable',
items: '.ItemLineWrapper',
dropOnEmpty: true,
axis: 'y',
receive: this.EH_SortableRecieve
});
}
There is something missing in your examples which is how EH_SortableRecieve is called. But based on what you say it should be used as I'm thinking it is used something like this:
htmlelement.onmouseup = shippingObject.EH_SortableRecieve;
In which case you should be aware of Javascript's binding of this in methods. Specifacally, in event handlers this is bound to the window object instead of the object the method belongs to. This is a general feature of javascript: methods can be re-bound at runtime. In other words, objects can steal other object's methods. For example, I can have my object slebetmans_object steal your method and re-bind its this with the following:
shippingObject.EH_SortableRecieve.apply(slebetmans_object,parameters);
There are several strategies to get around this. You can use a closure to capture your object:
htmlelement.onmouseup = function(){ shippingObject.EH_SortableRecieve() };
You can use a closure in the object's constructor to capture the correct reference to your object:
function ShippingUI () {
var self = this; // since self is resolved at the time the object is created
// it always reference to the "correct" object
this.EH_SortableRecieve = function(event, ui)
{
self.UpdateItemQTYs('ABCD',123);
}
}
There are probably other ways to do this but these are the two most common that I personally use.
The problem is that when you register the function as an event handler, its relationship to your object is lost.
How are you registering the event handler? If it's with jQuery, you can use the new "proxy" API:
$('#someButton').click($.proxy(myShippingUI, 'EH_SortableReceive'));
That'll make sure the object (I used "myShippingUI" as a sample instance of your class) is acting as the "this" pointer. There are other ways to do this to, but that's a simple one.
The "$.proxy" thing is new with 1.4.
function ShippingUI(){
...
}
ShippingUI.prototype = {
UpdateItemQTYs : function(ItemJQOBJ, NewQTY)
{
...
},
that = this,
EH_SortableRecieve = function(event, ui)
{
that.UpdateItemQTYs('ABCD',123);
...
}
};
The above should work too... All you need is the reference to ShippingUI object you are currently using in this.

Categories

Resources