I have a very strange bug, what appears to be a race condition makes a width(); call give different results.
I have this jQuery after call:
this.after(
"<div class='menuItemContainer'>" +
"<div class='menuItemTitle'>" +
$("option:selected", this).text() + "<span class='menuItemDownArrow'>▼</span>" +
"</div>" +
"<div class='menuItemList'>" +
selectionMarkup +
"</div>" +
"</div>"
);
In which I insert html into the DOM.
Next I need to know the combined widths of an element with the class menuItemColums and make their parent div that width.
This markup exists in the variable selectionMarkup.
To do this I do the following:
var w = 0;
$(".menuItemColumn").each(function(){
w += $(this).width();
console.log($(this));
console.log(w);
});
$(".menuItemList").css({
width: w}
);
This works exactly as expected around 70% of the times, but sometimes I get the wrong measure for some of the elements, or at least for sure the first one sometimes is wrong.
The weird part is that the object seem to be correct, and it's properties are the correct width, but the given widht() which is assigned to w is incorrect:
That image, just to clarify shows the result of logging w and the $(this) which should be the same, since $(this)'s width property seems to be the correct one.
I kept debugging and found out two things that lead be to believe that this is race condition:
If I execute the last piece of code I posted you again, when the bug is reproduced, it fixes the problem.
If I do the following calls, the give me different results, one incorrect and the other one (second one) correct:
console.log($(".menuItemColumn:first").width());
setTimeout(function(){console.log($(".menuItemColumn:first").width());},3000);
I was under the impression that the second part of the code will not execute until the first one is finished, what gives here?
Note: I'm not 100% sure that race condition is the word I was looking for, but still, the idea is that.
UPDATE:
As #WereWolf-TheAlpha the output of the console doesn't seem like jquery objects, I now can't reproduce that and I'm only getting this:
Without changing any code.
The issue here is that the Chrome inspector will provide a reference to an object while the object is collapsed in the inspector. When you click that arrow to expand the object, its attributes become real. You can run this to see it occur:
var obj = {};
for (i=0; i<100; i++)
obj['foo'+i] = i;
console.log(obj);
for (i=0; i<100; i++)
obj['foo'+i] = 'gotcha';
console.log(obj);
Now, expand the first object in your inspector. Gotcha!
Related
I'm trying to make a loop in jQuery that finds all 'img' elements and places a caption below them, according to the value of the element's 'caption' attribute. Whenever I run the loop below, I am left with no captions under any of the images.
for (var i = 0; i < $('.myimage').length; i++) {
$('.myimage')[i].after('<h6>' + $('.myimage').attr('caption') + '</h6>');
};
However, when I run this code
$('.myimage').after('<h6>TEST</h6>');
the word 'TEST' appears below all of the images. Therefore I know my html is correct, I have no typos, and the selector is working, I just cannot get the for loop to work... What have I done wrong?
$('.myimage')[i] returns a DOM element (not a jQuery object) so there is no after method. If you want to loop, simply use .each
$(".myimage").each(function() {
//this refers to each image
$(this).after('<h6>' + $(this).attr('caption') + '</h6>');
});
You can loop through the .myimage elements like this, using .after()'s callback function
$('.myimage').after(function(){
return '<h6>' + $(this).attr('caption') + '</h6>';
});
One minor note, don't make up your own attributes. use the custom data attribute instead, like data-caption="something".
jsFiddle example
I know that the empty method removes all children in the DOM element.
In this example however, why does removing the empty method result in duplicate entries:
and putting it in results in a normal page:
var renderNotesList = function()
{
var dummyNotesCount = 10, note, i;
var view = $(notesListSelector);
view.empty();
var ul = $("<ul id =\"notes-list\" data-role=\"listview\"></ul>").appendTo(view);
for (i=0; i<dummyNotesCount; i++)
{
$("<li>"+ "" + "<div>Note title " + i + "</div>" + "<div class=\"list-item-narrative\">Note Narrative " + i + "</div>" + "" + "</li>").appendTo(ul);
}
ul.listview();
};
I don't know why empty() doesn't work but I found this
... so until this is sorted everyone should just use:
el.children().remove(); instead of el.empty();
( jQuery.empty() does not destroy UI widgets, whereas jQuery.remove() does (using UI 1.8.4) )
Without seeing how your JavaScript is being used in your page, I suspect that you must be calling the renderNotesList() function twice and thus generating to unordered lists.
When you use the .empty() method, you are removing the first ul list, so you only see one instance. Without the call to .empty(), you retain both.
However, I can't say where or how this is happening in you web page without seeing more, but at least you now have some idea of what to look for.
Demo Fiddle
I built a demo using your JavaScript, but I was sort of guessing as to how you are using it.
Fiddle: http://jsfiddle.net/audetwebdesign/UVymE/
Footnote
It occurred to me that the function ul.listview() may actually be appending a second copy of the ul to the DOM. You need to check the code or post it for further review.
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
I'm attempting to set the IDs of a group of divs "galleryboxes" (that are inside a another div, "galleryimages") to be increasing increments, ie. picture1, picture2, etc.
I've seen questions similar to this and tried to get my head around them, but it's difficult. Right now my questions are, what do I get from the second line here? What is 'galleryboxes'? Am I able to cycle through it like an array and label ids like I'm attempting to?
var galleryimages = document.getElementById("galleryimages");
var galleryboxes = galleryimages.getElementsByTagName("div");
var k = 1;
for (var i = 0; i < galleryboxes.length; i++) {
galleryboxes[i].setAttribute=("id", "picture" + k);
k++;
}
The code seems to work, but I don't think I'm actually labeling the divs I want to be labeling; I check what the contents are of "picture1" and it comes up as null (It should be full of a picture and some text).
What do I get from the second line here?
The line in question is this:
var galleryboxes = galleryimages.getElementsByTagName("div");
What this does is provide a live nodeList complete with all the elements with the tag name <div> that are child to the element which the variable galleryimages represents. Note that this is not an actual array, and there are distinguishing characteristics between an array and a nodeList. Most notably, a nodeList does not inherit from the Array constructor, and thus does not contain essential methods like push, pop, and sort common to all arrays.
Am I able to cycle through it like an array and label ids like I'm attempting to?
Yes. This is made easy for us because fortunately nodeLists have a length property. You've already showed how you can loop through them, but the line on which you set the attribute is ill-formed:
galleryboxes[i].setAttribute=("id", "picture" + k);
The equal sign should not be there. This invokes a syntax error. If you take the equal out, the code works fine.
galleryboxes give you a node list which you can iterate through like an array but you have an error here
galleryboxes[i].setAttribute=("id", "picture" + k);
should be
galleryboxes[i].setAttribute("id", "picture" + k);
notice the lack of =
It can also be done using the id property.
galleryboxes[i].id = "picture" + k;
galleryboxes is an array that contains all the divs from within the element "galleryimages"
<html>
<head>
...
<head>
<body>
<div id="galleryimages">
<div class="img_container"></div>
<p>This text will not be in the array...</p>
<div class="img_container"></div>
<div class="img_container"></div>
</div>
</body>
</html>
If your code looked something like above, the variable galleryboxes would only contain the divs.
As for when you check the contents of "picture1" it is probably null, I think you have defined an background image for #picture1 in css, am I right?
Anyways what you should do is too use something like firebug to see the generated DOM. That way you can debug much easier.
You will need to set the attribute directly (galleryboxes[i].id) if you want to access it programatically afterwards (at least if you want it to work consistently). So:
var galleryimages = document.getElementById("galleryimages");
var galleryboxes = galleryimages.getElementsByTagName("div");
for (var i = 0; i < galleryboxes.length; i++) {
galleryboxes[i].id = "picture" + (i + 1);
}
See this jsfiddle for an example.
I know, you are looking for javascript answers here but you can also try this jQuery code, if you want too:
$("#galleryimages > div").each(function(i) {
$(this).prop("id", "picture" + (i + 1));
});
See just 3 lines of code :)
Okay this is frustrating me to no end. I recently coded a page in JS for a buddy of mine who wants to display wedding pictures to a family to see which ones they'd like to purchase.
I used a for loop to count 1-904:
for (beginnum=1;beginnum<=904;beginnum++) { yada yada...
Then, I used adobe bridge to rename the camera files to be 1-904 and their thumbnails (1-904 + _thumb) and used the loop number to display 904 image spaces, and the correctly numbered picture:
[note:using <) in place of the usual open tag since the site wont display it]
IE...
document.write(beginnum + ":" + "<img src='pictures" + beginnum + "_thumb.jpg' />");
Opera...
document.write("<div>" + beginnum + ":" + "<img src='pictures" + beginnum + "_thumb.jpg' /></div>")
This all works perfectly in IE and Opera (with external CSS modifying the div to not line break).
I then created a function to call up the large version of the picture when clicked on.
The problem is, when I try and nest this function into the JavaScript generated HTML I would need four delimiters. I've heard ''' or """ or the &+numeric; work in some cases as a third and fourth but I can't seem to get them to work... where I run into a problem is here...
[note:again using <) for open tag]
document.write("<a href='javascript:void(0); onClick=
Since I've already used up " and ' I now have nothing left to use to call the function when a picture is clicked.
I usually don't ask for any help, but this time I can't think of anything else that should work... I assume maybe using JS to generate the HTML leaves me with ONLY 2 delimiters that will be recognized by the browser but I am not sure, anyone know for sure? Any fixes anyone can think of?
Thanks,
~Z~
Maybe this will work
for (i=0; i<904;i++)
{
document.write("<div class=\"DivClassName\"><img src=\"pictures_" + i + "thumb.jpg\" onclick=\"OpenAWindowAndDisplayTheBigPhoto(" + i + ")\"></div>");
}
Another approach: Suppose you put everything inside a <DIV id="mainDIV">
var mainDIV = document.getElementByID("mainDIV");
var div, img, a;
for (i=0; i<904; i++)
{
div = document.createElement("DIV");
div.className = "DivClassName";
a = document.createElement("A");
a.href = "javascript:void(0)";
a.onclick = function() {OpenAWindowAndDisplayTheBigPhoto(i);};
img = document.createElement("IMG");
img.src = "pictures_" + i + "thumb.jpg";
mainDIV.appendChild(div);
div.appendChild(a);
a.appendChild(img);
};
Try building the string one piece at a time instead of trying to build the whole literal for the document.write.
Whenever things get too convoluted to follow, just do one part at a time.
var s;
s = "'Hello.' ";
s += '"I must be going."';
Without seeing code it is hard to say for a fact, but you may want to take more advantage of the fact that javascript is a first-class language, so you can create functions and pass them as arguments to other functions, or have functions return functions.
By doing this, you can decompose your page into something that sounds a bit more manageable.
Also, take advantage of the onclick event.
You should be able to simplify the javascript and so avoid this problem, IMO.