GetElementById from node object - javascript

I have the an html code which resembles this:
<div id="abc">
<div class="class1">some data</div>
<div class="class2">
<input type="number" id="row1-id1" />
<input type=number" id="row1-id2" />
</div>
<div class="class2">
<input type="number" id="row2-id1" />
<input type=number" id="row2-id2" />
</div>
</div>
I want to read all input tag and create a list from it. The input tag needs to be inside the div with class class2 which in turn should be under #abc.
I have written the following code to read them.
var rows = document.getElementById('abc').getElementsByClassName('class2');
for (var i=0;i<rows.length;i++) {
var id1 = rows[i].getElementById('row'+(i+1)+'-id1);
var id2 = rows[i].getElementById('row'+(i+1)+'-id2);
.......
.......
}
Now when i try to do the above i get an error saying 'getElementById is not a function'.
How do i access the element with specific Id inside the node?

First of fix the mistakes in your code. Your missing 2 " in your html, and 2 ' in your javascript.
Second, then use querySelector like: rows[i].querySelector('#row' + (i + 1) + '-id1'). Then your code works fine.
Demo
var rows = document.getElementById('abc').getElementsByClassName('class2');
for (var i = 0; i < rows.length; i++) {
var id1 = rows[i].querySelector('#row' + (i + 1) + '-id1');
var id2 = rows[i].querySelector('#row' + (i + 1) + '-id2');
console.log(id1)
}
<div id="abc">
<div class="class1">some data</div>
<div class="class2">
<input type="number" id="row1-id1" />
<input type="number" id="row1-id2" />
</div>
<div class="class2">
<input type="number" id="row2-id1" />
<input type="number" id="row2-id2" />
</div>
</div>
Please Note that since Id's should be unique, then you can always just use var id1 = document.getElementById('row' + (i + 1) + '-id1');

Related

A question about usage of cloneNode and insertBefore

I am trying to learn some javascript in web programming. Starting with a simple school registration webpage: the webpage allows to dynamically create any number of grades by clicking "Grade+" button; under each grade, any number of students can be created by clicking "Student+" button. "Grade+" button works as expected, however clicking "Student+" button does not present the student information, not sure what is happening. Any help will be highly appreciated. Thanks in advance.
The reference codes:
<!DOCTYPE html>
<html>
<body>
<div>
<label>Registration</label>
<div class="form-inline justify-content-center" id="school" style="display:none">
<label for="fname">Grade:</label>
<input type="text" id="grade" name="Grade"><br><br>
<div id="students">
<div id="student">
<label for="fname">First:</label>
<input type="text" id="first" name="First"><br><br>
<label for="lname">Last:</label>
<input type="text" id="last" name="Last"><br><br>
</div>
<div class="text-center" id="add_student">
<span id="idStudentRootCopy">----S----</span>
<button type="button" onclick="addItem('student', 'idGradeRootCopy', false)">Student+</button>
</div>
</div>
</div>
<div class="text-center" id="add_grade">
<span id="idGradeRootCopy">----G----</span>
<button type="button" onclick="addItem('school', 'idGradeRootCopy', true)">Grade+</button>
</div>
</div>
<script>
var count = 0;
function addItem(id, index, root) {
var original = document.getElementById(id);
var before = document.getElementById(index);
var clone = original.cloneNode(true);
clone.style.display='block';
clone.id = id + ++count;
var newFields = clone.childNodes;
for (var i = 0; i < newFields.length; i++) {
var fieldName = newFields[i].name;
if (fieldName)
newFields[i].name = fieldName + count;
}
if (root) {
original.parentNode.insertBefore(clone, before.parentNode);
} else {
original.insertBefore(clone, before);
}
}
</script>
</body>
</html>
If you open up the developer tools of your browsers and click the Student+ button you'll get an error message like:
Uncaught DOMException: Node.insertBefore: Child to insert before is
not a child of this node
So you're actually trying to put the cloned node into the wrong spot. Either way things are a bit confusing. Let's say you have clicked the Grade+ button three times and now you decide to click on Student+ of the first clone - how should it know where to put the student as there are three grades?
Well there's a fix of course. Each Student+ button is a child of an unique clone of the school <div> which you also gave an unique id yet (school1, school2,...). So if you pass the addItem() function a reference to the button you actually clicked, we can get it's parent div like:
clickedElement.parentNode.parentNode.parentNode
and add the cloned node using appendChild() instead of insertBefore().
Here's an example (just click on 'Run code snippet'):
var count = 0;
function addItem(id, index, root, clickedElement) {
var original = document.getElementById(id);
var before = document.getElementById(index);
var clone = original.cloneNode(true);
clone.style.display = 'block';
clone.id = id + ++count;
var newFields = clone.childNodes;
for (var i = 0; i < newFields.length; i++) {
var fieldName = newFields[i].name;
if (fieldName)
newFields[i].name = fieldName + count;
}
if (root) {
original.parentNode.insertBefore(clone, before.parentNode);
} else {
clickedElement.parentNode.parentNode.parentNode.appendChild(clone);
}
}
<div>
<label>Registration</label>
<div class="form-inline justify-content-center" id="school" style="display:none">
<label for="fname">Grade:</label>
<input type="text" id="grade" name="Grade"><br><br>
<div id="students">
<div id="student">
<label for="fname">First:</label>
<input type="text" id="first" name="First"><br><br>
<label for="lname">Last:</label>
<input type="text" id="last" name="Last"><br><br>
</div>
<div class="text-center" id="add_student">
<span id="idStudentRootCopy">----S----</span>
<button type="button" onclick="addItem('student', 'idGradeRootCopy', false,this)">Student+</button>
</div>
</div>
</div>
<div class="text-center" id="add_grade">
<span id="idGradeRootCopy">----G----</span>
<button type="button" onclick="addItem('school', 'idGradeRootCopy', true,this)">Grade+</button>
</div>
</div>
Update
If you click on the Grade+ button, it will automatically also 'create' a student input field as it's div is part of the school div. So move it out of the school div and change it's display mode to none.
If you want the new student input field to appear right before the Student+ button, we indeed need to use .insertBefore().
Here's the modified example:
var count = 0;
function addItem(id, index, root, clickedElement) {
var original = document.getElementById(id);
var before = document.getElementById(index);
var clone = original.cloneNode(true);
clone.style.display = 'block';
clone.id = id + ++count;
var newFields = clone.childNodes;
for (var i = 0; i < newFields.length; i++) {
var fieldName = newFields[i].name;
if (fieldName)
newFields[i].name = fieldName + count;
}
if (root) {
original.parentNode.insertBefore(clone, before.parentNode);
} else {
clickedElement.parentNode.insertBefore(clone, clickedElement);
}
}
<div>
<label>Registration</label>
<div id="student" style="display:none">
<label for="fname">First:</label>
<input type="text" id="first" name="First"><br><br>
<label for="lname">Last:</label>
<input type="text" id="last" name="Last"><br><br>
</div>
<div class="form-inline justify-content-center" id="school" style="display:none">
<label for="fname">Grade:</label>
<input type="text" id="grade" name="Grade"><br><br>
<div id="students">
<div class="text-center" id="add_student">
<span id="idStudentRootCopy">----S----</span>
<button type="button" onclick="addItem('student', 'idStudentRootCopy', false,this)">Student+</button>
</div>
</div>
</div>
<div class="text-center" id="add_grade">
<span id="idGradeRootCopy">----G----</span>
<button type="button" onclick="addItem('school', 'idGradeRootCopy', true,this)">Grade+</button>
</div>
</div>

create JSON from inputs

How can I create a javascript JSON (?) object based on two input fields per row, with 48 rows?
I have this:
<div><input name = "name" /></div> <div><input name = "rating" /></div>
<div><input name = "name" /></div> <div><input name = "rating" /></div>
<div><input name = "name" /></div> <div><input name = "rating" /></div>
...
...
I then want to sort the object on rating (desc). Can this be done?
Would this help?
var pl=[['Jack',1],['Jill',2],['Lucy',3],['Marc',4],['John',5],['Eva',6],['Anne',7],['Ben',8]];
divs=$('div[id^=NAME]');
pl.forEach(function(v,i){divs[i].innerHTML=v[0]+' (rank: '+v[1]+')';})
div {display: inline-block; width:120px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="NAME-1"></div> vs. <div id="NAME-2"></div><br/>
<div id="NAME-3"></div> vs. <div id="NAME-4"></div><br/>
<div id="NAME-5"></div> vs. <div id="NAME-6"></div><br/>
<div id="NAME-7"></div> vs. <div id="NAME-8"></div><br/>
<div id="NAME-9"></div> vs. <div id="NAME-10"></div><br/>
<div id="NAME-11"></div> vs. <div id="NAME-12"></div><br/>
<div id="NAME-13"></div> vs. <div id="NAME-14"></div><br/>
<div id="NAME-15"></div> vs. <div id="NAME-16"></div><br/>
<div id="NAME-17"></div> vs. <div id="NAME-18"></div><br/>
<div id="NAME-19"></div> vs. <div id="NAME-20"></div><br/>
<div id="NAME-21"></div> vs. <div id="NAME-22"></div><br/>
<div id="NAME-23"></div> vs. <div id="NAME-24"></div><br/>
<div id="NAME-25"></div> vs. <div id="NAME-26"></div>
pl is your sorted array of arrays and divs is a jquery object containing your target divs you want to put the values in. Maybe they can all be found in an enclosing container? In that case you can use a different selector for finding them.
Edit: (answer to edited question)
To collect the data from your input fields you could do the following:
$(function(){
var i,str='';
for (var i=1;i<11;i++)
str+='<input type="text" name="n'+i+'" placeholder="Name '+i+'"/> '
+'<input type="text" name="r'+i+'" placeholder="Rating '+i+'"/><br/>';
$('#frm1').prepend(str);
$('#go').click(go);
})
function go(){
var name, jsn=$.makeArray($('input','#frm1').map(function(i,o){
var v=$(o).val(); if (i%2 && v>0) return [[name,v]]; else name=v;}));
jsn.sort(function(a,b){ return a[1]-b[1];});
$('#result').text(JSON.stringify(jsn));
return false;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form id="frm1"><button id="go">go</button></form>
<pre id="result"></pre>
You can try my way:
Put all row to 1 form with unique name per input.
Get form data via jquery.
Use my formObj2Json() function convert form data to json with key per item is input unique name.
$(function(){
$(document).on('click', '#grap', function(){
var formData = $('#anph').serializeArray(),
rs = formObj2Json(formData);
$('#rs').html(JSON.stringify(rs, undefined, 2));
});
})
function formObj2Json(formArray) { //serialize data function
var returnArray = {};
for (var i = 0, len = formArray.length; i < len; i++)
returnArray[formArray[i].name] = formArray[i].value;
return returnArray;
}
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Knockout 1">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<button id="grap">grap</button>
<form id="anph">
<div class="group"><input name="name1" /><input name="rating1" /></div>
<div class="group"><input name="name2" /><input name="rating2" /></div>
<div class="group"><input name="name3" /><input name="rating3" /></div>
<div class="group"><input name="name4" /><input name="rating4" /></div>
<div class="group"><input name="name5" /><input name="rating5" /></div>
<div class="group"><input name="name6" /><input name="rating6" /></div>
<div class="group"><input name="name7" /><input name="rating7" /></div>
<div class="group"><input name="name8" /><input name="rating8" /></div>
<div class="group"><input name="name9" /><input name="rating9" /></div>
<div class="group"><input name="name10" /><input name="rating10" /></div>
</form>
<pre id="rs"></pre>
<script src="https://code.jquery.com/jquery-1.7.2.js"></script>
</body>
</html>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script>
$(function() {
var players = [{ name: "Paul", rank: 1}, {name: "Michael", rank: 2}];
$.each(players, function(key, value){
$("#containerofPlayers").append('<div id="' + value.name + '">' + value.name + ' is ranked ' + value.rank + '</div>');
});
});
</script>
What's happening is that you have an array of objects. You loop through each object within the array to get its data. You then create and append a new div with the values to the <div id="containerofPlayers"></div> element.
Hope this helps.
Edit: If you don't want to create the <div> elements in jQuery but simply assign the text, you could do this instead:
$('#'+value.name).text(value.name + " is ranked " + value.rank);
Edited answer:
Since you've changed your original question,
<div><input name="name" /></div> <div><input name = "rating" /></div>
do something more like ↓
<div id="players">
<div class="player">
<label>Name: </label><input class="playerName" type="text"/>
<label>Rank: </label><input class="playerRank" type="number"/>
</div>
<div class="player">
<label>Name: </label><input class="playerName" type="text"/>
<label>Rank: </label><input class="playerRank" type="number"/>
</div>
</div>
then in jQuery
$(function() {
var players = []; //create an Array
$("#players .player").each(function(i, obj){
var playerName = $(this).children('.playerName').val();
var playerRank = $(this).children('.playerRank').val();
$(this).push({"name": playerName, "rank": playerRank}); // this will give you an array of objects like in my previous answer, then you can use that data to display it.
});
players.sort(function(a, b) {
return (a.rank - b.rank) || a.name.localeCompare(b.name);
});
$.each(players, function(index, value){
$("#result").append('<div id="' + value.name + '">' + value.name + ' is ranked ' + value.rank + '</div>');
})
});

JavaScript - display keys and values of an array

I'm playing with a module object and trying to create a sort of blog (it's not going to be used in real life - just me learning stuff).
When a user fills a form and provides a tag, it checks whether the tag exists in an associative array, if not, it adds it with the value = 1. If the tag already exists, it adds +1 to the value. Now I want to display on a side how many entries for each tag there are, eg:
cooking(3)
sport(1)
It seems to partially work as when I add another tag, it displays in but keeps increasing the count of ALL the categories/tags:
cooking(1)
sport(1)
then
cooking(2)
sport(2)
...not just the one the user has just added.
var myArticles = (function () {
var s, articles;
return {
settings: {
articleList: "articles", // div with generated articles
articleClass: "article", // class of an article
articleIndex: 0,
sidebar: document.getElementById("sidebar"),
tagList: {},
// cats: Object.keys(this.settings.tagList)
},
init: function() {
// kick things off
s = this.settings;
articles = document.getElementById(this.settings.articleList);
this.createArticle();
},
createArticle: function() {
var div = document.createElement("div");
var getTag = document.getElementById("tag").value;
var getTitle = document.getElementById("title").value;
// Add classes
div.classList.add(this.settings.articleClass, getTag);
// Add title / content
var title = document.createElement("h2");
var textNode = document.createTextNode(getTitle);
title.appendChild(textNode);
div.appendChild(title);
// Add category
div.innerHTML += "Article" + this.settings.articleIndex;
articles.appendChild(div);
this.settings.articleIndex +=1;
this.updateCategories(getTag);
},
updateCategories: function(tag) {
// Create header
this.settings.sidebar.innerHTML = "<h3>Categories</h3>";
// Create keys and count them
if (tag in this.settings.tagList) {
this.settings.tagList[tag] += 1;
} else {
this.settings.tagList[tag] = 1;
}
var cats = Object.keys(this.settings.tagList);
// Create an unordered list, assign a class to it and append to div="sidebar"
var ul = document.createElement('ul');
ul.classList.add("ul-bare");
this.settings.sidebar.appendChild(ul);
// iterate over the array and append each element as li
for (var i=0; i<cats.length; i++){
var li=document.createElement('li');
ul.appendChild(li);
li.innerHTML=cats[i] + "(" + this.settings.tagList[tag] + ")";
}
}
};
}());
And HTML:
<body>
<div id="container">
<h1>My articles</h1>
<div id="genArticle" class="left">
<form id="addArt" method="post">
<div>
<label for="title">Title</label>
<input type="text" id="title" class="forma" placeholder="Title" required />
</div>
<div>
<label for="tag">Tag</label>
<input type="text" id="tag" class="forma" placeholder="Tag" required />
</div>
<div>
<label for="art">Article</label>
<textarea id="art" class="forma" required /></textarea>
</div>
<input type="button" onclick="myArticles.init()" value="Add Art">
<input type="reset" value="Reset Form">
<input type="range" size="2" name="satisfaction" min="1" max="5" value="3">
</form>
<div id="articles"></div>
</div> <!-- end of genArticle -->
<aside id="sidebar" class="right">
</aside>
</div> <!-- end of container -->
<script src="js/script.js"></script>
</body>
I think this line is wrong
li.innerHTML=cats[i] + "(" + this.settings.tagList[tag] + ")";
It is this.settings.tagList[cats[i]]
Not this.settings.tagList[tag]

How do I remove the whole last html line in div?

What I basically am trying to accomplish is a form, where one can add a whole html line dynamically using javascript using one button, or remove an existing line using another.
I got the add function to work, yet I cannot seem to figure out the remove function.
Here is my code:
window.onload = function(){
var addHw = document.getElementById("addhw");
var removeHw = document.getElementById("removehw");
// Here is my add function
addHw.addEventListener('click', function () {
var homeworkGrade = document.createElement('input');
homeworkGrade.className = 'grade';
homeworkGrade.type = 'text';
homeworkGrade.size = 3;
var overallGrade = document.createElement('homework');
overallGrade.className = 'homework';
overallGrade.type = 'text';
overallGrade.size = 3;
var form = document.getElementById("assignments");
var r = "HW <input class=\"grade\" type = \"text \"size=\"3 \">/<input class=\"homework \" type = \"text \" size= \"3 \"><br />";
form.insertAdjacentHTML('beforeend',r);
});
// Here is my attempt at the remove function:
removeHw.addEventListener('click', function () {
var form = document.getElementById("assignments").lastChild;
var hw = document.getElementById("homework");
var grade = document.getElementById("grade");
});
}
<form id="myForm">
<div id="assignments">
<!-- add HWs here -->
HW <input class="grade" type="text" size="3">/<input class="homework" type="text" size="3"><br />
HW <input class="grade" type = "text" size="3 ">/<input class="homework " type = "text " size= "3 "><br />
HW <input class="grade" type = "text "size="3 ">/<input class="homework " type = "text " size= "3 "><br />
</div>
<div>
<!-- add curve here -->
<input type="checkbox" name="curve" />Curve + 5?
</div>
<div id="resultsarea ">
<p>
<!--add buttons-->
<button type="button" id="compute">Compute!</button>
<button type="button" id="addhw">Add HW</button>
<button type="button" id="removehw">Remove HW</button>
<button type="button" id="clear">Clear</button>
</p>
<!-- add the results here -->
<div id="result"></div>
</div>
</form>
I tried the removeChild and tried to remove the last child of "assignments", with no luck.
If someone would like to comment on my code and if it's efficient or provide me some comments that would benefit my progress, I'll be the most thankful.
By far the easiest way to do it is to update your code so that your "HW" are wrapped (e.g. in a span), and give all of these spans a class (e.g. "hw").
If you want them to be in different lines anyway, you may as well use a p or a div and remove the <br />.
window.addEventListener('load', function(){
var addHw = document.getElementById('addhw');
var removeHw = document.getElementById('removehw');
var hwHTML = '<div class="hw">HW <input class="grade" type="text" size="3" />/<input class="homework" type="text" size="3" /></div>';
var form = document.getElementById("assignments");
// Add hw.
addHw.addEventListener('click', function () {
// A lot of core were useless here as you only
// use the string at the end (and it is sufficient).
form.insertAdjacentHTML('beforeend', hwHTML);
});
// Remove hw.
removeHw.addEventListener('click', function () {
form.removeChild(form.querySelector(".hw:last-child"));
});
});
<form id="myForm">
<div id="assignments">
<!-- add HWs here -->
<div class="hw">HW <input class="grade" type="text" size="3" />/<input class="homework" type="text" size="3" /></div>
<div class="hw">HW <input class="grade" type="text" size="3" />/<input class="homework" type="text" size="3" /></div>
<div class="hw">HW <input class="grade" type="text" size="3" />/<input class="homework" type="text" size="3" /></div>
</div>
<div>
<!-- add curve here -->
<input type="checkbox" name="curve" />Curve + 5?
</div>
<div id="resultsarea ">
<p>
<!--add buttons-->
<button type="button" id="compute">Compute!</button>
<button type="button" id="addhw">Add HW</button>
<button type="button" id="removehw">Remove HW</button>
<button type="button" id="clear">Clear</button>
</p>
<!-- add the results here -->
<div id="result"></div>
</div>
</form>
It would be great to place every HW into its container. Because removal of the whole container is much easier.
Javascript:
(function(){
var addHw = document.getElementById("addhw");
var removeHw = document.getElementById("removehw");
// Here is my add function
addHw.addEventListener('click', function () {
var form = document.getElementById("assignments");
var r = "<div>HW <input class=\"grade\" type = \"text \"size=\"3 \">/<input class=\"homework \" type = \"text \" size= \"3 \"></div>";
form.insertAdjacentHTML('beforeend',r);
});
// Here is my attempt at the remove function:
removeHw.addEventListener('click', function () {
var form = document.getElementById("assignments");
var lastHW = form.lastChild;
if(lastHW) {
form.removeChild(lastHW);
}
});
})();
Html:
...
<div id="assignments">
<!-- add HWs here -->
<div>HW <input class="grade" type="text" size="3">/<input class="homework" type="text" size="3"></div>
<div>HW <input class="grade" type = "text" size="3 ">/<input class="homework " type = "text " size= "3 "></div>
<div>HW <input class="grade" type = "text "size="3 ">/<input class="homework " type = "text " size= "3 "></div>
</div>
...
Example: https://jsfiddle.net/61ytuoyb/
Can you try wrapping the individual assignments in an assignment and add a unique identifier to each assignment ?
<div id="assignments">
<div id="assignment_1">HW etc ...</div>
<div id="assignment_2">HW etc ...</div>
<div id="assignment_3">HW etc ...</div>
</div>
Use like a global counter variable to create the unique identifier for each assignment.
Then use the javascript
var idToDelete;
addHw.addEventListener('click', function () {
idToDelete = this.id; //not sure this is how to obtain the id in pure js.
});
removeHw.addEventListener('click', function () {
var parent = document.getElementById("assignments");
var child = document.getElementById("assignment_" + idToDelete);
parent.removeChild(child);
});
This off the top of my head. Untested code.

Clone elements and add into mark-up code so form submission picks them up

When the clone button is clicked, size and length fields should be cloned and placed below. Crucial point is, I should be able to see these cloned mark-up code when I check the browser source code because I'll submit all fields.
HTML
<style>
div { display:inline-block; }
.needles, .yarns, .options { border:1px solid #14A; padding:10px; }
</style>
<div class='needles'>
<p>NEEDLES</p>
<div class='options'>
<div class='label'>Size</div>
<div class='field'>
<input id='size-0' name='size-0' value='Size' />
</div>
</div>
<br />
<div class='options'>
<div class='label'>Length</div>
<div class='field'>
<input id='length-0' name='length-0' value='Length' />
</div>
</div>
<br /><br />
<button>Clone</button>
</div>
<br />
<div class='yarns'>
<p>YARNS</p>
<div class='options'>
<div class='label'>Size</div>
<div class='field'>
<input id='size-0' name='size-0' value='Size' />
</div>
</div>
<br />
<div class='options'>
<div class='label'>Length</div>
<div class='field'>
<input id='length-0' name='length-0' value='Length' />
</div>
</div>
<br /><br />
<button>Clone</button>
</div>
I did something like this but I think it is a mess to be honest.
$("button").click(function(event) {
event.preventDefault();
var $parent = $(this).closest('div');
var $length = $parent.prev();
var $size = $length.prev();
var size = $size.clone(false).wrap('<p>').parent().html();
var length = $length.clone(false).wrap('<p>').parent().html();
var spanId = $(size).children('span').attr('id');
var idArray = spanId.split("__");
var currentIdentifierNo = idArray[idArray.length-2];
var newIdentifierNo = parseInt(currentIdentifierNo)+1;
var currentIdentifier = '__' + currentIdentifierNo + '__';
var newIdentifier = '__' + newIdentifierNo + '__';
var re = new RegExp(currentIdentifier, 'g');
size = size.replace(re, newIdentifier);
length = length.replace(re, newIdentifier);
var html = size + length;
$(this).before(html);
});
You have to change the name attribute for this to work:
length.find('input').attr('name', 'length-1');
size.find('input').attr('name', 'size-1');
If you send the same key / value pairs in the form, the server doesn't know which is which.

Categories

Resources