A question about usage of cloneNode and insertBefore - javascript

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>

Related

I can't get past the js div hiding problem

I want to hide the divi when I click the button and open it when I click it again. But I couldn't run the normally working code, what could be the reason?
The js code works when there is only one div, but it does not work due to this code I wrote, but I can't solve the problem.
Razor Page
#for (int i = 0; i < Model.quest.Count; i++)
{
<div class="row mt-12" >
<div class="col-md-1">
<input type="checkbox" id="questcb" asp-for="#Model.quest[i].check">
<span>#(i + 1) Soru</span>
</div>
<div class="col-md-9">
<textarea class="form-control" asp-for=#Model.quest[i].Question rows="3" id="question" hidden></textarea>
<label>#Model.quest[i].Question</label>
</div>
</div>
<div class="row mt-12">
<div class="col-md-1" hidden>
<button class="btn btn-outline-secondary" type="button" id="A" onclick="clickFunc(this.id)">A</button>
</div>
<div class="col-md-1" >
A)
</div>
<div class="col-md-11" hidden="hidden">
<input type="text" asp-for=#Model.quest[i].Answer1 class="form-control" placeholder="" id="answer"
aria-label="Example text with button addon" aria-describedby="button-addon1">
</div>
<div class="col-md-11" id="Amod_#i" style="display:inline-block">
#Model.quest[i].Answer1
</div>
<div class="col-md-2">
<button class="btn btn-primary" type="button" id="mod_#i" onclick="question(this.id)">Cevapları Görüntüle</button>
</div>
</div>
}
js code
let question = button => {
let element = document.getElementById(`A${button}`);
let buttonDOM = document.getElementById(`${button}`)
let hidden = element.getAttribute("hidden");
if (hidden) {
element.removeAttribute("hidden");
buttonDOM.innerText = "Cevapları Gizle";
}
else {
element.setAttribute("hidden", "hidden");
buttonDOM.innerText = "Cevapları Görüntüle";
}
}
</script>
If the id of div is unique in your code,your js should work.If it still doesn't work,you can try to find the div with the position of the button:
let question = button => {
let element = $("#" + button).parent().siblings(".col-md-11")[1];
let buttonDOM = document.getElementById(`${button}`);
if (element.hidden) {
element.removeAttribute("hidden");
buttonDOM.innerText = "Cevapları Gizle";
}
else {
element.setAttribute("hidden", "hidden");
buttonDOM.innerText = "Cevapları Görüntüle";
}
}
result:

remove parentElement and saving changes when reload again

Html code
<div class="cont">
<div class="row">
<p>anything</p>
<input type="button" id="1" class="Done" name="Go" value="done">
</div>
<div class="row">
<p>anything</p>
<input type="button" id="2" class="Done" name="Go" value="done">
</div>
<div class="row">
<p>anything</p>
<input type="button" id="3" class="Done" name="Go" value="done">
</div>
</div>
I have 3 of them[buttons]
javascript
var remove=document.getElementsByClassName("Done")
for(var i=0;i<remove.length;i++){
var button=remove[i]
button.addEventListener('click',function(event){
var bclick = event.target
bclick.parentElement.remove()
});
}
I tried that, it's work for the first time but when I reload I miss changes.
I think you can use localstorage to track your removed parentElement. Simply check your localstorage whether your parentElement is removed or not, if it is removed already just hide your row class's elements. It will show nothing once button is clicked. Hope it will help.
var remove = document.getElementsByClassName("Done");
for (var i = 0; i < remove.length; i++) {
var button = remove[i];
if (button) {
if (window.localStorage.getItem(remove[i].id) == 'true') {
document.getElementById(remove[i].id).parentNode.style.display = 'none';
}
}
}
for (var i = 0; i < remove.length; i++) {
var button = remove[i];
if (button) {
button.addEventListener('click', (event) => {
var bclick = event.target;
window.localStorage.setItem(bclick.id, 'true');
bclick.parentElement.remove();
});
}
}

how dynamically add new button by javascript

I have two textboxes and one button,
I want to add one new textfield, that should show card name from textbox1 and Link URL append from textbox2 when I click on button
//AddnNewCardNavigator
var counter=2;
var nmecardtxt= document.getElementById("textbox1").value;
var linkurltxt= document.getElementById("textbox2").value;
$("#addbutton").click(function(){
if(nmecardtxt ==""||nmecardtxt ==0||nmecardtxt ==null
&& linkurltxt ==""||linkurltxt ==""|| linkurltxt ==0||linkurltxt ==null){
alert("Please insert value in Card name and Link Url textboxes and must be correct");
return false;
}
var NewCarddiv = $(document.createElement('div')).attr("id",'cardlink'+counter);
NewCarddiv.after().html()
})
</script>
<!-- text boxes-->
<div class="row">
<div class="col-md-12">
<div id="textboxesgroup">
<div id="textboxdiv1">
<label style="color:blanchedalmond">Card Name: </label><input type="textbox" id="textbox1">
</div>
<div id="textboxdiv2">
<label style="color:blanchedalmond">Link Url:    </label><input type="textbox" id="textbox2">
</div>
</div>
</div>
</div>
Your variables nmecardtxt and linkurltxt must be created inside the click function,
because it's empty at the loading of the page.
I also took the liberty to use jQuery for that variables, as you're already using it, and tried to enhance some other things:
(See comments in my code for details)
//AddnNewCardNavigator
var counter = 2;
// On click function
$("#addbutton").click(function() {
// Here it's better
var nmecardtxt = $("#textbox1").val();
var linkurltxt = $("#textbox2").val();
// Modified you test here
if (!nmecardtxt || !linkurltxt) {
alert("Please insert value in Card name and Link Url textboxes and must be correct");
return false;
}
// Modified creation of the card
var link = $(document.createElement('a')).attr("href", linkurltxt).html(linkurltxt);
var NewCarddiv = $(document.createElement('div')).attr("id", 'cardlink' + counter).html(nmecardtxt + ": ").append(link);
$('#cards').append(NewCarddiv);
//NewCarddiv.after().html(); // Was that line an attempt of the above ?
});
body {
background: #888;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- text boxes-->
<div class="row">
<div class="col-md-12">
<div id="textboxesgroup">
<div id="textboxdiv1">
<label style="color:blanchedalmond">Card Name: </label><input type="textbox" id="textbox1">
</div>
<div id="textboxdiv2">
<label style="color:blanchedalmond">Link Url:   </label><input type="textbox" id="textbox2">
</div>
</div>
</div>
</div>
<!-- Added the below -->
<div id="cards">
</div>
<button id="addbutton">Add…</button>
Hope it helps.
Here's a simplified version of what you're trying to accomplish:
function addNewCard() {
var name = $('#name').val();
var url = $('#url').val();
var count = $('#cards > .card').length;
if (!name || !url) {
alert('Missing name and/or URL.');
}
var card = $('<div class="card"></div>').html("Name: " + name + "<br>URL: " + url);
$("#cards").append(card);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<label for="name">Name:</label>
<input type="text" id="name" name="name">
<label for="url">URL:</label>
<input type="text" id="url" name="url">
<input type="submit" value="Add Card" onclick="addNewCard();">
<div id="cards">
</div>

JavaScript text form validation from another field being updated by same form

I created below form: when you enter a name in first text box, it dynamically adds the names to another field below after pressing the + button. The function is implemented on the + button.
Now I want to add a validation logic within the same script, so that same name shouldn't be added twice. Please advise, only want to implement using javascript.
function promptAdd(list){
var text = "";
var inputs = document.querySelectorAll("input[type=text]");
for (var i = 0; i < inputs.length; i++) {
text += inputs[i].value;
}
var li = document.createElement("li");
var node = document.createTextNode(text);
li.appendChild(node);
document.getElementById("list").appendChild(li);
}
<!doctype html>
<html>
<div class="row">
<div class="col-lg-6 mb-1">
<div class="card h-100 text-left">
<div class="card-body">
<h4 class="card-title">Add Resources</h4>
<input type="text" class="form-control" name="employee" placeholder="Enter Name" />
<small id="message" class="form-text text-muted">Press + to add to your list</small>
<button id="bd1" class="btn add-more" onclick="promptAdd(list)" type="button">+</button>
<br></br>
<h5>List of Resources added</h5>
<div class="form-control" id="list">
<span id="list">
</div>
</div>
</div>
</div>
</div>
</html>
The validation could be implemented simply by looping through all the li's and comparing the text of every li with the value of the input and if the values matches just return false, like :
var lis = document.querySelectorAll('#list li');
for (var i = 0; i < lis.length; i++) {
if (lis[i].innerText == text) {
return false;
}
}
Hope this helps.
function promptAdd(list) {
var text = "";
var inputs = document.querySelectorAll("input[type=text]");
for (var i = 0; i < inputs.length; i++) {
text += inputs[i].value;
}
var lis = document.querySelectorAll('#list li');
for (var i = 0; i < lis.length; i++) {
if (lis[i].innerText == text ){
resetInputs();
return false;
}
}
var li = document.createElement("li");
var node = document.createTextNode(text);
li.appendChild(node);
document.getElementById("list").appendChild(li);
resetInputs();
}
function resetInputs(){
var inputs = document.querySelectorAll("input[type=text]");
for (var i = 0; i < inputs.length; i++) {
inputs[i].value = "";
}
}
<div class="row">
<div class="col-lg-6 mb-1">
<div class="card h-100 text-left">
<div class="card-body">
<h4 class="card-title">Add Resources</h4>
<input type="text" class="form-control" name="employee" placeholder="Enter Name" />
<small id="message" class="form-text text-muted">Press + to add to your list</small>
<button id="bd1" class="btn add-more" onclick="promptAdd(list)" type="button">+</button>
<br><br>
<h5>List of Resources added</h5>
<div class="form-control" id="list">
<span id="list"></span>
</div>
</div>
</div>
</div>
</div>
Loop though all li elements and check their innerText with the new text.
If you want to ignore capitalization you can use innerText.toUpperCase() === newText.toUpperCase()
function promptAdd(list) {
var text = "";
var inputs = document.querySelectorAll("input[type=text]");
for (var i = 0; i < inputs.length; i++) {
text += inputs[i].value;
}
if (textAlreadyExistsInList(text)) {
return;
};
var li = document.createElement("li");
var node = document.createTextNode(text);
li.appendChild(node);
document.getElementById("list").appendChild(li);
};
function textAlreadyExistsInList(text) {
var itemExists = false;
var items = document.getElementById("list").querySelectorAll('li');
for (var i = 0; i < items.length; i++) {
if (items[i].innerText === text) { //to ignore casing: items[i].innerText.toUpperCase() === text.toUpperCase()
itemExists = true;
break;
}
}
return itemExists;
}
<div class="row">
<div class="col-lg-6 mb-1">
<div class="card h-100 text-left">
<div class="card-body">
<h4 class="card-title">Add Resources</h4>
<input type="text" class="form-control" name="employee" placeholder="Enter Name" />
<small id="message" class="form-text text-muted">Press + to add to your list</small>
<button id="bd1" class="btn add-more" onclick="promptAdd(list)" type="button">+</button>
<br></br>
<h5>List of Resources added</h5>
<div class="form-control" id="list">
</div>
You need one input text so given that id is better . Here I set insert_name as id ! Get all li by querySelectAll and check text with innerHTML and input value .
function promptAdd(list){
var inputs = document.getElementById("insert_name").value;
if(checkDuplicate(inputs)) return; // check duplicate
var li = document.createElement("li");
var node = document.createTextNode(inputs);
li.appendChild(node);
document.getElementById("list").appendChild(li);
}
function checkDuplicate(name) {
var flag = false;
var lis = document.querySelectorAll("li");
for(var i = 0 ;i < lis.length;i++) {
if(name == lis[i].innerHTML) {
flag = true;
break;
}
}
return flag;
}

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]

Categories

Resources