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.
Related
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);
}
I was given this task with some existing code to change the string color of each of three selector.value(s) that is output onto an input element to three different colors. The code boils the three selectors into a single output variable. Without destroying the code, I cannot figure out how to select each individual variables prior to condensing them.
If I could use the fontcolor() method, my life would be great but it's 2018 and I can't. Is there any way you can think of to solve this issue?To clarify, I need to alter the colors of the strings that belong to output(red), select1.value(blue) and select2.value(black.
Most of the action for this is happening in the parseOutput() function but I'm just stuck and don't think it's possible without rewriting the entire program.
function updateSelector(result){
var options = result.options;
var elementId = "select" + result.element;
var logger = document.getElementById('logger');
var selector = document.getElementById(elementId);
//logger.innerHTML = JSON.stringify(elementId);
selector.innerHTML = options;
selector.disabled = false;
}
google.script.run.withSuccessHandler(updateSelector).processOptions(0);
plate();
function resetAll(){
for (var i = 0;i<3;i++){
var selector = document.getElementById('select' + i);
selector.disabled = true;
selector.innerHTML = "";
}
google.script.run.withSuccessHandler(updateSelector).processOptions(0);
}
function finalSelection(){
var output = document.getElementById('out');
//output.focus();
output.select();
}
function plate(){
var plate = document.getElementById('plate');
plate.innerHTML = atob('Q3JhZnRlZCBieTogWmFjaGFyeSBTdGFjaG93aWFr');
}
//Adds the location as initial output, followed by divider, application, and issue if select1 is selected
//else statement added so if select0 is [Costco Website Name], to ommit the " - "
function parseOutput(){
var output = "";
if (select1.value.length > 0 && select0.value !== "[Costco Website Name]"){
output = output + ' - ' + select1.value + ' // ' + select2.value;
} else{
output = output + select1.value + ' // ' + select2.value;
}
out.value=output.trim();
}
And this is the Div that displays the output:
<div class="wide"><p><input class="wide" type="readonly" id="out" onfocus="this.select();"></p></div>
A modern replacement for fontcolor would use a span and a style (or class), e.g.:
function modernFontColor(str, color) {
return '<span style="color: ' + color + '">' + str + '</span>';
}
or
function modernFontClass(str, cls) {
return '<span class="' + cls + '">' + str + '</span>';
}
...where the class defines the styling.
Premise:
I'm playing around with javascript and have been trying to display a populated JSON file with an array of people on the browser. I've managed to display it through ajax, but now I'm trying to perform the same task with jQuery.
Problem:
The problem is that it keeps saying customerdata[i] is undefined and can't seem to figure out why.
$(function() {
console.log('Ready');
let tbody = $("#customertable tbody");
var customerdata = [];
$.getJSON("MOCK_DATA.json", function(data) {
customerdata.push(data);
});
for (var i = 0; i < 200; i++) {
//Cell for name
let nameTD = $('<td>').text(customerdata[i].first_name + ", " + customerdata[i].last_name);
//Cell for birthdate
let mDate = moment(customerdata[i].birthdate);
let formattedmDate = mDate.format('YYYY-MM-DD');
let birthdateTD = $('<td>').text(formattedmDate);
//Cell for Address
let addressTD = $('<td>').html("City: " + customerdata[i].city + '<br>' + "Email: " + customerdata[i].email + '<br>' + '<a href=' + customerdata[i].website + '>Website</a>');
//Cell for Credits
let creditTD = $('<td>').text(customerdata[i].credits);
let row = $('<tr>').append(nameTD).append(birthdateTD).append(addressTD).append(creditTD);
tbody.append(row);
}
})
SAMPLE CONTENT OF MOCK_DATA.json
[
{"id":1,"first_name":"Tracey","last_name":"Jansson","email":"tjansson0#discuz.net","gender":"Female","ip_address":"167.88.183.95","birthdate":"1999-08-25T17:24:23Z","website":"http://hello.com","city":"Medellín","credits":7471},
{"id":2,"first_name":"Elsa","last_name":"Tubbs","email":"etubbs1#uol.com.br","gender":"Female","ip_address":"61.26.221.132","birthdate":"1999-06-28T17:22:47Z","website":"http://hi.com","city":"At Taḩālif","credits":6514}
]
Firstly, you're pushing an array into an array, meaning you're a level deeper than you want to be when iterating over the data.
Secondly, $.getJSON is an asynchronous task. It's not complete, meaning customerdata isn't populated by the time your jQuery is trying to append the data.
You should wait for getJSON to resolve before you append, by chaining a then to your AJAX call.
$.getJSON("MOCK_DATA.json")
.then(function(customerdata){
for(var i = 0; i < 200; i++){
//Cell for name
let nameTD = $('<td>').text(customerdata[i].first_name + ", " + customerdata[i].last_name);
//Cell for birthdate
let mDate = moment(customerdata[i].birthdate);
let formattedmDate = mDate.format('YYYY-MM-DD');
let birthdateTD = $('<td>').text(formattedmDate);
//Cell for Address
let addressTD = $('<td>').html("City: " +
customerdata[i].city + '<br>' + "Email: " +
customerdata[i].email + '<br>' + '<a
href='+customerdata[i].website+'>Website</a>');
//Cell for Credits
let creditTD = $('<td>').text(customerdata[i].credits);
let row = $('<tr>').append(nameTD).append(birthdateTD).append(addressTD).append(creditTD);
tbody.append(row);
}
})
You also won't need to define customerdata as an empty array at all with this approach.
The problem is that data is already an array.
so you should use:
customerdata = data;
otherwhise you are creating an array in the pos 0 with all the data
This is the code that I currently have, one problem that is happening is I cannot use test() because presets[index].name and value are not visible outside of their function scope, how should I declare my array of objects in the global scope in order for me to be able to access these two variables in other functions?
var presets = [];
var index;
function CreatePresetArray(AMib, AVar) {
var parentpresetStringOID = snmp.getOID(AMib, AVar);
var presetStringOID = parentpresetStringOID;
parentpresetStringOID = parentpresetStringOID.substring(0, parentpresetStringOID.length - 2);
log.error("parentpresetStringOID is " + parentpresetStringOID);
var presetswitches = {};
for (var i = 1; i < 41; i++) {
presets.push(presetswitches);
try {
log.error("presetStringOID before getNextVB= " + presetStringOID);
vb = snmp.getNextVB(presetStringOID);
presetStringOID = vb.oid;
log.error("presetStringOID after getnextVB= " + presetStringOID);
var presetStringVal = snmp.get(presetStringOID);
log.error("presetStringVal= " + presetStringVal);
index = i - 1;
presets[index].name = presetStringOID;
presets[index].value = presetStringVal;
log.error("preset array's OID at position [" + index + "] is" + presets[index].name + " and the value stored is " + presets[index].value);
//log.error("presets Array value ["+index+"] = "+presets[index].configs);
if (presetStringOID.indexOf(parentpresetStringOID) != 0) {
break;
}
} catch (ie) {
log.error("couldn't load preset array " + index);
};
};
}
CreatePresetArray(presetMib, "presetString");
function test() {
for (i = 1; i < 41; i++) {
log.error("test" + presets[index].name + " " + presets[index].value);
};
}
test();
The for loop in your function test iterates over i but uses index inside the loop. Perhaps you meant to use
for (i = 0; i < 40; i++) { // 1 lower as you were using `index = i - 1` before
log.error("test" + presets[i].name + " " + presets[i].value);
}
Re-wrote your code. I don't think I made that much by way of change. If this doesn't clear up your problem, consider: Is the catch happening each iteration? Is the problem actually coming from a different method which is only visible here? Also, consider logging the whole presets Array when debugging to see what it looks like.
var presets = [];
function CreatePresetArray(AMib, AVar) {
var parentPresetOID, presetOID, presetValue, preset, vb, i;
parentPresetOID = snmp.getOID(AMib, AVar);
presetOID = parentPresetOID; // initial
parentPresetOID = parentPresetOID.substring(0, parentPresetOID.length - 2);
log.error("parentPresetOID is " + parentPresetOID);
presets = []; // empty array in case not already empty
for (i = 0; i < 40; ++i) {
try {
preset = {}; // new object
// new presetOID
vb = snmp.getNextVB(presetOID);
presetOID = vb.oid;
log.error("presetOID after getnextVB= " + presetOID);
// new value
presetValue = snmp.get(presetOID);
log.error("presetValue= " + presetValue);
// append data to object
preset.name = presetOID;
preset.value = presetValue;
// append object to array
presets.push(preset);
// more logging
log.error(
"preset array's OID at position [" + i + "]" +
" is" + presets[i].name + " and " +
"the value stored is " + presets[i].value
);
if (presetOID.indexOf(parentPresetOID) !== 0) {
break;
}
} catch (ie) {
log.error("couldn't load preset array " + i);
if (presets.length !== i + 1) { // enter dummy for failed item
presets.push(null);
}
}
}
}
Two options come to mind immediately:
you could pass the preset array as a argument to test().
You could put both CreatePresetArray() and test() inside a wrapper function and declare preset array at the top of your wrapper. That would give them both access to the variable.
It's generally considered Bad Form to declare globals if it can be avoided. Pollutes the namespace.
The above screengrab is from Firefox. The cursor is hovering over the yellow spot at the left hand side of the image. It is an <img> element (well actually it's an image together with an image map containing a single circular <area> element, but I assume this distinction is unimportant) that has been created and styled in JavaScript, including the application of a title attribute (constructed by cutting and gluing strings). How can I get this to behave and show the intended character, an en dash, instead of –? It works for innerHTML (the text "Barrow-In-Furness" in the top middle-left is a div that was also created using JavaScript, and its innerHTML set.)
Edit: In response to question of Domenic: Here is the JavaScript function that builds and applies the title attribute (in addition to performing other jobs):
var StyleLinkMarker = function (LinkNumber, EltA, EltI) {
var AltText = LocationName[LinkStart[LinkNumber]] +
" to " +
LocationName[LinkEnd[LinkNumber]];
if (!EltA) {
EltA = document.getElementById("link_marker_area" + LinkNumber);
EltI = document.getElementById("link_marker_img" + LinkNumber);
}
if (LinkStatus[LinkNumber] === 9) {
var CanBuyLinkCode = BoardPreviewMode ? 0 : CanBuyLink(LinkNumber);
if (CanBuyLinkCode === 0) {
EltI.src = ImagePath + "icon-buylink-yes.png";
AltText += " (you can buy this " + LinkAltTextDescription + ")";
} else {
EltI.src = ImagePath + "icon-buylink-no.png";
AltText += " (you cannot buy this " + LinkAltTextDescription;
AltText += CanBuyLinkCode === 1 ?
", because you aren't connected to it)" :
", because you would have to buy coal from the Demand Track, and you can't afford to do that)";
}
} else if ( LinkStatus[LinkNumber] === 8 ||
(LinkStatus[LinkNumber] >= 0 && LinkStatus[LinkNumber] <= 4)
) {
EltI.src = ImagePath + "i" + LinkStatus[LinkNumber] + ".png";
if (LinkStatus[LinkNumber] === 8) {
AltText += " (orphan " + LinkAltTextDescription + ")";
} else {
AltText += " (" +
LinkAltTextDescription +
" owned by " +
PersonReference(LinkStatus[LinkNumber]) +
")";
}
} else {
throw "Unexpected Link Status";
}
EltA.alt = AltText;
EltA.title = AltText;
};
LocationName is as follows:
var LocationName = [
"Barrow–In–Furness", "Birkenhead", "Blackburn", "Blackpool",
"Bolton", "Burnley", "Bury", "Colne",
"Ellesmere Port", "Fleetwood", "Lancaster", "Liverpool",
"Macclesfield", "Manchester", "The Midlands", "Northwich",
"Oldham", "Preston", "Rochdale", "Scotland",
"Southport", "Stockport", "Warrington & Runcorn", "Wigan",
"Yorkshire"
];
You aren't setting the title attribute, you are setting the title property, which expects text and not HTML (although the setAttribute method also expects a text string).
Generally speaking, when dealing with DOM manipulation, you provide text and not HTML. .innerHTML is the notable exception to this rule.
Here's an easy way to convert from HTML to text:
function convertHtmlToText(value) {
var d = document.createElement('div');
d.innerHTML = value;
return d.innerText;
}
Your code could then be updated to this:
EltA.title = convertHtmlToText(AltText);