Javascript taking too long to run - javascript

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

Related

Laggy string search, how can it be improved?

I have a function that i use to search for a spesific string in the innerText of elements with a certain class name. When it finds a match it gives that element a display of block to show and if not display none to hide. When i use this to search (i have about 3 thousand elements with same class) it takes a few seconds (about 5s) before it completes. Is there a more performance based way to write this code and maybe cut a second or two?
function searchClass(ClassName, Value){
var c = document.getElementsByClassName(ClassName);
var val = Value.toLowerCase().trim();
for(var i=0;i<c.length;i++){
if(c[i].innerText.toLowerCase().trim().indexOf(val) > -1){
c[i].style.display = "block";
}else{
c[i].style.display = "none";
}
}
}
PS. there is a keyup event that triggers the function.
EDIT: (solution) My search is instantly alot faster just by changing innerText to textContent and some small changes thanks to comments!
pre-increment is faster than post-increment when you don't need the previous value, and you don't, so switch to pre-increment in the for loop (although id expect today's competitve js engines to auto-optimize this, gcc has done this for ages on -O1 and higher)
also, unless you have a lot of script elements, textContent should be a lot faster than innerText, so switch to textContent... other than that, i don't see much room for improvement

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.

Password Strength Meter

I'm trying to create my own JS Password Strength Meter.
It was working before but i didn't like how it worked so I tried using
{score +=10;}
Instead of just:
score++
This is my code:
http://jsfiddle.net/RSq4L/
Best Regards,
Shawn,
Hope someone can help
Multiple issues:
Your passwordStrength() function was not defined in the global scope in the jsFiddle so it wasn't getting called. This is probably an artifact of how you set up the jsFiddle, perhaps not an issue in your real code.
The method of getting the appropriate ratingMsg will not work because you don't have array values for every possible score so many scores will generate an "undefined" ratingMsg.
Your CSS classes are also sparse so there are many score values that they will not match for either and no appropriate CSS class/style will be in effect. If you want a specific class for each rating value, then perhaps you should put the classname in the ratings array so it can be fetched from there along with the ratingsMsg.
For the first issue, in your jsFiddle, you also have to make sure the password processing function is defined in the global scope. The way your jsFiddle is set up, it is not (it's in the onload handler). You can fix this in the jsFiddle by just setting the first drop-down in the upper left to "no wrap (head)".
For the second issue, you are using:
ratingMsg[score]
but, your array is a sparse array not guaranteed to have an entry for most possible scores. You simply can't do it that way because many elements you access will have undefined values which won't give you a meaningful message. For example, if score was 15, you would be accessing ratingMsg[15], but there is no value in that space in the array so you won't get a meaningful rating message.
The solution is to find a different way to select the right message. The simplest way would just be an if/else if/else if statement that would check which range the score is in and set the appropriate msg. There are more elegant table driven ways, but all will involve searching through a data structure to find which two values the current score is between and using that msg.
If you look at this jsFiddle http://jsfiddle.net/jfriend00/dA7XC/, you'll see that your code is getting called, but it only hits values in the array sometimes.
And, here's a rewritten algorithm that finds the appropriate msg no matter what the score show in this fiddle: http://jsfiddle.net/jfriend00/jYcBT/.
It uses a data structure like this:
var ratingMsg = [
0, "Unclassified",
10, "Weak",
20, "Fair",
50, "Better",
60, "Medium",
70, "Good",
90, "Strong"
];
and a for loop like this to get the appropraite ratingMsg:
for (var i = ratingMsg.length - 2 ; i >= 0; i-=2) {
if (score >= ratingMsg[i]) {
msg = ratingMsg[i+1];
break;
}
}
Here you go: http://jsfiddle.net/RSq4L/11/
The first problem is that in your fiddle you have the onLoad option set, so your passwordStrength function is not actually being declared in the global scope. It is being declared inside of the onLoad block that jsFiddle wraps your code with. This causes the page to error out when the keypress handler tries to invoke the function.
You can fix this problem in several different ways:
By explicitly declaring the function as global as per my example above.
By choosing one of jsFiddle's "no wrap" options instead of onLoad.
By dynamically binding your event-handler instead of setting it through the element's onkeydown attribute in the markup.
The second problem is how you are keying your score messages. You have:
var ratingMsg = new Array(0);
ratingMsg[0] = "Unclassified";
ratingMsg[10] = "Weak";
ratingMsg[30] = "Fair";
ratingMsg[50] = "Better";
ratingMsg[60] = "Medium";
ratingMsg[70] = "Good";
ratingMsg[90] = "Strong";
...and you lookup the message by doing ratingMsg[score]. This will only work if the score exactly matches one of your indices. And based upon your math this will not always be the case.
I would suggest doing something like:
ratingMsg = {};
ratingMsg[0] = "Unclassified";
ratingMsg[10] = "Weak";
ratingMsg[30] = "Fair";
ratingMsg[50] = "Better";
ratingMsg[60] = "Medium";
ratingMsg[70] = "Good";
ratingMsg[90] = "Strong";
function closestRating(score) {
var bestKey = 0;
var bestMatch = 100;
for (var key in ratingMsg) {
if (key <= score && score - key < bestMatch) {
bestMatch = score - key;
bestKey = key;
}
}
return ratingMsg[bestKey];
}
On an unrelated note, are you sure you want to be using onkeydown? I think onkeyup would work better.
Your fiddler script had several errors. Here's the corrected one: new script.
You were missing a semicolon here: document.getElementById("passwordDescription").innerHTML = "" + ratingMsg[score] + ""
You forgot to escape '^' on your regular expression
I just wrote this for it:
Jquery Plugin for password strength forcing

Disabling Checkboxes after specified amount are selected

I have been working on a page that will allow entry into a certain part of my website if the user selects 8 out of 25 checkboxes in the right sequence.
Here is what I have working so far CLICK HERE TO SEE A LIVE VERSION
My question is, how can I completely disable the rest of the checkboxes after 8 have been chosen, so far I am using javascript to keep count, and I have an alert popup keeping them from selecting any more, but I would like to completely disable the rest of the checkboxes if possible.
I have simplified your code and it's here.
and this will answer your question,
//update checkCount
checkCount = $(':checked').length;
if (checkCount >= maxChecks) {
//alert('you may only choose up to ' + maxChecks + ' options');
$(':checkbox[name=checkbox]').not(':checked').attr('disabled', true);
} else {
$(':checkbox[name=checkbox]:disabled').attr('disabled', false);
}
where you have the alert right now, use this bit of jQuery:
$('input[name=checkbox]:not(:checked)').attr('disabled','disabled');
See a fork of your code with this working: http://jsfiddle.net/fKwEQ/
You can do this by modifying the setChecks method as follows:
function setChecks(obj) {
if(obj.checked) {
checkCount = checkCount + 1;
//note: modify this selector to specify the actual table in question,
//of course. If this is not known beforehand (ie there are multiple
//tables of checkboxes), you can use
// $(this).closest('table').find('input:checkbox:not(:checked)')
//instead.
if(checkCount == 8)
$('table input:checkbox:not(:checked)').attr('disabled', 'disabled');
} else {
if(checkCount == 8)
$('table input:checkbox:not(:checked)').removeAttr('disabled');
checkCount = checkCount - 1;
}
}
Unrelated to the question at hand, but hopefully helpful: I know this is a jsfiddle example and thus probably not indicative of your actual code, but you should consider using consistent formatting, especially with regard to indentation (and to a lesser extent spacing). Also, it's good habit to always use semicolons when writing javascript, even though they are sometimes optional. Readable code is much easier to debug, extend, and definitely easier for others to understand.
Another thing you can do to simplify your code is use stylesheets instead of specifying width and align in every td element, and using unobtrusive javascript instead of the onclick event in every checkbox. Those can all be replaced by a simple jQuery bind statement in your document.ready event:
$('table input:checkbox').click(setChecks);
This would require modifying setChecks to receive an event parameter instead. (Edit) Like so:
function setChecks() {
if(this.checked) {
...
} else { ... }
}
You don't actually need the event parameter because this refers to the current event target, so you can just remove that parameter altogether.

Patterns for avoiding jQuery silent fails

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.

Categories