I am making a table in html that uses javascript to add a new row, have editable cells and then allows the user to click save and the edits to the cells become saved. However, the code I wrote only works and does this in the first row that is added. When I attemp to add a new row, my save() function no longer works. I know this is because I am accessing the table cells with ids and so multiple elements have the same id, but I do not know how to fix it.
<table id="myTable">
<tr>
<th>1</th>
<th>2</th>
</tr>
</table>
<button onclick="addRow()">Add row button</button>
<script>
function addRow() {
let tableRef = document.getElementById('myTable');
let newRow = tableRef.insertRow(-1);
let firstcell = newRow.insertCell(0);
firstcell.setAttribute("id","firstcell")
let secondcell = newRow.insertCell(1);
secondcell.setAttribute("id","secondcell")
var input1=document.createElement('input')
secondcell.appendChild(input1);
var btn = document.createElement('button');
btn.type = "button";
btn.className = "save";
let btntext= document.createTextNode("save button");
btn.appendChild(btntext)
btn.setAttribute("onclick","save()")
firstcell.appendChild(btn);
}
function save(){
input1rref=document.getElementById("input1")
input1value=input1rref.value
input1text=document.createTextNode(input1value);
x=document.getElementById("secondcell")
x.replaceChild(input1text,input1rref)
}
</script>
<style>
table{
padding:10px;
border: 2px solid black;
}
button{
padding:10px 20px;
}
</style>
If you have more than one form control (ex. <input>, <select>, <button>, etc), wrap everything in a <form>. Use .class, tagName, name, and [attribute] to reference elements. As you can see in the example below, id is used only once to reference the <form>, but even that could've been referenced by name easily.
Figure I - HTML Layout
<form id='formID' name='formName'>
<input id='inputID' name='formControl' type='number'>
<button name='formControl' type='button'>GO!</button>
<table class='tableClass'>
<tr><td></td><td></td></tr>
</table>
<select name='formControl'>
<option value='1'>I</option>
<option value='2'>II</option>
<option value='3'>III</option>
<option value='4'>IV</option>
</select>
<output name='outputName'></output>
</form>
Figure II - Referencing Elements
// Reference with .querySelector() to get the first element match
// "." prefix for class
const table = document.querySelector('.tableClass');
// "#" prefix for id
const form = document.querySelector('#formID');
// "[]" wrap for other attributes
const input = document.querySelector('[name="formControl"]');
// No prefix or wrap for tagName
const button = document.querySelector('button');
// Use .querySelectorAll() to gather all matches into a NodeList
const options = document.querySelectorAll('option')
// Reference the third <option>
const third = options[2]
// HTMLFormElement Interface
const form = document.forms.formID;
const formALT1 = document.forms.formName;
const formALT2= document.forms['formName'];
const formALT3 = document.forms[0];
// References all form controls under <form>
const fc = form.elements
// Reference form controls id, name, or index notation
const input = fc.inputID;
const output = fc.outputName;
const select = fc[2];
// HTMLFormControlsCollection Interface
// Group all form controls that share the same name
const allFCNames = fc.formControl
Read the following
Events
Event Delegation
Inline Event Handlers are Garbage
HTMLFormElement
HTMLFormControlsCollection
Form Controls
Details are commented in example
/**
* #desc Utility function that will append a given htmlString into
* a given element and renders it into HTML.
* #param {string|object} selector - If passed as a string it will
* find element by CSS selector. If passed as a variable, it
* will reference a DOM Object. This is the object to append
* HTML to.
* #param {string} htmlString - A string that resembles HTML layout
*/
function setHTML(selector, htmlString) {
const node = typeof selector === 'string' ?
document.querySelector(selector) :
selector;
node.insertAdjacentHTML('beforeend', htmlString);
}
// Reference the <form>
const tForm = document.forms.interface;
// Bind the click event to <form>
tForm.onclick = action;
// Bind the change event to <form>
tForm.onchange = format;
// Event handler passes Event Object by default
function action(e) {
// e.target is the <button> the user clicked
// The click event is delegated to the <button> clicked by className
switch (e.target.className) {
case 'add':
addRow(e);
break;
case 'delete':
deleteRow(e);
break;
case 'edit':
case 'edit open':
editRow(e);
break;
default:
break;
}
}
function addRow(e) {
// Reference <tbody>
const tData = document.querySelector('.data');
// Reference <thead>
const tHead = tData.parentElement.createTHead();
// Get the number of columns
let cQty = tHead.rows[0].cells.length;
// Define the value/text of each <option> in an array
const opts = ["Select Benchmark", "Cash", "Munis", "Bonds", "Stocks", "Commods", "Alts"];
// Reference inserted <tr>
let tRow = tData.insertRow();
// Iterate >cQty< times
for (let i = 0; i < cQty; i++) {
// Reference inserted <td>
let tCel = tRow.insertCell();
/*
Determine which is the current <td> by (i)ndex
Each case creates a <td> and it's content
Some elements have too many attributes so using .setAttribute()
would bloat the code signifigantly. In those cases render the
HTML instead (case 1, case 2, and case 4)
*/
switch (i) {
case 0:
const sel = document.createElement('select');
sel.name = 'benchmark';
opts.forEach(o => {
let opt = new Option(o, o);
sel.add(opt);
});
tCel.append(sel);
break;
case 1:
const tilt = `<input name='tilt' class='open' type='number' min='0' step='.01' placeholder='0.00'>`;
setHTML(tCel, tilt);
break;
case 2:
const comment = `<input name='comment' class='open' type='text' placeholder='Enter comment'>`;
setHTML(tCel, comment);
break;
case 3:
const log = document.createElement('time');
log.textContent = new Date().toLocaleString();
log.className = 'log';
tCel.append(log);
break;
case 4:
const buttons = `
<button name='btn' class='delete' type='button' title='Delete Row'>➖</button>
<button name='btn' class='edit open' type='button' title='Edit Row'>✏️</button>`;
setHTML(tCel, buttons);
break;
default:
break;
}
}
}
// Here we simply find the closest <tr> and remove it
function deleteRow(e) {
e.target.closest('tr').remove();
}
// This function just toggles the 2 <input> [readonly] attributes
function editRow(e) {
e.target.classList.toggle('open');
const row = e.target.closest('tr');
const inputs = row.querySelectorAll('input');
inputs.forEach(inp => {
inp.toggleAttribute('readonly');
inp.classList.toggle('open');
});
}
// Formats a value into a float with 2 decimal places
function format(e) {
if (e.target.matches('[name="tilt"].open')) {
e.target.value = parseFloat(e.target.value).toFixed(2);
}
}
html {
font: 300 2.5vmin/1 'Segoe Ui';
}
table {
table-layout: fixed;
border-collapse: collapse;
width: 96%;
}
th {
border-bottom: 3px solid black;
}
th:first-of-type,
th:nth-of-type(2) {
width: 11rem;
}
th:nth-of-type(3) {
width: 17rem;
}
th:nth-of-type(4) {
width: 13rem;
}
th:nth-of-type(5) {
width: 9rem;
}
td {
text-align: center;
padding: 4px;
}
input,
select,
button {
display: inline-flex;
align-items: center;
font: inherit;
}
button {
cursor: pointer;
}
input {
border: 0;
}
.open {
border-radius: 4px;
outline: 3px inset blue;
outline-offset: 1px;
}
[name="tilt"] {
width: 9rem;
text-align: right;
font-family: Consolas;
}
[name="comment"] {
width: 17rem;
}
time {
width: 10rem;
}
<form id='interface'>
<table>
<thead>
<tr>
<th>Benchmark</th>
<th>Tilt</th>
<th>Comment</th>
<th>Time Log</th>
<th><button name='btn' class='add' type='button'>➕ Add Row</button></th>
</tr>
</thead>
<tbody class='data'></tbody>
</table>
</form>
Reason is you are assigning the same ids to the controls in multiple rows (as the user adds rows).
What you should do:
Keep track of the number of rows and append row number to the controls in all the setAttribute("id"... lines. Example:
superassetCell.setAttribute("id", "benchmarkcell");
// should be
superassetCell.setAttribute("id", "benchmarkcell" + row_number);
getElementById works by definition for element with the unique id.
When calling save method, each row should pass the row number as argument to save.
Checkout this question
Related
I am storing the todo-items on an array of objects. I want to map the array to get the HTML elements. I am able to do this using innerHTML but I want to do it with createElement.
I am expecting <li><span>TEXT</span></li> output but I am getting only span. It seems append is not working.
HTML code :
<div class="todo">
<h1>Todo</h1>
<form name="todo--form">
<input type="text" name="todo--input" />
<button
type="submit"
name="todo--submit"
class="p-2 bg-slate-500 rounded"
>
Add
</button>
</form>
<ul class="todo--list"></ul>
</div>
JS Code :
const addTodoForm = document.querySelector(`[name="todo--form"]`);
const todoList = document.querySelector('.todo--list');
const todos = [];
// function getLabel() {}
function handleSubmit(e) {
e.preventDefault();
const text = this.querySelector(`[name="todo--input"]`).value;
const item = {
text: text,
finished: false,
};
if (text) {
todos.push(item);
addTodoForm.reset();
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.append(newSpan);
return newEl;
});
console.log(todoItems);
todoList.append(...todoItems);
}
return;
}
addTodoForm.addEventListener('submit', handleSubmit);
I am getting only <span><span> as output.
When the <form> is submitted there's only one item being appended to list at a time so there's no need to build a one item array. BTW event handlers (functions that are bound to an event) don't return values like a normal function can, but there are indirect ways to get values from them (like from your OP code, that array outside of the function pushing objects).
Details are commented in example
// Reference <form>
const form = document.forms.todo;
// Reference <ul>
const list = document.querySelector('.list');
// Define array for storage
let tasks = [];
// Bind "submit" event to <form>
form.onsubmit = handleSubmit;
// Event handlers passes Event Object by default
function handleSubmit(e) {
// Stop default behavior during a "submit"
e.preventDefault();
// Reference all form controls
const IO = this.elements;
/*
Define {item}
Add current timestamp to object {item}
Add <input> text to {item}
Add {item} to [tasks]
Clear <input>
*/
let item = {};
item.log = Date.now();
item.task = IO.data.value;
tasks.push(item);
IO.data.value = '';
/*
Create <time>
Assign timestamp <time>
Convert timestamp into a readable date and time
Add formatted datetime as text to <time>
*/
const log = document.createElement('time');
log.datetime = item.log;
let date = new Date(item.log);
log.textContent = date.toLocaleString();
/*
Create <output>
Add text of <input> to <output>
*/
const txt = document.createElement('output');
txt.value = item.task;
/*
Create <button>
Add type, class, and the text: "Delete" to <button>
*/
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'delete';
btn.textContent = 'Delete';
// Create <li>
const li = document.createElement('li');
// Append like crazy
li.append(log);
li.append(txt);
li.append(btn);
list.append(li);
console.log(tasks);
}
html {
font: 300 2ch/1.2 'Segoe UI';
}
input,
button {
font: inherit;
}
output {
display: inline-block;
margin: 0 8px 8px;
}
button {
cursor: pointer;
}
.as-console-row::after {
width: 0;
font-size: 0;
}
.as-console-row-code {
width: 100%;
word-break: break-word;
}
.as-console-wrapper {
max-height: 30% !important;
max-width: 100%;
}
<form id='todo'>
<fieldset>
<legend>ToDo List</legend>
<input id='data' type="text" required>
<button class="p-2 bg-slate-500 rounded">Add</button>
<ul class="list"></ul>
</fieldset>
</form>
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.append(newSpan);
return newLi;
});
In this block of code, I believe the newEl variable being created isn't correct. Try changing newLi.append(newSpan) to newLi.appendChild(newSpan)
This is also listed below
if (text) {
todos.push(item);
addTodoForm.reset();
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newSpan.textContent = todo.text;
let newEl = newLi.appendChild(newSpan);
return newEl;
});
console.log(todoItems);
todoList.append(...todoItems);
}
return;
Thanks for all the answers.
Upon reading on the documentation of append and appendChild, it seems elements cannot be saved into a variable upon using these methods.
I just need to return the append li element.
const todoItems = todos.map((todo, i) => {
let newSpan = document.createElement('span');
let newLi = document.createElement('li');
newLi.setAttribute('data-index', i);
const todoTextNode = document.createTextNode(todo.text);
newSpan.appendChild(todoTextNode);
newLi.appendChild(newSpan);
return newLi;
});
I want to add html elements to the body of my page as an unordered list. I have used DocumentFragment method to create a fragment of the reply button and comment span. Now I need to add a textbox and a add reply to that ul whenever a user clicks on the reply button and add all the replies as a list next to respective comment. Here is what I've tried:
function comment() {
var my_comment = document.getElementById('comments');
my_comment.innerHTML = "<textarea id='user_comment'> </textarea> <button onclick='addNewItem()'>Post Comment</button>";
}
function addNewItem() {
var thediv = document.getElementById("comments_and_replies");
var listItem = document.createElement("ul");
var replyBox = document.createElement("textbox");
var commentSpan = document.createElement("span");
var user_comment = document.getElementById('user_comment');
var replyButton = document.createElement("button");
listItem.className = "comments-list";
replyButton.innerText = "Reply";
replyButton.className = "reply";
replyButton.addEventListener("click", function() {
var g = document.getElementById('comments_and_replies');
for (var i = 0, len = g.children.length; i < len; i++) {
(function(index) {
g.children[i].onclick = function() {
listItem.insertBefore(replyBox, listItem.children[index]);
}
})(i);
}
})
commentSpan.textContent = user_comment.value;
var documentFragment = document.createDocumentFragment();
documentFragment.appendChild(listItem);
listItem.appendChild(commentSpan);
listItem.appendChild(replyButton);
thediv.appendChild(documentFragment);
}
<section><button onclick="comment()">Leave a comment</button></section>
<div id="comments"></div>
<div id="comments_and_replies"></div>
Event delegation on a single <form> can accommodate an unlimited amount of <button>s even if they are added after the page has loaded.
The example below uses the following:
document.forms
.elements
event.currentTarget
event.target
.matches()
.insertAdjacentHTML()
.previousElementSibling
.parentElement
.remove()
Note: Unless you are submitting data to a server, add type="button" to each <button>
Details are commented in code below
// Refernce <form>
const form = document.forms.commentsReplies;
// Any click on <form> invokes post()
form.onclick = post;
// Pass the event
function post(event) {
/* Reference all <fieldset>
(also <button>, <textarea>, etc) */
const field = event.currentTarget.elements;
// Reference the actual element clicked
const clicked = event.target;
// if element clicked has class postCom
if (clicked.matches('.postCom')) {
/* find <fieldset name="post"> and
insert HTML into it */
field.post.insertAdjacentHTML('beforeEnd', `<fieldset name='commentPost'><textarea></textarea><button class='comTxt' type='button'>Done</button></fieldset>`);
// Otherwise if clicked element has class comTxt
} else if (clicked.matches('.comTxt')) {
/* find the clicked element's element
that is right before it and get it's text */
const text = clicked.previousElementSibling.value;
/* find <fieldset name='comments'> and insert HTML */
field.comments.insertAdjacentHTML('afterBegin', `<fieldset>${text}<button class='postRep' type='button'>Reply</button><ul></ul></fieldset>`);
// Remove <fieldset name='commentPost'>
field.commentPost.remove();
} else if (clicked.matches('.postRep')) {
clicked.insertAdjacentHTML('afterEnd', `<ul><textarea></textarea><button class='repTxt' type='button'>Done</button></ul>`);
} else if (clicked.matches('.repTxt')) {
const text = clicked.previousElementSibling.value;
const list = clicked.parentElement;
list.insertAdjacentHTML('afterBegin', `<li>${text}<button class='postRep' type='button'>Reply</button></li>`);
clicked.previousElementSibling.remove();
clicked.remove();
} else {
return false;
}
}
button {
display: block;
margin-left: 25%;
}
<form id='commentsReplies'>
<fieldset name='post'><button class='postCom' type='button'>Leave a comment</button>
</fieldset>
<fieldset name="comments">
<legend>Comments</legend>
</fieldset>
</form>
I'm trying to submit multiple rows of data with an attachment from an input field at the end of each row. I am using a Google App-Script Webapp for this. I am successful in creating an array of objects with the text data (such as date, name, last, etc.), but cannot seem to send the attachment as part of the object. What am I doing wrong?
I should also clarify that this code won't work with either one or multiple attachments. I would hope that I could send multiple groupings of attachments (hence the array of objects) at one time.
Here is my code on the HTML/Javascript client-side:
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("tripPost").addEventListener("click", addLine);
document.getElementById("submitAll").addEventListener("click", addRecord);
});
//global variables for next functions
var submit = document.getElementById("tripPost");
var submittedTable = document.getElementById("submitted-data");
var mainEntry = document.getElementById("entry-table");
var submitAll = document.getElementById("submitAll");
submittedTable.addEventListener("click", addLine);
submittedTable.addEventListener("change", fileUpload);
function addLine() {
document.getElementById("table-container").style.display = "block";
var date = document.getElementById("date1").value;
var efirst = document.getElementById("efirst").value;
var elast = document.getElementById("elast").value;
var row = document.createElement("tr");
var col1 = document.createElement("td");
col1.appendChild(document.createTextNode(date));
col1.className = "postDateClass";
var col2 = document.createElement("td");
col2.appendChild(document.createTextNode(efirst));
col2.className = "postEfirstClass";
var col3 = document.createElement("td");
col3.appendChild(document.createTextNode(elast));
col3.className = "postElastClass";
var col4 = document.createElement("td");
row.appendChild(col1);
row.appendChild(col2);
row.appendChild(col3);
row.appendChild(col4);
submittedTable.appendChild(row);
var uniqueID = "id" + new Date().getTime();
var upload = document.createElement("input");
upload.type = "file";
upload.id = uniqueID;
upload.name = "myReceipt";
upload.className = "uploadClass";
var label = document.createElement("label");
label.innerHTML = "upload me please!";
label.htmlFor = uniqueID;
label.className = "custom-file-upload";
var form = document.createElement("form");
form.appendChild(upload);
form.appendChild(label);
col4.appendChild(form);
}
function fileUpload(e) {
if (e.target.className === "uploadClass") {
if (e.target.value) {
var span = document.createElement("span");
span.className = "uploadSpanText";
span.innerHTML = e.target.value.match(/[\/\\]([\w\d\s\.\-\(\)]+)$/)[1];
e.target.parentElement.appendChild(span);
e.target.nextElementSibling.innerHTML = "uploaded!";
e.target.nextElementSibling.style.border = "1px solid #a8e0b4";
e.target.nextElementSibling.style.color = "#8bca9e";
}
}
}
function getFile(file) {
return new Promise(resolve => {
const fr = new FileReader();
fr.onload = e => {
const data = e.target.result.split(",");
const obj = {
fileName: file.name,
mimeType: data[0].match(/:(\w.+);/)[1],
data: data[1]
};
resolve(obj);
};
if (file) {
fr.readAsDataURL(file);
} else {
reject("No File");
}
});
}
//gathers inputs and stores values in an object and runs the "addLine" function
async function addRecord(e) {
var dateLines = document.querySelectorAll(".postDateClass");
var eFirstLines = document.querySelectorAll(".postEfirstClass");
var eLastLines = document.querySelectorAll(".postElastClass");
var attachmentLines = document.querySelectorAll(".uploadClass");
var mileageData = [];
for (var i = 0; i < dateLines.length; i++) {
var mileageLines = {};
mileageLines.travelDate = dateLines[i].textContent;
mileageLines.firstName = eFirstLines[i].textContent;
mileageLines.lastName = eLastLines[i].textContent;
mileageLines.receipt = await getFile(attachmentLines[i].parentNode);
mileageData.push(mileageLines);
}
//send object to google. resets input elements
google.script.run.userMileageSubmit(mileageData);
}
Here is the HTML for the code that I'm working with.
<div id="entry-table">
<table>
<h3 style="text-align:left"><u><b>Enter mileage information below.</b></u><br></h3>
<thead>
<tr>
<th >Date</th>
<th >First:</th>
<th >Last:</th>
</tr>
</thead>
<tbody id="table-data">
<tr>
<td>
<div class="disabled-results" id="date">
<input placeholder="Start Date" id="date1" type="text" class="datekeeper" required>
<label for="date1" class="active">Date:</label>
</div>
<td>
<div class="disabled-results">
<input id ="efirst" type="text" class="validate" >
<label for="efirst" class="active">First:</label>
</div>
</td>
<td>
<div class="disabled-results">
<input id ="elast" type="text" class="validate" >
<label for="elast" class="active">Last:</label>
</div>
</td>
<td>
<div id="status">
<button id="tripPost" class="waves-effect waves-light btn-small blue darken-3">Add Trip</button>
</div>
</td>
</tr>
</tbody>
</table>
</div><!---CLOSE ROW --->
<div class="autocomplete" id="table-container" style=display:none>
<table>
<thead>
<tr id="header-titles">
<th >Date</th>
<th >First:</th>
<th >Last:</th>
<th >Receipt </th>
</tr>
</thead>
<form>
<tbody class="form" id="submitted-data">
<div>
<p>Thank you!</p>
</div>
</form>
</tbody>
</table>
<br><br>
</div>
<center>
<div class="row">
<button id="submitAll" class="waves-effect waves-light btn btn-large blue darken-3"><i class="material-icons left">directions_car</i>Submit All Mileage!</button>
</div>
</center>
Here is the CSS
body {
background-color: lightblue;
margin-top: 80px;
margin-bottom: 80px;
margin-right: 80px;
margin-left: 80px;
}
h1{
color: black;
text-align: center;
}
div.disabled-results{
width: 175px;
height: 80px;
padding: 5px;
margin: 5px;
display: inline-table;
box-sizing: border-box;
text-align: center;
}
input[type="file"]{
display: none;
}
.custom-file-upload{
border: 2px solid #000;
width: 85px;
display: inline-block;
padding: 2px 1px;
cursor: pointer;
text-align: center;
}
div.autocomplete{
width: 55px;
height: 80px;
padding: 5px;
margin: 5px;
display: inline-table;
box-sizing: border-box;
text-align: center;
}
I got everything else to work, except sending the attachment (if any) in each line as part of the object.
I am sure that it can be done. I tried to implement the solution from this video which shows you how to upload a file, but I don't use the onclick or this.parentNode since I'm not uploading immediately after selecting a file and instead doing a bulk upload when a user has made numerous entries.
Any help in understanding how this should work would be greatly appreciated.
Thank you.
How about this modification? Please think of this as jut one of several possible answers.
Unfortunately, in this case, the file object from HTML side cannot be directly sent to Google Apps Script as a blob. So as one of several workarounds, in this modification, the retrieved files are encoded to the base64 data and send it to Google Apps Script. Then, at Google Apps Script side, the data is decoded and save them as the files.
Please modify your script as follows.
HTML and Javascript side:
Please modify addRecord() and add getFile() as follows.
// Added
function getFile(file) {
return new Promise((resolve, reject) => {
const fr = new FileReader();
fr.onload = e => {
const data = e.target.result.split(",");
const obj = {fileName: file.name, mimeType: data[0].match(/:(\w.+);/)[1], data: data[1]};
resolve(obj);
}
if (file) {
fr.readAsDataURL(file);
} else {
reject("No file");
}
});
}
async function addRecord(e) { // Modified
var dateLines = document.querySelectorAll('.postDateClass');
var attachmentLines = document.querySelectorAll('.uploadClass');
var mileageData = [];
for (var i=0; i<dateLines.length; i++){
var mileageLines = {};
mileageLines.firstName = document.getElementById("efirst").value;
mileageLines.lastName = document.getElementById("elast").value;
mileageLines.date = dateLines[i].textContent;
mileageLines.receipt = await getFile(attachmentLines[i].files[0]).catch(e => console.log(e)); // Modified
mileageData.push(mileageLines);
};
google.script.run.userMileageSubmit(mileageData);
};
Google Apps Script side:
Please modify userMileageSubmit() as follows.
function userMileageSubmit(responses){
responses.forEach(function(e) {
var file = e.receipt;
if (file) {
var blob = Utilities.newBlob(Utilities.base64Decode(file.data), file.mimeType, file.fileName);
var mainFolder = DriveApp.getFolderById('real-drive-link');
var createFile = mainFolder.createFile(blob);
var fileUrl = createFile.getUrl();
Logger.log(fileUrl)
}
});
// row.appendChild(col4)
// submittedTable.appendChild(row)
}
I cannot understand about row.appendChild(col4) and submittedTable.appendChild(row).
Unfortunately, I couldn't understand about your goal at userMileageSubmit(). So in this modification, the retrieved files are saved to Google Drive. And the URL of the created file can be seen at the log.
Here, please modify this for your actual situation.
I'm not sure about real-drive-link. In this case, please set the folder ID that you want to save the file.
Note:
In this modification, it supposes that your current addRecord() works.
In this modification, the maximum file size is 50 MB, because the maximum blob size of Google Apps Script is 50 MB. Please be careful this.
When a lot of files are uploaded, the process time will increase. Please be careful this.
References:
FileReader
Class Utilities
I've been able to display JSON data (local file) into each row in my table, and when the data's corresponding checkbox is checked I want to push those selected values into an array. The end goal is to display the array in a div, but not until I'm able to see that the array is being populated.
JS snippet:
($("#km-table-id tbody tr")).append($("<input />", {"type": "checkbox"}).addClass("checkbox-class"));
let table = $("#km-table-id").DataTable();
let favesArr = [];
$(".checkbox-class").on("click", "tr", function() {
let data = table.row(this).data();
for (var i = 0; i < favesArr.length; i++) {
favesArr.push($(data).text());
$("#myFave.hs-gc-header").append(favesArr[i]);
}
console.log(data); // this was showing the selected data a short time ago, but not anymore
});
console.log(favesArr); // only showing empty
First of all, your last line will always print an empty array because you are only filling it in an event handler.
Secondly, you are using i < favesArr.length as your loop condition. favesArr is empty here yet, if not filled in other parts of the code. The loop body thus is never executed. You probably wanted data.length here.
Last but not least, you may want to push only data[i] and not the whole array into your favesArray.
I would recommend that you capture whether the checkbox is checked. You can check if the item is already in the array by grabbing the data index.
Not sure what your HTML looks like...
(function($) {
$.fn.appendText = function(text) { return this.text(this.text() + text); };
$.fn.appendHtml = function(html) { return this.html(this.html() + html); };
})(jQuery);
const $table = $('#km-table-id');
$table.find('tbody tr').append($("<input>", { type: 'checkbox', class : 'checkbox-class'}));
let table = $table.DataTable();
let favesArr = [];
$table.on('click', '.checkbox-class', function(e) {
let data = table.row(this.parentNode).data(),
checked = $(this).is(':checked'),
dataIndex = favesArr.indexOf(data);
if (checked) {
if (dataIndex === -1) {
favesArr.push(data); // Add item
}
} else {
if (dataIndex > -1) {
favesArr.splice(dataIndex, 1); // Remove item
}
}
$('#myFave.hs-gc-header').appendHtml('>> ' + favesArr.map(x => x.join(', ')).join('; ') + '<br/>');
});
body {
background: #666;
}
.table-wrapper {
background: #fff;
width: 80%;
margin: 0 auto;
margin-top: 1em;
padding: 0.25em;
}
#myFave.hs-gc-header {
background: #fff;
width: 81%;
margin: 0 auto;
margin-top: 0.5em;
height: 5em;
overflow: scroll;
}
<link href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
<div class="table-wrapper">
<table id="km-table-id">
<thead>
<tr>
<th>A</th><th>B</th><th>C</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td><td>2</td><td>3</td>
</tr>
<tr>
<td>4</td><td>5</td><td>6</td>
</tr>
<tr>
<td>7</td><td>8</td><td>9</td>
</tr>
</tbody>
</table>
</div>
<div id="myFave" class="hs-gc-header"></div>
I would like to add a row to a table with 4 cells: indexnumber, firstname, lastname and points. As a newbie in Javascript I am complety lost in textnodes, childs and other Dom elements. Who can help creating a dynamic indexnumber like 1,2,3 in the first column etc..
var button = document.getElementById("addRow");
button.onclick = addRowFunc;
var firstName = document.getElementById("firstN");
var lastName = document.getElementById("lastN");
var points = document.getElementById("pnt");
function addRowFunc() {
var tabel = document.getElementById("myTable");
var row = tabel.insertRow(-1);
var cel1 = row.insertCell(0);
var cel2 = row.insertCell(1);
var cel3 = row.insertCell(2);
var cel4 = row.insertCell(3);
cel1.value = function () {
for (var i = 1; i < rij.length; i++) {
//createTextNode ..
}
}
cel2.innerHTML = firstName.value;
cel3.innerHTML = lastName.value;
cel4.innerHTML = points.value;
}
<table id="myTable">
<tr>
<th>Rownumber</th>
<th>FirstName</th>
<th>LastName</th>
<th>Points</th>
</tr>
</table>
<br />
<br />
<br />
FirstName <input type="text" id="firstN" /><br />
LastName <input type="text" id="lastN" /><br />
Points <input type="text" id="pnt" /> <br />
<br />
<button type="button" id="addRow">Add</button>
One approach to the problem you're facing, that of showing the number of the table-row element can be solved using CSS, with the following rules:
tbody {
counter-reset: rownumber;
}
tr {
counter-increment: rownumber;
}
td:first-child::before {
content: counter(rownumber, decimal);
}
The above defines the counter, rownumber and has it be reset via the <tbody> element; so if rows are appended over multiple <tbody> elements then the <tr> descendants of each will be numbered independently of each other. If you'd prefer a continuous count of all <tr> elements then simply move the counter-reset rule up to the <table> element, or to any other (ideally the closest possible) ancestor within which you require a cumulative count.
This counter is incremented, by 1, in each <tr> element, and then shown in the ::before pseudo-element of the first <td> child of each <tr>. The second argument to counter() is the type of counter you wish to use (see this answer for the (current) possible options: https://stackoverflow.com/a/16943843/82548 (disclaimer: it's one of my answers, the second Snippet will allow you to see the results of different counter types).
So, that part covered I've also rewritten – I'd like to say revised or refactored, but that would be, at best, an understatement – your posted code into the following function. Code explained in the comments:
// a named function to call:
function addRow() {
// using 'let' to declare variables, this is mostly a
// matter of personal preference in this case, and if
// 'let' is unavailable to you, or your users, can be
// replaced with 'var'.
// here we define a 'details' Object to hold the
// variables from the <input> elements we find
// later:
let details = {},
// we use document.querySelector() to retrieve the first,
// if any, elements matching the supplied CSS selector;
// this is the element to which new content will be added:
target = document.querySelector('#myTable tbody'),
// and again we use document.querySelector() to find the
// element we'll be cloning in order to append the new
// new content:
source = document.querySelector('#myTable tfoot tr.template')
// we pass (Boolean) true as an argument in order to copy
// the descendants of the cloned node as well as the node
// itself:
.cloneNode(true),
// here we use Array.from() to convert the Array-like
// HTMLCollection returned from document.querySelectorAll()
// (which retrieves all the elements which match the
// supplied CSS selector) into an Array:
inputs = Array.from(
document.querySelectorAll('form label input')
);
// we make use of the Array, via Array methods such as,
// here, Array.prototype.forEach(), which iterates over
// the elements of the Array:
inputs.forEach(
// using an Arrow function, rather than the traditional
// anonymous function; here 'input' refers to the current
// Array element of the Array of <input> elements over
// which we're iterating.
// here we update the details Object, by adding a new
// key (details[input.id]) and setting the value of that
// key to the value (input.value) held in the <input>:
input => details[input.id] = input.value
);
// here we convert the Array-like NodeList of child-
// elements of the source (the <tr> element we cloned
// earlier) into an Array, again using Array.from, and
// then iterating over those elements using, again,
// Array.prototype.forEach():
Array.from(source.children).forEach(
// in this Arrow function we're naming the
// current Array-element 'cell', the name is
// purely a personal choice, and can be almost
// anything, so long as it's valid in JavaScript.
// here we set the text-content of the current
// cell (<td> element) to be equivalent to the
// value held in the details Object at the same
// custom data-* attribute, here data-from, as
// the current <td> (details.cell.dataset.from);
// if there is no key of that name, we set the
// text to an empty String (''):
cell => cell.textContent = details[cell.dataset.from] || ''
);
// we append the newly modified <tr> element to
// the target element (the <tbody>):
target.appendChild(source);
// and here we iterate over the <input> elements:
inputs.forEach(
// and set the value of each <input> back to
// its default value, the value it held on
// page-load in order to save the user having
// to first delete existing content before
// entering new content to add:
input => input.value = input.defaultValue
);
}
document.querySelector('#addRow').addEventListener('click', addRow);
function addRow() {
let details = {},
target = document.querySelector('#myTable tbody'),
source = document.querySelector('#myTable tfoot tr.template')
.cloneNode(true),
inputs = Array.from(
document.querySelectorAll('form label input')
);
inputs.forEach(
input => details[input.id] = input.value
);
Array.from(source.children).forEach(
cell => cell.textContent = details[cell.dataset.from] || ''
);
target.appendChild(source);
inputs.forEach(
input => input.value = input.defaultValue
);
}
document.querySelector('#addRow').addEventListener('click', addRow);
body {
box-sizing: border-box;
}
label {
display: block;
width: 55%;
overflow: hidden;
margin: 0 0 0.5em 0;
}
table {
table-layout: fixed;
width: 90%;
margin: 1em auto;
border-collapse: collapse;
}
label input {
width: 50%;
float: right;
}
th,
td {
border-left: 1px solid #000;
border-bottom: 1px solid #000;
line-height: 2em;
height: 2em;
}
th {
text-align: center;
}
th::after {
content: ': ';
}
td:first-child,
th:first-child {
border-left-color: transparent;
}
tbody {
counter-reset: rownumber;
}
tbody tr {
counter-increment: rownumber;
}
tbody td:first-child::before {
content: counter(rownumber, decimal);
}
tfoot tr.template {
display: none;
}
<form action="#">
<fieldset>
<legend>Add new details:</legend>
<label>FirstName
<input type="text" id="firstN" />
</label>
<label>LastName
<input type="text" id="lastN" />
</label>
<label>Points
<input type="text" id="pnt" />
</label>
<button type="button" id="addRow">Add</button>
</fieldset>
</form>
<table id="myTable">
<thead>
<tr>
<th>Rownumber</th>
<th>FirstName</th>
<th>LastName</th>
<th>Points</th>
</tr>
</thead>
<tfoot>
<tr class="template">
<td></td>
<td data-from="firstN"></td>
<td data-from="lastN"></td>
<td data-from="pnt"></td>
</tr>
</tfoot>
<tbody>
</tbody>
</table>
JS Fiddle demo.
References:
CSS:
Automatic counters and numbering.
:first-child pseudo-class.
JavaScript:
Array.from().
Array.prototype.forEach().
Arrow functions.
document.querySelector().
document.querySelectorAll().
EventTarget.addEventListener().
HTMLElement.dataset API.
Node.appendChild().
let statement.
Node.cloneNode().
`Node.textContent.
var statement.
Here's the easiest way to get how nodes and DOM work :)
var table = document.getElementById('myTable');
var row = document.createElement('tr');
var cell1 = document.createElement('td');
var cell2 = document.createElement('td');
var cell3 = document.createElement('td');
var cell4 = document.createElement('td');
cell1.innerHTML = '1';
cell2.innerHTML = '2';
cell3.innerHTML = '3';
cell4.innerHTML = '4';
row.appendChild(cell1);
row.appendChild(cell2);
row.appendChild(cell3);
row.appendChild(cell4);
table.appendChild(row);
so let's assume you have an Array of data formatted like:
data = [
{
indexNumber: 1,
firstName: 'Bon',
lastName: 'Jovi',
points: 50,
},
{
indexNumber: 2,
firstName: 'Ann',
lastName: 'Hathaway',
points: 60,
},
];
now you can process this Array in a forEach cycle:
var table = document.getElementById('myTable');
data.forEach(function(element) {
var row = document.createElement('tr');
var cell1 = document.createElement('td');
var cell2 = document.createElement('td');
var cell3 = document.createElement('td');
var cell4 = document.createElement('td');
cell1.innerHTML = element.indexNumber;
cell2.innerHTML = element.firstName;
cell3.innerHTML = element.lastName;
cell4.innerHTML = element.points;
row.appendChild(cell1);
row.appendChild(cell2);
row.appendChild(cell3);
row.appendChild(cell4);
table.appendChild(row);
});
to insert a new row use append
<table id="mytable">
<tr><td >1</td> <td >sd</td> <td >sd</td> <td >sd</td></tr>
<tr><td >2</td> <td >sd</td> <td >sd</td> <td >sd</td></tr>
<tr><td >3</td> <td >sd</td> <td >sd</td> <td >sd</td></tr>
</table>
add this with jquery
var total_rows = $('#mytable tr').length;
var new_row = parseInt(total_rows)+parseInt(1);
var yourdata_variable = "<tr><td >"+new_row+"</td> <td >sd2</td> <td >sd2</td> <td >sd2</td></tr>";
$('#mytable').append(yourdata_variable);
if you just want to use javascript
var yourdata_variable = same as above;
var table = document.getElementById('mytable').append(yourdata_variable);
comment if this is what you want.