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]
Related
Here is what is needed to do:
<input type="text" id="main" placeholder="Put URL Here">
Every time the User Presses enter (or a Button on side of screen) I need Jquery to create:
<input type="hidden" id="url_1" readonly value='<-- input value from main-->'>
<!-- user adds another to #main -->
<input type="hidden" id="url_2" readonly value='<-- input value from main-->'>
<!-- etc -->
Here is what I got So far (only using HTML)
<figure class="mb-4">
<input type="text" name="" id="" placeholder="Image URL">
<button id="addimage">Add Image</button>
<button id="uploadimage_js">Upload Image</button>
</figure>
For the Upload Image button, It submits a Image to my PHP upload image, and just returns the URL to the image
This will append the hidden inputs to the end of your <form> tag. It also keeps an array of urls in case that's useful. For this snippet, it shows the array in a div.
let urls = [], limit = 2, main, addButton, resetButton
window.addEventListener('load', () => {
main = document.querySelector('#main'),
addButton = document.querySelector('[data-url-saver]'),
resetButton = document.querySelector('[data-url-reset]');
addButton.addEventListener('click', () => saveURL())
resetButton.addEventListener('click', () => reset())
})
const saveURL = () => {
let u = main.value;
urls.push(u);
let h = `<input type="hidden" data-url-hidden id="url_${urls.length}" readonly value=${u} />`
document.querySelector('form').insertAdjacentHTML('beforeend', h);
main.value = "";
let de = document.querySelector('#debug');
de.innerHTML = urls.join(", ");
if (urls.length >= limit) {
addButton.setAttribute('disabled', 'disabled');
main.setAttribute('disabled', 'disabled');
main.setAttribute('placeholder', 'Maximum URLs accepted')
}
}
const reset = () => {
urls = [];
document.querySelectorAll('[data-url-hidden]').forEach(e => e.parentNode.removeChild(e));
addButton.removeAttribute('disabled');
main.removeAttribute('disabled');
document.querySelector('#debug').innerHTML = "";
}
<form>
<input type="text" id="main" placeholder="Put URL Here">
<button data-url-saver type='button'>enter</button>
<button data-url-reset type='button'>reset</button>
</form>
<div id='debug'></div>
Create a div for hidden urls like
<div id="urls"></div>
try in jquery
var counter = 1;
$('#main').keypress(function (e) {
if(e.which== 13){ // enter key code
var url = $('#main').val();
$('#urls').append('<input type="hidden" id="url_'+ counter +'" readonly value="' + url + '" >');
$('#main').val(''); //clearing the input
counter++;
}
});
If you want to add ascending URL and if you have the link, then you can use the function below.
function addImage(){
var link = document.querySelector('#main').value;
var all_links = document.getElementById('all_urls');
if (link.length > 0){
code = `
<input type="hidden" id="url_${all_links.children.length}" value="${link}"/>
`
all_links.innerHTML += code;
console.log(document.getElementById('all_urls'))
}
}
<figure class="mb-4">
<input type="text" id="main" placeholder="Put URL Here">
<div id="all_urls">
<!--All the links are added here-->
</div>
<button id="addimage" onclick="addImage()">Add Image</button>
<button id="uploadimage_js">Upload Image</button>
</figure>
In jQuery
// Cache some elements
const div = $('div');
const main = $('#main');
$('button').click(() => {
// Grab the value
const val = main.val();
// Create the id based on the current
// number of inputs
const id = div.find('input').length + 1;
// Append the new input
div.append(`<input readonly id="url_${id}" value="${val}" />`);
// Reset the main input
main.val('');
});
div { margin-top: 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" id="main" />
<button>Click</button>
<div></div>
And the equivalent in vanilla JS:
// Cache some elements
const div = document.querySelector('div');
const main = document.querySelector('#main');
const button = document.querySelector('button');
button.addEventListener('click', handleClick, false)
function handleClick() {
// Create the id based on the current
// number of inputs
const id = div.querySelectorAll('input').length + 1;
const html = `<input readonly id="url_${id}" value="${main.value}" />`;
// Append the new input
div.insertAdjacentHTML('beforeend', html);
// Reset the main input
main.value = '';
};
div { margin-top: 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input type="text" id="main" />
<button>Click</button>
<div></div>
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>
[EDIT] Revising original question for better clarity
This section of this form allows the user to add as many rows as necessary, and works. My problem is that I cannot figure out how to make the character count work on the textarea of the cloned rows.
(Thanks to zer00ne for the all the awesome help here. Great, concise coding! Also provided a jQuery character count in a fraction of lines compared to my former Javascript code.)
Here's a fiddle: https://jsfiddle.net/RationalRabbit/2vmqk26b/4/
CSS
textarea,
output,
button {font-size:inherit;}
output:nth-of-type(n+2) {margin-left:3px;}
.msg {border:none;}
.clearfix:before,
.clearfix:after {display:table; content: "";}
.clearfix:after {clear:both;}
.RowDeleteButton {float:right; font-family:arial, sansserif; font-size:14px; display:inline-block; text-decoration:none; color:#AC0F0F; font-weight:900; cursor:pointer;}
.RowDeleteButton:hover, .RowDeleteButton:focus {color:#FF0000;}
HTML
<fieldset>
<div class="parent-group">
<div class="form-group">
<input id="Name" name="Name[]" size="20" value="" />
<input type="checkbox" id="HM" name="HM[]" value="X" />
<textarea class="txt" id="TA" rows="1" cols="30" name="TA[]" maxlength="100"></textarea>
<input class='msg' name="Output" id="Output" size="3" readonly value="100" />
<input type="text" name="Location[]" id="Location" size="30" value="" />
<div class="form-group RowDelete">
<a class="RowDeleteButton" id="DeleteRow" href="javascript:void(0)"> X </a>
</div>
<div class="Clear"></div>
</div>
</div>
<div class="clearfix"></div>
<div id="container"></div>
<div class="form-group">
<a id="AddRow" href="javascript:void(0)"><span style="color:#0F61AC;">Add Row</span></a>
</div>
</fieldset>
jQuery
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript">
// onkeyup invoke charCount
$('.txt').on('keyup', charCount);
// onclick...
$('#DeleteRow').closest('.form-group').hide();
$('#AddRow').on('click', function (e)
{
var len = $('.child-border').length;
$('.parent-group').clone(true, false).find(':input').each(function (idx, ele)
{
ele.name = ele.name + len;
ele.id = ele.id + len;
ele.value = '';
}).end().find('.form-group').toggle(true).end()
.toggleClass('parent-group child-border').hide()
.appendTo('#container').slideDown('slow');
});
$('#container').on('click', '[id^=DeleteRow]', function(e)
{
var jsonData = $(this).closest('.child-border, .parent-group')
.find(':input:not(button)').get()
.reduce(function (acc, ele)
{
acc[ele.name || ele.id] = ele.value;
return acc;
}, {});
$(this).closest('.child-border, .parent-group').remove();
console.log(jsonData);
});
function charCount(e)
{
// Get the text
var chars = this.value;
// Get maxlength as a number
var charMax = Number(this.getAttribute('maxlength'));
// Number of chars typed
var charDone = chars.length;
// Chars remaining is 100 - chars typed
var charToGo = charMax - charDone;
// Display chars remaining
$(this).next('.msg').val(charToGo);
}
</script>
2nd Update
I already said what the issue was in Update 1:
$('.parent-group').clone(true, false).find(':input')
🔺
The second parameter should be true
This will allow the clone() method to keep registered events on the clone. Notice I had said the same thing on the 1st Update, but I failed to change the code in Demo 2.
Demo 3 is a heavy modification of the most currently updated OP code. It is fully functional and it retains registered events on clones just like Demo 2. Added features are: local/sessionStorage, sends data to a live test server, and displays server response.
Demo 4 is OP code and one simple change...want to take a wild guess as to what that might be?.
1st Update
When cloning, use the first parameter to determine whether the clone keeps the registered event handlers the original node has.
$('#original').clone(true, true);
See Demo 2
Not sure what you mean by "one row". This demo is streamlined compared to OP code. I added auto height instead of cloning rows.
Details commented in demo
Demo 1
// On keyup...
$('#txt').on('keyup', function() {
// Get the text
var chars = this.value;
// if there are any linebreaks...
if (chars.match(/\n/g)) {
/* The value of rows attribute equals
|| the number of line breaks +2
*/
this.rows = chars.match(/\n/g).length + 2;
}
/* Get value of maxlength attribute convert
|| to number
*/
var charMax = Number(this.getAttribute('maxlength'));
// Number of chars typed
var charDone = chars.length;
// Chars remaining is 100 - chars typed
var charToGo = charMax - charDone;
// Display chars remaining
$('#msg').val(charToGo + ' characters remaining');
});
<textarea id='txt' rows="1" cols="30" maxlength="100"></textarea><br>
<output id='msg' for='txt'></output>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Demo 2
// onkeyup invoke charCount
$('.txt').on('keyup', charCount);
// onclick...
$('button').on('click', function() {
/* clone the first .txt and .msg
|| true: keep registered events
|| false: copy content
|| set .val('') to blank
|| add to fieldset
*/
$('.txt:first').clone(true, true).val('').appendTo('fieldset');
$('.msg:first').clone(true, true).val('').appendTo('fieldset');
});
function charCount(e) {
// Get the text
var chars = this.value;
// Get maxlength as a number
var charMax = Number(this.getAttribute('maxlength'));
// Number of chars typed
var charDone = chars.length;
// Chars remaining is 100 - chars typed
var charToGo = charMax - charDone;
// Display chars remaining
$(this).next('.msg').val(charToGo + ' characters remaining');
}
textarea,
output,
button {
font-size: inherit
}
output {
display: inline-block;
vertical-align: top;
}
output:nth-of-type(n+2) {
margin-left: 3px
}
button {
margin-left: 90%
}
<button type='button'>Add</button>
<fieldset>
<textarea class='txt' rows="1" cols="30" maxlength="100"></textarea>
<output class='msg'></output>
</fieldset>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Demo 3
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
<title></title>
<style>
textarea,
output,
button {
font-size: inherit;
}
output:nth-of-type(n+2) {
margin-left: 3px;
}
.msg {
border: none;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both;
}
.del {
float: right;
margin-top: .5px;
font-family: arial, sansserif;
font-size: 14px;
display: inline-block;
text-decoration: none;
color: #AC0F0F;
font-weight: 900;
cursor: pointer;
}
.del:hover,
.del:focus {
color: #FF0000;
}
main {
display: table;
}
</style>
<script>
/* Global counter variable */
var counter = 0;
</script>
</head>
<body>
<main class="main-group">
<!--This form submits to a live test server, the [target] attribute value
is that of an iframe's name attribute. By targeting the iframe the
form can display the test server's response in the iframe
-->
<form id='main' action='https://httpbin.org/post' method='post' target='response'>
<!--The original fieldset is cloned-->
<fieldset id='set' class="form-group">
<button id="del" class='ui del' type='button'> X </button>
<input id='ID' name='ID' class='data' type='hidden'>
<input id="name" name="name" class='data name' size="25">
<input id="chx" name="chx" class='data chx' type="checkbox" value="X">
<br>
<textarea id="txt" name="txt" class='data txt' rows="1" cols="30" maxlength="100"></textarea>
<output class='ui msg'></output>
<br>
<input id="loc" name="loc" class='data loc' size="30">
</fieldset>
</form>
<nav class="btn-group">
<a id="add" href="#/" class='ui'> <b style="color:#0F61AC;">Add Row</b> </a>
<!--This submit button must use the [form] attribute with the ID of the
form to be accociated with-->
<input type='submit' form='main' class='ui'>
</nav>
<iframe src='about:blank' name='response' class='ui'></iframe>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script>
// Click...
$('#add').on('click', function(e) {
// Increment counter
counter++;
/* clone fieldset#set true: clone descendants /
|| TRUE: KEEP EVENTS ON CLONES
|| Gather all of the .data in clone then on each clone...
*/
var dupe = $('#set').clone(true, true);
dupe[0].id = 'set' + counter;
dupe.find('.data').each(function(idx, ele) {
// Set all .data with name and id, the counter suffix makes them unique
ele.name = this.name + counter;
ele.id = this.id + counter;
// Clear all data in each .data
ele.value = '';
// Cool animation and append clone to form#main
}).end().find('.form-group').toggle(true).end().hide().appendTo('#main').slideDown('slow');
// Clear .ui of data
dupe.find('output').val('');
dupe.find(':checkbox').prop('checked', false);
});
// Clicking any button.del...
$('.del').on('click', function(e) {
// Define arrays
var jsonData = [];
var JSONKeys = [];
// This collects all accossiated .data of ,del
var dataRow = $(this).nextAll('.data').toArray();
// This map() will create an object literal and add it to an array
jsonData = dataRow.map(function(data, idx) {
var D = {};
D.k = data.id;
D.v = data.value;
return D;
});
console.log(jsonData);
// Proceedure to timestamp data
var stamp = new Date();
var jKey = stamp.toJSON();
// Fill an array of keys for future reference
JSONKeys.push(jKey);
/* Store JSON data in sessionStorage (can be localStorage also) */
setData(jKey, jsonData);
// Save an index of the jsondata
setData('JSONKeys', jKey);
// if there's only one fieldset, reset the form if user tries to delete it
if ($('fieldset').is(':only-child')) {
$('#main')[0].reset();
} else {
// Remove fieldset
$(this).parent('.form-group').remove();
}
});
// onkeyup invoke charCount
$('.txt').on('keyup', charCount);
function charCount(e) {
// Get the text
var chars = this.value;
// Get maxlength as a number
var charMax = Number(this.getAttribute('maxlength'));
// Number of chars typed
var charDone = chars.length;
// Chars remaining is 100 - chars typed
var charToGo = charMax - charDone;
// Display chars remaining
$(this).next('.msg').val(charToGo);
}
function setData(dataKey, dataVal) {
sessionStorage.setItem(dataKey, JSON.stringify(dataVal));
}
function getData(dataKey) {
return JSON.parse(sessionStorage.getItem(dataKey));
}
</script>
</body>
</html>
Demo 4
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
<title></title>
</head>
<body>
<fieldset>
<div class="parent-group">
<div class="form-group">
<input id="Name" name="Name[]" size="20" value="" />
<input type="checkbox" id="HM" name="HM[]" value="X" />
<textarea class="txt" id="TA" rows="1" cols="30" name="TA[]" maxlength="100"></textarea>
<input class='msg' name="Output" id="Output" size="3" readonly value="100" />
<input type="text" name="Location[]" id="Location" size="30" value="" />
<div class="form-group RowDelete">
<a class="RowDeleteButton del" href="javascript:void(0)"> X </a>
</div>
<div class="Clear"></div>
</div>
</div>
<div class="clearfix"></div>
<div id="container"></div>
<div class="form-group">
<a id="AddRow" href="javascript:void(0)"><span style="color:#0F61AC;">Add Row</span></a>
</div>
</fieldset>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript">
// onclick...
$('#DeleteRow').closest('.form-group').hide();
$('#AddRow').on('click', function(e) {
var len = $('.child-border').length;
$('.parent-group').clone(true, true).find(':input').each(function(idx, ele) {
ele.name = ele.name + len;
ele.id = ele.id + len;
ele.value = '';
}).end().find('.form-group').toggle(true).end()
.toggleClass('parent-group child-border').hide()
.appendTo('#container').slideDown('slow');
});
$('.del').on('click', function(e) {
var jsonData = $(this).closest('.child-border, .parent-group')
.find(':input:not(button)').get()
.reduce(function(acc, ele) {
acc[ele.name || ele.id] = ele.value;
return acc;
}, {});
$(this).closest('.child-border, .parent-group').remove();
console.log(jsonData);
});
function charCount(e) {
// Get the text
var chars = this.value;
// Get maxlength as a number
var charMax = Number(this.getAttribute('maxlength'));
// Number of chars typed
var charDone = chars.length;
// Chars remaining is 100 - chars typed
var charToGo = charMax - charDone;
// Display chars remaining
$(this).next('.msg').val(charToGo);
}
// onkeyup invoke charCount
$('.txt').on('keyup', charCount);
</script>
</body>
</html>
Using zer00ne's Demo 4, this is what I finally came up with. the differences are:
1. Set the first working row as array for database insert using regex. Also designated unique row input id's using parentheses.
2. Set delete button so that it does not appear on first row.
3. My script sets a max number of rows (8).
4. Global variable used to hold row count. Important in the event that the user deletes a row or rows in the middle of the set, then adds rows below.
5. Set checkbox so that the value is kept in cloned rows. "checked" set to false.
The PHP shows how I handled the array server-side so that I come out with arrays in numerical order. This could probably be handled differently, but worked for me. Without using the LastArrayValue variable to handle the last array value, and the ChildCount variable, which says how many rows there actually are, if you delete, for instance, rows 2 & 3 out of eight, you would only have 6 rows, but not be allowed to add additional. Also, without those variables, when rows are deleted in the middle, you will end up with duplicate array keys. This becomes even more critical when you have a max number of rows to deal with.
https://jsfiddle.net/uyz2zjj6/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no">
<title></title>
<style type="text/css">
textarea,
output,
button {font-size:inherit;}
output:nth-of-type(n+2) {margin-left:3px;}
.msg {border:none;}
.clearfix:before,
.clearfix:after {display:table; content: "";}
.clearfix:after {clear:both;}
.RowDeleteButton {float:right; font-family:arial, sansserif; font-size:14px; display:inline-block; text-decoration:none; color:#AC0F0F; font-weight:900; cursor:pointer;}
.RowDeleteButton:hover, .RowDeleteButton:focus {color:#FF0000;}
</style>
</head>
<body>
<fieldset>
<div class="parent-group">
<div class="form-group">
<input id="Name(0)" name="Name[0]" size="20" value="" />
<input type="checkbox" id="HM(0)" name="HM[0]" value="X" />
<textarea class="txt" id="TA(0)" rows="1" cols="30" name="TA[0]" maxlength="100"></textarea>
<input class='msg' name="Output" id="Output(0)" size="3" readonly value="100" />
<input type="text" name="Location[0]" id="Location(0)" size="30" value="" />
<div class="form-group" style="display:inline-block;">
<a class="RowDeleteButton del" id="DeleteRow" href="javascript:void(0)"> X </a>
</div>
<div class="Clear"></div>
</div>
</div>
<div class="clearfix"></div>
<div id="container"></div>
<div class="form-group">
<a id="AddRow" href="javascript:void(0)"><span style="color:#0F61AC;">Add Row</span></a>
</div>
</fieldset>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript">
// onclick...
window.LastArrayValue = 0;
$('#DeleteRow').closest('.form-group').hide();
$('#AddRow').on('click', function(e)
{
var ChildCount = $('.child-group').length;
if(ChildCount == 7)
{
alert("Sorry, 8 is the maximum number of rows");
}
else
{
var len = window.LastArrayValue;
window.LastArrayValue = len + 1;
$('.parent-group').clone(true, true).find(':input').each(function(idx, ele)
{
var ename = ele.name;
var eid = ele.id
var ArrayValue = len+1;
ele.name = ename.replace(/(\[\/?[^\]]*\])/g, "["+ArrayValue+"]");
ele.id = eid.replace(/(\(\/?[^\]]*\))/g, "("+ArrayValue+")");
if(ele.type == "checkbox"){ele.checked = false;}
else{ele.value = '';}
}).end().find('.form-group').toggle(true).end()
.toggleClass('parent-group child-group').hide()
.appendTo('#container').slideDown('slow');
}
});
$('.del').on('click', function(e)
{
var jsonData = $(this).closest('.child-group, .parent-group')
.find(':input:not(button)').get()
.reduce(function(acc, ele)
{
acc[ele.name || ele.id] = ele.value;
return acc;
}, {});
$(this).closest('.child-group, .parent-group').remove();
console.log(jsonData);
});
function charCount(e)
{
// Get the text
var chars = this.value;
// Get maxlength as a number
var charMax = Number(this.getAttribute('maxlength'));
// Number of chars typed
var charDone = chars.length;
// Chars remaining is 100 - chars typed
var charToGo = charMax - charDone;
// Display chars remaining
$(this).next('.msg').val(charToGo);
}
// onkeyup invoke charCount
$('.txt').on('keyup', charCount)
</script>
</body>
</html>
PHP
// Get the last key number in the array in the event that rows were deleted
// TA is the only required field. If that field is not filled, the row is ignored
end($_POST['TA']); // move the internal pointer to the end of the array
$key = key($_POST['TA']); // fetches the key of the element pointed to by the internal pointer
$Ct = $key+1; // account for array start at 0
reset($_POST['TA']); // Reset the array back to the top for processing.
$j=0; // Count only if TA has a value (revaluate $Ct when complete for later use ($Ct = $j))
for($i=0;$i<$Ct;$i++)
{
if(empty($_POST['TA'][$i])) // Empty may be that field is empty or there is no such row.
{
continue;
}
$Name[$j] = $_POST['Name'][$i];
$HM[$j] = $_POST['HM'][$i];
$TA[$j] = $_POST['TA'][$i];
$Location[$j] = $_POST['Location'][$i];
$j++;
}
$Ct = $j; // $Ct now holds the number of records and those records are in numerical order, 1-8
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.
I am using a javascript function to populate html element generated automatically after submitting a form from a different div.
Here is the html:
<html >
<body>
<div class="wrapper">
<div id="one">
<form name="form">
<fieldset>
<legend>BILLING</legend>
<div> <label for="ex1">Procedure/Drug</label>
<input type="text" name="procdrug" id="procdrug"/><br><br>
<label>Amount</label>
<input type="text" name="amount" id="amount"/><br><br>
<input type="button" onclick="addInput()" name="add" value="Add input field" style="margin-left:150px" />
</div>
</fieldset>
</form>
</div>
<div id="two">
<fieldset>
<legend>TN QUEUE</legend>
<label><B>Procedure/Drug</b></label><label><b>Amount</b></label><br>
<div id="text">
</div>
<label><b>Total</b></label>
<input type="text" name="total" id="total"/>
</fieldset>
</div>
</body>
</html>
Here is the javascript function
<script language="javascript">
fields = 0;
function addInput() {
var amount=document.getElementById('amount').value;
var pd=document.getElementById('procdrug').value;
if (fields != 10)
{
document.getElementById('text').innerHTML += "<input id='pdgen' type='text'/>";
document.getElementById('text').innerHTML += "<input id='amtgen' type='text'/><br />";
document.getElementById('pdgen').value=pd;
document.getElementById('amtgen').value=amount;
fields += 1;
}
else
{
document.getElementById('text').innerHTML += "<br />Only A Maximum of 10 is allowed.";
document.form.add.disabled=true;
}
}
</script>
The generated elements values are posted from the form and increment on every submit. My problem is the only on submit first element is updated with the new value:
Sample results
Procedure/Drug Amount
Amoxyl 200
blank element blank element
blank element blank element
blank element blank element
Total
The problem is you are adding your elements with the .innerHtml += method which is avoiding the values entered before. You need to use appendChild method to add new elements. Here is your new code :
fields = 0;
function addInput() {
var amount=document.getElementById('amount').value;
var pd=document.getElementById('procdrug').value;
var br = document.createElement('br');
if (fields != 10)
{
var input1 = document.createElement('input');
input1.setAttribute('id','pdgen' + fields);
input1.value = pd;
var input2 = document.createElement('input');
input2.setAttribute('id','amtgen' + fields);
input2.value = amount;
document.getElementById('text').appendChild(input1);
document.getElementById('text').appendChild(input2);
document.getElementById('text').appendChild(br);
fields++;
}
else
{
document.getElementById('text').innerHTML += "<br />Only A Maximum of 10 is allowed.";
document.form.add.disabled=true;
}
}
FIDDLE