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.
});
Related
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.
I need a method to enable and disable any element. Disable meaning set the opacity to 0.6 and remove the onClick callbacks. Enable meaning set the opacity to 1 and add the callback again.
My first two attempts failed miserably, the callback methods just got stacked and instead of running it once after each click the method was running more and more times.
function disableElement(element){
var el = $('#'+element);
el.css('opacity','0.6');
el.on('click',null); //this doesn't work
el.removeAttr('onClick'); // this doesn't help either.
}
function enableElement(element,callback){
var el = $('#'+element);
el.css('opacity','1');
el.on('click',callback);
}
Then i tried using the el.data:
disableElement:function(element){
var el = $('#'+element);
el.css('opacity','0.6');
el.data('element-enabled','false');
//el.click(function (){
// alert('disabled');
//});
},
enableElement:function(element,callback){
console.log('enabling');
var el = $('#'+element);
console.log(el);
if(el.data('element-enabled') == "true")
return;
console.log("setOpacity");
el.css('opacity','1');
el.data('element-enabled','true');
el.click(function(){
if(el.data('element-enabled') == "true")
callback();
});
}
Now they don't stack, as long as I don't disable it. If I disable and then enable it again, it gets stacked. Which means, if i run enableElement multiple times the callbacks don't stack. But once I run disableElement and then enableElement, if i click in the item, it'll happen twice.
Can achieve that somehow?
UPDATE
That was close. The off worked for me but i also had to remove it on the enableElement. Occasionally I have to call it twice, so it was still stacking. Finally this worked, thank you!
disableElement:function(element){
var el = $('#'+element);
el.css('opacity','0.6');
el.off('click');
},
enableElement:function(element,callback){
var el = $('#'+element);
el.off('click');
el.css('opacity','1');
el.on('click',callback);
}
To remove the event just use .off()
el.off('click');
To add the event back you can just do
el.on('click',callback);
You need to use .off as in .off('click'). That will remove all bound events of the click type. Documentation for .off
Note that if you use .off it doesn't return the event or anything, and you can't simply rebind with .on. However, since you have defined the callback in a separate function, you're good to go since you do re-bind as .on('click', function_name). It's just something to be aware of.
Unbind will remove all handlers assigned to the object for some event:
$('#foo').unbind('click');
You can also set this to some specific function by adding it as a second argument
$('#foo').unbind('click', myfunctionname);
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
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
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.