Just starting out with front end development here, but despite my assuredly poor practices I've been enjoying things a lot. Basically I'm trying to add an onclick function to an element when the page loads. It was working fine as:
$("li a:contains('Search')").ready(function(){
$("li a:contains('Search')").on("click", function(){
$("li a:contains('Search)").text("test")});
});
But my programmer spidey-senses were strongly activated by the quite frankly gross state of this code, so I tried to refactor and my thought for the easiest first-step refactoring was this:
function replaceWithSearch(element){
element.text("test");
}
$("li a:contains('Search')").ready(function(){
$("li a:contains('Search')").on("click", replaceWithSearch($("li a:contains('Search')")));
});
However, when I changed to that, the onclick function now launches immediately. Is there a way to force the function to only be evaluated when called as a callback? I would prefer to have the function be reusable, also if you have any other comments on refactoring my code or best practices I would be very glad to hear them. Again, just starting out with Javascript and I would love to build some good practices.
Optimized code:
$(document).ready(function() {
$("li a:contains('Search')").bind("click", function() {
$(this).text("test");
});
});
If you prefer to use a function, proper way is:
$(document).ready(function() {
$("li a:contains('Search')").bind("click", replaceWithSearch);
});
And in the function you don't have to use argument:
function replaceWithSearch(){
$(this).text("test");
}
Live test case.
If your elements are created after page load, use the .on() like this:
$(document).on("click", "li a:contains('Search')", replaceWithSearch);
Updated fiddle.
Wrap the handler in a function like:
$("li a:contains('Search')").ready(function(){
$("li a:contains('Search')").on("click", function() {replaceWithSearch($("li a:contains('Search')"))});
});
That way, the handler is defined and associated with the click event, rather than executing immediately.
Related
I have the following jQuery on a Rails page:
$(document).on('click','.reportsArrow', function() {
if ( $(this).parent().hasClass('reportCollapsed') ) {
console.log("A");
$(this).parent().removeClass('reportCollapsed');
}else{
$(this).parent().addClass('reportCollapsed');
console.log("B");
}
});
When I click on an element with reportsArrow and without reportCollapsed, the log shows
B
A
Meaning it is executing the else part and then executing the if part. I want the function to only be executed once per click, and to only follow one code path. Why is it being executed twice and how do I stop this? I should point out that this toggles correctly in the mockups created by the web designer (on HTML/CSS/JS only). It looks like the problem is Rails related.
EDIT:
We have found a working solution:
$('.reportsArrow').click(function() {
$(this).parent().toggleClass('reportCollapsed');
});
The event would be getting fired more then once and propagated up-ward in the DOM tree. Use event.stopPropagation(). You can also use the toggleClass instead of branching.
$(document).on('click','.commonClass', function(event) {
event.stopPropagation();
$(this).parent().toggleClass('newClass');
});
Not sure why, but my days in unobtrusive javascript have taught me to be as specific and as least fuzzy as I can.
Never worried why, as long as it worked. Having been asked why (just here), my answer is "I will have to look it up". Sorry.
Thus, I would avoid setting a catch method on THE document and then filter actions: I would directly point the event catches on the element (or set of elements) I want to watch.
So, instead of using:
$(document).on('click','.reportsArrow', function() {
//...
});
I would go the direct way:
$('.reportsArrow').click(function () {
//..
});
Having read the API documentation for jQuery .on(), it appears to me that it would be probably more suitable to use .one() instead, so there is no continuation after hit "#1". But I have not tested it, so I can't say for sure.
You need to stop event propogation to child elements.also you can use toggleClass instead:
$(document).on('click','.commonClass', function(e) {
e.stopPropagation();
$(this).parent().toggleClass('newClass')
});
Try this,
You need to avoid event bubbling up the DOM tree. There must be a parent causing the event to fire twice or more time.
To avoid this use event.stopPropagation()
$(document).on('click','.commonClass', function(event) {
event.stopPropagation();
$(this).parent().toggleClass('newClass');
});
I could not reproduce your problem. Your code is working fine in my Firefox on a simple HTML page.
Please try this piece of code and come back with the console output:
function onClick(ev) {
console.log(ev.currentTarget, '\n', ev.target, '\n', ev);
if(ev.target === ev.currentTarget)
console.log($(this).parent().toggleClass('newClass').hasClass('newClass') ? 'B' : 'A');
};
EDIT:
and of course:
$(document).on('click', '.commonClass', onClick);
For readability put the logic into the jQuery selector using the :not like this
$(document).on('click','.reportCollapsed > .reportsArrow', function() {
$(this).parent().removeClass('reportCollapsed')
console.log("A");
})
$(document).on('click','not:(.reportCollapsed) > .reportsArrow', function() {
$(this).parent().addClass('reportCollapsed')
console.log("B");
})
Given that this works one time (click > else > B) could it be that something listens for DOMSubtreeModified or other DOMChange Events which again trigger a click on the document ?
Have you tried debugging/following the calls after the inital click? Afaik chrome has a nice gui to do this.
I'm using infinite-scroll, a plugin that replaces the standard pagination by fetching new pages through ajax.
The problem with this is that jquery functions don't register the new posts, causing functions like these:
jQuery(document).ready(function($) {
$('.vote-a, .vote-b').click(function() {
//do stuff
});
$('.vote-b').click(function() {
//do other stuff
});
});
to stop running. To solve this, the plugin provides callback, and let's you include codes that you'd like to be called whenever a new page is loaded.
What I did was simply putting the code above there. It worked but I ended up with several instances of the same code.
So the question is how do I solve this? One way I can think of is destroying/removing the old instance with each callback.
Or somehow reinitiliaze/restart/invoke the function.
You can register the click events at a root level instead of by finding the individual elements and assigning a click event to them.
https://api.jquery.com/on/
and the older method
https://api.jquery.com/live/
jQuery(document).ready(function($) {
$(document).on('click', '.vote-a, .vote-b', function() {
//do stuff
});
$(document).on('click', '.vote-b', function() {
//do other stuff
});
});
I would like to call function when slideUp or slideDown are performed on an element. Is this possible?
Something like:
$('#panel').on('slideUp', function() { open--; });
$('#panel').on('slideDown', function() { open++; });
Update: The problem is that there are a ton of slide calls (e.g.: $().slideUp()) all over the page, within ajax responses, hash link clicks, etc.. I was hoping to bind to the slide itself somehow rather than add code to each calling function.
You cannot bind to an event since there is no such.
But you can pass a handler that will be called after animation is finished
$('#panel').slideUp(function() { ... });
http://api.jquery.com/slideUp/
If you really want to do this, you can use custom events and your own little plugin, something like this:
$.fn.mySlideToggle = function() {
this.slideToggle();
this.trigger('mySlideToggle');
}
$('div').on('mySlideToggle', function(){ console.log('hey') });
$('button').on('click', function(){ $('div').mySlideToggle(); });
Here's a little demo (check console): http://jsbin.com/asejif/2/edit
In your case it is redundant though, since you can use the callback that the slide events provide, but it might be useful for other things...
really basic stuff here. I'd like to give a click function a name and assign some parameters to it. The goal is code reusability such that I can write only one generic function for common tasks such as for enabling users to delete various data.
Here's a jsfiddle to show you what I mean.
And here's that code:
the HTML:
<button>delete this</button>
<div data-id="3" class="delete">something bad</div>
<div data-id="4" class="delete">something else bad</div>
and the JS:
// this function would be loaded on my site's template and therefore would be available across my entire site.
function deleteThis(data_id){
$('button').on('click', 'button', function(){
$('div[data-id="'+data_id+'"]').hide();
});
}
var clicked_id=3;
function deleteThis(clicked_id);
// this function would be called on the various pages where users can delete things and this variable, clicked_id, would be assigned=3 by the user's action on that page.
How do I give this button click event a name?
update thanks all! the $('button') should have been $(document.body) or the button's parent element. It works if you make that simple change. You can also do it as Michael Buen suggests below.
Just refactor your code, put the delete functionality on its own function
<button>delete this</button>
<div data-id="3" class="delete">something bad</div>
<div data-id="4" class="delete">something else bad</div>
$('button').on('click', function() { deleteImmediately(3) });
function deleteImmediately(id) { -- refactored code
$('div[data-id='+id+']').hide();
}
Live test: http://jsfiddle.net/e2kuj/2/
In your fiddler, the (function deleteThis(){})() is making it private and you are trying to access it as a global!
I think you're misunderstanding events. deleteThis only makes sense if it's in the handler.
corrected HTML: (don't use custom attributes for referencing HTML - They're slower)
<button>delete this</button>
<div id="del_3" class="delete">something bad</div>
<div id="del_4" class="delete">something else bad</div>
JS: (untested)
var deleteTargetId = 'del_3'; //clicked_id renamed for clarity
function deleteThis(targetId){
$('#'+targetId).remove(); //removes rather than hides the html
}
$('button').click( function(){
deleteThis(deleteTargetId);
} );
Now you could swap deleteTarget and the HTML with that ID would get yoinked.
However, if this is homework, I'm wondering if you understand the assignment. The var named 'clicked_id' suggests the idea is to click the divs to make them disappear and use delegation. That one's easy.
You'll need to understand event delegation and event bubbling to see what's going on here. Basically when something is clicked, the event then fires on the parent element and then that parent element's parent element, all the way up to the HTML tag. This happens with all events and doesn't cause the trouble you might think because containers are rarely assigned listeners for events. Links and buttons are more typically end point nodes or at most contain a span or an image. Usually when bubbling causes a problem it's because somebody's doing something awful with HTML or they should've been using delegation in the first place.
'on' is the new piss-poor name for the once appropriately named and less confusion-prone 'delegate' jquery method. Essentially anything in the body with the class 'delete' triggers the handler. We don't care about the ID since the idea is to kill the div that was clicked and 'this' gives us a reference to it. This is unusual behavior for JQ, since most methods would have 'this' point at the 'body' but it's obviously a more useful thing to point at for event delegation.
$('body').on('click', '.delete', function(e){
$(this).remove(); //this refers to the originally clicked element
} );
//note: not the solution you asked for, but possibly the one you needed.
It was almost right. I updated it -> http://jsfiddle.net/fz5ZT/41/
function deleteThis(id){
$('button').click(function(){
$('div[data-id="'+id+'"]').hide();
});
};
deleteThis(3);
you can remove the element in a different way:
$('button').on('click', function(){
$(this).next("div").remove();
});
http://jsfiddle.net/fz5ZT/46/
You could just use $('button').on('click', 'button', clickHandler); to reference the clickHandler function.
I am big fan of such things since apart from being reusable it has the following advantages.
I will be able to just send across a patch in case there's a bug in the clickHandler
Someone can augment my method which is not possible with anonymous methods
Readable, and also useful to see the stack trace in case of errors
Hope that helps.
Update:
function clickHandler(ev) {
ev.preventDefault();
// ... handler code
}
I have a somewhat odd situation. I understand the premise of the live() and bind() functions, yet in a situation where i believe i dont need them, i seemingly do. I will explain.
I made an autosuggest in jquery. I included autosuggest.js at the top of my page. I then have an input field.
The basis of the JS works around:
$(".autosuggest").keyup(function()
{
}
This works - on keyup, my function executes etc as expected - i dont need to use live() or bind() as the input field is on the page from the get go...
Now.. I have also made a 'star rater' esque script.
I have various elements (which are styled), and on hover they are restyled...
$('.rating li').mouseover(function() {
}
does NOT work, YET
$('.rating li').live('mouseover',function() {
}
DOES.
Why do i need to use 'live' in this situation, when i dont in the case of the autosuggest?
Thanks
The only thing I can imagine that would cause this is a lack of a domready event. This should work:
$(function () {
$('.rating li').mouseover(function() {
}
});
the .ratings li isn't parsed yet when you have .mouseover() not working.
You can wrap it in $(document).ready(function() {...}); or use .live() (which creates the binding for any currently parsed at that point in the script and any elements added in the future).
Did you put $('.rating li').mouseover(function() {
}
in $(document).ready(function() {....} ?
Even if you include a .js file, if the elements in the page ('rating li') are not loaded, the bind will not be made.
Without seeing more of you code, it's difficult to say for sure. But my guess would be that your script is running before the pageload completes. try wrapping your bindings (and anything else that depends on particular dom elements to exist) with a call to $(document).ready(...).
something like this:
$(document).ready( function() {
$('.rating li').mouseover(function() {
// whatever
});
$(".autosuggest").keyup(function() {
// whatever else
});
});
If that's not it, then post more of your code, and we'll dig in further.
good luck.