Patterns for avoiding jQuery silent fails - javascript

Is there any good practice to avoid your jQuery code silently fail?
For example:
$('.this #is:my(complexSelector)').doSomething();
I know that every time this line get executed, the selector is intended to match at least one element, or certain amount of elements. Is there any standard or good way to validate that?
I thought about something like this:
var $matchedElements = $('.this #is:my(complexSelector)');
if ($matchedElements.length < 1)
throw 'No matched elements';
$matchedElements.doSomething();
Also I think unit testing would be a valid option instead of messing the code.
My question may be silly, but I wonder whether there is a better option than the things that I'm currently doing or not. Also, maybe I'm in the wrong way checking if any element match my selector. However, as the page continues growing, the selectors could stop matching some elements and pieces of functionality could stop working inadvertently.

You could write a plugin:
jQuery.fn.requireElements = function (amount, exactMatch) {
if (amount === undefined) {
amount = 1;
};
if (this.length < amount || this.length > amount && exactMatch) {
throw new Error(this.length " elements instead of " (exactMatch ? "at least " : "") + amount);
};
return this;
};
Then:
$('yourSelector').requireElements(2).bind('click', function () {
});

It's not necessarily wrong when a selector matches nothing; it depends on your application. You could write your own validateNonEmpty plugin:
jQuery.fn.validateNonEmpty = function() {
if (this.length == 0)
throw "Empty list detected - selector: " + this.selector;
return this; // thanks #Matt
};
Then you can do:
$('something something').validateNonEmpty().doSomething();
Also, note that you want to check to see if the length is 0, not less than 0.

I would recommend using jQuery lint.
for example
$(function(){
var foo = $('.this #is:my(complexSelector)');
});
will complain both that you have an invalid selector (as given example selector is in fact invalid, though I assume you know that :)), and that no elements where in fact found.
See http://js.azatoth.net/test/lint.html for an example.

Related

Why does this dynamic programming optimization actually make code slower?

This is from Leetcode problem: Concatenated Words.
Below is a working solution. I added what I thought to be an optimization (see code comment), but it actually slows down the code. If I remove the wrapping if statement, it runs faster.
To me, the optimization helps avoid having to:
call an expensive O(n) substring()
check inside wordsSet
making an unnecessary function call to checkConcatenation
Surely if (!badStartIndices.has(end + 1)) isn't more expensive than all the above, right? Maybe it has something to do with Javascript JIT compilation? V8? Thoughts?
Use the following test input:
// Notice how the second string ends with a 'b'!
const words = [
'a',
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab',
];
// Function in question.
var findAllConcatenatedWordsInADict = function (words) {
console.time();
// 1) put all words in a set
const wordsSet = new Set(words);
let badStartIndices;
// 2) iterate words, recursively check if it's a valid word
const concatenatedWords = [];
function checkConcatenation(word, startIdx = 0, matches = 0) {
if (badStartIndices.has(startIdx)) {
return false;
}
if (startIdx === word.length && matches >= 2) {
concatenatedWords.push(word);
return true;
}
for (let end = startIdx; end < word.length; end++) {
// I ADDED THE IF STATEMENT AS AN OPTIMIZATION. BUT CODE RUNS FASTER WITHOUT IT.
// NOTE: Code is correct with & without if statement.
if (!badStartIndices.has(end + 1)) {
const curWord = word.substring(startIdx, end + 1);
if (wordsSet.has(curWord)) {
if (checkConcatenation(word, end + 1, matches + 1)) {
return true;
}
}
}
}
badStartIndices.add(startIdx);
return false;
}
for (const word of words) {
// reset memo at beginning of each word
badStartIndices = new Set();
checkConcatenation(word);
}
console.timeEnd();
return concatenatedWords;
};
Turns out this depends entirely on the input data, not on JavaScript or V8. (And as of writing this, I don't know what data you used for benchmarking.)
With the example input from the Leetcode page you've linked, badStartIndices never does anything useful (both of the .has checks always return false); so it's fairly obvious that doing this fruitless check twice is a little slower than doing it just once. In that case, the "dynamic programming" mechanism of the solution never kicks in, so the effective behavior degenerates to brute force, which is good enough because the input data is well-behaved. (In fact, deleting badStartIndices entirely would be even faster for such a test case.)
If I construct "evil" input data that actually leads to exponential combinatorial blow-up, i.e. where the badStartIndices.has(...) checks actually have something to do, then adding the early check does have a (small) performance benefit. (And without either of the checks, the computation would take "forever" for such inputs.)
So, taking a step back, this is one more example to illustrate that benchmarking is difficult; in particular, in order to get useful results, care must be taken to select relevant/realistic input data.
If the tests are too simple, developers are likely to not build optimizations that would help (a little or a lot) in high-load situations.
If the tests are too demanding, developers are likely to waste time on overly complicated code that ends up being slower than it could be for its target use case.
And if the code must handle any input with maximum performance, then as the developer you have the extra challenge of avoiding overhead for simple inputs while still scaling well to tough inputs...

Unchecking and simulating a click on checkboxes in Javascript

I am admittedly a super newbie to programming in general. I am trying to design a quick piece of javascript to inject on a website for a class that will both uncheck and simulate a click on a series of checkboxes. This is nothing malicious, the web form we use to download data for use in this class presents way more variables than necessary, and it would be a lot more convenient if we could 'uncheck' all and only check the ones we want. However, simply unchecking the boxes via javascript injection doesn't yield the desired result. A mouse click must be simulated on each box. I have been trying to use the .click() function to no avail. Any help is greatly appreciated. My code below fails with an error of:
"TypeError: Cannot read property 'click' of null"
CODE:
var getInputs = document.getElementsByTagName("input");
for (var i = 0, max = getInputs.length; i < max; i++){
if (getInputs[i].type === 'checkbox')
getInputs[i].checked = false;
document.getElementById('shr_SUBJECT=VC' + i).click();
}
--------EDIT#1--------------
FYI, this is the website that I am trying to use this on:
http://factfinder2.census.gov/faces/nav/jsf/pages/searchresults.xhtml
if you search for and open up any of these tables they are huge. It would be awesome if I could easily pare down the variables by 'unchecking' and 'clicking' them all at once via javascript.
The code at the bottom ALMOST works.
The problem I am running into now is that it throws an error after the first or second run through the for loop:
"TypeError: document.getElementById(...) is null"
I understand that this is because the value it's trying to find doesn't exist? Sometimes on these tables the checkboxes are greyed out/don't exist or are otherwise 'unclickable'. My theory as to why I am getting this error is because in the table/form the 'available' ID's will start around:
shr_SUBJECT=VC03 or sh_SUBJECT=VC04
and it may then skip to:
shr_SUBJECT=VC06 then skip to shr_SUBJECT=VC09 and so on...
So if the for loop hits an ID that isn't available such as 05 or 07, it returns a null error :(
I did some reading and learned that javascript is able to 'catch' errors that are 'thrown' at it? My question now is that I'm wondering if there is an easy way to simply iterate to the next ID in line if this error is thrown.
Again, any and all help is appreciated, you guys are awesome.
OLD DRAFT OF SCRIPT
var getInputs = document.getElementsByTagName("input");
for (var i = 3, max = getInputs.length; i < max; i++){
if (getInputs[i].type === 'checkbox' && i < 10){
var count = i;
var endid = count.toString();
var begid = "shr_SUBJECT=VC0";
var fullid = begid.concat(endid);
document.getElementById(fullid).click();
}
else if(getInputs[i].type === 'checkbox' && i >= 10){
var count = i ;
var endid = count.toString();
var begid = "shr_SUBJECT=VC";
var fullid = begid.concat(endid);
document.getElementById(fullid).click();
}
}
--------EDIT#2----------
An example of a table that I am trying to manipulate can be found at this URL:
http://factfinder2.census.gov/faces/tableservices/jsf/pages/productview.xhtml?pid=ACS_12_5YR_DP02&prodType=table#
If you click on the 'Modify Table' button, you are able to select/deselect specific variables via the checkboxes. If you right-click on a couple of 'active' checkboxes and inspect the elements, and it looks something like this:
<input id="shr_SUBJECT=VC03" checked="" alt="hide SUBJECT=VC03" name="" value="" onclick="javascript:hiderow('SUBJECT=VC03');" type="checkbox">
<input id="shr_SUBJECT=VC25" checked="" alt="hide SUBJECT=VC25" name="" value="" onclick="javascript:hiderow('SUBJECT=VC25');" type="checkbox">
Thank you so much #Jonathan Steinbeck for the tip about the ternary operator, it really cleaned up my code.
The script works properly, but the problem I am running into now is that it doesn't iterate enough times after the try, catch statement. If there is a gap in the id #'s; say it jumps from shr_SUBJECT=VC19 to shr_SUBJECT=VC=24 the script will stop running. Is there a way to make it keep retrying the try/catch until it gets a valid ID # or one that exists/is an active checkbox?
CURRENT DRAFT OF SCRIPT :
var getInputs = document.getElementsByTagName("input");
for (var i = 3, max = getInputs.length; i < max; i += 1) {
try {
if (getInputs[i].type === 'checkbox'){
document.getElementById("shr_SUBJECT=VC" + (i < 10 ? "0" : "") + i).click();
}
}
catch (err) {
i+=1;
if (getInputs[i].type === 'checkbox'){
if (getInputs[i].type === 'checkbox'){
document.getElementById("shr_SUBJECT=VC" + (i < 10 ? "0" : "") + i).click();
}
}
}
}
When you call document.getElementById() with a non-existing ID, null is returned. Therefore this error means that you're trying to call the .click() method on null, which can't work.
So you should check what the correct ID naming scheme for the elements you want is. Maybe the elements' count starts with 1 instead of 0?
Also, the .click() doesn't work for all elements like you would expect as far as I know. So depending on the kind of element you are trying to retrieve you might have to create and dispatch your own event as suggested by RobG's comment.
EDIT in response to your recent edit:
You can wrap code that throws errors in a try-catch like this:
for (var i = 3, max = getInputs.length; i < max; i += 1) {
try {
document.getElementById("the_ID").click();
}
catch (error) {
console.error(error);
// continue stops the current execution of the loop body and continues
// with the next iteration step
continue;
}
// any code here will only be executed if there's not been an error thrown
// in the try block because of the continue in the catch block
}
Also, what are you doing with the 'i' variable? It doesn't make sense to assign it to so many variables. This does the same:
document.getElementById("shr_SUBJECT=VC" + (i < 10 ? "0" : "") + i).click();
The ... ? ... : ... is an operator (called the 'ternary operator') that works like this: evaluate the expression before the "?" - if it results in a truthy value, the expression between "?" and ":" is evaluated and becomes the result of using the operator; if the condition results to false, the part after the ":" is evaluated as the value of the operator instead. So while "if" is a statement in JavaScript (and statements usually don't result in a value), the ternary operator can be used as an expression because it results in a value.
By concatenating a string with something else, you are forcing the 'something else' to be converted to string. So an expression like this will usually result in a string:
"" + someNonStringVar
Also, it doesn't make sense to define variables in a loop body in JavaScript. JavaScript variables have function scope, not block scope. What this means is that any variables defined in the loop body exist inside the whole function as well. Therefore it is recommended to write all of the "var"s at the top of your function to make it clear what their scope is. This behaviour of JavaScript is called 'hoisting', by the way.
I've furthermore taken a look at the URL you've given in your recent edit but I fail to find the kind of naming scheme for IDs you describe. In which table did you find those?
Edit in response to your second edit:
You shouldn't mess with the 'i' variable inside the body of a for loop. It makes your code much harder to reason about and is probably not what you want to do anyway. You don't need to handle the next step of the iteration in the catch block. The 'i' variable is incremented even if there's an error during fetching the element from the DOM. That's why you use catch in the first place.

Stripping useless elements from the DOM

I want to strip all elements within the DOM that contains useless spaces or are empty (I'm working with an outdated back-end system that generates some messy HTML and these elements can sometimes add unwanted spaces in the design). Using the filter function I'm able to test for specific cases but for some reason it doesn't seem to work with empty elements so I tried to test to see if any elements length is < 0 and then remove it. Why doesn't this work, and is there a way to do it with the filter function? I've tried
("<br>", "", " ");
but it doesn't seem to work.
stripEmpties();
function stripEmpties() {
var domChildren = $("*").children();
if (domChildren.length <= 0) {
$(this).remove();
} else {
domChildren.filter(function () {
return $.trim(this.innerHTML) === ("<br>", " ");
}).remove();
}
}
Actually after playing with it I now see that only the last match is even used in the filter so it looks like I can't use this list format I've come up with. So I guess another question would be is there a way to get similar functionality while checking multiple cases with the filter function?
It's not as easy as it sounds and it certainly is not efficient if the document is large. For example this will remove all br, img (tags with no innerHtml) etc tags aswell:
$(function () {
$("*").filter(function () {
return ($(this).text().trim() === "");
}).remove();
});
So maybe you should use a better selector than "*", for example "div".

Javascript taking too long to run

I have a script that is taking too long to run and that is causing me This error on ie : a script on this page is causing internet explorer to run slowly.
I have read other threads concerning this error and have learned that there is a way to by pass it by putting a time out after a certain number of iterations.
Can u help me apply a time out on the following function please ?
Basically each time i find a hidden imput of type submit or radio i want to remove and i have a lot of them . Please do not question why do i have a lots of hidden imputs. I did it bc i needed it just help me put a time out please so i wont have the JS error. Thank you
$('input:hidden').each(function(){
var name = $(this).attr('name');
if($("[name='"+name+"']").length >1){
if($(this).attr('type')!=='radio' && $(this).attr('type')!=='submit'){
$(this).remove();
}
}
});
One of the exemples i found : Bypassing IE's long-running script warning using setTimeout
You may want to add input to your jquery selector to filter out only input tags.
if($("input[name='"+name+"']").length >1){
Here's the same code optimised a bit without (yet) using setTimeout():
var $hidden = $('input:hidden'),
el;
for (var i = 0; i < $hidden.length; i++) {
el = $hidden[i];
if(el.type!=='radio' && el.type!=='submit'
&& $("[name='" + el.name + "']").length >1) {
$(el).remove();
}
}
Notice that now there is a maximum of three function calls per iteration, whereas the original code had up to ten function calls per iteration. There's no need for, say, $(this).attr('type') (two function calls) when you can just say this.type (no function calls).
Also, the .remove() only happens if three conditions are true, the two type tests and check for other elements of the same name. Do the type tests first, because they're quick, and only bother doing the slow check for other elements if the type part passes. (JS's && doesn't evaluate the right-hand operand if the left-hand one is falsy.)
Or with setTimeout():
var $hidden = $('input:hidden'),
i = 0,
el;
function doNext() {
if (i < $hidden.length) {
el = $hidden[i];
if(el.type!=='radio' && el.type!=='submit'
&& $("[name='" + el.name + "']").length >1) {
$(el).remove();
}
i++;
setTimeout(doNext, 0);
}
}
doNext();
You could improve either version by changing $("[name='" + el.name + "']") to specify a specific element type, e.g., if you are only doing inputs use $("input[name='" + el.name + "']"). Also you could limit by some container, e.g., if those inputs are all in a form or something.
It looks like the example you cited is exactly what you need. I think if you take your code and replace the while loop in the example (keep the if statement for checking the batch size), you're basically done. You just need the jQuery version of breaking out of a loop.
To risk stating the obvious; traversing through the DOM looking for matches to these CSS selectors is what's making your code slow. You can cut down the amount of work it's doing with a few simple tricks:
Are these fields inside a specific element? If so you can narrow the search by including that element in the selector.
e.g:
$('#container input:hidden').each(function(){
...
You can also narrow the number of fields that are checked for the name attribute
e.g:
if($("#container input[name='"+name+"']").length >1){
I'm also unclear why you're searching again with $("[name='"+name+"']").length >1once you've found the hidden element. You didn't explain that requirement. If you don't need that then you'll speed this up hugely by taking it out.
$('#container input:hidden').each(function(){
var name = $(this).attr('name');
if($(this).attr('type')!=='radio' && $(this).attr('type')!=='submit'){
$(this).remove();
}
});
If you do need it, and I'd be curious to know why, but the best approach might be to restructure the code so that it only checks the number of inputs for a given name once, and removes them all in one go.
Try this:
$("[type=hidden]").remove(); // at the place of each loop
It will take a short time to delete all hidden fields.
I hope it will help.
JSFiddle example

How to search DOM to count the number of $ symbol found on a product page?

I am looking to find the best possible way to find how many $ symbols are on a page. Is there a better method than reading document.body.innerHTML and calc how many $-as are on that?
Your question can be split into two parts:
How can we get the the webpage text content without HTML tags?
We can generalize the second question a bit.
How can we find the number of string occurrences in another string?
And the 'best possible way to do this':
Amaan got the idea right of finding the text, but lets take it further.
var text = document.body.innerText || document.body.textContent;
Adding textContent to the code helps us cover more browsers, since innerText is not supported by all of them.
The second part is a bit trickier. It all depends on the number of '$' symbol occurrences on the page.
For example, if we know for sure, that there is at least one occurrence of the symbol on the page we would use this code:
text.match(/\$/g).length;
Which performs a global regular expression match on the given string and counts the length of the returned array. It's pretty fast and concise.
On the other hand, if we're not sure if the symbol appears on the page at least once, we should modify the code to look like this:
if (match = text.match(/\$/g)) {
match.length;
}
This just checks the value returned by the match function and if it's null, does nothing.
I would recommend using the third option only when there is a large occurrence of the symbols in the page or you're going to perform the search many many times. This is a custom function (taken from here) to count the occurrence of the specified string in another string. It performs better than the other two, but is longer and harder to understand.
var occurrences = function(string, subString, allowOverlapping) {
string += "";
subString += "";
if (subString.length <= 0) return string.length + 1;
var n = 0,
pos = 0;
var step = (allowOverlapping) ? (1) : (subString.length);
while (true) {
pos = string.indexOf(subString, pos);
if (pos >= 0) {
n++;
pos += step;
} else break;
}
return (n);
};
occurrences(text, '$');
I'm also including a little jsfiddle 'benchmark' so you can compare these three different approaches yourself.
Also: No, there isn't a better way of doing this than just getting the body text and counting how many '$' symbols there are.
You should probably use document.body.innerText or document.body.textContent to avoid getting your HTML give you false positives.
Something like this should work:
document.body.innerText.match(/\$/g).length;
An alternate way I can think of, would be to use window.find like this:
var len = 0;
while(window.find('$') === true){
len++;
}
(This may be unreliable because it depends on where the user clicked last. It will work fine if you do it onload, before any user interaction.)

Categories