I am creating a 'photos' page on a website. It uses PHP to retrieve the filenames in a directory, and then attempts to create divs (with images in them) programmatically with javascript. However, when I try to create 'w3-third' divs, edit the innerHTML so that it embeds an image, and (the problematic step) add them to the 'w3-row' div, it removes the existing children. Hence, there is only one image per row.
I have been looking for alternate code / solutions, but the element.appendChild() function seems to be the only method; I have tried element.children.push(), but element.children is an [HTMLCollection] which (I guess) is read-only.
$.getJSON("content/photospage/get_filenames.php", function(data){
var photoFileNames = data;
console.log(photoFileNames.length + " images to display.");
var photosDiv = document.getElementById("divPhotos");
for(var i = 0; i < photoFileNames.length; i += 3){
console.log("Loop! i=" + i);
var newRow = document.createElement("div");
newRow.classList.add("w3-row");
newRow.classList.add("w3-padding-8")
var newImg1 = newImg2 = newImg3 = document.createElement("div");
newImg1.classList.add("w3-third")
newImg2.classList.add("w3-third")
newImg3.classList.add("w3-third")
newImg1.innerHTML = "<img src='" + dir + photoFileNames[i] + "' class='w3-round w3-margin-bottom constrained'>";
newRow.appendChild(newImg1);
console.log("displayed img " + (i))
if(i+1 < photoFileNames.length){
newImg2.innerHTML = "<img src='" + dir + photoFileNames[i+1] + "' class='w3-round w3-margin-bottom constrained'>";
newRow.appendChild(newImg2);
console.log("displayed img " + (i+1))
}
if(i+2 < photoFileNames.length){
newImg3.innerHTML = "<img src='" + dir + photoFileNames[i+2] + "' class='w3-round w3-margin-bottom constrained'>";
newRow.appendChild(newImg3);
console.log("displayed img " + (i+2))
}
console.log(newRow.children);
photosDiv.appendChild(newRow);
}
The html element that exists by default:
<div class="w3-container w3-content w3-center w3-padding-32 " id="divPhotos">
</div>
Sorry for the large amount of code above. Thanks for any assistance, and I'm happy to clarify anything that I failed to mention. :)
Also, I am aware that the code is clunky and inefficient, so let me know if you pick up on anything I could do better. Thanks again! :)
With
var newImg1 = newImg2 = newImg3 = document.createElement("div");
you've created one object (an HTMLDivElement) in memory, which 3 variable names (newImg1, newImg2, newImg3) refer to. You do not have 3 separate elements. When you call appendChild with one of the elements, you remove it from wherever it previously existed in the DOM.
Since you want separate elements, you should do so explicitly:
var newImg1 = document.createElement("div");
var newImg2 = document.createElement("div");
var newImg3 = document.createElement("div");
You could make the code less repetitive by using another for loop instead of creating separate standalone elements:
for (let j = 0; j < 3; j++) {
const thisIndex = i + j;
if (thisIndex >= photoFileNames.length) {
break;
}
const img = document.createElement("div");
img.innerHTML = "<img src='" + dir + photoFileNames[thisIndex] + "' class='w3-round w3-margin-bottom constrained'>";
newRow.appendChild(img);
}
Related
I am trying to figure out how I can clear the <p> elements that are generated from a for loop before the for loop starts.
Essentially. I have a webpage where someone searches something and a list of results are shown. However if I search for something else, the new results get appended instead of clearing the old results first.
Here is the code:
async function parseitglinkquery() {
var queriedresults = await getitglinkquery();
console.log(queriedresults);
const output = document.querySelector('span.ms-font-mitglue');
output.removeChild("createditginfo"); \\tried to clear the <pre> here and it failed
for (let i = 0; i < queriedresults.length; i++) {
let text = "Company: " + JSON.stringify(queriedresults[i]["data"]["attributes"].organization-name) + "<br>"+
"Name: " + JSON.stringify(queriedresults[i]["data"]["attributes"].name) + "<br>" +
"Username: " + JSON.stringify(queriedresults[i]["data"]["attributes"].username).replace("\\\\","\\") + "<br>" +
"Password: " + JSON.stringify(queriedresults[i]["data"]["attributes"].password);
let pre = document.createElement('p');
pre.setAttribute("id", "createditginfo")
pre.innerHTML = text;
pre.style.cssText += 'font-size:24px;font-weight:bold;';
output.appendChild(pre);
console.log(typeof pre)
}
}
I tried to create a try and catch block where it would try to clear the <p> using removeChild() but that didn't seem to work either.
async function parseitglinkquery() {
var queriedresults = await getitglinkquery();
console.log(queriedresults);
const output = document.querySelector('span.ms-font-mitglue');
try {
output.removeChild("createditginfo");
}
catch(err){
console.log(err)
}
for (let i = 0; i < queriedresults.length; i++) {
let text = "Company: " + JSON.stringify(queriedresults[i]["data"]["attributes"].organization-name) + "<br>"+
"Name: " + JSON.stringify(queriedresults[i]["data"]["attributes"].name) + "<br>" +
"Username: " + JSON.stringify(queriedresults[i]["data"]["attributes"].username).replace("\\\\","\\") + "<br>" +
"Password: " + JSON.stringify(queriedresults[i]["data"]["attributes"].password);
let pre = document.createElement('p');
pre.setAttribute("id", "createditginfo")
pre.innerHTML = text;
pre.style.cssText += 'font-size:24px;font-weight:bold;';
output.appendChild(pre);
console.log(typeof pre)
}
}
You only have to clear the output-node right before the loop using the innerHTML-property.
output.innerHTML = '';
https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
There are other ways too, if you want to remove only specific childs. You can make use of Node.childNodes together with a loop. With this, you have the opportunity to remove only specific children.
[...output.childNodes].forEach(childNode => {
output.removeChild(childNode)
});
// or specific
[...output.childNodes].forEach(childNode => {
// remove only <div>-nodes
if (childNode.nodeName == 'DIV') {
childNode.remove();
}
});
https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes
https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
https://developer.mozilla.org/en-US/docs/Web/API/Element/remove
The answer above is correct, but I believe that the original code and the answer can be further improved:
for variables that do not change, use const instead of let - this helps explaining the intention.
there seems to be a bug - if you have an attribute called "organization-name", you cannot access it as a property (...["attributes"].organization-name), but you can use array access instead: ...["attributes"]["organization-name"]. Otherwise, the code end up effectively as ...["attributes"].organization - name
when you have long property paths that are repeated a lot (like queriedresults[i]["data"]["attributes"] in your case), consider assigning them to a local variable.
This makes the code more readable and is actually better for performance (because of less array lookups):
// old code
let text = "Company: " + JSON.stringify(queriedresults[i]["data"]["attributes"].organization-name) + "<br>"+
"Name: " + JSON.stringify(queriedresults[i]["data"]["attributes"].name) + "<br>";
// new code
const attrs = queriedresults[i]["data"]["attributes"];
let text = "Company: " + JSON.stringify(attrs['organization-name']) + "<br>"+
"Name: " + JSON.stringify(attrs.name) + "<br>";
you are creating several pre's in a loop, this is ok, however the element id must be different! Each id must be unique in the whole page:
// wrong
for (let i = 0; i < queriedresults.length; i++) {
...
pre.setAttribute("id", "createditginfo")
...
}
// right/better
for (let i = 0; i < queriedresults.length; i++) {
...
pre.setAttribute("id", "createditginfo" + i) // "i" for uniqueness
...
}
you can use innerText, in which case you do not need to encode the attributes, plus it simplifies the code:
const pre = document.createElement('p');
pre.innerText = [
"Company: " + attrs["organization-name"],
"Name: " + attrs.name,
"Username: " + attrs.username, // presumably you don't need to decode "/" anymore :)
"Password: " + attrs.password
].join("\n") // so we declared a bunch of lines as an array, then we join them with a newline character
Finally, regarding the original question, I see three main ways:
simply clearing the contents of the parent with output.innerHtml = ''
iterating over each child and removing it with output.removeChild(childPre)
you can keep references to the generated pre's (eg, store each element in an array) and remove the later with point 2, but this is less automatic but in some cases it might be more efficient if you have a tone of elements at that same level.
I'm developing a way to export Woocommerce order data into a PDF doc table, using javascript.
For that I'm storing the innerText values of each order table cell in different let variables and later
use those variables to populate a HTML table which will appear in my final PDF doc.
I feel like I'm close to hitting gold, but I get a console error of Uncaught TypeError: Cannot read property 'innerText' of undefined on browser.
This is my code so far, any idea why I'm getting this error and any way I could fix it?
function printData(){
let orderTableData = document.getElementsByClassName("woocommerce_order_items")[0].innerHTML;
let orderTableSize = document.getElementById("order_line_items");
let tamanho = orderTableSize.rows.length;
let orderPricesData = document.getElementsByClassName("wc-order-totals")[0].innerHTML;
let orderItemName = document.getElementById("order_line_items").getElementsByClassName("name")[0].innerText;
let orderItemPrice = document.getElementById("order_line_items").getElementsByClassName("item_cost")[0].innerText;
let orderItemQtd = document.getElementById("order_line_items").getElementsByClassName("quantity")[0].innerText;
let orderItemTotal = document.getElementById("order_line_items").getElementsByClassName("line_cost")[0].innerText;
for(var j = 0; j <= tamanho; j++){
window.frames["print_frame"].document.body.innerHTML = "<table border='1' width='100%'><tr><th>Item</th><th>Preço uni.</th><th>Qtd.</th><th>Total</th></tr><tr><td>" + document.getElementById("order_line_items").getElementsByClassName("name")[j].innerText + "</td><td>" + document.getElementById("order_line_items").getElementsByClassName("item_cost")[j].innerText + "</td><td>" + document.getElementById("order_line_items").getElementsByClassName("quantity")[j].innerText + "</td><td>" + document.getElementById("order_line_items").getElementsByClassName("line_cost")[j].innerText + "</td></tr></table>";
}
window.frames["print_frame"].window.focus();
window.frames["print_frame"].window.print();
}
I'll also leave a print of the Woocommerce table I'm using for test.
In your loop, you don't need to use the condition <= (less than or equal) because you are begining with 0 (because its a array index), just use < (less than) in these cases.
Another thing, You should use a iframe to put the content you want to print, I didn't understand the window.frames logic you used.
I tested that snippet and it works:
function printData(){
let orderTableData = document.getElementsByClassName("woocommerce_order_items")[0].innerHTML;
let orderTableSize = document.getElementById("order_line_items");
let tamanho = orderTableSize.rows.length;
let orderPricesData = document.getElementsByClassName("wc-order-totals")[0].innerHTML;
let orderItemName = document.getElementById("order_line_items").getElementsByClassName("name")[0].innerText;
let orderItemPrice = document.getElementById("order_line_items").getElementsByClassName("item_cost")[0].innerText;
let orderItemQtd = document.getElementById("order_line_items").getElementsByClassName("quantity")[0].innerText;
let orderItemTotal = document.getElementById("order_line_items").getElementsByClassName("line_cost")[0].innerText;
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.style.display = 'none';
for(var j = 0; j < tamanho; j++){
iframe.contentDocument.body.innerHTML = "<table border='1' width='100%'><tr><th>Item</th><th>Preço uni.</th><th>Qtd.</th><th>Total</th></tr><tr><td>" + document.getElementById("order_line_items").getElementsByClassName("name")[j].innerText + "</td><td>" + document.getElementById("order_line_items").getElementsByClassName("item_cost")[j].innerText + "</td><td>" + document.getElementById("order_line_items").getElementsByClassName("quantity")[j].innerText + "</td><td>" + document.getElementById("order_line_items").getElementsByClassName("line_cost")[j].innerText + "</td></tr></table>";
}
iframe.contentWindow.focus();
iframe.contentWindow.print();
document.body.removeChild(iframe);
}
I need some help. I am trying to iterate through some Json items, create some li's with some information from the items, but also attach click handlers for the li's, but with a value transmited as parameter.
The problem is that the last value of that parameter is set for or the li's in the list. From what i searched i understood that it has something to do with javascript closure, but can't seem to understand how to fix it.
I am using jQuery and here is the code:
for (var i = 0; i < items.length; i++)
{
// information that will be displayed for each video
var entry = items[i];
var title = entry.title;
var image = entry.thumbnail.hqDefault;
var id = entry.id;
var li = $("<li class='video-single'>");
li.append("<img src='" + image + "' alt='" + title + "'>");
li.append("<h4>" + title + "</h4>");
$(li).click(function() {
displayPopUp(id);
});
ul.append(li);
}
Could anyone please help me fix this code?
Best regards, Marius.
The issue is that JS is function scoped so the id within your closure is the last id from your for loop. To fix this use forEach so you run each iteration of your loop in a separate scope e.g.
items.forEach(function (el, i) {
// information that will be displayed for each video
var entry = items[i];
var title = entry.title;
var image = entry.thumbnail.hqDefault;
var id = entry.id;
var li = $("<li class='video-single'>");
li.append("<img src='" + image + "' alt='" + title + "'>");
li.append("<h4>" + title + "</h4>");
$(li).click(function() {
displayPopUp(id);
});
ul.append(li);
});
you need delegated event as elements are created dynamically, you can use class that is being added on li which is video-single:
$(document).on('click','.video-single',function() {
displayPopUp(id);
});
you can read about Delegated Events HERE
in order to bind an event to a dynamically added element you need to delegate it as below:
for (var i = 0; i < items.length; i++)
{
// information that will be displayed for each video
var entry = items[i];
var title = entry.title;
var image = entry.thumbnail.hqDefault;
var id = entry.id;
var li = $("<li class='video-single'>");
li.append("<img src='" + image + "' alt='" + title + "'>");
li.append("<h4>" + title + "</h4>");
$(document).bind('click',li,function() {
displayPopUp(id);
});
ul.append(li);
}
I am trying to populate a div (class='thumb") with thumnailpictures. The name of the pictures is gathered from an xml file. Every thumnail picture is encapsulated with an a-tag that points to a function to show the full picture.
On some browsers the first thumbnailpicture seems not to have this a-tag...clicking on it gives no action...
var rubriek = gup('rubriek'), // rubriek is a parameter in the url of this page pointing a specific photogallery
gallerij = rubriek + "gallery.xml",
xmlDoc = loadXMLDoc(gallerij),
x = xmlDoc.getElementsByTagName("filename"),
legebron = x[0].childNodes[0].nodeValue;
for (i = 0; i < x.length; i++) {
var beeldnummer = x[i].childNodes[0].nodeValue,
index = i + 1,
kleinbeeld = "/" + rubriek + "images/thumb/" + beeldnummer;
$('.thumb').append('<p><a class="kleintje" onclick="toonDezeFoto("' + beeldnummer + '",' + index + ',' + x.length + ');">
<img src="' + kleinbeeld + '" id="' + index + '">
</a></p>');
}
See http://www.karinedaelman.be/foto.php?rubriek=Mensen/
Found the solution: the z-index of the div that keeps the thumbnails was too low. Another div that has the site logo in it had a higher z-index and a width of 100%. Because of that the first thumbnail image was on a layer below the site logo and could not be accessed by the mouseevents....
var subjectList;
function PageMaster()
{
this.contentDiv = document.getElementById("content");
}
/**
* Builds the main part of the web page based on the given XML document object
*
* #param {Object} xmlDoc the given XML document object
*/
PageMaster.prototype.doIt = function(xmlDoc)
{
alert("PageMaster()");
alert("Clear page...");
this.contentDiv.innerHTML = "";
if (null != xmlDoc)
{
alert("Build page...");
//create div Post
var divPost = document.createElement("div");
divPost.className = "post";
//create h1 element
var h1Element = document.createElement("h1");
var headingText = document.createTextNode("Invitations");
h1Element.appendChild(headingText);
//insert h1 element into div post
divPost.appendChild(h1Element);
subjectList = xmlDoc.getElementsByTagName("subject");
var groupList = xmlDoc.getElementsByTagName("group");
for (var i = 0; i < subjectList.length; i++) //for each subject
{
var divEntry = document.createElement("div");
divEntry.className = "entry";
var subjectNum = subjectList[i].attributes[0].nodeValue;
var subjectName = subjectList[i].attributes[1].nodeValue;
var groupId = groupList[i].attributes[0].nodeValue;
var groupName = groupList[i].attributes[1].nodeValue;
var ownerId = groupList[i].attributes[2].nodeValue;
//set up the invitation table attributes
var table = document.createElement("table");
table.width = 411;
table.border = 3;
table.borderColor = "#990000"
tableRow = table.insertRow(0);
tableCell = tableRow.insertCell(0);
var cellContent = "";
//create invitation message
var invitationMsg = "<p>Subject : " + subjectNum + " - " + subjectName + "</p>";
invitationMsg += "<p>You are invited to join " + groupName + " (groupId : " + groupId + ") by owner Id:" + ownerId + "</p>";
cellContent += invitationMsg;
//create buttons
cellContent += "<input type='button' id='acceptButton" + i + "' value='Accept' onclick='acceptInvitation(i)'>"
cellContent += "<input type='button' id='declineButton" + i + "' value='Decline'>"
tableCell.innerHTML = cellContent;
divEntry.appendChild(table);
var blankSpace = document.createElement("p");
divEntry.appendChild(blankSpace);
divPost.appendChild(divEntry);
}
//insert div post into div content
this.contentDiv.appendChild(divPost);
}
};
function acceptInvitation(i)
{
alert("hello");
//alert(subjectList[i].attributes[0].nodeValue);
}
above is extract of my javascript code. What the code do is to create a table of inviting group from the xml file with accept and decline button. when user press accept, the table will disappear and the table below will move up. For now I am only testing my accept invitation button to see if it works.But
my onclick function in the accept button does not work for some reason I don't understand. the alert in acceptInvitation() is not read. Any help will be appreciated. Thanks
What about:
cellContent += "[...] onclick='acceptInvitation("+i+")'>"
This ensures that i is evaluated with the value of the variable instead of as a literal
Try to call it like this
onclick='acceptInvitation();'
Not like this
onclick='acceptInvitation(i)'
dont know if that's what's causing your problem but your outputting onclick='acceptInvitation(i)' Im guessing you want to output acceptInvitation(value-of-i), that is acceptInvitation(" + i + ")
while perhaps not addressing the central problem,
onclick='acceptInvitation(i)'
in this case i would be undefined.
onclick='acceptInvitation("+i+")'
would solve at least one problem. Also, you're using an unusual mixture of innerHTML and DOM methods. Why not stick to the DOM methods and use attachEvent/AddEventListener?
edit: A list apart has a good article on binding of variables at http://www.alistapart.com/articles/getoutbindingsituations/
The following is a somewhat specialized example. See the article for more generalized case (or use a library like Prototype)
var click_hdlr = function() {
return function() {
return acceptInvitation.apply(this,arguments);
}
}
var acc_btn = document.createElement("input");
acc_btn.setAttribute("type","button");
acc_btn.setAttribute("id","accept");
acc_btn.setAttribute("value","Accept");
acc_btn.addEventListener("click",click_hdlr(i),false);