jQuery Sanity Check - javascript

This is driving me crazy. Please someone tell me I'm not crazy:
var constraints = $('.traffic-constraints :input');
console.log(constraints);
var i;
for (i = 0; i < constraints.length; i++) {
if (constraints[i].val() > 0) { //<-------- errorrzz
....
the console tells me that i do, in fact, have input objects in my selector (5 of them). however, i get the following error: constraints[i].val is not a function
wtf?

First, use .each() it's easier :)
But, to answer the question why doesn't it work...you're dealing with a dom element with that index, you need to wrap it or .eq(i) acesss it:
for (i = 0; i < constraints.length; i++) {
if ($(constraints[i]).val() > 0) {
Or (better, but still use .each()!):
for (i = 0; i < constraints.length; i++) {
if (constraints.eq(i).val() > 0) {

Accessing elements like that (e.g. $('.traffic-constraints :input')[0]) returns the HTML element (or DOM node, to be more pedantic). If you want to get the element jQuery-wrapped, call constraints.eq (i)

jQuery has an .each() method to loop through the elements of a collection. You can use it as such:
$('.traffic-constraints :input').each(function(index) {
if($(this).val() > 0) {
doSomething();
}
});
The reason why your loop isn't working is that the elements are not extended with jQuery's methods. The following fixes it:
var constraints = $('.traffic-constraints :input');
console.log(constraints);
var i;
for (i = 0; i < constraints.length; i++) {
if ($(constraints[i]).val() > 0) {
doSomething();
}
}
But for reasons of code maintainability and best practices, use .each(). It isn't noticeably slower and is easier to maintain and understand.

Why don't you use each?

Related

Javascript Performance improvement

I am Using Jquery plugin to show a dropdown which looks like this
Now in the edit page, this drop-down opens with checked checkboxes, I do this with the help of Javascript which is as below
var setValues = $("#SelectedFrameworks").val().split(",");
for (var i = 0; i < setValues.length; i++) {
var selectedElement = $("#frameworksDropDown").find('*[data-id="' + setValues[i] + '"]');
selectedElement.find("i").addClass("fa-check-square-o").removeClass("fa-square-o");
SelectParent(selectedElement);
}
function SelectParent(_element) {
var count = 0;
for (var i = 0; i < $(_element).parent().children().length; i++) {
if ($(_element).parent().children().eq(i).find("i").attr("class") == "fa select-box fa-check-square-o") {
count++;
}
}
if (count == $(_element).parent().children().length) {
$(_element).closest("ul").siblings("i").click();
}
}
I store this value first in the hidden field then use it to Check the checkboxes. (as shown in the code)
Now the problem is, it takes too much time when data is a lot. this causes the page to hang.
I found that operation
selectedElement.find("i").addClass("fa-check-square-o").removeClass("fa-square-o");
takes too much time. how can I optimize this code to have a better result
EDIT
Here is the HTML for this dropdown.
Note: this HTML is autogenarated.
Thanks.
So one of the big issues with this code is the amount of times you're calling the DOM. Everytime you do $(el) you're calling document.getElementByClassName or id etc. Which is gonna be slow and is unnecessary to make that many calls.
So you can change
function SelectParent(_element) {
var count = 0;
for (var i = 0; i < $(_element).parent().children().length; i++) {
if ($(_element).parent().children().eq(i).find("i").attr("class") == "fa select-box fa-check-square-o") {
count++;
}
}
if (count == $(_element).parent().children().length) {
$(_element).closest("ul").siblings("i").click();
}
}
To this, which accesses the DOM once, stores a reference to the element. This will cut down on the amount of DOM calls you make. The biggest advantage to this is of course, speed. I always make a point of naming jquery variables beginning with $ so that it's much easier and quicker to tell what that variable is in the future, or if someone else comes to work on your code.
function SelectParent(_element) {
var count = 0;
var $element = $(_element);
var $children = $element.parent().children();
for (var i = 0, length = $children.length; i < length; i++) {
if ($children.eq(i).find("i").attr("class") == "fa select-box fa-check-square-o") {
count++;
}
}
if (count == $children.length) {
$element.closest("ul").siblings("i").click();
}
}
Now of course you can refactor the rest to speed it up ;)

How to write Javascript to search nodes - without getElementsByClassName

I'm very new at recursion, and have been tasked with writing getElementsByClassName in JavaScript without libraries or the DOM API.
There are two matching classes, one of which is in the body tag itself, the other is in a p tag.
The code I wrote isn't working, and there must be a better way to do this. Your insight would be greatly appreciated.
var elemByClass = function(className) {
var result = [];
var nodes = document.body; //<body> is a node w/className, it needs to check itself.
var childNodes = document.body.childNodes; //then there's a <p> w/className
var goFetchClass = function(nodes) {
for (var i = 0; i <= nodes; i++) { // check the parent
if (nodes.classList == className) {
result.push(i);
console.log(result);
}
for (var j = 0; j <= childNodes; j++) { // check the children
if (childNodes.classList == className) {
result.push(j);
console.log(result);
}
goFetchClass(nodes); // recursion for childNodes
}
goFetchClass(nodes); // recursion for nodes (body)
}
return result;
};
};
There are some errors, mostly logical, in your code, here's what it should have looked like
var elemByClass = function(className) {
var result = [];
var pattern = new RegExp("(^|\\s)" + className + "(\\s|$)");
(function goFetchClass(nodes) {
for (var i = 0; i < nodes.length; i++) {
if ( pattern.test(nodes[i].className) ) {
result.push(nodes[i]);
}
goFetchClass(nodes[i].children);
}
})([document.body]);
return result;
};
Note the use of a regex instead of classList, as it makes no sense to use classList which is IE10+ to polyfill getElementsByClassName
Firstly, you'd start with the body, and check it's className property.
Then you'd get the children, not the childNodes as the latter includes text-nodes and comments, which can't have classes.
To recursively call the function, you'd pass the children in, and do the same with them, check for a class, get the children of the children, and call the function again, until there are no more children.
Here are some reasons:
goFetchClass needs an initial call after you've defined it - for example, you need a return goFetchClass(nodes) statement at the end of elemByClass function
the line for (var i = 0; i <= nodes; i++) { will not enter the for loop - did you mean i <= nodes.length ?
nodes.classList will return an array of classNames, so a direct equality such as nodes.classList == className will not work. A contains method is better.
Lastly, you may want to reconsider having 2 for loops for the parent and children. Why not have 1 for loop and then call goFetchClass on the children? such as, goFetchClass(nodes[i])?
Hope this helps.

How to get html element's class tags

I'm using a custom modernizer config which has selected the features I employ in my page (and only those features).
So, I'd like to simply grab the className of the <html> of the page so I can check to see how many no- prefixed classes are present (maybe checking classlist.match(/no-/g).length) and determine if my javascript should just give up.
It's not clear whether I should use
document.getElementsByTagName('html').className
or
$('html').attr('class')
or
document.documentElement.className
I will go for:
document.documentElement.className;
Because doesn't involve any function's call, neither an additional layer like jquery. Ideally this one is the cleanest and the fastest.
you can use jQuery hasClass` method:
Determine whether any of the matched elements are assigned the given class.
if ( $('html').hasClass('no-something')) {
// do something here
}
If using plain JS, the equivalent would be:
document.getElementsByTagName('html')[0].className
If you are using jquery in your project why to not use this one:
var classList = $("html").attr('class').split(/\s+/);
var prefix = 'no-';
$.each( classList, function(index, item){
if (item.substring(0, 2) === prefix) {
//do something
}
});
?
var allClasses = [];
var allElements = document.querySelectorAll('*');
for (var i = 0; i < allElements.length; i++) {
if (allElements[i].hasAttribute("class")) {
var classes = allElements[i].className.toString().split(/\s+/);
for (var j = 0; j < classes.length; j++) {
var cls = classes[j];
if (cls && allClasses.indexOf(cls) === -1)
allClasses.push(cls);
}
}
}
console.log(allClasses);

How to run through each checked checkbox with a classname using JavaScript without jQuery

Is there an immediate equivalent in javascript for the below jquery code?
$('.checkbox').each(function() {
if ($(this).is(':checked')) {
//logic here
}
});
I'm trying to run through all the checkboxes on a page with class = 'checkbox' - the client doesn't want to use jQuery, so I need an alternative for the above.
I'm hoping I can avoid writing a long function from scratch to do this and simply use something built-in to JavaScript, but it's looking like it's not possible.
Many older browsers don't support querySelectorAll or getElementsByClassName, so you'd have to loop over all <input> elements in those browsers. It's always best to check for those functions first, though.
Secondly, you should never use $(this).is(":checked") — not even in jQuery — it's a very slow path to take when looking for this.checked.
This should get you going:
var base = document,
inps, tmp, i = 0, reg = /\bcheckbox\b/;
// getElementsByClassName is the fastest method
if (base.getElementsByClassName)
inps = base.getElementsByClassName("checkbox");
// Followed by querySelectorAll
else if (base.querySelectorAll)
inps = base.querySelectorAll(".checkbox");
// But if neither exist, loop through all the elements and check the class
else {
inps = [];
var tmp = base.getElementsByTagName("input");
i = tmp.length;
while (i--) {
if (reg.test(tmp[i].className)
inps.push(tmp[i]);
}
}
// Finally, loop through the matched elements and apply your logic
i = inps.length;
while (i--) {
var current = inps[i];
if (current.checked) {
// logic here
}
}
In the example above, you can change the value of base to any element. This means that, if all these elements have a common parent or ancestor node, you can set that element as the base and it should run faster, e.g:
var base = document.getElementById("myForm");
var checkboxes = document.getElementsByClassName('checkbox');
for(var i = 0; i < checkboxes.length; i++){
if(checkboxes[i].checked){}
else {}
}
See comments below. you can use getElementsByTagName for older versions of IE and other older browsers.
Try the following
var all = document.getElementsByClassName('checkbox');
for (var i = 0; i < all.length; i++) {
var current = all[i];
if (current.checked) {
// Logic here
}
}
JavaScript has built-in methods for getting DOM elements by ID, or by tag name, but selecting by class isn't supported in older versions of IE. However, it would be fairly fast to obtain all inputs and test them for the checkbox type:
var x=document.getElementsByTagName("input");
for (var i=0; i<x.length; i++) {
if (x[i].type === "checkbox" && x[i].checked) {
// do something
}
}
You can also test if their class is "checkbox", but this gets complicated if they have more than one class. If they don't:
var x=document.getElementsByTagName("input");
for (var i=0; i<x.length; i++) {
if (x[i].className === "checkbox" && x[i].checked) {
// do something
}
}
It's kind of browser dependent then. But with a modern browser you'd use document.getElementsByClassName('checkbox') to get an array that you'd iterate through, then is(':checked') becomes the more common if(array[i].checked){}.
Feel free to read about the compatible browsers. You'll find that it doesn't work in Internet Explorer 5.5, 6, and 7.
I think jQuery works around this in sizzle about here.
So it might look like:
var allCheckbox;
if (document.getElementsByClassName) {
allCheckbox = document.getElementsByClassName('checkbox');
} else {
allCheckbox = new Array();
var i = 0;
var all = document.getElementsByTagName('*') {
for (var j=0; j < all.length; j++) {
//Comparison cribbed from sizzle
if ((" " + (all[j].className || all[j].getAttribute("class")) + " ")
.indexOf('checkbox') > -1
) {
allCheckbox[i++] = all[j];
}
}
}
for (var i = 0; i < allChecked.length; i++) {
if (allChecked[i].checked) {
// Logic here
}
}
Final note: getElementsByTagName('*') doesn't work with Internet Explorer 5.5.

Element sort routine works in Firefox, but crashes in Chrome

I've written the following routine to sort the option elements within a select:
function SortSelect(select)
{
var tmpAry = new Array();
for (var i in select.options)
tmpAry[i] = select.options[i];
tmpAry.sort(function(opta, optb)
{
if (opta.id > optb.id) return 1;
if (opta.id < optb.id) return -1;
return 0;
});
while (select.options.length > 0)
select.options[0] = null;
for (var i in tmpAry)
select.appendChild(tmpAry[i]);
}
It works just fine with Firefox as part of a Greasemonkey script. However, in Chrome, both with and without TamperMonkey, I get this:
Uncaught Error: NOT_FOUND_ERR: DOM Exception 8
SortSelect:125
As is typical for Javascript debuggers, the error line number is totally wrong, so it's difficult to pin down exactly why this is breaking and where. I'm open to suggestions as to why the code is bugging out or ways to effectively debug it (I'm new to Chrome). Thanks.
You should iterate over the options collection using an index, not a for..in loop. The following:
for (var i in select.options) {
tmpAry[i] = select.options[i];
should be:
var options = select.options;
for (var i=0, iLen=options.length; i<iLen; i++) {
tmpAry[i] = options[i];
}
You are likely getting properties from the options collection that aren't option elements, such as length.
You also should not assign "null" to an option. If you want to remove all the options, just set the length of options to zero:
var options.length = 0;
Finally, you should iterate over tmpAray using an index since for..in will not return the options in the same order in every browser and may return non-numeric enumerable properties if there are any, use an index. Also, you can just assign the option back to the select's options collection, there is no need for appendChild:
select.options.length = 0;
for (var i=0, iLen=tmpAry.length; i<iLen; i++) {
select.options[i] = tmpAry[i];
}
If you are not removing any options, you should be able to just assign them in the new order, but some browsers can't handle that so removing them first is best.
Edit
Note that while the options property of a select element is readonly, the properties of an options collection are not. You can assign values (which should be references to option elements) directly to them.
What are you trying to do with this piece of code?
while (select.options.length > 0)
select.options[0] = null;
If the answer is you're trying to clear all the select options, that seems dangerous. I could see how this could easily be an infinite loop.
It looks to me like this would be a lot safer:
for (var i = select.options.length - 1; i > 0; i--) {
select.remove(i);
}
Then, there's an select.options.add() method for adding them back.
FYI, it is also considered risky practice to use this construct on arrays or pseudo arrays:
for (var i in select.options)
for (var i in tmpAry)
as that can pick up properties that have been added to the object in addition to just array elements.
More typing, but safer to use:
for (var i = 0, len = tmpAry.length; i < len; i++) {
// code here
}
These lines can cause an infinite loop or an access violation:
while (select.options.length > 0)
select.options[0] = null;
Also, you do not need to delete the nodes and reinsert them; appendChild() works fine in all browsers to move nodes around.
So, this code will work and should be more efficient than removing and recreating nodes (which can also trash any event listeners). :
See it in action at jsFiddle.
function SortSelect (select)
{
var tmpAry = [];
for (var J = select.options.length - 1; J >= 0; --J)
tmpAry.push (select.options[J] );
tmpAry.sort ( function (opta, optb) {
if (opta.id > optb.id) return 1;
if (opta.id < optb.id) return -1;
return 0;
} );
while (tmpAry.length) {
select.appendChild ( document.getElementById (tmpAry[0].id) );
tmpAry.shift ();
}
}

Categories

Resources