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?
Related
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>");
}
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);
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.
I'm confused on how to change text content of div with the DOM. When event is triggered, I see that the new text replace the old but it is in a new div. I want to keep it in "transcriptText" to keep all attributes.`How can I do that?
This is my old div with text inside:
var transcriptText = document.getElementById("transcriptText");
these are my new text SPAN elements
var newTranscript = document.createElement("div");
This is how I handle the event
function EventHandler() {
transcriptText.parentNode.replaceChild(newTranscript, transcriptText);
}
Here is the JSFiddle on how it currently works:
http://jsfiddle.net/b94DG/
What you're doing now is creating a new div, newTranscript, which you create by appending a bunch of spans based on the old text. Then in your event handler you replace the old one with the new one. Instead of that, you could still copy the text from the old one, but then clear it and append the children on the old div, replacing line 36 with:
transcriptText.appendChild(newSpan);
To clear the old element, it might work to just set innerHTML to "", or if necessary you could remove all the children with removeChild as described at https://developer.mozilla.org/en-US/docs/Web/API/Node.removeChild
EDIT:
I modified your fiddle to reflect this:
http://jsfiddle.net/b94DG/1/
You can change the innerHTML of transcriptText instead of creating a new div.
var transcriptText = document.getElementById("transcriptText");
var divideTranscript = document.getElementById("divideTranscript");
divideTranscript.onclick = function() {
var sArr = transcriptText.innerHTML.split(" ");
var newInnerHTML = "";
for (var i = 0; i < sArr.length; i++) {
var item = sArr[i];
var newText = "<span class='highlight' id='word" + i + "'>" + item + " </span>";
newInnerHTML += newText;
}
transcriptText.innerHTML = newInnerHTML;
var mouseOverFunction = function () {
this.style.backgroundColor = 'yellow';
};
var mouseOutFunction = function () {
this.style.backgroundColor = '';
};
var highlight = document.getElementsByClassName("highlight");
for (i = 0; i < highlight.length; i++) {
highlight[i].onmouseover = mouseOverFunction;
highlight[i].onmouseout = mouseOutFunction;
}
};
Here's the fiddle: http://jsfiddle.net/khnGN/
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";
};