'click' event disappears in Backbone.View after using Quicksand - javascript

There is a 'View' in the model with the event click. After using the Quicksand effects plug-in for jQuery, the objects loose their event handlers. I have tried to add the listener for the event with standard methods in backbone.js:
events: {
"click .objContact" : "openChat"
}
and the same tools jQuery delegate:
var self=this;
this.$el.delegate('.objContact','click', function(){
self.openChat();
});
and live:
var self=this;
this.$el.find('.objContact').live('click', function(){
self.openChat();
});
but the click event disappears.
What could be the problem? And how do I solve it?
UPD: Calling 'Quicksand' is in Backbone.Router (subject to change is obtained directly by means of jQuery, not Backbone), so changes are not handled in Backbone.View
UPD 2: The problem is solved in the following way - by moving the handling of the click event from the View-model to View-collection. And treated with live (did not work in on)

Simple Answer: instead of linking the function to the link with the classic ajax method that is
$('a.oldJqueryClass').click(function(){....
you need to make that function standalone, declaring a new function
function myfunction(params) {alert(params);}
than in the link you call that with the old school way:
Click here
In this way the cloned element will contain itself the call to the function and you can forget about restoring the dom integrity broken by the cloning of quicksand.
I did it in my project, it works fine.

Do a call to delegateEvents() after the related DOM entries have changed or become overwritten. In a traditional Backbone app this is typically done in the render method, but you probably need for figure out when and where quicksand does it's magic (I do not know anything about it), and call delegateEvents that will reactivate the events for the current elements in the DOM.

Related

How do I bind a click event to an object that is not yet in the DOM?

First of all, I tried the answers in this, and this similar questions, but that does not seem to work for me at all.
I would like to do stuff on a click event bound to an element that is created via ajax so it is not in the DOM at first.
I notice that the following coffeescript in my_asset.js.coffee works as expected:
$ ->
$('#div').on "click", ".link", (e) ->
#do stuff
According to JQuery Doc:
this function is bound to all "selected_div" click events, even if they are added to the DOM via ajax later
And the do stuff part works ok
However I would like to:
$(this).after("<%= insert some long view here %>")
So, in order to do that, I guess I should move the $(this).after part from the asset.js.coffee to my_view.js.erb
where I could embed render partial
There, in my_view.js.erb, I have tried the following, (equivalent) javascript:
$(function() {
$("#div").on("click", ".link", function() {
$(this).after("<%= render partial here %>");
});
});
But it does not work for the first click, (it does, however, for the subsequent clicks)
I think it is related to the fact that .link is not in the DOM when the page loads for the first time.
Why is this happening? Is that the cause? And how could I get in the view the same behaviour that I get in the asset ?
You'll bind to a container element (typically document or body), and delegate the event to the to-be-created object:
$(document).on("click", "#div .link", function() {
$(this).after("<%= insert some long view here %>");
});
Binding
As you've pointed out, JS binds events to elements of the DOM on page load
If your element is not present at load, JS won't be able to bind, thus causing a problem. To fix this, you'll have to delegate the bind to an element higher in the DOM hierarchy, allowing JS to bind the event to any new elements in relation to your container

Triggering an event from a select to a function in a backbone view

I've tried looking around, but I probably haven't searched precise enough.
So basically I'm writing a web app that uses backbone.js
So my problem is that I'm trying to go back to a view to call a function.
So I have a template that contains a select with an ID of "sel".
I render that template in view A.
that select also has a onchange="callme()". (I use the jquery change(), this is just for simplicity sake)
view A has the function callme(), but when the the user clicks on it, console will say, callme undefined.
The problem is that callme() is assumed to be global, A.callme() doesn't work, since it's not that instance of it, this.callme() doesn't either because 'this' is the select statement, and the view isn't really there on the html page.
So.... what must I do? Thanks in advance.
You need to subscribe to your event directly from view and handle control event there.
MyView = Backbone.View.extend({
events: {
'change select': 'selectHandler'
},
selectHandler: function(event) {
// do my stuff
}
});
In this case in the selectHandler function this will be the context of current view, because Backbone using jQuery $.proxy for delegating execution of control handlers to the view itself. To get the control instance in the handler you need to use $(event.target) property which will return control object.
Also you don't need onchange attribute.
P.S. Your control should be in the control which is set to el property of Backbone view.

Detect when a view is removed from the page using Backbone.js or Marionette.js

using Backbone.js with Marionette.js (Go Derick Bailey!). Need to detect when a view is removed from the page. Specifically, I'm overwriting it with another view.
Is there an event I can detect of function I can overload to detect when this happens?
Thanks!
Marionette provides the View.onClose method for this purpose:
Backbone.Marionette.ItemView.extend({
onClose: function(){
// custom cleanup or closing code, here
}
});
In vanilla Backbone you can override the View.remove method:
Backbone.View.extend({
remove: function(){
// custom cleanup or closing code, here
// call the base class remove method
Backbone.View.prototype.remove.apply(this, arguments);
}
});
Neither of these methods will work if you are simply clobbering the view's DOM element. If that is your case, the solution is simple: Don't do that. Remove the previous view explicitly before rendering another view in its place.
The region show function is going to do most of what you are looking for
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#basic-use
And look at the on show event later in the page

jquery issue with on and live

I have the following code:
var $reviewButton = $('span.review_button');
$reviewButton
.live('click',
function(){
$('#add_reviews').show();
}
)
Later in the script, I use an AJAX call to load some content and another instance of $('span.review_button') enters the picture. I updated my code above to use '.live' because the click event was not working with the AJAX generated review button.
This code works, as the .live(click //) event works on both the static 'span.review_button' and the AJAX generated 'span.review_button'
I see however that .live is depracated so I have tried to follow the jquery documentations instructions by switching to '.on' but when I switch to the code below, I have the same problem I had before switching to '.live' in which the click function works with the original instance of 'span.review_button' but not on the AJAX generated instance:
var $reviewButton = $('span.review_button');
$reviewButton
.on('click',
function(){
$('#add_reviews').show();
}
)
Suggestions?
The correct syntax for event delegation is:
$("body").on("click", "span.review_button", function() {
$("#add_reviews").show();
});
Here instead of body you may use any static parent element of "span.review_button".
Attention! As discussed in the comments, you should use string value as a second argument of on() method in delegated events approach, but not a jQuery object.
This is because you need to use the delegation version of on().
$("#parentElement").on('click', '.child', function(){});
#parentElement must exist in the DOM at the time you bind the event.
The event will bubble up the DOM tree, and once it reaches #parentElement, it is checked for it's origin, and if it matches .child, executes the function.
So, with this in mind, it's best to bind the event to the closest parent element existing in the DOM at time of binding - for best performance.
Set your first selector (in this case, div.content) as the parent container that contains the clicked buttons as well as any DOM that will come in using AJAX. If you have to change the entire page for some reason, it can even be change to "body", but you want to try and make the selector as efficient as possible, so narrow it down to the closest parent DOM element that won't change.
Secondly, you want to apply the click action to span.review_button, so that is reflected in the code below.
// $('div.content') is the content area to watch for changes
// 'click' is the action applied to any found elements
// 'span.review_button' the element to apply the selected action 'click' to. jQuery is expecting this to be a string.
$('div.content').on('click', 'span.review_button', function(){
$('#add_reviews').show();
});

Knockout binding handler teardown function?

I have a knockout binding handler that uses plupload for drag and drop and ajax uploads.
To use the plupload script I create an instance of plupload which in turn is binding event listeners to DOM elements.
That works fine.
However, I have a list of "folders" and when I click a folder I display a list of files in that folder. I reuse the same DOM elements for this by binding selectedFolder().documents using foreach.
The problem I have is that in my binding handler I do all my plupload stuff in the init function and since I reuse the DOM elements they get multiple event handlers bound to them. This causes the drag and drop events to be sent to alla handlers. This means that if I drop a file on the rendered file list, the drop event fires on all previously rendered file lists too.
What I am looking for is some sort of teardown or cleanup function in the binding handler, so that I can unregister all of the events whenever a file list get unrendered (is that a word?).
Maybe we cannot detect unrendering? How would I then handle this? I would prefer not to have a global instance, since that would prevent me from using the binding on multiple places at the same time.
Sorry about not giving you any code. I'm on my cell phone atm.
Cheers!
You can register a handler that will be executed whenever KO removes elements (like when a template is re-rendered). It looks like:
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).datepicker("destroy");
});
So, in your "init" function you would register a dispose callback for the element that is being bound and you would have an opportunity to run whatever clean-up code that you would like.
I believe the solution provided here will only work if Knockout is the one that removes the DOM node (ie when it rejigs templates). I had a hard time getting it to trigger under certain conditions. There might be scenarios where you need a callback to be executed regardless of how your element got removed; whether it be with Knockout, or via jQuery.html(), etc (especially in a single page application).
I brewed a different approach for adding such a hook with a little help from jQuery. Using the special events API (which are well described here), you can add a method that gets execute when a particular event is removed from a DOM node (something that happens on teardown).
If you are using Knockout in conjunction with jQuery, you can wrap this into a knockout binding to look something like this:
ko.bindingHandlers.unload = {
init: function (element, valueAccessor) {
var eventName = 'your_unique_unLoad_event'; // Make sure this name does not collide
if (!$.event.special[eventName]) {
$.event.special[eventName] = {
remove: function (o) {
o.data.onUnload()
}
};
}
$(element).on(eventName, { onUnload: valueAccessor()}, $.noop);
}
};
You can then use this on any element like this:
<div id="withViewModelMethod" data-bind="unload: aMethodOnMyViewModel" />
<div id="withInLineMethod" data-bind="unload: function() { /* ... */ }" />
I owe credits to this SO post.

Categories