Strange Javascript for loop behavior when using Jquery append - javascript

I'm probably missing/doing something silly, but I can't seem to work this out:
Here's a fiddle showing the problem:
https://jsfiddle.net/jhqjmcn4/1/
Note that you will need to open your console to see what is happening.
Basically, in this example, I have two functions containing a for loop that are identical to each other except the second one contains a JQuery append.
The goal of the function is to get the html elements from within a string, which works fine in the first function, but not the second.
As can be seen in the console, this causes anything that is not a text node to be ignored and not added to the list. In this case, the b and p tags are not being included.
Here is the code again:
JS:
function parse_text(text) {
var div = document.createElement("DIV");
div.innerHTML = text;
var elements = div.childNodes;
var container = jQuery("#container");
var list = [];
for (var i = 0; i < elements.length; i++){
var element = elements[i];
list.push(element);
console.log("First", list);
}
}
function parse_text_two(text) {
var div = document.createElement("DIV");
div.innerHTML = text;
var elements = div.childNodes;
var container = jQuery("#container2");
var list = [];
for (var p = 0; p < elements.length; p++){
var element = elements[p];
list.push(element);
console.log("Second", list);
container.append(element);
}
}
var text = "Here is <b>some</b> text with <p>html</p> in it";
parse_text(text);
parse_text_two(text);
html (irrelevant):
<div id="container">
</div>
<div id="container2">
</div>
Thanks in advance.

I suppose you need to have a Array.prototype.filter() method to get the html elements:
function parse_text_two(text) {
var div = document.createElement("DIV");
div.innerHTML = text;
var elements = [].filter.call(div.childNodes, function(el) {
return el.nodeType !== 3;
});
var container = jQuery("#container2");
var list = [];
for (var p = 0; p < elements.length; p++) {
var element = elements[p];
list.push(element);
console.log("Second", list);
container.append(element);
}
}
var text = "Here is <b>some</b> text with <p>html</p> in it";
parse_text_two(text);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container">
</div>
<div id="container2">
</div>

You can check updated fiddle here jsfiddle.net/bharatsing/jhqjmcn4/3/
Its return same result for both methods in console as per you want.
function parse_text_two(text) {
var div = document.createElement("DIV");
div.innerHTML = text;
var elementsOrg = div.childNodes;
var elements = Array.prototype.slice.call(elementsOrg);;
var container = jQuery("#container2");
var list = [];
for (var p = 0; p < elements.length; p++){
var element = elements[p];
list.push(element);
console.log("Second", list);
container.append(element);
}
}

The issue I saw by putting a debug point inside the loop was this:
The container.append(element); statement was actually modifying the elements array and was removing the appended element from the array. Which meant that in the loop for various values of 'p' the elements array looked like this:
p = 0 -> elements : [ text, b, text, p, text ] // text node at 0 picked
p = 1 -> elements : [ b, text, p, text ] // text node at 1 picked
p = 2 -> elements : [ text, p, text ] // text node at 2 picked
That is why the loop only ran 3 times instead of the original length of the elements array , i.e. 5.
This probably happens because jQuery 'moves' the node from its original place to the container div.
You can either clone the element node and then append into the container:
function parse_text_two(text) {
var div = document.createElement("DIV"),
p,
element,
elements,
container = jQuery("#container2"),
list = [];
div.innerHTML = text;
elements = div.childNodes;
for (p = 0; p < elements.length; p++){
element = elements[p];
list.push(element);
console.log("Second", list);
container.append($(element).clone());
}
}
Or use a while loop as suggested in Venkatesh's answer. Anyway, it is always better to know the root cause. :)
Hope this helps.

In second function in each loop comment this line container.append(element);

Related

How to create multiple paragraphs with Javascript with shorter code?

I want to create multiple paragraphs with each two inputfield with Javascript.
I wanted to know, if there is a way to have a shorter code but the same result?
It should have the same result like this but with a shorter code:
var para1 = document.createElement("p");
var i1 = document.createElement("input");
var i2 = document.createElement("input");
para1.appendChild(i1);
para1.appendChild(i2);
var element = document.getElementById("div1");
element.appendChild(para1);
var para2 = document.createElement("p");
var i3 = document.createElement("input");
var i4 = document.createElement("input");
para2.appendChild(i3);
para2.appendChild(i4);
var element = document.getElementById("div1");
element.appendChild(para2);
var para3 = document.createElement("p");
//etc.
<div id="div1"></div>
I could not think of any other solution than using a for loop 😁
This definitely reduces the code by half length though.
numberOfParagraphs = 3
for(let i = 0; i< numberOfParagraphs;i++){
var para= document.createElement("p");
var i1 = document.createElement("input");
var i2 = document.createElement("input");
para.appendChild(i1);
para.appendChild(i2);
document.getElementById("div1").appendChild(para);
}
<div id="div1"></div>
Wrap your code into a function
function createPara() {
var para1 = document.createElement("p");
var i1 = document.createElement("input");
var i2 = document.createElement("input");
para1.appendChild(i1);
para1.appendChild(i2);
var element = document.getElementById("div1");
element.appendChild(para1);
}
Call the function n times
createPara()
createPara()
Additionally you can pass params such as class, id etc.
well the way you have it written, you are executing the exact same code multiple times. why not put it in a function?
createPara();
createPara();
createPara();
//etc.
function createPara() {
var para2 = document.createElement("p");
var i3 = document.createElement("input");
var i4 = document.createElement("input");
para2.appendChild(i3);
para2.appendChild(i4);
var element = document.getElementById("div1");
element.appendChild(para2);
}
Create a document fragment and append it to DIV instead of creating individual elements.
In the current setup, HTML elements will reflow each time you append any element.
With DocumentFragment you can save multiple reflows as it reflows only once when attached.
Please refer https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment for information.
wrap your code into a function and give it number of para :
function createPara(n) {
let parentDiv = document.getElementById("div1")
for(let i =0; i<n; i++){
let para = document.createElement("p");
let i1 = document.createElement("input");
let i2 = document.createElement("input");
para1.appendChild(i1);
para1.appendChild(i2);
parentDiv.appendChild(para);
}
}
}
Call the function and give it the number u want to repeat for exemple 5 time :
createPara(5)
you can also give it the number of inputs
I thought I would do something for a more general case, but might have gotten a bit carried away; anyway:
const new_children = [
{ tag: 'p', children: [
{ tag: 'input' },
{ tag: 'input' },
] },
];
const element_for_def = (def) => {
const element = document.createElement(def.tag);
if(def.children && def.children.length > 0)
append_children_to_ele(element, def.children);
return element;
};
const append_to_element = (parent) => (child) => parent.appendChild(child);
const append_children_to_ele = (parent, children) =>
children
.map(element_for_def)
.forEach(append_to_element(parent));
const three_new_children = [1,2,3].reduce(acc => acc.concat(new_children), []);
append_children_to_ele(document.getElementById("div1"), three_new_children);
<div id="div1"></div>
ma is a reference to an element object which you want to create multiple paragraphs.
I use 10 for multiple paragraphs line. You can use your required number.
let ma = document.getElementById("multiple-para").innerHTML;
for(var i =0; i<10; i++){
document.write(ma + "<br>");
}

Div's are being generated dynamically and even the (obj.search) fetches the data but nothing is being displayed in it. How do i display the data?

JS doesn't display the output
for (var i = 0; i < obj.Search.length; i++){
var divTag = document.createElement("div");
divTag.id = "div"+i;
divTag.className = "list";
document.getElementById('div'+i).innerHTML+=obj.Search[i].Title+obj.Search[i].Year;
}
Image here
You missed adding the newly created element to the DOM. Example:
document.getElementById("yourDivContainer").appendChild(divTag);
Fiddle:
http://jsfiddle.net/mbpfgm49/
You need to append your div tags to some element (e.g: body), to make text appear on page
// Let's create some sample data
var obj = {
Search: []
}
var currentYear = (new Date).getFullYear();
for (var i = currentYear - 10; i <= currentYear; i++) {
obj.Search.push({
Title: 'Test',
Year: i
})
}
// Here goes your code fixed
for (var i = 0; i < obj.Search.length; i++) {
var divTag = document.createElement("div");
divTag.id = "div" + i;
divTag.className = "list";
divTag.innerHTML = obj.Search[i].Title + ' ' + obj.Search[i].Year;
document.body.appendChild(divTag);
}
Yes, you have to add the element to the DOM.
More basically, it is an anti-pattern to construct IDs for elements and use those as the primary means for referring to elements, by means of calling getElementById at every turn. I guess this approach is one of the many lingering after-effects of the jQuery epidemic.
Instead, keep references to elements directly in JS where possible, and use them directly:
for (var i = 0; i < obj.Search.length; i++){
var divTag = document.createElement("div");
divTag.className = "list";
parent.appendChild(divTag);
^^^^^^^^^^^^^^^^^^^^^^^^^^ INSERT ELEMENT
divTag.innerHTML+=obj.Search[i].Title+obj.Search[i].Year;
^^^^^^ REFER TO ELEMENT DIRECTLY
}
To be absolutely pedantically correct, what you are creating is not a "tag", it's an "element". The element is the DOM object. The "tag" is the div which characterizes the element type.

Javascript - How to get all elements and do same thing on each of them

I want to get all DIVs in DIV(id = room) and do the same javascript code on each one.
I think it should look like this
Get element by id room -> Get all divs inside -> do something on them(change each class to "grass")
or by using a loop.
How to do that?
Please don't use jQuery.
Modern browsers (IE9+):
var divs = document.querySelectorAll('#room div');
[].forEach.call(divs, function(div){
div.className = 'green';
});
var a = document.getElementById("room").getElementsByTagName("div");
for(i = 0; i < a.length; i++)
{
a[i].className = "grass";
}
Do you want to get all divs inside, or just direct children?
This one traverses direct children. If you want to go through all internal nodes, you need to recurse it.
function grassify(nodeId) {
var node = document.getElementById(nodeId);
for(var i in node.childNodes) {
// Do things with node.childNodes[i], for example:
node.childNodes[i].className = 'grass';
}
}
Then just:
grassify('room');
var room=document.getElementByID("#room");
var divs=room.getElementsByTagName("div");
for(var i=0;i<divs.length;i++){
doSomething(divs[i]);
}
Use getElementByID and getElementsByTagName
Use getElementsByTagName
First get a reference to the container element, then use getElementsByTagName for the type of element you want.
See http://jsfiddle.net/aQtTx/
JS:
var targetDiv = document.getElementById("div1");
var nestedDivs = document.getElementsByTagName("div");
for(var divIndex = 0; divIndex < nestedDivs.length; divIndex++)
{
nestedDivs[divIndex].style.backgroundColor = 'red';
}
function myFunction()
{
var a=document.getElementById('room').childNodes;
for (i=0; i<a.length; i++)
{
a[i].className="grass";
};
}
JsFiddle
var parent = document.getElementById("room");
var divs = parent.getElementsByTagName('div');
for (i=0; i<divs.length; i++)
{
divs[i].className="grass";
};

Select all element with same tag name using javascript and highlight content partially

I made a code that should highlight searched string but it is not working.
Here is the code:
<body>
<div>div is here</div>
<div id="divid">
<div>this is a div 1</div>
<div> this is a div 2</div>
<div> this is a div3</div>
</div>
<div> another div is here</div>
</body>
Here is a javascript code.
function checkit(){
var hlWord = "div";
var nregex = new RegExp(hlWord,"gi");
var div = document.getElementById("divid").getElementsByTagName('div');
for(var i=0; i <= div.length; i++){
var div1 = div[i].innerHTML;
var rword = div1.replace(nregex,"<b>"+hlWord+"</b>");
div1.innerHTML = rword;
}
}
There are begginer mistakes in your code. Let me correct them:
function checkit(){
var hlWord = "div"; //Define string that will be emphasized by <b> tag
var nregex = new RegExp(hlWord,"gi");//Create global, case-insensitive regexp
var div = document.getElementById("divid").getElementsByTagName('div'); //Get element collection of divs in div#divid
for(var i=0; i < div.length; i++){ //Loop through my element collection
var div1 = div[i].innerHTML; //Get the innerHTML of on of the divs
var rword = div1.replace(nregex,"<b>"+hlWord+"</b>"); //Surround my string with <b>
div[i].innerHTML = rword; //Change the innerHTML back
}
}
You used this for condition: i<=div.length. This is wrong. Do not forget, that we count from 0 so: [0, 1, 2, 3].length = 4. Last element for such array has index 3. The [] is an array literal.
By mistake, you assigned div1.innerHTML. div1 was a string. The element you want to change is div[i].
I made a JSFiddle too!
The problem with you code will be, amongst other problems, that nested div elements will be broken. You should use some kind of recursion if you want to highlight the word 'div'.
Here is such a function:
function highLight(term,root){
var allDiv = root.querySelectorAll('div'),
replacer = function(a){return '<span class="highlight">'+a+'</span>'};
for (var i=0; i<allDiv.length; i+=1){
if (allDiv[i].querySelectorAll('div').length){
highLight(term, allDiv[i]);
} else {
var re = RegExp('('+term+')','gi');
allDiv[i].innerHTML = allDiv[i].innerHTML.replace(re,replacer);
}
}
}
And here is a jsfiddle to play around with it
A more advanced jsfiddle
You have several errors: there you go:
http://jsfiddle.net/55U6j/1/
function checkit(){
var hlWord = "div";
var nregex = new RegExp(hlWord,"gi");
var divs = document.getElementById("divid").getElementsByTagName('div');
for(var i=0; i < divs.length; i++){
var div = divs[i];
var html = div.innerHTML;
var rword = html.replace(nregex,"<b>"+hlWord+"</b>");
div.innerHTML = rword;
}
}

Javascript Replace Child/Loop issue

I have this really bizarre issue where I have a forloop that is supposed to replace all divs with the class of "original" to text inputs with a class of "new". When I run the loop, it only replaces every-other div with an input, but if I run the loop to just replace the class of the div and not change the tag to input, it does every single div, and doesn't only do every-other.
Here is my loop code, and a link to the live version: live version here
function divChange() {
var divs = document.getElementsByTagName("div");
for (var i=0; i<divs.length; i++) {
if (divs[i].className == 'original') {
var textInput = document.createElement('input');
textInput.className = 'new';
textInput.type = 'text';
textInput.value = divs[i].innerHTML;
var parent = divs[i].parentNode;
parent.replaceChild(textInput, divs[i]);
}
}
}
Because the divs collection is updated when one of its div elements is removed from the DOM, you end up skipping over divs because your i isn't updated with the reindexing of the collection.
A common solution is to iterate in reverse instead.
function divChange() {
var divs = document.getElementsByTagName("div");
for (var i=divs.length - 1; i > -1; i--) {
if (divs[i].className == 'original') {
var textInput = document.createElement('input');
textInput.className = 'new';
textInput.type = 'text';
textInput.value = divs[i].innerHTML;
divs[i].parentNode.replaceChild(textInput, divs[i]);
}
}
}
Another solution you could use is to copy the live HTMLCollection to an inert array, and use your original logic:
function divChange() {
var divs = document.getElementsByTagName("div");
divs = Array.prototype.slice.call( divs ); //convert to array
for (var i = 0; i < divs.length; i++) {
if (divs[i].className == 'original') {
var textInput = document.createElement('input');
textInput.className = 'new';
textInput.type = 'text';
textInput.value = divs[i].innerHTML;
var parent = divs[i].parentNode;
parent.replaceChild(textInput, divs[i]);
}
}
}
divChange();
http://jsfiddle.net/2UCZa/1/
Yet another solution is to create an Array from an array-like object, and iterate over this. For example:
var divs = document.getElementsByTagName("div");
Array.from(divs).forEach(function(el) {
if (el.className == 'original') {
var textInput = document.createElement('input');
textInput.className = 'new';
textInput.type = 'text';
textInput.value = el.innerHTML;
var parent = el.parentNode;
parent.replaceChild(textInput, el);
}
});
I like this one the best, as it produces the least amount of code, and is very clear!
I don't know why, but this one seemed to work in the end:
ModalBody.insertAdjacentHTML('afterbegin', loader.outerHTML);
my loader is basically just a new div, but inside of the div there is this loading symbol, which appears when the content is loaded.
var loader = document.createElement('div');
loader.classList.add('loader');
loader.classList.add('is-loading');
loader.classList.add('mt-5');
So with just this line
ModalBody.insertAdjacentHTML('afterbegin', loader);
...while the content was loaded a got [object HTMLDivElement] shown shortly, after 3 sec more or less the right content appeared. As soon as I added this ".outerHTML" things got right. I am still a super beginner. So, maybe someone could also explaine why this worked?

Categories