jQuery Event Handler created in loop - javascript

So I have a group of events like this:
$('#slider-1').click(function(event){
switchBanners(1, true);
});
$('#slider-2').click(function(event){
switchBanners(2, true);
});
$('#slider-3').click(function(event){
switchBanners(3, true);
});
$('#slider-4').click(function(event){
switchBanners(4, true);
});
$('#slider-5').click(function(event){
switchBanners(5, true);
});
And I wanted to run them through a loop I am already running something like this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners(i, true);
});
}
In theory that should work, but it doesnt seem to once I load the document... It doesnt respond to any specific div id like it should when clicked... it progresses through each div regardless of which one I click. There are more event listeners I want to dynamically create on the fly but I need these first...
What am I missing?

This is a very common issue people encounter.
JavaScript doesn't have block scope, just function scope. So each function you create in the loop is being created in the same variable environment, and as such they're all referencing the same i variable.
To scope a variable in a new variable environment, you need to invoke a function that has a variable (or function parameter) that references the value you want to retain.
In the code below, we reference it with the function parameter j.
// Invoke generate_handler() during the loop. It will return a function that
// has access to its vars/params.
function generate_handler( j ) {
return function(event) {
switchBanners(j, true);
};
}
for(var i = 1; i <= totalBanners; i++){
$('#slider-' + i).click( generate_handler( i ) );
}
Here we invoked the generate_handler() function, passed in i, and had generate_handler() return a function that references the local variable (named j in the function, though you could name it i as well).
The variable environment of the returned function will exist as long as the function exists, so it will continue to have reference to any variables that existed in the environment when/where it was created.
UPDATE: Added var before i to be sure it is declared properly.

Instead of doing something this .. emm .. reckless, you should attach a single event listener and catch events us they bubble up. Its called "event delegation".
Some links:
http://davidwalsh.name/event-delegate
http://net.tutsplus.com/tutorials/javascript-ajax/quick-tip-javascript-event-delegation-in-4-minutes/
http://www.sitepoint.com/javascript-event-delegation-is-easier-than-you-think/
http://lab.distilldesign.com/event-delegation/
Study this. It is a quite important thing to learn about event management in javascript.

[edit: saw this answer get an upvote and recognized it's using old syntax. Here's some updated syntax, using jQuery's "on" event binding method. The same principle applies. You bind to the closest non-destroyed parent, listening for clicks ON the specified selector.]
$(function() {
$('.someAncestor').on('click', '.slider', function(e) {
// code to do stuff on clicking the slider. 'e' passed in is the event
});
});
Note: if your chain of initialization already has an appropriate spot to insert the listener (ie. you already have a document ready or onload function) you don't need to wrap it in this sample's $(function(){}) method. You would just put the $('.someAncestor')... part at that appropriate spot.
Original answer maintained for more thorough explanation and legacy sample code:
I'm with tereško : delegating events is more powerful than doing each click "on demand" as it were. Easiest way to access the whole group of slider elements is to give each a shared class. Let's say, "slider" Then you can delegate a universal event to all ".slider" elements:
$(function() {
$('body').delegate('.slider', 'click', function() {
var sliderSplit = this.id.split('-'); // split the string at the hyphen
switchBanners(parseInt(sliderSplit[1]), true); // after the split, the number is found in index 1
});
});
Liddle Fiddle: http://jsfiddle.net/2KrEk/
I'm delegating to "body" only because I don't know your HTML structure. Ideally you will delegate to the closest parent of all sliders that you know is not going to be destroyed by other DOM manipulations. Often ome sort of wrapper or container div.

It's because i isn't evaluated until the click function is called, by which time the loop has finished running and i is at it's max (or worse overwritten somewhere else in code).
Try this:
for(i = 1; i <= totalBanners; i++){
$('#slider-' + i).click(function(event){
switchBanners($(this).attr('id').replace('slider-', ''), true);
});
}
That way you're getting the number from the id of the element that's actually been clicked.

Use jQuery $.each
$.each(bannersArray, function(index, element) {
index += 1; // start from 0
$('#slider-' + index).click(function(event){
switchBanners(index, true);
});
});
You can study JavaScript Clousure, hope it helps

Related

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.

Common JavaScript Closure issue on jQuery elements in an Array

I am working at building a widget that calls a particular plugin on each jQuery DOM element inside an array.
MyApp.forms is an array of Objects. Each Object has a jQuery wrapped DOM element.
I am doing the following:
$(MyApp.forms).each(function(i){
var individualForm = this;
/*
individualForm is an Object {
prop: 'value,
$el: somejQueryElement,
...
}
*/
individualForm.$el.thePlugin({
// options
})
.on('pluginEvent', function() {
individualForm; // refers to the last object in MyApp.forms
this; // refers to the last
$(this); // same problem
}).on('pluginEvent2', function() {
// same problem as above here.
});
});
The events pluginEvent and pluginEvent2 get attached to all individualForm's $el. But when they fire, I always get the last element.
I feel this is a common JavaScript Closure problem.
I tried using a for loop and creating an IIFE inside but it doesn't work, as the function executes when the event fires. And though both events fire on all elements, I only get the handler attached to last element executed.
Update:
Found The fix. But don't know why and how it worked.
Every individualForm.$el element is an input element with class="some-class".
Somewhere else in the code, another developer is doing $('.some-class').bind(... with an older version of jQuery. And then again with a newer version of jQuery (using noConflict $). There are 2 jQuery's on the page. The fix was to delete the first .bind.
Can you please try the following:
$(MyApp.forms).each(function(i){
var form = this;
(function(individualForm) {
individualForm.$el.on('something', function() {
individualForm; // refers to the last object in MyApp.forms
this; // refers to the last
$(this); // same problem
}).on('somethingElse', function() {
// same problem as above here.
});
})(form);
});
You should wrap individualForm in a closure. Otherwise the scope is changed and it points to the last element of the array.

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

Sending data with an event listener

I have a predicament: I want to send some data with an event listener but also be able to remove the listener. Here's the standard closure approach...
var fn = function(e){method(e,data)};
el.addEventListener('click',fn,false);
el.removeEventListener('click',fn,false);
and you could remove the event, just fine. But say, the element was removed from the DOM? Then, you'd be left with the fn function sitting around. After removing a couple thousand DOM elements, it will result in something of a memory leak.
I've considered attaching a DOMNodeRemoved event handler, that would remove any left over functions/data along with the removed node. But apparently, that event isn't cross-browser compatible.
The only other option I've come up with would be modifying the element's DOM. Consider...
el.MyEventData = function(e){method(e,data)};
el.addEventListener('click',el.MyEventData,false);
el.removeEventListener('click',el.MyEventData,false);
Is modifying the DOM acceptable in this situation? The only sticky part of that solution is when you try to add more than one event listener. Let's say we made a custom function to parse the adding/removing of events...
function makeEvent(fn,data){
var dataFn = function(e){fn(e,data)};
//create object to hold all added events
el.myEvents = {};
//make ID for this specific event
var eventID = ranString();
//add the event to the events object
el.myEvents[eventID] = [fn,dataFn];
//finally add the listener
el.addEventListener('click',dataFn,false);
}
function destroyEvent(fn){
//find all fn references
for(var id in el.myEvents){
if (el.myEvents[id][0] == fn){
el.removeEventListener('click',el.myEvents[id][1],false);
el.myEvents[id] = null;
}
}
}
It still modifies the DOM, as before, and certainly isn't a very elegant solution either. Does anyone know of any alternative, better method for passing data?
EDIT: So, I've looked into a little of jQuery's data/event scripts. I don't completely understand the code, so if someone would clarify, it would be helpful. But it seems as though they use a similar method, by making some type of el.cache property, that holds event data.
Considering that you use addEventListener this is not an issue as all modern garbage collectors can take care of such situations. The problem with event listeners only exists in IE's implementation (7-).
Test - 10 000 addEventListener and remove element (see Windows Task Manager)
When a DOM object contains a reference
to a JavaScript object (such an event
handling function), and when that
JavaScript object contains a reference
to that DOM object, then a cyclic
structure is formed. This is not in
itself a problem. At such time as
there are no other references to the
DOM object and the event handler, then
the garbage collector (an automatic
memory resource manager) will reclaim
them both, allowing their space to be
reallocated. The JavaScript garbage
collector understands about cycles and
is not confused by them.
http://www.crockford.com/javascript/memory/leak.html
Did you consider .delegate()?
According to your jQuery question:
Each jQ object has a data property. It does not stored inside the element itself - it's very important. jQ use general storage for all elements - jQuery.cache. So when you add anything to the element like this:
$('#myEl').data('someValue', 1);
jQ do the following:
jQuery.cache[elementUniqId]['someValue'] = 1;
So element does not contain its data object. It only have an uniq id that is allows it to access to the data recorde at the global storage. (elementUniqId is autogenerated)
jQ events are stored into the element data as well:
$('#myEl').click(function() { first listener });
$('#myEl').mouseenter(function() { one more listener });
$('#myEl').click(function() { anotheer listener });
Will be stored:
jQuery.cache[elementUniqId]['events'] = {
click: [function() { first listener }, function() { anotheer listene }],
mouseenter: [function() { one more listener }]
};
It allows jQ to store the order of execution for all listeners attached to each event. And later, when you delete dom element, using jQuery - .remove(), jQuery loops through the jQuery.cache[elementUniqId]['events'] and remove each listener from the element, and after removes element cache record. It allows jQ to preven memory leaks
A possible solution to maybe take you in a different direction: add the function as an inline sibling of the element.
<span id='element12345'>Test</span><script
type='text/javascript'>function fn12345() { /* ... */ }</script>
Then, when you remove all the event listeners that you want, you can also remove the "nextSibling()" of the element you're working with.
how about a setup like this? (using IE syntax since that's what I have available right now)
<div id="clickbox" style="width: 100px; height: 100px; border: 1px solid orange;">
click here to test</div>
<input id="Button1" type="button" value="clear handler" />
<script>
var data = "derp1";
var el = document.getElementById('clickbox');
var btn = document.getElementById('Button1');
// methods
var method = function (e, dat2) { alert(dat2); };
var fn = function (e) { method(e, data) };
var remover = null;
// attachment
el.attachEvent('onclick', fn, false);
(function (id, handler) {
// handler variable is local but points to the right function
remover = function (e) {
if (document.getElementById(id)) {
// remove the original listener (handler is fn)
document.getElementById(id).detachEvent('onclick', handler, false);
alert('detached');
}
// remove last reference to the original method
handler = null;
alert('method nulled');
// clean up the remover method
e.srcElement.detachEvent('onclick', remover);
remover = null;
};
btn.attachEvent('onclick', remover);
})('clickbox', fn);
// clear the original variable but the method still exists as an event listener
fn = null;
// you should be able to remove the div element and any references to it
// without leaving any stray bits around.
setTimeout( function() {
var d = document.getElementById('clickbox');
if (d){ d.parentNode.removeChild(d) ; }
} , 6000 );
el = null;
btn = null;
</script>
I'm assuming you don't want the listener removed immediately after adding it but rather want to be able to remove it at a later time. to deal with this, the cleanup routine is given its own scope by creating an anonymous function which is immediately invoked with fn as a parameter. the anon function then has its own reference to fn maintained in the handler variable. after that, fn can be cleaned up and the remaining references to the original method exist in the listener list for your element and in the scope of the anonymous function.
within the anonymous function scope, the function remover has access to the handler variable and can use it to detach the listener. remover then detaches and clears itself so there should be nothing left with access to any version of fn/handler.
I don't have any way to verify all this right now but I think it makes sense and should hold up in modern browsers.
why not take a look at this
Binding Events To Non-DOM Objects With jQuery
http://www.bennadel.com/blog/1520-Binding-Events-To-Non-DOM-Objects-With-jQuery.htm

Keeping track of eventListeners on DOM elements

What is the best way to keep track of eventListener functions on DOM elements? Should I add a property to the element which references the function like this:
var elem = document.getElementsByTagName( 'p' )[0];
function clickFn(){};
elem.listeners = { click: [clickFn, function(){}] };
elem.addEventListener( 'click', function(e){ clickFn(e); }, false );
Or should I store it in my own variable in my code like below:
var elem = document.getElementsByTagName( 'p' )[0];
function clickFn(){};
// Using window for the sake of brevity, otherwise I wouldn't =D
// DOM elements and their listeners are referenced here in a paired array
window.listeners = [elem, { click: [clickFn, function(){}] }];
elem.addEventListener( 'click', function(e){ clickFn(e); }, false );
Obviously the second method would be less obtrusive, but it seems it could get intensive iterating through all those possibilities.
Which is the best way and why? Is there a better way?
As the other answers have mentioned, since you tagged this question with jQuery, it doesn't make sense that you're trying to add and track events with plain JavaScript like this. In addition, the way you seem to be doing it doesn't really track anything, because you're manually adding a function reference to your listeners properties, independent of actually assigning them to event handlers on the elements. When you do that there's no way to ensure that the ones you're "tracking" are actually what's going to be called when the event fires.
If you're insistent on avoiding jQuery, here's an example of how you could add multiple handlers to multiple events and keep track of which functions have been assigned to handle which events later:
function addTrackedListener(element, type, handler) {
if (!element.trackedEvents) {
element.trackedEvents = {};
}
if (!element.trackedEvents[type]) {
element.trackedEvents[type] = [];
}
element.trackedEvents[type].push(handler);
element[type] = function () {
handleAllEvents(element.trackedEvents[type]);
};
}
function handleAllEvents(events) {
for (var i = 0, l = events.length; i < l; i++) {
events[i]();
}
}
Example usage:
// grab some element
var div = document.getElementById('test');
// add a click handler to it
addTrackedListener(div, 'onclick', function() {
console.log('first handler');
});
// add another click handler to it
addTrackedListener(div, 'onclick', function() {
console.log('second handler');
});
When the click event fires, you'd see this in your console:
first handler
second handler
And if you inspect div.trackedEvents, you'll see a property for each type of event, whose value will be an array of all the functions that will execute when that event is triggered.
This is a slightly odd question, considering it's also tagged as "jQuery".
In jQuery, event handlers are made extremely simple. Your first example can be rewritten as the following:
function clickFn(){};
$("p:first").click(function(e) {
clickFn(e)
});
That, or I've wildly misunderstood the question.

Categories