This front-end JavaScript code works, it creates li itens in a ol tag from a list of string data. I would like to do it in just one line...
qelements.map(element => { let litag = document.createElement("li");
litag.innerHTML = element;
oltag.appendChild(litag) });
But this one I get an error: "qlist.html:32 Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'."
qelements.map(element => oltag.appendChild(document.createElement("li").innerHTML = element));
Taking out the ".innerHTML = element" it creates empty li elements inside a ol tag as expected
This:
document.createElement("li").innerHTML = element
is an assignment expression. Its resulting value is going to be whatever is on the right hand side of that equals sign (which I'm assuming is a string).
So when you do this:
qelements.map(element => oltag.appendChild(document.createElement("li").innerHTML = element));
You are essentially passing element to appendChild and abandoning the newly created li element, which isn't what you want to do.
If you want to avoid having multiple lines in that particular part of your code, you can factor out a function for creating the li:
function makeLi(content) {
let litag = document.createElement("li");
litag.innerHTML = content;
return litag:
}
Then use it:
qelements.forEach(element => oltag.appendChild(makeLi(element)));
Note also that you seem to be abusing the map() method. If you just want to iterate over a series of values and do something with each one, use .forEach or for .. of.
In my opinion:
You should use snake_case or CamelCase for naming your variables. (qelements -> qElements or q_elements
You should write simple, easy to understand code, not combining complex codes. If you are in a team and you do this, you will kill every one else, they will have a hard time understand what you are writing. Even if you come back and read these codes after a long time, you will not understand yourself.
In this case you can create a helper function to make it one line:
const createLiTagWithText = text => {
const li = document.createElement("li");
li.innerHTML = text;
return li;
}
qElements.map(element => oltag.appendChild(createLiTagWithText(element)));
oltag.appendChild(document.createElement("li").innerHTML = element)
This passes the return value of the assignment expression to appendChild, in other words, it's the same as oltag.appendChild(element). The first version is already pretty much the most readable way to do it.
Related
I'm having trouble doing a function in javascript
like she takes the form data and lists it below
I'm doing it this way
<pre>
> function changeText2 () {
> var assembly = ['staff', 'description']
> var node = document.createElement ("LI");
> var textnode = document.createTextNode (assembly);
> node.appendChild (textnode);
document.getElementById ("demo"). appendChild (node);
</pre>
only it returns only ids "staff" and "description"
I want you to take several input
An example
what pretty much what I want you to do is this
only with several campuses
Change it to something like this below. Don't forget to modify HTML accordingly as written in JSfiddle link
const list = document.getElementById('demo');
const changeText2= () => {
let staff = document.getElementById('staff').value;
let desc = document.getElementById('desc').value;
let node = document.createElement ("LI");
let textnode = document.createTextNode(`${staff}, ${desc}`);
node.appendChild (textnode);
document.getElementById ("demo"). appendChild (node);
}
https://jsfiddle.net/1gjs789f/
To expand on your code, first you didn't specify whether you want inputs taken from the fields specified within HTML DOM. You do that by separately calling getElementById on specific fields (in this case, 'staff' and 'desc', as per your code example) and call .value function on them to get the value from the fields.
After that, configure the text that will be displayed inside the LI element.
Using `` and ${} constructions of ES6 as in my example its quite easy, but you can as well use the example below if you find it easier. I may argue it's indeed easier on the eyes, even though I prefer ES6 way.
let textnode = document.createTextNode(staff + ", " + desc);
Finally, append the text node, then append the li element. Done.
I'm building a law firm website in vanilla JS, that involves a lot of pages with content. I'm trying to have a single page display the different pages of content as the user clicks on different links.
I'm doing this by having the html already written with a default property of display: none, and as the user clicks on links, the target link can dynamically change that element's CSS to display: block, while making sure that all other elements are set to display: none, so that it can show one at a time. I have an array that contains all content elements and using splice to remove the target element from the map function which is supposed to set all elements to display: none, and them I'm setting the target element to display: block.
The problem is map is not executing when I use it within the event handler, but it does work when I use it outside the event handler. It's not throwing any errors, it's just not setting the display to none.
I've tried different ways of getting the same results such as using the forEach() function, but this is also having a similar result to map. It's not throwing any errors but it's not working. I tried creating a universal class with display: none, and dynamically adding that class to all target elements using map or forEach but that's also not working.
Also, splice was not working because it kept telling me that splice is not a function. After a lot of trial and error, and tons of research the only way I was able to fix this was by manually targeting each element using document.getElementById() which is extremely inefficient. It never worked when I used document.getElementsByClassName(), or when I used document.querySelectorAll()
Problem One: splice is not a function
Grabbing all elements and destructuring them for more efficiency
let contentText = document.getElementsByClassName('content-text');
let [immigrationText, contractsText, divorceText, debtCollectionText, escrowText, expungementText, mediationText, personalInjuryText, willsAndTrustsText] = contentText;
Using splice within the event handler
let immigrationEvent = () => {
contentText.splice(0, 1);
contentText.map((link) => link.style.display = 'none');
immigrationText.style.display = 'block';
};
Error
Uncaught TypeError: contentText.splice is not a function
at HTMLLIElement.immigrationEvent
Problem 2: Map feature doesn't execute
Grabbing all links and destructuring them. This is so the user can click on these links, and when they click on them, it should map through all the links that are not being called at the time, and should change the css display to none
let contentLinks = Array.from(document.getElementsByClassName('side-
nav-items'));
let [immigration, contracts, divorce, debtCollection, escrow,
expungement, mediation, personalInjury, willsAndTrusts] =
contentLinks;
Grabbing all elements that contain the content we wish to display, and destructuring them, and adding them to the array called contentText
let contentText = Array.from(document.getElementsByClassName('content-
text'));
let [immigrationText, contractsText, divorceText, debtCollectionText,
escrowText, expungementText, mediationText, personalInjuryText,
willsAndTrustsText] = contentText;
Splicing the array to exclude the page that's currently being called by the user, and then mapping the rest so that they switch the display property to none. This, of course, is the event handler function
let immigrationEvent = () => {
contentText.splice(0, 1);
contentText.map((link) => link.style.display = 'none');
immigrationText.style.display = 'block';
};
let contractsEvent = () => {
contentText.splice(1, 1);
contentText.map((link) => link.classList.add('content-hide'));
contractsText.style.display = 'block';
};
Calling the Event handler function. I'm including 2 examples
immigration.addEventListener('click', immigrationEvent, false);
contracts.addEventListener('click', contractsEvent, false);
Problem 1: Expected result, is I want splice to work when I use some type of select all feature, but it only works when I target them using
getElementById()
Problem 2: I want the map feature to set all target elements within the array to set to display none. Instead what happens is, as I click through the links, it adds the elements to the same page but doesn't hide the previous elements
This results in a "TypeError: contentText.splice is not a function":
let contentText = document.getElementsByClassName('content-text');
contentText.splice(0, 1);
...because getElementsByClassName returns "an array-like object" (a "live" HTMLCollection, which changes to track with the DOM), not a static array.
To make a copy in a real Array object, use Array.from(document.getElementsByClassName(...)).
For #2 the problem seems to be outside of the code you posted. Please post the code that reproduces the issue. Note that using .map, while valid, is not recommended if you just want to do something for each element of a collection.
let immigrationEvent = () => {
const elts = Array.from(document.getElementsByClassName("hideme"));
const [visibleElt] = elts.splice(0, 1);
elts.map((elt) => elt.style.display = 'none');
visibleElt.style.display = 'block';
}
document.querySelector("button").addEventListener('click', immigrationEvent, false);
<div class="hideme">one</div>
<div class="hideme">two</div>
<div class="hideme">three</div>
<button>Run</button>
I finally figured it out
Problem one: Nickolay was spot on. I needed to create an instance of my original array in order for JS to be able to parse through it
let contentText =
Array.from(document.getElementsByClassName('content-
text'));
Array from allowed me to use map, and splice etc.
Problem Two: Thanks to Nickolay I was able to notice that my map feature was working, but every time the splice was running, it was removing elements, and the page was not able to read those changes. Instead I used slice, and targeted the sliced item to be the one that would display the data, and the original array would remain intact, and the map feature would remove the display from all elements in the array. See code below
Destructuring the links that are going to trigger the event
let contentLinks =
Array.from(document.getElementsByClassName('side-nav-
items'));
let [immigration, contracts, divorce, debtCollection,
escrow, expungement, mediation, personalInjury,
willsAndTrusts] = contentLinks;
Destructuring the elements that are going to be displayed on the page
let contentText =
Array.from(document.getElementsByClassName('content-
text'));
let [immigrationText, contractsText, divorceText,
debtCollectionText, escrowText, expungementText,
mediationText, personalInjuryText, willsAndTrustsText] =
contentText;
One of the events that are supposed to display only the target content. We slice the target code block and have it display block while having all elements display none
let immigrationEvent = () => {
let targetElement = contentText.slice(0, 1);
contentText.map((text) => text.style.display = 'none');
targetElement[0].style.display = 'block';
console.log(targetElement)
};
I've got some JS code here. Basically, I am trying to change the ID of an element to some value from a previous variable.
Here's what I got so far;
function() {
var colorarray = [ "RANDOMCOLOR_0", "RANDOMCOLOR_1", "RANDOMCOLOR_2" ];
var RANcolorarray = colorarray[Math.rsound(Math.random() * (colorarray.length - 1))];
document.getElementsByClassName('RANDOMCOLOR').setAttribute('id', RANcolorarray);
}
This code throws an error in Chrome for line 4: Uncaught TypeError: undefined is not a function which is weird because JsLint finds no errors.
I also tried using the other way to setting id's;
document.getElementsByClassName('RANDOMCOLOR').id = RANcolorarray;
However, although this method does not throw an error on chrome or jslint - it does not work at all after inspecting the element.. :/
Any ideas?
document.getElementsByClassName('RANDOMCOLOR') returns a list of DOM nodes (even if there's only one match) so you can't just call .setAttribute() on the list as the list doesn't have that method.
You can either get the first item out of the list and call .setAttribute() on that one or use a for loop to iterate through the list and call it on all of them. Of course, since you're setting the id, you should not be setting multiple elements to the same id, so I'll assume you just want one element:
document.getElementsByClassName('RANDOMCOLOR')[0].id = RANcolorarray;
Or, a little more safe:
var elems = document.getElementsByClassName('RANDOMCOLOR');
if (elems && elems.length) {
elems[0].id = RANcolorarray;
}
I have a javascript variable like below:
var treeNode= [{"id":"T1"},{"id":"T2","children":[{"id":"T3"},{"id":"T4"},{"id":"T5","children":[{"id":"T6"}, {"id":"T7"},{"id":"T8"}]},{"id":"T9"},{"id":"T10"}]},{"id":"T11"},{"id":"T12"}];
node t3,t4,t5,t6,t7,t8,t9,t10 are the child of node t2
i have a link of deactivate on each node.on click on deactivate link make active and delete link .mentioned in image.
now i want to make same active and delete link on all child node of parent node.
for example T3,T4,T5,T6,T7,T8,T9,T10 are the child of T2.
if i click on T5 then this will work on T6,T7,T8.
I tried below recursive code.may be my approach is not right.please advice.
var objTreeNode = eval(treeNode);
trav(objTreeNode);
function trav(TreeNodeObj){
var i=0;
for (i=0;i<TreeNodeObj.length;i++){
if(!TreeNodeObj[i].children){
if(objID==TreeNodeObj[i].id){ // will get T2 if click on deactivate link of Item T2
document.getElementById('span_'+TreeNodeObj[i].id).innerHTML = 'Activate <a href="javascript:deleteNode(\'' + objID
+'\');">Delete</a>';
}
}
else{
childObj = TreeNodeObj[i].children;
trav(childObj)
}
}
}
There are a few silly things in your code, let me fix them:
1. "Eval is evil!"
var treeNode= [{"id":"T1"},{"id":"T2","children":[{"id":"T3"}]}];
var objTreeNode = eval(treeNode);
trav(objTreeNode);
Why would you call eval()?
Let's see what MDN has to say about this:
Don't use eval! It's dangerous and slow. There are safe (and fast!) alternatives to eval() for common use-cases.
So what is your "use-case"? Why do you call eval here? What is the "better" solution? If you read the whole documentation on MDN you can read that:
If the argument of eval() is not a string, eval() returns the argument unchanged.
So unless treeNode is a string var objTreeNode = eval(treeNode); basically equals to var objTreeNode = treeNode;
You can drop that whole eval() line and just use treeNode. It's already an object.
2. camelCase
function trav(TreeNodeObj) {
This is not an error just a convention: In JavaScript (and also in most languages with C-like syntax) the parameters of a function are written with lower camel case (first letter is lowercase, and every other word's first letter is uppercase).
function trav(treeNodeObj) {
3. objID is undefined
There is no objID variable defined in your code. Although it is possible that you have a global defined elsewhere at the given time, it is much safer to introduce it as a parameter in your function.
function trav(treeNodeObj, objID) {
4. What your code does with what and when
Let me just figure out what your code currently does:
Iterates over a given object's children property (which is hopefully an array).
If an element has no children
Check if the array item has a desired ID property, and change it's innerHTML
Else
Call the function on the children
So what it does: Changes the element with the given ID if it has no children.
What you need is: Change the element with the given ID and also it's children.
I just modified your function like this:
function trav(treeNodeObj, objID, activate) {
var i = 0;
for (i = 0; i < treeNodeObj.length; i++) {
var childrenActive = false;
if (objID === treeNodeObj[i].id || activate) { // will get T2 if click on deactivate link of Item T2
childrenActive = true;
document.getElementById('span_' + treeNodeObj[i].id).innerHTML = 'Activate Delete';
}
if (treeNodeObj[i].children) {
childObj = treeNodeObj[i].children;
trav(childObj, objID, childrenActive);
}
}
}
Since you need to change all the child elements I needed to introduce a cut. This is the activate parameter. If the activate parameter is true you don't need to check the ID anymore you know that we are iterating over the subelements of the element with the given ID, and therefore change the element anyway.
Also you need to change the elements even if they have child nodes, so I restructured the if-s.
I have also made a jsfiddle for you to test: http://jsfiddle.net/JZ52g/3/
You can change the id parameter at the function call.
A simple question I'm sure, but I can't figure it out.
I have some JSON returned from the server
while ($Row = mysql_fetch_array($params))
{
$jsondata[]= array('adc'=>$Row["adc"],
'adSNU'=>$Row["adSNU"],
'adname'=>$Row["adname"],
'adcl'=>$Row["adcl"],
'adt'=>$Row["adt"]);
};
echo json_encode(array("Ships" => $jsondata));
...which I use on the client side in an ajax call. It should be noted that the JSON is parsed into a globally declared object so to be available later, and that I've assumed that you know that I formated the ajax call properly...
if (ajaxRequest.readyState==4 && ajaxRequest.status==200 || ajaxRequest.status==0)
{
WShipsObject = JSON.parse(ajaxRequest.responseText);
var eeWShips = document.getElementById("eeWShipsContainer");
for (i=0;i<WShipsObject.Ships.length;i++)
{
newElement = WShipsObject.Ships;
newWShip = document.createElement("div");
newWShip.id = newElement[i].adSNU;
newWShip.class = newElement[i].adc;
eeWShips.appendChild(newWShip);
} // end for
}// If
You can see for example here that I've created HTML DIV elements inside a parent div with each new div having an id and a class. You will note also that I haven't used all the data returned in the object...
I use JQuery to handle the click on the object, and here is my problem, what I want to use is the id from the element to return another value, say for example adt value from the JSON at the same index. The trouble is that at the click event I no longer know the index because it is way after the element was created. ie I'm no longer in the forloop.
So how do I do this?
Here's what I tried, but I think I'm up the wrong tree... the .inArray() returns minus 1 in both test cases. Remember the object is globally available...
$(".wShip").click(function(){
var test1 = $.inArray(this.id, newElement.test);
var test2 = $.inArray(this.id, WShipsObject);
//alert(test1+"\n"+test2+"\n"+this.id);
});
For one you can simply use the ID attribute of the DIV to store a unique string, in your case it could be the index.
We do similar things in Google Closure / Javascript and if you wire up the event in the loop that you are creating the DIV in you can pass in a reference to the "current" object.
The later is the better / cleaner solution.
$(".wShip").click(function(){
var id = $(this).id;
var result;
WShipsObject.Ships.each(function(data) {
if(data.adSNU == id) {
result = data;
}
});
console.log(result);
}
I could not find a way of finding the index as asked, but I created a variation on the answer by Devraj.
In the solution I created a custom attribute called key into which I stored the index.
newWShip.key = i;
Later when I need the index back again I can use this.key inside the JQuery .click()method:
var key = this.key;
var adt = WShipsObject.Ships[key].adt;
You could argue that in fact I could store all the data into custom attributes, but I would argue that that would be unnecessary duplication of memory.