jQuery find a class and create callback for its last element - javascript

In 'jQuery' I can execute callback for all elements of a class like this way -
jQuery(form_id).find(".multiple_upldprev").each(function () {
But I want to execute only last element of the class. I tried this way -
jQuery(form_id).find(".multiple_upldprev").last(function () {
It doesn't work.
How can I do that?

last() will reduce the set of matched elements to the final one in the set. And it does not take a handler. You can use:
$(...).last().each(function() {
});
Which usually doesn't make sense since .last() just returns the element:
var $lastElement = $(...).last();
// Do something with $lastElement
In cases where you have a predefined handler each might make sense:
$(...).last().each(myHandler);
// The same can roughly be archived with:
myHandler.call($(...).get(-1));

You dont event need a callback.
Since you are interested only in executing a function on last element, You can just do this with a self executing function and the last element in a variable.
(function(){
var $last = jQuery(form_id).find(".multiple_upldprev").last();
// now use this $last
})();
Or for some reason you want to use callback by using each but still execute the callback function only for the last element then you can do this with the help of .is()..
jQuery(form_id).find(".multiple_upldprev").each(function () {
var isLastElement = $(this).is(".multiple_upldprev:last"); //return true only for last element in loop
if(isLastElement ){
//execute your logic for last element
}
});

Related

How to refresh the jQuery .each() function?

Explaining by example:
$(selector).each(function () {
if (expression) {
$(this).next().remove();
}
....
});
Based on an expression I remove an element that also are a part of the selector, and in effect are removing the next element the .each() function will get.
The .each() function doesn't seem to care and are running it's code on the removed element breaking the iteration process on my code in the each function. Basically meaning a counter in the .each() function will count the deleted element even though it shouldn't.
Is there a way to refresh or update the .each() function so it skips elements that where removed after its initiation?
Better to filter your elements before:
$(selector).filter(function() {
if (expression) {
return false;
}
}).each(function() { .. });
I do not think you can modify the collection from inside the each call.
Even better, you can split this in two calls, since if you do all of it in one call only, the selector will be cached:
$(selector).filter(function() {
return expression;
}).next().remove();
// and then
$(selector).each(function() { ... }); // now *without* removed elements.
The first time you query the DOM with $(selector) you are given back a jQuery collection containing all the elements (specifically, references to the elements) that satisfy that selector. Any changes to the DOM do not affect that collection. If you happen to modify the DOM in such a way that the elements in the collection no longer satisfy the initial selector, this will not be reflected.
The correct solution to this problem is to not modify the DOM in the each loop, and basically find another way to solve your problem. But a simple (computationally expensive) solution is just to recheck the element on the initial selector upon iteration... you can use the .is method to achieve this:
$(selector).each(function () {
var $this = $(this);
if ($this.is(selector)) {
if (expression) {
$this.next().remove();
}
}
....
});

Hide Collection of jQuery elements in Array Using Hide()/Show()

Seemingly-easy problem here: I'm trying to create a streamlined way to hide/show a collection of DOM elements using some jQuery/vanilla JS. This was from a refactor where several fragmented functions were re-done as better encapsulated versions of their former selves.
What these functions are trying to do take elements from an array (by ID), use map to convert them to jQuery objects, and then hide or show them.
Using jQuery 1.11 in an Angular project, but the angular aspect doesn't seem to interfere with this case, since it won't work in a jsFiddle either.
main problem: When I run the function (usually using a click event), I don't get any sort of error from console and I don't get any sort of result in the DOM either. Any ideas? I'm sure it's a simple thing I'm missing, but I need other eyes on it.
Here's a jsFiddle with the below code loaded in, ready for fixin'. Thanks!
http://jsfiddle.net/sLgqvdku/
function showItem(item) {
return item.show();
}
function hideItem(item) {
return item.hide();
}
function showItemsWithIDs(ids) {
ids.map($).forEach(showItem);
}
function hideItemsWithIDs(ids) {
ids.map($).forEach(hideItem);
}
var itemsHiddenToFocusTribute = ['#form', '#ask', "#submitButton", "#sidebar", "#AmountCtrl", "#giftbutton"];
It appears that only the first element in the array is actually being converted into a jQuery object in your code.
Here's what's happening: vanilla-JS .map passes three arguments to the specified callback function: the current element, the index, and the array.
If the callback function takes only one argument, the second and third are ignored. However, jQuery's $ actually allows two arguments: a selector, and a context (container element). So your code is passing (as the second argument) the array index as a context, resulting in an empty jQuery object -- except for the first element in itemsHiddenToFocusTribute, which has index 0 which is interpreted as no context at all.
You can fix this with an anonymous function that only passes the selector string to $:
function hideItemsWithIDs(ids) {
ids.map(function (i) {
return $(i);
}).forEach(hideItem);
}
http://jsfiddle.net/mblase75/e23qgad5/
However, a more jQuery-friendly way would be to create a single jQuery object of all the desired elements and loop through them using .each:
function hideItem(i,item) {
return $(item).hide();
}
function hideItemsWithIDs(ids) {
$(ids.join()).each(hideItem);
}
http://jsfiddle.net/mblase75/mm2L4xn1/
This is probably more efficient, too, since you're calling $ just once instead of array.length times.
All you're wanting is to send each id through the foreach loop? Then I'd just use each like so:
$(ids).each(function(index, id) {
hideItem(id);
});
You don't need to use map($) to convert them to jQuery objects, just put the object inside the dollar sign function call, like so: $(ids).
Also make sure you pass the actual id to showItem and hideItem when you call them, like so: hideItem(id). You also need to make sure that you use a jQuery object in your hideItem and showItem functions. I changed your code to this:
function showItem(item) {
return $(item).show();
}
function hideItem(item) {
return $(item).hide();
}
function showItemsWithIDs(ids) {
$(ids).each(function(index, id) {
showItem(id);
});
}
function hideItemsWithIDs(ids) {
$(ids).each(function(index, id) {
hideItem(id);
});
}
var itemsHiddenToFocusTribute = ['#form', '#ask', "#submitButton", "#sidebar", "#AmountCtrl", "#giftbutton"];
$('#clicker').click(function(){
hideItemsWithIDs(itemsHiddenToFocusTribute);
});
And here's the updated Fiddle

TypeError: <Array>.each is not a function

I have three references to three drop downs on my page, and as each one is changed, I want to run a JavaScript function called validateForm();
My code is below:
jQuery(document).ready(function() {
var drpSupplier = document.getElementById('supplier');
var drpChargeRate = document.getElementById('formElementChargeRate');
var drpIDSEmail = document.getElementById('formElementEmailIDS');
var formLevel2DDs = new Array();
formLevel2DDs.push(drpSupplier);
formLevel2DDs.push(drpChargeRate);
formLevel2DDs.push(drpIDSEmail);
formLevel2DDs.each(function() {
$(this).change(function() {
validateForm()
});
});
});
But this code is giving me the error:
TypeError: formLevel2DDs.each is not a function
I am using jQuery version 1.8.3 (it is a legacy system).
There is no each function on arrays.
As Anton points out in the comments, you don't need each at all for what you're doing; see below the fold.
But if you want each, you have three choices:
Wrap your array in a jQuery instance and use jQuery's each: $(formLevel2DDs).each(function(index, entry) { ... });
Use jQuery's $.each: $.each(formLevel2DDs, function(index, entry) { ... });
Note that this is not the same function as above.
Use forEach (MDN | Spec): formLevel2DDs.forEach(function(entry, index, array) { ... });
Note that forEach is new as of ECMAScript5. All modern browsers have it, but you'll need a shim/polyfill for older ones (like IE8). Also note that the order of the arguments to the callback is different than either of the options above.
But to Anton's point, you can do that much more simply:
There's no reason to use getElementById directly in this case, it's not in a tight loop or anything, so:
jQuery(document).ready(function() {
$("#supplier, #formElementChargeRate, #formElementEmailIDS").change(validateForm);
});
Note that I've also removed the wrapper function from around validateForm. You may need to add it back if validateForm has a return value, and you don't want that return value to be used by jQuery (specifically: if it returned false, jQuery would stop propagation and prevent the default action of the change event).
If you really wanted to have direct access to the DOM elements using those variables:
jQuery(document).ready(function() {
var drpSupplier, drpChargeRate, drpIDSEmail;
var formLevel2DDs = [
drpSupplier = document.getElementById('supplier'),
drpChargeRate = document.getElementById('formElementChargeRate'),
drpIDSEmail = document.getElementById('formElementEmailIDS')
];
$(formLevel2DDs).change(validateForm);
});
If you want to use .each() you must wrap the array with jQuery like this
$(formLevel2DDs).each(function() {
It's not necessary to use a loop in this case, just use .change() on the array wrapped with jQuery
$(formLevel2DDs).change(function(){
validateForm()
});
The native iterator function is forEach, not each.
The jQuery .each function takes just one argument, that being the callback. The callback function is passed two parameters: the index into the list, and the value. The callback is invoked such that the list value is also the this value.
With forEach, the parameters are passed in reverse order: the value is first, followed by the index. The native function also passes the entire array as the third parameter. The native function does not bind this when the callback is invoked unless a second parameter is passed to .forEach after the callback function. If there is such a parameter, it is used as the value of this in the callback.
The native iterator skips elements of the array that have not been set. The jQuery .each() does not skip such elements, instead always iterating from index 0 to length - 1.
try
$(formLevel2DDs).each(function() {
$(this).change(function() {
validateForm()
});
});
added one line
jQuery(document).ready(function() {
var drpSupplier = document.getElementById('supplier');
var drpChargeRate = document.getElementById('formElementChargeRate');
var drpIDSEmail = document.getElementById('formElementEmailIDS');
var formLevel2DDs = new Array();
formLevel2DDs.push(drpSupplier);
formLevel2DDs.push(drpChargeRate);
formLevel2DDs.push(drpIDSEmail);
formLevel2DDs = jQuery(formLevel2DDs);//this line
formLevel2DDs.each(function() {
$(this).change(function() {
validateForm()
});
});
});
use a for ... in loop
for(key in formLevel2DDs) { ... }

Need a javascript onclick decorator

I have legacy code that includes links with hard-coded onclick handlers (bad, I know).
link
I need to decorate that onclick function. In other words, I need to to replace the hard-coded onclick function with another function of my own that will first call the original function and then allow me to call my own code.
This is what I've tried, to no avail:
var oldFunc = $('a.linkClass').attr('onclick');
function newFunc(oldFunc) {
return function(oldFunc) {
oldFunc();
// my add'l code
}
}
$('a.linkClass').attr('onclick', '').unbind('click');
$('a.linkClass').click(newFunc(oldFunc));
First issue is need proper selector to find the onclick elements. $('[onclick]') will find all elements in page with that attribute. Also you need to remove the onclick attribute, trying to unbind with jQUery won't work
Assuming each onclick only has one function you could try:
$('[onclick]').each(function(){
var $el=$(this)
var funcName=$el.attr('onclick').replace('()','');
$el.click(window[funcName]).removeAttr('onclick');
});
Just realizing this won't work if any params in function
You are trying to wrap multiple events, but to do that you need to loop over the elements and wrap each one in turn (javascript can't store multiple functions in a single variable like that!) Also you get a string from onclick, so you'll need to eval it (which is generally a BadThing (tm). But the real bad thing here is that you're using onclick in the first place.
var elementsToFix = $('a.linkClass');
for( var i = 0; i < elementsToFix.length; ++ i ) {
var obj = elementsToFix.eq( i );
var oldFnc = obj.attr( 'onclick' );
obj.removeAttr( 'onclick' ).on( 'click', (function( curOldFnc ) {
return function( ) {
// pre-wrapper here
eval( curOldFnc );
// post-wrapper here
}
}( oldFnc )) );
}
This uses the super-evil eval. That's bad, and the only reason I'm using it is because you get a string from attr('onclick') instead of a function. The correct solution is to never use onclick in the first place.
jsfiddle example: http://jsfiddle.net/Jdn6V/1
As the function called from the onclick handler is in the global scope, the easy solution would be to just remove the onclick handler, store the string, and eval it later.
Of course eval is bad, and there are ways to parse the string, remove the parenthesis and keep any arguments, and call it as window[oldFunc] etc. but as a quick example, here it is with eval (which is evil)
$('a.linkClass').each(function() {
var old = $(this).attr('onclick');
$(this).attr('onclick', '').on('click', function(e) {
e.preventDefault();
eval.apply(window, [old]);
alert('new');
});
});
FIDDLE
without eval
$('a.linkClass').each(function() {
var old = $(this).attr('onclick'),
func = old.split('(')[0],
param = old.split('(')[1].replace(')','').split(',');
$(this).attr('onclick', '').on('click', function(e) {
e.preventDefault();
window[func].apply(window, param);
alert('new')
});
});
FIDDLE
.attr() always returns a string, so you'd have to eval it to execute the function.
You can get the original function this way, and not need to eval it later:
var oldFunc = $('a.linkClass')[0].onclick;
([0] picks the "pure" DOM element that has the onclick function.)
Now you can bind the function to the element:
$('a.linkClass').click( function(e) {
// new code here
// ...
oldFunc(e);
});
(e given as the parameter, just in case the old function uses it.)
The additional benefit of this is that it works regardless of what's in the old onclick attribute because it's wrapped in a function, so e.g. onclick="foo(); bar(); return false;" works correctly.

jQuery set call back on child elements

I am trying to attach an onChange callback to all the input elements under the div #dim. It selects all 3 input elements, but returns an exception:
Uncaught TypeError: Object 0 has no method 'change'
It may be because x may not be a jQuery object. How would I make this work?
function registercb() {
var sel = $("div.dim > input");
for (x in sel) {
x.change(function() {
dosomething();
});
}
}
You can simply do:
function registercb() {
$("div.dim > input").change(dosomething);
}
A few things to watch for:
Inside that iteration (don't use this, see the next point) x is the DOM element, not a jQuery object which has the .change() method, you would need to wrap it in a jQuery object like $(x), but again that isn't the correct solution here.
Don't use a for(...in...) loop to iterate an object (a jQuery object is array-like), that type of loop is for enumeration.
Most jQuery functions (almost all) run on more than one element, so just run it on the set to affect all elements, .change() is one of these.
In the cases you do need to loop, check out .each(), as it'll make your life much easier, don't use this here, it's only an example of what it would look like:
Example:
function registercb() {
$("div.dim > input").each(function() {
$(this).change(dosomething);
});
}
You don't have to loop over the elements. You can think of a jQuery object as holding a collection. When you do:
var sel = $("div.dim > input");
It means that sel has all the input elements in it, so then when you run a method like change() it will affect all of the elements in the collection. Thus you can do something like this:
function registercb() {
$("div.dim > input").change(function(){
dosomething();
});
}
Bonus knowledge: Now your problem is that when you were doing for( x in sel ) you are getting a lot of stuff on the jQuery object itself that you don't want. If you run the following code in chrome you'll see it outputting a lot unexpected stuff:
for( x in sel ){
console.log( x );
}
Instead jQuery has the each that lets you loop over the things you want:
sel.each(function(index, item){
console.log(item);
});
You can even use it on other things, which is really handy!
$([1,2,3]).each(function( index item ){
console.log( item ); // 1,2,3
})
Assuming your 'dim' div has an ID rather than a class of dim, you can simply do this:
$("#dim > input").change(function() { dosomething(); });
Working example.
In the text you refer to #dim whereas in the code you're refering to .dim - # selects by ID and . selects by class, so if your div is in the format <div id="dim"> then you won't find any matched elements with div.dim as your selector.

Categories

Resources