Why are these nested triggers for jQuery not working? - javascript

I am using the following code to load two underscore.js templates. Once the first link is clicked, the skeleton template is loaded. The first trigger executes the find bind, which executes the loadBookmarks function correctly, but the 'loaded' trigger never fires and the loadFriendBookmarks never executes. Why is this? Is there another way to make this happen?
$('#bookmarks-link').click(function() {
$('#bookmarks-count').text("0");
var skeleton = modalTemplate();
$('#bookmarks').append(skeleton);
$('#bookmarks').trigger('skeleton');
});
$('#bookmarks').bind('skeleton', function() {
$('#bookmarks .thumbnails').loadBookmarks( getBookmarksUrl(1) );
// If I add an alert('hi') here, it works perfectly.
$('#bookmarks').trigger('loaded');
});
$('#bookmarks').bind('loaded', function() {
$('#bookmarks .thumbnails a').each(function() {
$(this).bind('click', function() {
$('#bookmarks .bookmarks-table tbody').empty();
$('#bookmarks .bookmarks-table tbody').loadFriendBookmarks(
getFriendBookmarksUrl($(this).attr('data-item'))
);
});
});
});
So interesting enough, the triggers do work correctly: If I stick an alert in between loadBookmarks and trigger, everything works fine. If I take it out, then it doesn't. Any idea why?

Based on your description and common sense, it sounds like loadBookmarks() loads data from a remote source, such as an ajax call. This means that trigger('loaded') can fire before loadBookmarks() has received the data. You can add a callback argument to loadBookmarks() and trigger the event there:
$('#bookmarks .thumbnails').loadBookmarks( getBookmarksUrl(1) , function() {
$('#bookmarks').trigger('loaded');
});
But this requires your loadBookmarks to know to call this function after it receives the data and creates the needed HTML - I can't demonstrate this without seeing the actual code you have in loadBookmarks.
Additional suggestion: don't bind handlers this way, use event delegation instead:
$('#bookmarks').on('click', '.thumbnails a', function(e) {
e.preventDefault(); // don't want the link to actually be followed, do we
var url = getFriendBookmarksUrl($(this).attr('data-item'));
if(url) { // in case it's clicked before the data attribute is set
var $tbody = $('#bookmarks .bookmarks-table tbody');
$tbody.empty();
$tbody.loadFriendBookmarks(url);
}
});
This means that all elements matching the selector '#bookmarks .thumbnails a' will call this click handler, even if they were added to the document after you called on. Meaning you can delegate these events even before calling loadBookmarks, removing the need for the loaded event at all. Plus, this way you only have one copy of the handler function in memory, as opposed to your bind which created a separate copy of the function for each a node.

the problem is else where in your code. probably some js error in loadBookmarks* functions.
see:
http://jsfiddle.net/BBESV/
triggers work perfectly

Related

Jquery off not removing event handler in Ember application

I am not able to remove a event handler from the ember application by using off.
my handler function is
setClickEvent: function(){
var panel = this.$();
if(panel && panel.find(event.target).length === 0 && this.get('isOpen')){
this.send('cancel');
}
}
and I am calling this function in didInsertElement function using the below code
didInsertElement: function(){
Ember.$('body').on('click', Ember.run.bind(this, this.setClickEvent));
}
and calling the off in willDestroyElement function as below
willDestroyElement: function(){
Ember.$('body').off('click', this.setClickEvent);
this._super();
}
I tried to go through many stackoverflow questions related to it. Which suggested to place an empty on after the off, I tried it but it didn't work.
The problem is that whenever I am moving away from the page and coming back I am finding two handlers handling the same event, because the previous event handler has not got removed and the event handlers are getting stacked up.
That's unrelated to ember actually. The issue is this part:
Ember.run.bind(this, this.setClickEvent)
What it does is bind creates a new function bound to this. Then that new function is attached as a handler. However, you try to off() the original setClickEvent instead of the new function.
Try to bind the function when the object is created and store it for later use:
init: function () {
this._super();
this._setClickEvent = Ember.run.bind(this, this.setClickEvent);
}
Then in your didInsertElement and willDestroyElement, use the bound function directly, this._setClickEvent.
The point being off must be given the exact function that was given to on, otherwise it will not find it in its watcher list and will not remove it.

Observe a JS event, when you only know PART of the event name?

I've inherited some JS (that I can't change) that fires a bunch of events:
jQuery(document).trigger('section:' + section);
// where "section" changes dynamically
And I want to observe for ALL of these events, and parse out the value for section, and do something different depending on it's contents.
If it didn't change I could do this:
jQuery(document).on('section:top', doStuff );
But how do I observe an event if I only know the first part of that event name?
You cannot listen for all events in the style of $().on('section:*'), unfortunately. If you can change the code, I would do the following:
jQuery(document).trigger({
type: 'section',
section: section
});
Then you listen for it and don't need to parse anything out
jQuery(document).on('section', function(e){
if (e.section === 'top') {
// Something happened to the top section
}
});
If you want to minimize your code changes, leave the old event in there, that way existing code will be unaffected.
A different approach would be to use event namespaces.
jQuery(document).trigger('section.' + section);
jQuery(document).on('section', function(e){
if (e.namespace === 'top') {
// Something happened to the top section
}
});
I, however, prefer the first approach because event namespaces are most commonly used for a different purpose: to be able to remove events without being forced to keep a reference to the handler itself. See http://css-tricks.com/namespaced-events-jquery/ and http://ejohn.org/apps/workshop/adv-talk/#13. I prefer to use styles that other developers are used to, if they do the job.
I'm really not sure about your use case but you could overwrite $.fn.trigger method:
(function ($) {
var oldTrigger = $.fn.trigger;
$.fn.trigger = function () {
if (arguments[0].match(/^section:/)) {
doStuff(arguments[0].split(':')[1]);
}
return oldTrigger.apply(this, arguments);
};
})(jQuery);
var section = "top";
jQuery(document).trigger('section:' + section);
function doStuff(section) {
alert(section);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Here's what I ended up doing.
It's a combination of Juan Mendes's solution, and using a method from the prototype library
Originally, there was a function that ran this code:
myObject.adjustSection(section) {
jQuery(document).trigger('section:' + section);
}
// I couldn't edit this function
So I extended the function with prototype's wrap method, since my project used prototype as well as jQuery.
// My custom function wrapper
// extend adjustSection to include new event trigger
myObject.prototype.adjustSection = myObject.prototype.adjustSection.wrap(
function(parentFunction, section) {
// call original function
parentFunction(section);
// fire event w/section info
jQuery(document).trigger({
type: 'adjustSection',
section: section
});
}
);
Then, it runs the original one, but also fires my custom event that includes the section info.
Now, I can do this to observe that event and get the section type:
jQuery(document).on('adjustSection', function(event) {
event.section; // contains the section I need
});
Of course, this means I have to utilize both prototype and jquery within the same scope, which isn't the best thing in the world. But it worked.

jQuery script doesn't work without an alert

I have an script that uses jquery, but i have a problem in the next part:
$("#botonAgregar").click(function() {
$.ajax({
type: "GET",
url: $(this).attr('href'),
success: function(html) {
$("#dialogDiv").html(html);
$("#dialogDiv").dialog('open');
}
});
alert();
$("a[type='submit']").click(function() {
var formName = $(this).attr("nombreform");
var formSelector = "form#" + formName;
$(formSelector).submit();
});
return false;
});
It works as it is, but if i remove the "alert();" line it doesnt add the click event to $("a[type='submit']") objects. What could be wrong?
it doesnt add the click event to $("a[type='submit']") objects
Yes it does. However, if more of those elements are being added during the AJAX call then you'll need to re-add the handler to those new elements after those are added to the DOM. Currently you're not doing that because the code after your call to .ajax() is happening before the AJAX call completes. This is because AJAX is, by definition, asynchronous. It's possible for the AJAX call to complete before later code is executed, but it is not guaranteed. (And in the case of code that's immediately after it, it's highly unlikely.)
Your success handler is called when the AJAX call completes, so that would be an opportune time to do this:
$("#dialogDiv").html(html);
$("#dialogDiv").dialog('open');
$("a[type='submit']").click(function() {
var formName = $(this).attr("nombreform");
var formSelector = "form#" + formName;
$(formSelector).submit();
});
However, there is a much better way to do this.
One of the problems with the approach you have is that you're going to re-add the handler to the same elements over and over. You're also adding the same handler to many elements, when you really only need one. Take a look at the jQuery .on() function. Essentially what it does is add a single handler to a common parent element of the target elements, and then filter the events based on the target element selector. So you only need to add the handler once:
$(function () {
$(document).on('click', 'a[type=submit]', function () {
var formName = $(this).attr('nombreform');
var formSelector = 'form#' + formName;
$(formSelector).submit();
});
});
In this case I'm using document as the common parent, though any other parent will work. (The body tag, a div which contains all of the target elements, etc. It just needs to be a common parent element which doesn't change throughout the life of the DOM.)

Waiting for model's data to be loaded using jQuery when -- experiencing timing differences?

I have an MVC application. I am trying to load a model from the server using jQuery's load. This works perfectly fine. I am now trying to run some JavaScript after all of my views have been loaded. As such, I am introducing jQuery's deferred promise functionality through use of jQuery .when
My limited understanding of this functionality has lead me to believe that the two bits of code below should run identically. It seems to me that my 'then' method is executing too soon, though. I'm not sure how to confirm that.
Old Code (Works):
$('#OrderDetails').load('../../csweb/Orders/OrderDetails', function () {
$("fieldset legend").off('click').click(function () {
var fieldset = $(this).parent();
var isWrappedInDiv = $(fieldset.children()[0]).is('div');
if (isWrappedInDiv) {
fieldset.find("div").slideToggle();
} else {
fieldset.wrapInner("<div>");
$(this).appendTo($(this).parent().parent());
fieldset.find("div").slideToggle();
}
});
});
Now, I would like to extend that to wait for multiple load events. To keep things simple, though, I am just going to try and wait for OrderDetails:
New Code (Doesn't Work):
var onDisplayLoadSuccess = function () {
console.log("Done!");
console.log('Fieldset legend:', $('fieldset legend'); //Not all found.
$("fieldset legend").off('click').click(function () {
var fieldset = $(this).parent();
var isWrappedInDiv = $(fieldset.children()[0]).is('div');
if (isWrappedInDiv) {
fieldset.find("div").slideToggle();
} else {
fieldset.wrapInner("<div>");
$(this).appendTo($(this).parent().parent());
fieldset.find("div").slideToggle();
}
});
};
var onDisplayLoadFailure = function () {console.error("Error.");};
$.when($('#OrderDetails').load('../../csweb/Orders/OrderDetails')).then(onDisplayLoadSuccess, onDisplayLoadFailure);
I do not see any errors fire. I see 'Done' print to the console, but the timing seems to be different. Legends which existed on the page prior to calling when/load/then have the click event applied to them, but legends which are loaded from in the view given back by OrderDetails do not have the click event bound to them.
By contrast, the old code's success function applied the click event to all legends appropriately. Why would this be the case?
To capture events on DOM elements that are added dynamically after binding an Event, you need to delegate it (http://api.jquery.com/on/).
Something like:
$('fieldset').on('click', 'legend', function(){

Jquery if its the first time element is being clicked

I need my script to do something on the first time an element is clicked and continue to do something different on click 2,3,4 and so on
$('selector').click(function() {
//I would realy like this variable to be updated
var click = 0;
if (click === 0) {
do this
var click = 1;
} else {
do this
}
});//end click
really I think it should rely on the variables but I can't think of how to update the variable from here on out any help would be awesome.
Have a look at jQuery's .data() method. Consider your example:
$('selector').click(function() {
var $this = $(this),
clickNum = $this.data('clickNum');
if (!clickNum) clickNum = 1;
alert(clickNum);
$this.data('clickNum', ++clickNum);
});
See a working example here: http://jsfiddle.net/uaaft/
Use data to persist your state with the element.
In your click handler,
use
$(this).data('number_of_clicks')
to retrieve the value and
$(this).data('number_of_clicks',some_value)
to set it.
Note: $(this).data('number_of_clicks') will return false if it hasn't been set yet
Edit: fixed link
Another alternative might be to have two functions, and bind one using the one function in $(document).ready() (or wherever you are binding your handlers), and in that function, bind the second function to be run for all subsequent clicks using bind or click.
e.g.
function FirstTime(element) {
// do stuff the first time round here
$(element.target).click(AllOtherTimes);
}
function AllOtherTimes(element) {
// do stuff all subsequent times here
}
$(function() {
$('selector').one('click', FirstTime);
});
This is super easy in vanilla Js. This is using proper, different click handlers
const onNextTimes = function(e) {
// Do this after all but first click
};
node.addEventListener("click", function onFirstTime(e) {
node.addEventListener("click", onNextTimes);
}, {once : true});
Documentation, CanIUse
If you just need sequences of fixed behaviors, you can do this:
$('selector').toggle(function(){...}, function(){...}, function(){...},...);
Event handlers in the toggle method will be called orderly.
$('#foo').one('click', function() {
alert('This will be displayed only once.');
});
this would bind click event to Corresponding Html element once and unbind it automatically after first event rendering.
Or alternatively u could the following:
$("#foo").bind('click',function(){
// Some activity
$("#foo").unbind("click");
// bind it to some other event handler.
});

Categories