Backbone.js events in jQuery - javascript

I am attempting to extend existing jQuery widgets with Backbone events so they can publish Backbone events that can be listened to. My strategy is to proxy the event inside of the widget's native event callback method. For example, in the jQueryUI slider widget's change event, I want to trigger an event named "trigger." My conundrum is that this code works as intended:
$(function() {
var $slider = $("#slider");
_.extend($slider, Backbone.Events);
$slider.on("trigger", function(msg){
alert("triggered: " + msg)
});
$slider.slider({
change: function(event, ui) {
$slider.trigger("trigger", ui.value);
}
});
});
Which is 3/4 of the way to where I want to go, but I'd prefer to be able to just do something like this in the change event:
change: function(event, ui) {
$(this).trigger("trigger", ui.value);
}
...to completely encapsulate the widget and not worry about the actual singleton instance of the widget. My issue is that this second approach doesn't work and I was wondering if someone can explain to me why. In Firebug, both $(this) and $slider point to the same DOM element.

The context of your change handler (this) is the DOM element which triggered the event. This is the same DOM element you've already assigned to your $slider variable.
I think you may be complicating things unnecessarily, though it might be that I don't fully understand what you're trying to do. However, you should be able to pick up events triggered by plug-ins on the child elements of a backbone view using the built-in events hash. Try the following (and see fiddle here):
var SliderView = Backbone.View.extend({
// Set the view's el property to existing element in DOM
el: '#slider-view',
events: {
// jQuery UI slider triggers a slidechange event which we can subscribe
// to using the view's event object
'slidechange #slider': 'handleSliderChange'
},
render: function() {
// Worth caching a reference to the element in case we want to refer
// to it again elsewhere in the view - maybe to clean it up on exit
this.$slider = this.$el.find('#slider').slider({});
},
handleSliderChange: function(e, ui) {
// Our handler is invoked with the same ui parameter received
// by the slider's change handler.
console.log('Slider changed: ', e, ui.value);
}
});
// Create and render an instance of the view
new SliderView().render();

You can use _.extend(jQuery.fn, Backbone.Events) to add all the event methods to anything wrapped in $()
Have look at this jsFiddle.
http://jsfiddle.net/Zct7D/
It works similarly to what you are looking for. The biggest shortfall I see is that you have to .trigger on the same reference you bound (.on) to.
In the example I use.
var derpDiv = $("#derp").on("....
derpDiv.trigger("update....
That works. But something like this does not appear to work.
$("#derp").on("update", function() {});
// somewhere else in the code
$("#derp").trigger("update", "Message");
But with some more tweaking it could be made to work.

Related

How can I utilize JQuery's ".on()" method to work with non-native jquery elements?

I have recently posted a question here that told me to use .on() to attach elements to dynamically created objects. I'm just having an issue implementing this into my scripts, because I use an element that is from EasyUI:
$(".item").draggable({ ... });
And I'm not quite sure how to modify this to work with .on().
I have tried the following:
$('body').on('draggable', '.item', function() { ... }); (gives me an error that : is an unexpected token (the line of code is revert:true,)).
And:
$('body').on('draggable', '.item', draggable({ ... })); but that says draggable is not defined.
Sorry if this is really easy, It's just all going a bit over my head :P
Thanks for any help
draggable isn't an event, so you can't attach an event handler to it. It's a jQuery plugin method, and you can't call it on an element until the element exists.
If you're creating the element dynamically, you need to put the code that initializes the plugin into the code that creates the element. So it would look something like:
newElement = $("<div>", { "class": "item" }).draggable({...}).appendTo(something);
When using .load, you can attach the widget in the callback function.
$("#shop").load("showcontent/cat1.html", function() {
$(this).find(".item").draggable({ ... });
});
for the first issue, it looks like revert:true needs to be an object {revert:true}.
for the second issue you need to pass a callback function to the event listener, you can declare one inline using the function keyword eg.
$('body').on('draggable', '.item', function draggable({ ... }));
or you can pass an anonymous function
$('body').on('draggable', '.item', function({ ... }));
or declare the function elsewhere and pass it to the event listener by reference eg.
function draggable( event ){ ... }
$('body').on('draggable', '.item', draggable));
Looks like you are confusing the name of the element with the name of the event. You would call $(".item").draggable({...}) to generate a new element that can be dragged.
To listen to the event of when the element gets dragged, you want to listed to either the onStartDrag, onDrag or onStopDrag. Like this $('.item').on('onStartDrag', function(event) {...}).
See full documentation here: http://www.jeasyui.com/documentation/index.php

Backbone click event: Trigger only on element actually clicked on

In a Backbone application, I instantiate a view for every model in a collection.
If one of these views is clicked, I want to call a function.
Unfortunately, the function is called n times, where n is the number of models/view instantiated. I’ve managed to get around this by finding out what element has been clicked on, but I still don’t feel comfortable knowing that one event might be triggered 200+ times in the very same moment.
The event is bound like this:
var Item = Backbone.View.extend({
events: {
'click .list-group-item': function(event) { this.doSomething(event); },
},
doSomething: function(event) {
$(event.currentTarget).toggleClass('active');
},
});
In the code above you can also see my workaround using event.currentTarget, but how can I avoid this? Is there a way to distinguish the .list-group-item elements without resorting to event.currentTarget, so preferable right in the moment an element is clicked?
Another approach would be to bind the event to the parent element, so it is only triggered once and then using event.currentTarget, but that also seems kind of fishy to me.
Since you want to attach to a click anywhere in the view, you don't need to specify .list-group-item. Also, you only need to specify the name of the event callback function:
var Item = Backbone.View.extend({
events: {
'click': 'doSomething'
},
doSomething: function(event) {
$(event.currentTarget).toggleClass('active');
},
});

Reinitialize (or Re-declare) Dojo widget

I have a piece of code that initialises a resize handler in the following way:
dojo._hasResource["dojox.layout.ResizeHandle"] = true;
dojo.provide("dojox.layout.ResizeHandle");
dojo.experimental("dojox.layout.ResizeHandle");
dojo.declare("dojox.layout.ResizeHandle", [dijit._Widget, dijit._Templated], {
_init: function(){},
create: function(){
this.connect(this.resizeHandle, "mouseover", "_init");
}
// ... more properties
});
This is written in a core app file which I cannot edit. I need to rebind this resize handler to respond to touch events. I was thinking of overwriting this widget and rebind the resize handler with "touch" events. Something like so,
this.connect(this.resizeHandle, "touchstart", "_init");
I have never worked on Dojo before. So, I am not sure how the module system works. I tried creating a new widget by changing the string that identifies the widget ("dojox.layout.ResizeHandle") but no luck there.
How do I destroy the existing widget and rebind with touch events?
This code does not initialize a widget. The declare function creates a class. In this case it creates a class called "dojox.layout.ResizeHandle". To use this class you need to require it and then instantiate it. Something like this
require(["dojox/layout/ResizeHandle"], function(ResizeHandle) {
var resize = new ResizeHandle();
}
From there you can attach new handlers.
on(resize.resizeHandle, "touchstart", "_init);
It's also worth pointing out that you are using old deprecated dojo syntax (connect vs on, etc) although you may be using an older version of dojo.

Backbone Subviews and Event Unbinding

I've been working with Backbone a few days, reading up on design patterns and what have you. Today I was messing with sub-views, after reading a bunch of resources. Primarily, these 2 posts-
Derrick Bailey
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
Ian Storm Taylor
http://ianstormtaylor.com/assigning-backbone-subviews-made-even-cleaner/
These and others were very useful for helping me set up some subViews and handle their closing in what I thought was a correct pattern:
Backbone.View.prototype.close = function(){
var ctx = this;
_.each(ctx.subViews(), function(view) {
view.close();
});
this.remove();
this.unbind();
}
No problems here, seems to do what I expected. But I wanted to test it, just to see what happened. So I stopped calling close on subViews and looped my render like 20,000 times:
Backbone.View.prototype.close = function(){
var ctx = this;
_.each(ctx.subViews(), function(view) {
//view.close();
});
this.remove();
this.unbind();
}
No zombie event handlers or DOM nodes here. This was a little surprising to me - I'm not an expert in jQuery's internals and I expected to still have the event handlers from the child nodes at least. But I guess because my subViews are all contained within the parent view, which was still being removed and unbound, jQuery clears all the children fine. So I stopped unbinding the parent element:
Backbone.View.prototype.close = function(){
var ctx = this;
_.each(ctx.subViews(), function(view) {
//view.close();
});
this.remove();
//this.unbind();
}
My event handler count in the Chrome inspector still didn't go up.
So my question are:
What is a "real" example of when you need to cleverly handle event unbinding and subViews in this way? Is it any object reference outside of the immediate scope of your View? Is it only if your subviews aren't contained by the parent view's $el?
When you remove a parent view from the DOM, jQuery does clean up any DOM events that were hooked up in the children. unbind() is an alias for Backbone's Events.off, which removes any events you may have hooked up using myChildView.on('someEvent', ...). For example, a parent view might listen to an event you trigger inside a child view. If you did that, you would need the call to this.unbind() or this.off().
Now that Backbone.Events (as of 0.9.9) has listenTo() and stopListening(), you could consider adding this.stopListening() to your close(). Then if, within your view, you used something like this.listenTo(this.model, ...) they would also be cleaned up properly.

jQuery draggable - what happens if it is applied twice to an element?

Until recently I had this set up, which was called multiple times:
$('.rsh')
.draggable('destroy')
.draggable({ blah blah details });
The destroy was there to stop multiple draggable handlers accumulating on the class. New elements are being created by AJAX, and an initial attachment of draggable to the class doesn't touch subsequently created elements.
However, when I updated to version 1.9.2 of jQuery UI, it started giving me this error:
Error: cannot call methods on draggable prior to initialization; attempted to call method 'destroy'
So I removed the destroy line, and it's sweet. Except... I suspect that I may now be adding more and more handlers to the class (which is why the destroy was there in the first place).
I tried this, but it didn't like it:
if ($('.rsh').length) {
$('.rsh').draggable('destroy');
}
Two questions: (1) Will there be more and more handlers attached to the class each time I fire the draggable set up line? (2) If so, any solutions on how to remove them?
No, there won't be extra handlers bound. jQuery registers the initialized instance to the element and won't create a new instance of the same widget for the same element.
As you're worried about handlers, here's a quick check (jQuery 1.8+ and UI 1.9+):
$('div').draggable();
console.log( $._data($('div')[0], 'events') );
$('div').draggable();
console.log( $._data($('div')[0], 'events') );
Fiddle
As you can see, the attached handlers object is not altered after trying to initialize a new draggable instance on the same element.
edit: Subsequent calls with parameters won't be ignored though, rather they will extend the existing widget as shown on #Jason Sperske's answer.
Subsequent calls to .draggable() extend previous calls when attached to the same object (and not replace them as I had originally thought). See this example (extended from Fabrício Matté's) (demo)
<div>foo</div>
<script>
$('div').draggable({
start: function () {
console.log("drag 1");
}
});
console.log($._data($('div')[0], 'events'));
$('div').draggable({
stop: function () {
console.log("drag 2");
}
});
console.log($._data($('div')[0], 'events'));
</script>
In the console.log you see only these messages:
drag 1 <- on start
drag 2 <- on stop

Categories