jQuery: Trouble inserting DOM elements - javascript

I want to add a set of lists to the children of another DOM element:
var req_subsets = $("#req_subsets");
$.each(subsets, function(index, subset) {
var subset_list = $("<ul></ul>");
// add DOM elements to subset_list
req_subsets.append(subset_list);
});
However, only one DOM element is ever added. This makes me suspect that when I assign a new value to subset_list, the old one is overwritten. If that is the problem, how do I avoid it? If not, what else am I doing wrong?
UPDATE: I changed something else, and I'm almost entirely certain that this is fixed.

I strongly recommend using Easy DOM Creation for this kind of things
but you can try this anyway
fixed:
var req_subsets = $("#req_subsets");
$.each(subsets, function(index, subset) {
var subset_list = $("<ul></ul>");
// add DOM elements to subset_list
subset.append(subset_list);
});

Related

jQuery selector - select new elements too after append/load within variable

I have:
$elements = $('.elements');
$element = $('.element');
function appendText(element){
element.append('<em> appended text</em>');
}
appendText($element);
$('button').on('click', function(){
$elements.append('<span class="element">Span Element Appended after load</span>');
appendText($element);
});
The appendText function, after button click, appends only to the initial element and that is due to JS cache I presume.
I know that I can do appendText($('element')); and the problem will be solved, but I don't want to change all my code now.
Is there any way to make jQuery consider this $element variable as not a cached element and look into the full DOM each time I call that variable?
Please find the jsfiddle if you wish to play or understand better: http://jsfiddle.net/adyz/733Xd/
If you add this:
$element = $('.element:last-child')
before
appendText($element);
I think will solve your problem
jsFindle here: http://jsfiddle.net/733Xd/5/.
Best regards!
That is an expensive thing to do. I would advise against it for performance reasons.
I did this pluggin in the beggining of last year https://github.com/fmsf/jQuery-obj-update
It doesn't trigger on every call, you have to request the update yourself:
$element.update();
The code is small enough to be pasted on the answer:
(function ( $ ) {
$.fn.update = function(){
var newElements = $(this.selector),i;
for(i=0;i<newElements.length;i++){
this[i] = newElements[i];
}
for(;i<this.length;i++){
this[i] = undefined;
}
this.length = newElements.length;
return this;
};
})(jQuery);
I think below one will solve your problem
appendText($element); //here you always referring to the node which was there initial.
http://jsfiddle.net/s9udJ/
Possible Solution will be
$(function(){
$elements = $('.elements');
$element = $('.element');
function appendText(element){
element.append('<em> appended text</em>');
}
appendText($element);
$('button').on('click', function(){
$elements.append('<span class="element">Span Element Appended after load</span>');
appendText($elements.find('span').last());
});
})
I don't think what you're asking is easily possible - when you call $element = $('.element'); you define a variable which equals to set of objects (well, one object). When calling appendText($element); you're operating on that object. It's not a cache - it's just how JS (and other programming languages) works.
The only solution I can see is to have a function that will update the variable, every time jquery calls one of its DOM manipulation methods, along the lines of this:
<div class='a'></div>
$(document).ready(function()
{
var element = $('.a');
$.fn.appendUpdate = function(elem)
{
// ugly because this is an object
// also - not really taking account of multiple objects that are added here
// just making an example
if ($(elem).is(this.selector))
{
this[this.length] = $(this).append(elem).get(0);
this.length++;
}
return this;
}
element.appendUpdate("<div class='a'></div>");
console.log(element);
});
Then you can use sub() to roll out your own version of append = the above. This way your variables would be up to date, and you wouldn't really need to change your code. I also need to say that I shudder about the thing I've written (please, please, don't use it).
Fiddle

Does jQuery use create document fragment inside each loops?

So I've read that jQuery uses document fragments internally to make rendering faster. But I am wondering if anyone knows if jQuery would use createDocumentFragment in this situation where I'm appending img elements to the DOM using the each loop?
var displayArray = []; // Lots of img elements
$.each(displayArray, function()
{
$('#imgSection').append(this);
});
Or would I need to use this code in order to reduce the number of browser reflows?
var displayArray = []; // Lots of img elements
var imgHolder = $('<div/>');
$.each(displayArray, function()
{
imgHolder.append(this);
});
$('#imgSection').append(imgHolder);
Also, the displayArray is populated by other code, not shown here, that creates img elements based off of paths in a JSON file.
Thank you for any advice.
Why all the looping to add elements?
$('#imgSection').append("<div>" + displayArray .join("") + "</div>");
Okay so it is elements.
The quickest way is going to be using append with the array itself.
$("#out").append(elems);
other option using one div to append is
var div = $("<div/>").append(elems);
$("#out").append(div);
BUT appending a lot of images at once is going to be bad unless they are preloaded. That will be a bunch of http requests being queued up.
jsPerf test cases
No, if you use $.each() then jQuery won't use a DocumentFragment - jQuery has no way of knowing what you're going to do inside the loop and each iteration is independent.
The point of the document fragment is that you don't have to wrap all your new elements up in a wrapper element as you've done in your second example to limit the reflows.
jQuery apparently will use a document fragment if you pass an array of elements directly to .append() instead of iterating over them yourself.
If you really care about reflows (and have noticed the displaying to be slow), you can hide and show the image-holding element:
var displayArray = […]; // Lots of img elements
var holder = $('#imgSection').hide();
for (var i=0; i<displayArray.length; i++)
holder.append(displayArray[i]);
holder.show();

find and replace text , word translation

I have some problems with finding and replacing words in PHP files, especially when there is tons of them. So I thought that I will try to use javascript / jQuery.
I'd like to create table witch word_to_replace#new_word to do so.
This is my code which doesn't work (and runs very long), filter doesn't seem to work,
any advices?
(function($){
var arr = [ 'photo-board.pl przyjazny portal fotograficzny# ','Upload images from your#Upload zdjęć z ',
'Total number of images# Całkowita liczba zdjęć'];
for(var i in arr)
{
var st_to_replace = arr[i].split('#')[0];
// alert(st_to_replace);
$('*').filter(function() {
return $(this).text() == st_to_replace;
}).html(arr[i].split('#')[1]);
}
}) (jQuery)
You're getting the text() of every page element (which will include the text of child elements) and replacing within it. That means, when you get the 'body' element you replace all the text, and then you get all the elements within body, and replace all the text, etc.
Something like this may work better:
(function($){
var arr = [ 'photo-board.pl przyjazny portal fotograficzny# ','Upload images from your#Upload zdjęć z ',
'Total number of images# Całkowita liczba zdjęć'];
var bodyText = $('body').html();
$.each(arr, function(i, v) {
var words = v.split('#');
var fromTxt = words[0], toTxt = words[1];
bodyText = bodyText.replace(fromTxt, toTxt);
});
$('body').html(bodyText);
})(jQuery);
Demo here.
It's worth noting though that since this destroys and recreates the entire body content, you'll loose event handlers and data set using .data(...).
One of the performance issues of your script is the immediate call to .html every iteration of the filter. This is causing the browser to repaint the element every iteration.
You might consider editing the html detached from the dom and then, after the for and the filter loops, replacing the html in the dom.

JavaScript moving element in the DOM

Let's say I have three <div> elements on a page. How can I swap positions of the first and third <div>? jQuery is fine.
There's no need to use a library for such a trivial task:
var divs = document.getElementsByTagName("div"); // order: first, second, third
divs[2].parentNode.insertBefore(divs[2], divs[0]); // order: third, first, second
divs[2].parentNode.insertBefore(divs[2], divs[1]); // order: third, second, first
This takes account of the fact that getElementsByTagName returns a live NodeList that is automatically updated to reflect the order of the elements in the DOM as they are manipulated.
You could also use:
var divs = document.getElementsByTagName("div"); // order: first, second, third
divs[0].parentNode.appendChild(divs[0]); // order: second, third, first
divs[1].parentNode.insertBefore(divs[0], divs[1]); // order: third, second, first
and there are various other possible permutations, if you feel like experimenting:
divs[0].parentNode.appendChild(divs[0].parentNode.replaceChild(divs[2], divs[0]));
for example :-)
Trivial with jQuery
$('#div1').insertAfter('#div3');
$('#div3').insertBefore('#div2');
If you want to do it repeatedly, you'll need to use different selectors since the divs will retain their ids as they are moved around.
$(function() {
setInterval( function() {
$('div:first').insertAfter($('div').eq(2));
$('div').eq(1).insertBefore('div:first');
}, 3000 );
});
.before and .after
Use modern vanilla JS! Way better/cleaner than previously. No need to reference a parent.
const div1 = document.getElementById("div1");
const div2 = document.getElementById("div2");
const div3 = document.getElementById("div3");
div2.after(div1);
div2.before(div3);
All modern browsers are supported!
Browser Support
jQuery.fn.swap = function(b){
b = jQuery(b)[0];
var a = this[0];
var t = a.parentNode.insertBefore(document.createTextNode(''), a);
b.parentNode.insertBefore(a, b);
t.parentNode.insertBefore(b, t);
t.parentNode.removeChild(t);
return this;
};
and use it like this:
$('#div1').swap('#div2');
if you don't want to use jQuery you could easily adapt the function.
var swap = function () {
var divs = document.getElementsByTagName('div');
var div1 = divs[0];
var div2 = divs[1];
var div3 = divs[2];
div3.parentNode.insertBefore(div1, div3);
div1.parentNode.insertBefore(div3, div2);
};
This function may seem strange, but it heavily relies on standards in order to function properly. In fact, it may seem to function better than the jQuery version that tvanfosson posted which seems to do the swap only twice.
What standards peculiarities does it rely on?
insertBefore
Inserts the node newChild before the existing child node refChild. If
refChild is null, insert newChild at
the end of the list of children.
If newChild is a DocumentFragment object, all of its children are
inserted, in the same order, before
refChild. If the newChild is already
in the tree, it is first removed.
Jquery approach mentioned on the top will work.
You can also use JQuery and CSS .Say for e.g on Div one you have applied class1 and div2 you have applied class class2 (say for e.g each class of css provides specific position on the browser), now you can interchange the classes use jquery or javascript (that will change the position)
Sorry for bumping this thread
I stumbled over the "swap DOM-elements" problem and played around a bit
The result is a jQuery-native "solution" which seems to be really pretty (unfortunately i don't know whats happening at the jQuery internals when doing this)
The Code:
$('#element1').insertAfter($('#element2'));
The jQuery documentation says that insertAfter() moves the element and doesn't clone it

Unique element ID, even if element doesn't have one

I'm writing a GreaseMonkey script where I'm iterating through a bunch of elements. For each element, I need a string ID that I can use to reference that element later. The element itself doesn't have an id attribute, and I can't modify the original document to give it one (although I can make DOM changes in my script). I can't store the references in my script because when I need them, the GreaseMonkey script itself will have gone out of scope. Is there some way to get at an "internal" ID that the browser uses, for example? A Firefox-only solution is fine; a cross-browser solution that could be applied in other scenarios would be awesome.
Edit:
If the GreaseMonkey script is out of scope, how are you referencing the elements later? They GreaseMonkey script is adding events to DOM objects. I can't store the references in an array or some other similar mechanism because when the event fires, the array will be gone because the GreaseMonkey script will have gone out of scope. So the event needs some way to know about the element reference that the script had when the event was attached. And the element in question is not the one to which it is attached.
Can't you just use a custom property on the element? Yes, but the problem is on the lookup. I'd have to resort to iterating through all the elements looking for the one that has that custom property set to the desired id. That would work, sure, but in large documents it could be very time consuming. I'm looking for something where the browser can do the lookup grunt work.
Wait, can you or can you not modify the document? I can't modify the source document, but I can make DOM changes in the script. I'll clarify in the question.
Can you not use closures? Closuses did turn out to work, although I initially thought they wouldn't. See my later post.
It sounds like the answer to the question: "Is there some internal browser ID I could use?" is "No."
The answer is no, there isn't an internal id you can access. Opera and IE (maybe Safari?) support .sourceIndex (which changes if DOM does) but Firefox has nothing of this sort.
You can simulate source-index by generating Xpath to a given node or finding the index of the node from document.getElementsByTagName('*') which will always return elements in source order.
All of this requires a completely static file of course. Changes to DOM will break the lookup.
What I don't understand is how you can loose references to nodes but not to (theoretical) internal id's? Either closures and assignments work or they don't. Or am I missing something?
Closure is the way to go. This way you'll have exact reference to the element that even will survive some shuffling of DOM.
Example for those who don't know closures:
var saved_element = findThatDOMNode();
document.body.onclick = function()
{
alert(saved_element); // it's still there!
}
If you had to store it in a cookie, then I recommend computing XPath for it (e.g. walk up the DOM counting previous siblings until you find element with an ID and you'll end up with something like [#id=foo]/div[4]/p[2]/a).
XPointer is W3C's solution to that problem.
A bit confused by the wording of your question - you say that you "need a string ID that [you] can use to reference that element later, " but that you "can't store the references in [your] script because when [you] need them, the GreaseMonkey script itself will have gone out of scope."
If the script will have gone out of scope, then how are you referencing them later?!
I am going to ignore the fact that I am confused by what you are getting at and tell you that I write Greasemonkey scripts quite often and can modify the DOM elements I access to give them an ID property. This is code you can use to get a pseudo-unique value for temporary use:
var PseudoGuid = new (function() {
this.empty = "00000000-0000-0000-0000-000000000000";
this.GetNew = function() {
var fourChars = function() {
return (((1 + Math.random()) * 0x10000)|0).toString(16).substring(1).toUpperCase();
}
return (fourChars() + fourChars() + "-" + fourChars() + "-" + fourChars() + "-" + fourChars() + "-" + fourChars() + fourChars() + fourChars());
};
})();
// usage example:
var tempId = PseudoGuid.GetNew();
someDomElement.id = tempId;
That works for me, I just tested it in a Greasemonkey script myself.
UPDATE: Closures are the way to go - personally, as a hard-core JavaScript developer, I don't know how you didn't think of those immediately. :)
myDomElement; // some DOM element we want later reference to
someOtherDomElement.addEventListener("click", function(e) {
// because of the closure, here we have a reference to myDomElement
doSomething(myDomElement);
}, false);
Now, myDomElement is one of the elements you apparently, from your description, already have around (since you were thinking of adding an ID to it, or whatever).
Maybe if you post an example of what you are trying to do, it would be easier to help you, assuming this doesn't.
UPDATE: Closures are indeed the answer. So after fiddling with it some more, I figured out why closures were initially problematic and how to fix it. The tricky thing with a closure is you have to be careful when iterating through the elements not to end up with all of your closures referencing the same element. For example, this doesn't work:
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var button = document.createElement("button");
button.addEventListener("click", function(ev) {
// do something with element here
}, false)
}
But this does:
var buildListener = function(element) {
return function(ev) {
// do something with event here
};
};
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var button = document.createElement("button");
button.addEventListener("click", buildListener(element), false)
}
Anyway, I decided not to select one answer because the question had two answers: 1) No, there are no internal IDs you can use; 2) you should use closures for this. So I simply upvoted the first people to say whether there were internal IDs or who recommended generating IDs, plus anyone who mentioned closures. Thanks for the help!
If you can write to the DOM (I'm sure you can). I would solve this like this:
Have a function return or generate an ID:
//(function () {
var idCounter = new Date().getTime();
function getId( node ) {
return (node.id) ? node.id : (node.id = 'tempIdPrefix_' + idCounter++ );
}
//})();
Use this to get ID's as needed:
var n = document.getElementById('someid');
getId(n); // returns "someid"
var n = document.getElementsByTagName('div')[1];
getId(n); // returns "tempIdPrefix_1224697942198"
This way you don't need to worry about what the HTML looks like when the server hands it to you.
If you're not modifying the DOM you can get them all by indexed order:
(Prototype example)
myNodes = document.body.descendants()
alert(document.body.descendants()[1].innerHTML)
You could loop through all of the nodes and give them a unique className that you could later select easily.
You can set the id attribute to a computed value. There is a function in the prototype library that can do this for you.
http://www.prototypejs.org/api/element/identify
My favorite javascript library is jQuery. Unfortunately jQuery does not have a function like identify. However, you can still set the id attribute to a value that you generate on your own.
http://docs.jquery.com/Attributes/attr#keyfn
Here is a partial snippet from jQuery docs that sets id for divs based on the position in the page:
$(document).ready(function(){
$("div").attr("id", function (arr) {
return "div-id" + arr;
});
});
You can generate a stable, unique identifier for any given node in a DOM with the following function:
function getUniqueKeyForNode (targetNode) {
const pieces = ['doc'];
let node = targetNode;
while (node && node.parentNode) {
pieces.push(Array.prototype.indexOf.call(node.parentNode.childNodes, node));
node = node.parentNode
}
return pieces.reverse().join('/');
}
This will create identifiers such as doc/0, doc/0/0, doc/0/1, doc/0/1/0, doc/0/1/1 for a structure like this one:
<div>
<div />
<div>
<div />
<div />
</div>
</div>
There are also a few optimisations and changes you can make, for example:
In the while loop, break when that node has an attribute you know to be unique, for example #id
Not reverse() the pieces, currently it is just there to look more like the DOM structure the ID's are generated from
Not include the first piece doc if you don't need an identifier for the document node
Save the identifier on the node in some way, and reuse that value for child nodes to avoid having to traverse all the way up the tree again.
If you're writing these identifiers back to XML, use another concatenation character if the attribute you're writing is restricted.
Use mouse and/or positional properties of the element to generate a unique ID.
In javascript, you could attach a custom ID field to the node
if(node.id) {
node.myId = node.id;
} else {
node.myId = createId();
}
// store myId
It's a bit of hack, but it'll give each and every node an id you can use. Of course, document.getElementById() won't pay attention to it.
You can also use pguid (page-unique identifier) for unique identifier generation:
pguid = b9j.pguid.next() // A unique id (suitable for a DOM element)
// is generated
// Something like "b9j-pguid-20a9ff-0"
...
pguid = b9j.pguid.next() // Another unique one... "b9j-pguid-20a9ff-1"
// Build a custom generator
var sequence = new b9j.pguid.Sequence({ namespace: "frobozz" })
pguid = sequence.next() "frobozz-c861e1-0"
http://appengine.bravo9.com/b9j/documentation/pguid.html
I 'think' I've just solved a problem similar to this. However, I'm using jQuery in a browser DOM environment.
var objA = $("selector to some dom element");
var objB = $("selector to some other dom element");
if( objA[0] === objB[0]) {
//GREAT! the two objects point to exactly the same dom node
}
OK, there is no ID associated to DOM element automatically.
DOM has a hierarchycal structure of elements which is the main information.
From this perspective, you can associate data to DOM elements with jQuery or jQLite. It can solve some issues when you have to bind custom data to elements.

Categories

Resources