Append Child will not work on concatenated array items - javascript

I have queried the DOM getting all the DOM elements with a particular tag (item), and stored them in an array. I then cloned that array and concatenated 2 copies of the clone to the array essentially tripling the amount of elements from 20 to 60. So after I am done I have a new array with 60 elements. When I try to loop through those 60 elements and surround every 6 of them with a parent div, appendChild method will not append the copies which were concatenated to the array. It will only work on the original 20 which I assume are from the original clone.
function buildProductsList_() {
var concatNumber = 1;
cloneItemArray = baseItemElementsByAttribute.slice();
if (baseItemElementsCount % baseItemElementsPerSlide == 0) {
return false;
}
while (baseItemElementsCount * concatNumber %
baseItemElementsPerSlide != 0) {
concatNumber++;
}
for (i = 0; i < concatNumber; i++) {
newItemList = newItemList.concat(cloneItemArray);
}
newItemListCount = newItemList.length;
offsetFrameCount = newItemListCount % baseItemElementsPerSlide;
}
function constructCarouselSlides_() {
for (i = 0; i < newItemListCount; i += baseItemElementsPerSlide) {
var offset = baseItemElementsPerSlide;
if (offsetFrameCount + i == newItemListCount) {
offset = offsetFrameCount;
}
var section = newItemList.slice(i, i + offset);
var itemsClones = itemsInstance.cloneNode();
for (j = 0; j < section.length; j++) {
itemsClones.appendChild(section[j]);
}
carouselContainer.appendChild(itemsClones);
}
}
See Screenshot: The first 6 items elements do not have any of the item elements appended to them. It seems to only have worked on the original 20 item elements. Any help would be appreciated.
Screenshot of DOM
https://www.dropbox.com/s/55n0y8j7ezet1u7/Screen%20Shot%202015-09-23%20at%203.00.50%20PM.png?dl=0

Making a copy of an array of DOM elements with .slice() does NOT make new DOM elements. It just makes a second array that contains references to the same set of DOM elements. So, when you try to append those same DOM element references from the cloned array, it just moves them from where they were originally.
If you want a new array of newly created DOM elements, you will have to clone each element in the array to actually create a new set of DOM elements. Here's a function that will clone an array of DOM nodes:
function cloneDOMArray(arr) {
return arr.map(function(item) {
return item.cloneNode();
});
}
It returns an array of cloned nodes.
More Explanation:
At the core of this is in Javascript, a primitive such as a number or boolean is assigned by copying the value.
var a = 2;
var b = a;
a = 3;
console.log(a); // 3 (shows the newly assigned value)
console.log(b); // 2 (it has a copy of the original value of a)
But, objects in Javascript (which includes DOM objects) are assigned by pointer:
var x = document.createElement("div");
x.innerHTML = "Hello";
var y = x;
x.innerHTML = "Goodbye";
console.log(y.innerHTML); // "Goodbye"
console.log(x.innerHTML); // "Goodbye"
So, when you assign one object to two different variables, each variable points at the exact same object. If you modify that object, you will see that modification through both variables (because they both point at the exact same object).
So, if you have an array of DOM elements references and you then make a copy of that array with .slice(), you will just have two arrays with the exact same set of DOM element references in it.
var x = document.getElementById("one");
var y = document.getElementById("two");
var items = [x,y];
var copyItems = items.slice(0);
console.log(items[0] === copyItems[0]); // true, same element reference
So, when you assign an object to a second variable and you want the second variable to contain a copy of that object, you have to explicitly make a copy. How you best make a copy depends upon the object (you would do it differently for an array of DOM element references vs. an array of something else).

Related

How to find with JavaScript only two out of three instances of the same html class?

I have three <div> elements with the same class of "child". Imagine that I cannot add any other class to the <div>. How can I find only two of those divelements (second and third) with JavaScript? (the code below is just for better understanding of what I mean - mind that <div> elements with the same class are not siblings).
<div class="parent">
<div class="child"></div>
<div class="child">
<div class="new">
<div class="child"></div>
</div>
</div>
</div>
The DOM defines a getElementsByClassName function, which returns a HTMLCollection object:
var elementsWithChildClass = document.getElementsByClassName("child");
In your case, this will return the 3 div elements with the "child" className. You can then access a given element on this list using the bracket notation syntax, for example:
var secondElem = elementsWithChildClass[1];
var thirdElem = elementsWithChildClass[2];
In your case, both of these objects will be HTMLDivElement instances (which inherits from HTMLElement).
Note: the index of the HTMLCollection object is zero-based, which means the first element is accessed using [0], second element using [1], and so on.
var slice = function(elements, start, end) {
var sliced = Array.prototype.slice.call(elements, start, end);
return sliced;
};
var totalChildElem = document.getElementsByClassName("child");
if (totalChildElem.length)
{
var myChildElem = slice(totalChildElem,1);
}
Revised Question: How can I to get the last two elements with class="child", not looking at the level they are nested (second and third) with JavaScript?
You do not need jQuery to do this.
An answer that works all the way down to at least ES3 might look like this. In this case, supplying a parentId allows you to target groups of three .child classes, instead of making JavaScript collect all .child classes.
function get2ndAnd3rdClassNodes(parentId, targetClass)
{
var targets = [];
var nodes = document.getElementById(parentId).getElementsByClassName(targetClass);
for(var i = 1, length = nodes.length; i < length; ++i)
{
target[i - 1] = nodes[i];
}
nodes = null;
return targets;
}
A more modern answer might look like this:
function get2ndAnd3rdClassNodes(parentId, targetClass)
{
var nodes = document.getElementById(parentId).getElementsByClassName(targetClass);
return Array.prototype.splice.call(nodes, 1, 2);
}
Sometimes it is advantageous to separate DOM access from the code that manipulates the values returned from the DOM. In that case, the function could work with nodes, instead of starting with an id (and target classname). .getElementsByClassName() returns a nodeList, which is an array-like object, but an object nonetheless. In order to use JavaScript array methods with it, you need to, in effect, cast it to an array.
function get2ndAnd3rdClassNodes(nodes, targetClass)
{
nodes = nodes.getElementsByClassName(targetClass);
return Array.prototype.splice.call(nodes, 1, 2);
}
Or, in one line:
function get2ndAnd3rdClassNodes(nodes, targetClass)
{
return Array.prototype.splice.call(nodes.getElementsByClassName(targetClass), 1, 2);
}
Finally, if you want to be able to target how many elements to get rid of in a flexible way:
function spliceClassNodes(nodes, targetClass, startIndex, nodesNeeded)
{
var minNodes = startIndex + nodesNeeded;
try
{
if(nodes.length < minNodes)
{
throw new RangeError('Only ' + nodes.length + ' received. Needed ' + minNodes + ' to process this nodeList correctly.');
}
return Array.prototype.splice.call(nodes.getElementsByClassName(targetClass), startIndex, nodesNeeded);
}
catch(e)
{
if(e instanceof RangeError)
{
//Handle the problem.
}
}
return;
}
Invoked as:
var childArray = spliceClassNodes(nodes, 'child', 1, 2);
Feel free to add this to a prototype.

Value of variable changing over time inside function w/o reassignment

<script>
document
.getElementById('country')
.addEventListener('change', function() {
'use strict';
var value1 = this.value;
console.log(value1);
var vis = document.querySelectorAll('.input-group-addon'),
country = document.getElementsByClassName(value1);
console.log(country.length);
// Point One
var i;
if (vis !== null) {
for (i = 0; i < vis.length; i++)
vis[i].className = 'input-group-addon inv';
console.log(country.length);
// Point Two
}
if (country !== null) {
for (i = 0; i < country.length; i++) {
country[i].className = 'input-group-addon';
// Point Three
}
}
});
</script>
This has been bothering me for a while now. I am trying to get the value of a selected value in
document.querySelectorAll('.input-group-addon')
and find matching class names in
document.getElementsByClassName(value1)
The nodelist of country is available at Point One and changes to null at Point Two.
Is there a basic logic or syntax error in my code?
and changes to null at Point Two
I assume you mean that the list is empty. The variable should not magically become null.
getElementsByClassName returns a live HTMLCollection. Meaning it will always reflect the current state of document. If you change the class name of an element, it will automatically either be added or removed from the collection.
If you don't want that, then either use querySelectorAll, which returns a collection that is not live, or convert the collection to an array.

Javascript recursion for tree creation

i am trying to create funcion for creating decision tree with state of game in every node in game (doesnt matter what game). I wrote recursive function (DFS) like this:
function makeTree(anchor,count,player){
var subTree=null;
var nodes=[];
if(player)var newPlayer=false;
else var newPlayer=true;
for (var i = 0; i <= 9; i++) {
for (var j = 0; j <= 9; j++) {
if(anchor["state"][i][j]==0){
var newState=anchor["state"];
if(player)newState[i][j]=1;
else newState[i][j]=2;
var node={name:i+"_"+j, contents:[],state:newState, value:null, player:newPlayer};
if(count>0){
var newCount=count-1;
subTree=makeTree(node,newCount,newPlayer);
node["contents"]=subTree;
}
nodes.push(node);
}else{
continue;
}
}
}
return nodes;
}
And with call:
var tree={};
var hrac=true;
var plocha=[[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0]];
var state=plocha;
tree={name: "root",
contents:[],
state:state,
value:null,
player: hrac};
tree["contents"]=makeTree(tree,3,hrac);
But the function change variables in different scope, so the output tree will be like this:
"root" - node - node - node
- node
- node
- node
- node
I cant figure out what is going on with variable newState in that function, because after finish the recursion the original variable plocha has the value of the latest node["state"]. Any suggestions what to do?
EDIT: Thanks to Bergi i realize that i need to do deep copy of array insted of make reference to it, so i make funcion for copy of array and now this works. Thank you Bergi!
Your state property is an array, which is mutable. On every assignment, you change the one multidimensional array that is the state of all nodes. You'll want to make every newState a new array, instead of passing your plocha reference recursively through all functions:
…
var newState = anchor["state"].slice(); // create copy of the outer array
newState[i] = newState[i].slice(); // copy of the row to be modified
newState[i][j] = player ? 1 : 2;
…

delete specific xml node Javascript

My xml file is like:
it contains different 'object' nodes and in different objects there are different parameters one is deleted parameter.
I want to delete the all 'object' nodes that contains the deleted parameter 1.
This is the code that deletes the node object which has a parameter node deleted =1:
x=xmlDoc.documentElement;
for(var count=0; count<5;count++){
var y=x.getElementsByTagName("deleted")[count]; //Find that nodes arent
if(y.textContent == "1") {
var z=y.parentNode; //delete the node from the parent.
x.removeChild(z);
Xml2String1= new XMLSerializer().serializeToString(x);
}
}
Your loop is incorrect:
for(var x1=0; x1<5;x1++){
var y=x.getElementsByTagName("deleted")[x1];
Your loop runs for 5 iterations without regard for the number of <deleted> elements are found. Each time through the loop you search again and get a new NodeList/HTMLCollection of the remaining <deleted> elements, but your loop counter is incremented regardless.
Try this instead:
var deletedNodesList = x.getElementsByTagName("deleted");
var nodesToDelete = [];
for (var index = 0; index < deletedNodes.length ; index += 1)
{
var node = deletedNodes[index];
if (node.textContent == "1")
{
nodesToDelete.push( node.parentNode ); //delete the node from the parent
}
}
nodesToDelete.forEach( function() { x.removeChild(this); } );
Note that, per the documentation on MDN, the NodeList is a live collection, so don't modify it while you are processing it.
PS.
I second raam86's recommendation to use sane (meaningful) variable names. Meaningful variable names make it easier to understand the code, which makes it easier to write correct code and to resolve problems in incorrect code.

How can I keep a jQuery DOM element reference when splicing/sorting an array?

I have an array of objects. One of the properties of these objects is a jQuery reference to a DOM element that may or may not actually be attached to the DOM at any given time;
For example:
Array = [{
name : 'whatever 1',
element : $('<div id="item_1" class="item"><img src="" /></div>')
},
{
name : 'whatever 2',
element : $('<div id="item_2" class="item"><img src="" /></div>')
}];
When this array is untouched I can detach and append these elements to the DOM without any troubles as well as use standard jQuery methods upon the elements.
For example:
Array[0].element.find('img');
...Will work fine.
However if I sort or splice this array, I lose the references.
I understand the reason why this is happening but what I would like to know is if there is anyway around this so that this element can continually be changed, attached, detached, modified while sorting or splicing the overall array itself?
Thanks in advance.
EDIT:
Here is a code sample of my rearrange function:
rearrangeItems : function(){
var self = this;
var offset = 0;
// get number of items that are less than insertindex
for(var i = 0; i < self.cache.selecteditems.length; i++) {
if(self.cache.selecteditems[i] < self.cache.rearrangepos){
offset++;
}
}
//subtract the offset from the intended insertion index
var rearrangeindex = self.cache.rearrangepos - offset;
var removedItems = [];
//sort the selected element indexes into ascending order
self.cache.selecteditems.sort(function (a, b) {
if (a < b) return -1;
else if (b < a) return 1;
return 0;
});
//remove the selected array elemens from the overall array and push them into the temporary array
for(var i = 0; i < self.cache.selecteditems.length; i++) {
var index = self.cache.selecteditems[i];
removedItems.push(self.cache.items.splice(index - removedItems.length, 1)[0]);
}
//Add the selected array elements back into the main array at the correct insertion point
self.cache.items.splice.apply(self.cache.items, [rearrangeindex, 0].concat(removedItems));
}
When calling this function all array elements are reordered exactly as intended.
Before reordering I can do the following:
self.cache.items[index].html.find('img');
Afterwards however, it will result in an empty object (the html property is the equivalent of the element property in my example above).
I would work with the ID, cause you have one. Don't know if this is the cleanest solution but it will work.
Calling your example like this:
$('#' + Array[0].element.attr('id')).find('img');
Hope this works for you.
Sadly this was down to my own stupidity. In my code I was referencing the element incorrectly.
I was actually doing the following:
self.cache.items[index].html.find('#image_' + index);
After reordering the elements I was intentionally resetting indexes afterwards, therefore when calling this after a sort/reorder the element was incorrect.
by switching to a class selector everything was fixed.
self.cache.items[index].html.find('.image_item');
How embarrassing! My apologies to all.

Categories

Resources