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
I used Mirth about 10 years ago and realize I have forgotten pretty much everything and a lot of my resources are no longer available. I am looking to iterate through OBR and OBX segments to pull the OBR 4.2, 7.1, 16.2 to be used as a section header and OBX 3.2, 5.1, 7.1 fields to show below on the report.
Basically take this:
MSH|^~\&|LAB|LIS||EMR|202107201651||ORU^R01|082017045701|P|2.3|| |NE|NE
PID|1||M1302^^^MR||TEST^PATIENT^T^^||19670101|M||| 123 8th street^apt.22b^long beach^CA^90802^||(714)555-1212|(714)222-5555||||873lfif|4441113||||||||||
PV1|1|O|RGH^^^RGH^^^^||||999999^TEST^TEST^^^^^L||||||||||288^ALBRIGHT^MD^^^^^L ||V4735|||||||||||||||||||||||||201008201648|
ORC|RE||15175|||||||||288^ALBRIGHT^MD^^^^^L|
OBR|1||15175|CBC^COMPLETE BLOOD COUNT^L|R|202107200800|202107200800|||BB^RGH^^^^^^ ^^^^^^RGH||||201008201648|B|288^ALBRIGHT^MD^^^^^L| |||||20100820170246|||F|^|||||100.0||||||||||||||G A
OBX|1|NM|WBC^WBC^L||4.0|10\S\3/MM\S\3|3.6-11.1||||F|||202107200759|RGH|ADM
OBX|2|NM|RBC^RBC^L||5.0|10\S\6/MM\S\3|4.50-5.90||||F|||202107200758|RGH|ADM
OBX|3|NM|HGB^HGB^L||12.5|g/dL|12.0-18.0||||F|||202107200757|RGH|ADM
ORC|RE||15175|||||||||288^ALBRIGHT^MD^^^^^L|
OBR|2||15175|ALC^ALCOHOL,ETHYL^L|R|202107201650|20 1008201648|||BB^RGH^^^^^^^^^^^^RGH||||201008201648 |B|288^ALBRIGHT^MD^^^^^L||||||20100820170246|||F|^ |||||||||||||||||||GA
OBX|1|NM|ALC^ALCOHOL (ETHANOL)^L||36|mg/dL|0-32|H|||F|||201008201650|RGH|ADM
and make:
CBC
202107260800
ALBRIGHT
WBC
4.0
3.6-11.1
RBC
5.0
4.50-5.90
HGB
12.5
12.0-18.0
ALCOHOL, ETHYL
202107201650
ALBRIGHT
ALCOHOL (ETHANOL)
36
0-32
Any assistance is greatly appreciated.
You can play around with the html and css still, but here's how you can organize the data and loop over it in velocity. We start by looping over the OBR segments. Then we use the getSegmentsAfter function for getting the OBX segments associated with the current OBR segment. The function returns the results as an array of xml objects, which we then convert to plain js objects.
The code template for the getSegmentsAfter function can be found here https://github.com/nextgenhealthcare/connect-examples/tree/master/Code%20Templates/Get%20Segments%20After%20a%20Particular%20Segment
This works because javascript arrays implement the java.util.Collection interface, and javascript objects implement the java.util.Map interface, both of which velocity knows how to access.
Transformer
var reportData = [];
for each (var obr in msg.OBR) {
var sectionHeader = {
panelName: obr['OBR.4']['OBR.4.2'].toString().trim(),
dateTime: obr['OBR.7']['OBR.7.1'].toString().trim(),
providerName: obr['OBR.16']['OBR.16.3'].toString().trim() + ' ' + obr['OBR.16']['OBR.16.2'].toString().trim()
};
var sectionData = getSegmentsAfter(msg, obr, 'OBX')
.map(function(obx) {
return {
testName: obx['OBX.3']['OBX.3.2'].toString().trim(),
result: obx['OBX.5']['OBX.5.1'].toString().trim(),
unitOfMeasure: obx['OBX.6']['OBX.6.1'].toString().trim(),
referenceRange: obx['OBX.7']['OBX.7.1'].toString().trim(),
abnormalFlag: obx['OBX.8']['OBX.8.1'].toString().trim()
}});
reportData.push({
header: sectionHeader,
data: sectionData
});
}
$co('reportData', reportData);
Document Writer template
<html>
<head>
<style type="text/css">
div table {
width: 100%;
margin-bottom: 5px;
}
div table th, td {
word-wrap: break-word;
border: 1px solid lightgrey;
text-align: left;
}
div {
border: 1px solid grey;
margin: 5px;
}
</style>
</head>
<body>
<h1>Laboratory Results</h1>
#foreach ($section in $reportData)
<div>
<table>
<tr>
<th>Panel Name</th><th>DateTime</th><th>Provider</th>
</tr>
<tr>
<td>$section.header.panelName</td>
<td>$section.header.dateTime</td>
<td>$section.header.providerName</td>
</tr>
</table>
<table>
<tr>
<th>Test</th><th>Result</th><th>Unit of Measure</th><th>Range</th><th>Flag</th>
</tr>
#foreach ($row in $section.data)
<tr>
<td>$row.testName</td>
<td>$row.result</td>
<td>$row.unitOfMeasure</td>
<td>$row.referenceRange</td>
<td>$row.abnormalFlag</td>
</tr>
#end
</table>
</div>
#end
</body>
</html>
Results
Mirth User Guide provides some interesting documentation about what you are trying to achieve. There is a section named Iterating Over Message Segments which explains it and gives some examples.
But the idea is quite similar the following
//To iterate over all segments, follow this example:
for each (var segment in msg.children()) {
if (segment.name().toString() == "ORC") {
// Do something...
}
}
//To iterate through specifically named segments, use this formula:
for each (var segment in msg.OBX) {
// Do something...
}
The complex part will be how to associate each OBX with its corresponding OBR and OCR. I dit not find a clean way to achieve this. I used Lists and Maps and process all segments sequentially, adding the relevant fields to some data structures which help me to get my final result. That table in your case.
Here is what I've done, it is super ugly and doesn't quite meet my expectations (yet) so please feel free to help me improve. I could not seem to get a nested foreach in the report writer's html to process through a 2nd OBR, so lets just hope I don't get one until I figure it out.
Source Transformer: I mapped the Patients info using the Mapper
Destination Transformer:
var _OBR45PanelName = Lists.list();
var _OBR71DateTime = Lists.list();
var _OBR163ProviderFirstName = Lists.list();
var _OBR162ProviderLastName = Lists.list();
var _OBX32TestName = Lists.list();
var _OBX51Result = Lists.list();
var _OBX61UnitofMeasure = Lists.list();
var _OBX71ReferenceRange = Lists.list();
var _OBX81AbnormalFlag = Lists.list();
for (var i = 0; i < getArrayOrXmlLength(msg['OBR']); i++) {
var mapping;
try {
mapping = msg['OBR'][i]['OBR.4']['OBR.4.5'].toString().trim();
} catch (e) {
mapping = '';
}
_OBR45PanelName.add(validate(mapping, '', new Array()));
var mapping;
try {
mapping = msg['OBR'][i]['OBR.7']['OBR.7.1'].toString().trim();
} catch (e) {
mapping = '';
}
_OBR71DateTime.add(validate(mapping, '', new Array()));
var mapping;
try {
mapping = msg['OBR'][i]['OBR.16']['OBR.16.3'].toString().trim();
} catch (e) {
mapping = '';
}
_OBR163ProviderFirstName.add(validate(mapping, '', new Array()));
var mapping;
try {
mapping = msg['OBR'][i]['OBR.16']['OBR.16.2'].toString().trim();
} catch (e) {
mapping = '';
}
_OBR162ProviderLastName.add(validate(mapping, '', new Array()));
for (var J = 0; J < getArrayOrXmlLength(msg['OBX']); J++) {
var mapping;
try {
mapping = msg['OBX'][J]['OBX.3']['OBX.3.2'].toString().trim();
} catch (e) {
mapping = '';
}
_OBX32TestName.add(validate(mapping, '', new Array()));
var mapping;
try {
mapping = msg['OBX'][J]['OBX.5']['OBX.5.1'].toString().trim();
} catch (e) {
mapping = '';
}
_OBX51Result.add(validate(mapping, '', new Array()));
var mapping;
try {
mapping = msg['OBX'][J]['OBX.6']['OBX.6.1'].toString().trim();
} catch (e) {
mapping = '';
}
_OBX61UnitofMeasure.add(validate(mapping, '', new Array()));
var mapping;
try {
mapping = msg['OBX'][J]['OBX.7']['OBX.7.1'].toString().trim();
} catch (e) {
mapping = '';
}
_OBX71ReferenceRange.add(validate(mapping, '', new Array()));
var mapping;
try {
mapping = msg['OBX'][J]['OBX.8']['OBX.8.1'].toString().trim();
} catch (e) {
mapping = '';
}
_OBX81AbnormalFlag.add(validate(mapping, '', new Array()));
}
}
channelMap.put('OBR4.5 Panel Name', _OBR45PanelName.toArray());
channelMap.put('OBR7.1 DateTime', _OBR71DateTime.toArray());
channelMap.put('OBR16.3 Provider FirstName', _OBR163ProviderFirstName.toArray());
channelMap.put('OBR16.2 Provider LastName', _OBR162ProviderLastName.toArray());
channelMap.put('OBX3.2 Test Name', _OBX32TestName.toArray());
channelMap.put('OBX5.1 Result', _OBX51Result.toArray());
channelMap.put('OBX6.1 Unit of Measure', _OBX61UnitofMeasure.toArray());
channelMap.put('OBX7.1 Reference Range', _OBX71ReferenceRange.toArray());
channelMap.put('OBX8.1 Abnormal Flag', _OBX81AbnormalFlag.toArray());
For the report writer I have:
<html>
<head>
<style type="text/css">
div table {
float: left;
}
div table th, td {
word-wrap: break-word;
border: 1px solid lightgrey;
padding-right: 5px;
height: 25px;
text-align: left;
}
</style>
</head>
<body>
<h1>Laboratory Results</h1>
<table>
<tr><th>Name:</th><th>DOB:</th></tr>
<tr><td>${maps.get('PID5.2 Patient FirstName')} ${maps.get('PID5.3 Patient MiddleName')} ${maps.get('PID5.1 Patient LastName')}</td><td>${maps.get('PID7.1 Patient DoB')}</td></tr>
<tr><th>Ordering Provider:</th><td>${maps.get('OBR16.3 Provider FirstName')} ${maps.get('OBR16.2 Provider LastName')}</td></tr>
</table>
<table>
#foreach ($item in ${maps.get('OBR4.5 Panel Name')})
<tr><th>${maps.get('OBR4.5 Panel Name')}</th></tr>
<div>
<table>
<tr><th>Test</th></tr>
#foreach ($item in ${maps.get('OBX3.2 Test Name')})
<tr><td>$item</td></tr>
#end
</table>
<table>
<tr><th>Result</th></tr>
#foreach ($item in ${maps.get('OBX5.1 Result')})
<tr><td>$item</td></tr>
#end
</table>
<table>
<tr><th>Unit of Measure</th></tr>
#foreach ($item in ${maps.get('OBX6.1 Unit of Measure')})
<tr><td>$item</td></tr>
#end
</table>
<table>
<tr><th>Range</th></tr>
#foreach ($item in ${maps.get('OBX7.1 Reference Range')})
<tr><td>$item</td></tr>
#end
</table>
<table>
<tr><th>Flag</th></tr>
#foreach ($item in ${maps.get('OBX8.1 Abnormal Flag')})
<tr><td>$item</td></tr>
#end
</table>
</div>
#end
</table>
</body>
</html>
and it turns out (again ugly) like this
I have a sheet with ticket number, call date, customer mobile no, customer name
I want to take the user input (Ticket number) in a Webapp.
From that, I will find the customer mobile number in the table.
From the customer mobile number, I want to display all the matching rows (in the same table) to the user in HTML. I want to display all the calls made by the customer (he could have made many calls before)
I referred to
How to search and filter a Google Sheet based on two parameters in two different columns
and Tried
code.gs
function doGet() {
return HtmlService.createTemplateFromFile('index').evaluate();
}
//
function getValuesFromSS(search) {
var ss = SpreadsheetApp.openByUrl("https://docs.google.com/spreadsheets/d/1zObr0he1SYJkOXMMyFrOWk-0OtV6w/edit#gid=926906658")//service calls
var calsht=ss.getSheetByName('Calls');
//var lastRow = calsht.getLastRow();
var arange = calsht.getRange("A:D").getValues();
for (m= arange.length-1; m>0; m--){
if (arange[m][0]==search.name){//search.name
var cusmob=arange[m][3];
//Logger.log(m);
//Logger.log(cusmob);
}
}
var names = '';
var techs = '';
var eqips = '';
var urls = '';
var lastCol = calsht.getLastColumn();
for (m= arange.length-1; m>0; m--){
if (arange[m][3]==cusmob){
var values = calsht.getRange("A"+(m+1)+":AL"+(m+1)).getValues(); //get all values for the row
var name = values[0][4]; //column E
var tech = values[0][5]; //column F
var eqip = values[0][14]; //column O
var url = values[0][37]; // AL
//Logger.log(url);
names+=Utilities.formatString("<td>" + name + "</td>");
techs+=Utilities.formatString("<td>" + tech + "</td>");
eqips+=Utilities.formatString("<td>" + eqip + "</td>");
urls+=Utilities.formatString('<td>' + 'Inv' + '</td>');
}//if
}//for
return {
first: names,
second: techs,
third: eqips,
fourth: urls
}
}
index.html
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript">
function setPageValues () {
var search = document.getElementsByName('searchtext')[0].value;
var obj = {};
if (!search) alert("Ticket No is required");
if (search) {
obj.name = search;
}
google.script.run.withSuccessHandler(disp).getValuesFromSS(obj);
}
function disp(values){
document.getElementById("results1").innerHTML = values.first;
document.getElementById("results2").innerHTML = values.second;
document.getElementById("results3").innerHTML = values.third;
document.getElementById("results4").innerHTML = values.fourth;
}
</script>
</head>
<style>
table {
border-collapse: collapse;
}
tr {
display: block;
float: left;
}
td {
border: 1px solid Black;
display: block;
}
</style>
<body>
<input type="text" name="searchtext">
<input type="button" value="Search" onclick="setPageValues();">
<br>
<div name="resultbox">
<table>
<tr id="results1">
</tr>
<tr id="results2">
</tr>
<tr id="results3">
</tr>
<tr id="results4">
</tr>
</table>
</div>
</body>
<script>
</script>
</html>
Now it seems to be working.
I changed from findall to for loop.
Take a look at google.script.run
you can display your results with the withSuccessHandler
I have a button and onclick of that button, i am running a AJAX call and getting values from another jsp page. My rows are indexed.
<%
List<DiskSheet> ds1 = new ArrayList<DiskSheet>();
if (request.getParameter("numLLP") != null && !request.getParameter("numLLP").isEmpty()) {
int numLLP = Integer.valueOf(request.getParameter("numLLP"));
for (int i=0;i<numLLP;i++) {
DiskSheet d = new DiskSheet();
d.setCH5Limiter(request.getParameter("limiter_"+i));
d.setMfrPrice(request.getParameter("diskvalues_"+i));
d.setDiskCyc(request.getParameter("diskcyc"));
ds1.add(d);
}
request.getSession().setAttribute("engine" + request.getParameter("diskid"), ds1);
}
<%
List<DiskSheet> ds = (List<DiskSheet>) request.getSession().getAttribute("engine" + request.getParameter("diskid"));
if (ds == null) {
ds = new ArrayList<DiskSheet>();
}
String disksheet = request.getParameter("disksheet");
if (disksheet != "") {
String engine = request.getParameter("Engines");
if (ds.size() == 0) {
ds = DiskSheet.getLLPEngine(engine);
}
%>
<div><input type="text" style="text-align:right;" name="limiter_<%=i%>" id="limiter" class="limiter" value="<%=airs.getCH5Limiter()%>" size="10" onblur="getDiskSheetCyc()"></div>
<div><input type="hidden" class="diskvalues" id="diskvalues" name="diskvalues_<%=i%>" size="10" value="<%=airs.getMfrPrice()%>" onblur="getDiskSheetCyc()"></div>
<div><input type="text" class="diskcyc" id="diskcyc" name="diskcyc" size="10" value="<%=airs.getDiskCyc()%>" onblur="getDiskSheetCyc()"></div>
I am trying to perform a simple calculation and print the values in the third row however, it only displays the value in one of the cells. Here's what i tried.
function showPopup(diskid){
document.getElementById("popup_win").style.display = 'block';
}
function getDiskSheet(diskid) {
var form = document.getElementById("airplaneForm");
var id = diskid;
var myAjax = new Ajax.Updater("ch5limiteroutput",
"/jsp/Ch5Limiter.jsp", {
asynchronous : true,
postBody : Form.serialize(form),
data: id,
method : 'post',
onComplete : function() {
displayLimiter();
getDiskSheetCyc();
document.getElementById("id").innerHTML = id;
}
});
}
function displayLimiter() {
var form = document.getElementById("airplaneForm");
var limiteroutput = document.getElementById("ch5limiteroutput").innerHTML;
document.getElementById("limiter").innerHTML = limiteroutput;
}
function getDiskSheetCyc(){
var diskvalues = document.getElementsByClassName("diskvalues");
var limiter = document.getElementsByClassName("limiter");
for (var i = 0; i < diskvalues.length; i++) {
var diskval = parseInt(diskvalues[i].value);
var limiter = parseInt(limiter[i].value);
diskcyc = diskval/limiter;
}
document.getElementById('diskcyc').value = diskcyc;
}
<td class="trigger_popup" ><input type="button" id="1" value="Disk Sheet" name="disksheet" class="disksheet" onclick="showPopup(this.id);getDiskSheet(this.id);">
</td>
<div class="popup_win" id="popup_win">
<span class="helper"></span>
<div>
<br>
<div id="TableBox" class="TableBox" style="width: 110%;">
<div>
<div><span class="id" id="id"></span></div>
<div><span class="limiter" id="limiter"></span></div>
</div>
</div>
</div>
</div>
<div id="ch5limiteroutput" style="display: none"></div>
Also tried doing it through jQuery but it doesn't seem to go inside the loop. I am not sure what i am doing wrong here. Any help is greatly appreciated. Thank you.
function getDiskSheetCyc(){
const jQuerytable = jQuery('#TableBox');
const jQueryrow = jQuerytable.find('> div');
jQuery(".jQueryrow").each(function() {
const jQuerythis = jQuery(this);
const diskvalues = parseInt(jQuerythis.find('.diskvalues').val());
const limiter = parseInt(jQuerythis.find('.limiter').val());
const diskcyc = diskvalues/limiter;
if (!isNaN(diskcyc)) jQuerythis.find('.diskcyc').val(diskcyc);
});
}
Your code at present does not make a lot of sense. For a jQuery solution, consider the following code example.
$(function() {
function showPopup() {
$("#popup_win").show();
}
function displayLimiter() {
var limiteroutput = $("#ch5limiteroutput").html();
$("#limiter").html(limiteroutput);
}
function getDiskSheetCyc() {
var diskvalues = $(".diskvalues");
var limiter = $(".limiter");
var diskcyc = 0;
diskvalues.each(function(i, el) {
var diskval = $(el).val() == "" ? parseInt($(el).val()) : 0;
var limiter = $(el).val() == "" ? parseInt($(el).val()) : 0;
if (diskval > limiter) {
diskcyc = diskval / limiter;
}
});
$('#diskcyc').val(diskcyc);
}
function getDiskSheet(diskid) {
var form = $("#airplaneForm");
var id = diskid;
$.post("/jsp/Ch5Limiter.jsp", {
postBody: form.serialize(),
data: id
}, function(results) {
displayLimiter();
getDiskSheetCyc();
$("#id").val(id);
});
}
$("[type='button']").click(function() {
showPopup();
getDiskSheet($(this).attr("id"));
});
});
.popup {
border: 1px solid #ccc;
border-radius: 6px;
width: 340px;
height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<table>
<tr>
<td class="trigger_popup">
<input type="button" id="1" value="Disk Sheet" name="disksheet" class="disksheet">
</td>
</tr>
</table>
<div class="popup window" id="popup_win" style="display: none;">
<span class="helper"></span>
<div>
<br>
<div id="TableBox" class="TableBox" style="width: 110%;">
<div>
<div><span class="id" id="id"></span></div>
<div><span class="limiter" id="limiter"></span></div>
</div>
</div>
</div>
</div>
<div id="ch5limiteroutput" style="display: none"></div>
So this encounters issues all over due to a number of missing elements that were not provided. The basic idea is you want to show a popup, post some data, and then update some content. I don't see where you do anything with data returned from the AJAX call... I am guessing it's just updating the DB with no feedback.
It would be helpful to provide an example of the data that is retuened if you need more help there.
In regards to the calculation, you're on the right path. You just need to make sure the proper fields get populated with the right details. I am assuming this is where the data from the POST gets to be used.
Examine your Network tab in development tools. You should see the POST action, the Headers, and the Response. This should help you identify if you're getting an error in your AJAX or maybe getting different data back than expected.
I'm building a drag-and-drop-to-upload web application using HTML5, and I'm dropping the files onto a div and of course fetching the dataTransfer object, which gives me the FileList.
Now I want to remove some of the files, but I don't know how, or if it's even possible.
Preferably I'd like to just delete them from the FileList; I've got no use for them. But if that's not possible, should I instead write in checks in code that interacts with the FileList? That seems cumbersome.
If you want to delete only several of the selected files: you can't. The File API Working Draft you linked to contains a note:
The HTMLInputElement interface
[HTML5] has a readonly FileList
attribute, […]
[emphasis mine]
Reading a bit of the HTML 5 Working Draft, I came across the Common input element APIs. It appears you can delete the entire file list by setting the value property of the input object to an empty string, like:
document.getElementById('multifile').value = "";
BTW, the article Using files from web applications might also be of interest.
Since JavaScript FileList is readonly and cannot be manipulated directly,
BEST METHOD
You will have to loop through the input.files while comparing it with the index of the file you want to remove. At the same time, you will use new DataTransfer() to set a new list of files excluding the file you want to remove from the file list.
With this approach, the value of the input.files itself is changed.
removeFileFromFileList(index) {
const dt = new DataTransfer()
const input = document.getElementById('files')
const { files } = input
for (let i = 0; i < files.length; i++) {
const file = files[i]
if (index !== i)
dt.items.add(file) // here you exclude the file. thus removing it.
}
input.files = dt.files // Assign the updates list
}
ALTERNATIVE METHOD
Another simple method is to convert the FileList into an array and then splice it.
But this approach will not change the input.files
const input = document.getElementById('files')
// as an array, u have more freedom to transform the file list using array functions.
const fileListArr = Array.from(input.files)
fileListArr.splice(index, 1) // here u remove the file
console.log(fileListArr)
This question has already been marked answered, but I'd like to share some information that might help others with using FileList.
It would be convenient to treat a FileList as an array, but methods like sort, shift, pop, and slice don't work. As others have suggested, you can copy the FileList to an array. However, rather than using a loop, there's a simple one line solution to handle this conversion.
// fileDialog.files is a FileList
var fileBuffer=[];
// append the file list to an array
Array.prototype.push.apply( fileBuffer, fileDialog.files ); // <-- here
// And now you may manipulated the result as required
// shift an item off the array
var file = fileBuffer.shift(0,1); // <-- works as expected
console.info( file.name + ", " + file.size + ", " + file.type );
// sort files by size
fileBuffer.sort(function(a,b) {
return a.size > b.size ? 1 : a.size < b.size ? -1 : 0;
});
Tested OK in FF, Chrome, and IE10+
If you are targeting evergreen browsers (Chrome, Firefox, Edge, but also works in Safari 9+) or you can afford a polyfill, you can turn the FileList into an array by using Array.from() like this:
let fileArray = Array.from(fileList);
Then it's easy to handle the array of Files like any other array.
Since we are in the HTML5 realm, this is my solution. The gist is that you push the files to an Array instead of leaving them in a FileList, then using XHR2, you push the files to a FormData object. Example below.
Node.prototype.replaceWith = function(node)
{
this.parentNode.replaceChild(node, this);
};
if(window.File && window.FileList)
{
var topicForm = document.getElementById("yourForm");
topicForm.fileZone = document.getElementById("fileDropZoneElement");
topicForm.fileZone.files = new Array();
topicForm.fileZone.inputWindow = document.createElement("input");
topicForm.fileZone.inputWindow.setAttribute("type", "file");
topicForm.fileZone.inputWindow.setAttribute("multiple", "multiple");
topicForm.onsubmit = function(event)
{
var request = new XMLHttpRequest();
if(request.upload)
{
event.preventDefault();
topicForm.ajax.value = "true";
request.upload.onprogress = function(event)
{
var progress = event.loaded.toString() + " bytes transfered.";
if(event.lengthComputable)
progress = Math.round(event.loaded / event.total * 100).toString() + "%";
topicForm.fileZone.innerHTML = progress.toString();
};
request.onload = function(event)
{
response = JSON.parse(request.responseText);
// Handle the response here.
};
request.open(topicForm.method, topicForm.getAttribute("action"), true);
var data = new FormData(topicForm);
for(var i = 0, file; file = topicForm.fileZone.files[i]; i++)
data.append("file" + i.toString(), file);
request.send(data);
}
};
topicForm.fileZone.firstChild.replaceWith(document.createTextNode("Drop files or click here."));
var handleFiles = function(files)
{
for(var i = 0, file; file = files[i]; i++)
topicForm.fileZone.files.push(file);
};
topicForm.fileZone.ondrop = function(event)
{
event.stopPropagation();
event.preventDefault();
handleFiles(event.dataTransfer.files);
};
topicForm.fileZone.inputWindow.onchange = function(event)
{
handleFiles(topicForm.fileZone.inputWindow.files);
};
topicForm.fileZone.ondragover = function(event)
{
event.stopPropagation();
event.preventDefault();
};
topicForm.fileZone.onclick = function()
{
topicForm.fileZone.inputWindow.focus();
topicForm.fileZone.inputWindow.click();
};
}
else
topicForm.fileZone.firstChild.replaceWith(document.createTextNode("It's time to update your browser."));
I have found very quick & short workaround for this. Tested in many popular browsers (Chrome, Firefox, Safari);
First, you have to convert FileList to an Array
var newFileList = Array.from(event.target.files);
to delete the particular element use this
newFileList.splice(index,1);
I know this is an old question but it's ranking high on search engines in regards to this issue.
properties in the FileList object cannot be deleted but at least on Firefox they can be changed. My workaround this issue was to add a property IsValid=true to those files that passed check and IsValid=false to those that didn't.
then I just loop through the list to make sure that only the properties with IsValid=true are added to FormData.
This is extemporary, but I had the same problem which I solved this way. In my case I was uploading the files via XMLHttp request, so I was able to post the FileList cloned data through formdata appending. Functionality is that you can drag and drop or select multiple files as many times as you want (selecting files again won't reset the cloned FileList), remove any file you want from the (cloned) file list, and submit via xmlhttprequest whatever was left there. This is what I did. It is my first post here so code is a little messy. Sorry. Ah, and I had to use jQuery instead of $ as it was in Joomla script.
// some global variables
var clon = {}; // will be my FileList clone
var removedkeys = 0; // removed keys counter for later processing the request
var NextId = 0; // counter to add entries to the clone and not replace existing ones
jQuery(document).ready(function(){
jQuery("#form input").change(function () {
// making the clone
var curFiles = this.files;
// temporary object clone before copying info to the clone
var temparr = jQuery.extend(true, {}, curFiles);
// delete unnecessary FileList keys that were cloned
delete temparr["length"];
delete temparr["item"];
if (Object.keys(clon).length === 0){
jQuery.extend(true, clon, temparr);
}else{
var keysArr = Object.keys(clon);
NextId = Math.max.apply(null, keysArr)+1; // FileList keys are numbers
if (NextId < curFiles.length){ // a bug I found and had to solve for not replacing my temparr keys...
NextId = curFiles.length;
}
for (var key in temparr) { // I have to rename new entries for not overwriting existing keys in clon
if (temparr.hasOwnProperty(key)) {
temparr[NextId] = temparr[key];
delete temparr[key];
// meter aca los cambios de id en los html tags con el nuevo NextId
NextId++;
}
}
jQuery.extend(true, clon, temparr); // copy new entries to clon
}
// modifying the html file list display
if (NextId === 0){
jQuery("#filelist").html("");
for(var i=0; i<curFiles.length; i++) {
var f = curFiles[i];
jQuery("#filelist").append("<p id=\"file"+i+"\" style=\'margin-bottom: 3px!important;\'>" + f.name + "<a style=\"float:right;cursor:pointer;\" onclick=\"BorrarFile("+i+")\">x</a></p>"); // the function BorrarFile will handle file deletion from the clone by file id
}
}else{
for(var i=0; i<curFiles.length; i++) {
var f = curFiles[i];
jQuery("#filelist").append("<p id=\"file"+(i+NextId-curFiles.length)+"\" style=\'margin-bottom: 3px!important;\'>" + f.name + "<a style=\"float:right;cursor:pointer;\" onclick=\"BorrarFile("+(i+NextId-curFiles.length)+")\">x</a></p>"); // yeap, i+NextId-curFiles.length actually gets it right
}
}
// update the total files count wherever you want
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
});
});
function BorrarFile(id){ // handling file deletion from clone
jQuery("#file"+id).remove(); // remove the html filelist element
delete clon[id]; // delete the entry
removedkeys++; // add to removed keys counter
if (Object.keys(clon).length === 0){
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
jQuery("#fileToUpload").val(""); // I had to reset the form file input for my form check function before submission. Else it would send even though my clone was empty
}else{
jQuery("#form p").text(Object.keys(clon).length + " file(s) selected");
}
}
// now my form check function
function check(){
if( document.getElementById("fileToUpload").files.length == 0 ){
alert("No file selected");
return false;
}else{
var _validFileExtensions = [".pdf", ".PDF"]; // I wanted pdf files
// retrieve input files
var arrInputs = clon;
// validating files
for (var i = 0; i < Object.keys(arrInputs).length+removedkeys; i++) {
if (typeof arrInputs[i]!="undefined"){
var oInput = arrInputs[i];
if (oInput.type == "application/pdf") {
var sFileName = oInput.name;
if (sFileName.length > 0) {
var blnValid = false;
for (var j = 0; j < _validFileExtensions.length; j++) {
var sCurExtension = _validFileExtensions[j];
if (sFileName.substr(sFileName.length - sCurExtension.length, sCurExtension.length).toLowerCase() == sCurExtension.toLowerCase()) {
blnValid = true;
break;
}
}
if (!blnValid) {
alert("Sorry, " + sFileName + " is invalid, allowed extensions are: " + _validFileExtensions.join(", "));
return false;
}
}
}else{
alert("Sorry, " + arrInputs[0].name + " is invalid, allowed extensions are: " + _validFileExtensions.join(" or "));
return false;
}
}
}
// proceed with the data appending and submission
// here some hidden input values i had previously set. Now retrieving them for submission. My form wasn't actually even a form...
var fecha = jQuery("#fecha").val();
var vendor = jQuery("#vendor").val();
var sku = jQuery("#sku").val();
// create the formdata object
var formData = new FormData();
formData.append("fecha", fecha);
formData.append("vendor", encodeURI(vendor));
formData.append("sku", sku);
// now appending the clone file data (finally!)
var fila = clon; // i just did this because I had already written the following using the "fila" object, so I copy my clone again
// the interesting part. As entries in my clone object aren't consecutive numbers I cannot iterate normally, so I came up with the following idea
for (i = 0; i < Object.keys(fila).length+removedkeys; i++) {
if(typeof fila[i]!="undefined"){
formData.append("fileToUpload[]", fila[i]); // VERY IMPORTANT the formdata key for the files HAS to be an array. It will be later retrieved as $_FILES['fileToUpload']['temp_name'][i]
}
}
jQuery("#submitbtn").fadeOut("slow"); // remove the upload btn so it can't be used again
jQuery("#drag").html(""); // clearing the output message element
// start the request
var xhttp = new XMLHttpRequest();
xhttp.addEventListener("progress", function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
}, false);
if ( xhttp.upload ) {
xhttp.upload.onprogress = function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
var percent = done / total;
jQuery("#drag").html(Math.round(percent * 100) + "%");
};
}
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var respuesta = this.responseText;
jQuery("#drag").html(respuesta);
}
};
xhttp.open("POST", "your_upload_handler.php", true);
xhttp.send(formData);
return true;
}
};
Now the html and styles for this. I'm quite a newbie but all this actually worked for me and took me a while to figure it out.
<div id="form" class="formpos">
<!-- Select the pdf to upload:-->
<input type="file" name="fileToUpload[]" id="fileToUpload" accept="application/pdf" multiple>
<div><p id="drag">Drop your files here or click to select them</p>
</div>
<button id="submitbtn" onclick="return check()" >Upload</button>
// these inputs are passed with different names on the formdata. Be aware of that
// I was echoing this, so that's why I use the single quote for php variables
<input type="hidden" id="fecha" name="fecha_copy" value="'.$fecha.'" />
<input type="hidden" id="vendor" name="vendorname" value="'.$vendor.'" />
<input type="hidden" id="sku" name="sku" value="'.$sku.'"" />
</div>
<h1 style="width: 500px!important;margin:20px auto 0px!important;font-size:24px!important;">File list:</h1>
<div id="filelist" style="width: 500px!important;margin:10px auto 0px!important;">Nothing selected yet</div>
The styles for that. I had to mark some of them !important to override Joomla behavior.
.formpos{
width: 500px;
height: 200px;
border: 4px dashed #999;
margin: 30px auto 100px;
}
.formpos p{
text-align: center!important;
padding: 80px 30px 0px;
color: #000;
}
.formpos div{
width: 100%!important;
height: 100%!important;
text-align: center!important;
margin-bottom: 30px!important;
}
.formpos input{
position: absolute!important;
margin: 0!important;
padding: 0!important;
width: 500px!important;
height: 200px!important;
outline: none!important;
opacity: 0!important;
}
.formpos button{
margin: 0;
color: #fff;
background: #16a085;
border: none;
width: 508px;
height: 35px;
margin-left: -4px;
border-radius: 4px;
transition: all .2s ease;
outline: none;
}
.formpos button:hover{
background: #149174;
color: #0C5645;
}
.formpos button:active{
border:0;
}
I hope this helps.
Thanks #Nicholas Anderson simple and straight , here is your code applied and working at my code using jquery.
HTML .
<input class="rangelog btn border-aero" id="file_fr" name="file_fr[]" multiple type="file" placeholder="{$labels_helpfiles_placeholder_file}">
<span style="cursor: pointer; cursor: hand;" onclick="cleanInputs($('#file_fr'))"><i class="fa fa-trash"></i> Empty chosen files</span>
JS CODE
function cleanInputs(fileEle){
$(fileEle).val("");
var parEle = $(fileEle).parent();
var newEle = $(fileEle).clone()
$(fileEle).remove();
$(parEle).prepend(newEle);
}
There might be a more elegant way to do this but here is my solution. With Jquery
fileEle.value = "";
var parEle = $(fileEle).parent();
var newEle = $(fileEle).clone()
$(fileEle).remove();
parEle.append(newEle);
Basically you cleat the value of the input. Clone it and put the clone in place of the old one.
If you have the luck to be sending a post request to the database with the files and you have the files you want to send in your DOM
you can simply check if the file in the file list is present in your DOM, and of course if it's not you just don't send that element to de DB.
I realize this is a pretty old question, however I am using an html multiple file selection upload to queue any number of files which can be selectively removed in a custom UI before submitting.
Save files in a variable like this:
let uploadedFiles = [];
//inside DOM file select "onChange" event
let selected = e.target.files[0] ? e.target.files : [];
uploadedFiles = [...uploadedFiles , ...selected ];
createElements();
Create UI with "remove a file":
function createElements(){
uploadedFiles.forEach((f,i) => {
//remove DOM elements and re-create them here
/* //you can show an image like this:
* let reader = new FileReader();
* reader.onload = function (e) {
* let url = e.target.result;
* // create <img src=url />
* };
* reader.readAsDataURL(f);
*/
element.addEventListener("click", function () {
uploadedFiles.splice(i, 1);
createElements();
});
}
}
Submit to server:
let fd = new FormData();
uploadedFiles.forEach((f, i) => {
fd.append("files[]", f);
});
fetch("yourEndpoint", {
method: "POST",
body: fd,
headers: {
//do not set Content-Type
}
}).then(...)
I mix the solutions of many developers and reach to this solution.
It change the original array list after deletion which means if we want to save the images then we can do so.
<script>
var images = [];
function image_select() {
var image = document.getElementById('image').files;
for (i = 0; i < image.length; i++) {
images.push({
"name" : image[i].name,
"url" : URL.createObjectURL(image[i]),
"file" : image[i],
})
}
document.getElementById('container').innerHTML = image_show();
}
function image_show() {
var image = "";
images.forEach((i) => {
image += `<div class="image_container d-flex justify-content-center position-relative">
<img src="`+ i.url +`" alt="Image">
<span class="position-absolute" onclick="delete_image(`+ images.indexOf(i) +`)">×</span>
</div>`;
})
return image;
}
function delete_image(e) {
images.splice(e, 1);
document.getElementById('container').innerHTML = image_show();
const dt = new DataTransfer()
const input = document.getElementById('image')
const { files } = input
for (let i = 0; i < files.length; i++) {
const file = files[i]
if (e !== i)
dt.items.add(file);
}
input.files = dt.files;
console.log(document.getElementById('image').files);
}
</script>
*******This is html code ******
<body>
<div class="container mt-3 w-100">
<div class="card shadow-sm w-100">
<div class="card-header d-flex justify-content-between">
<h4>Preview Multiple Images</h4>
<form class="form" action="{{route('store')}}" method="post" id="form" enctype="multipart/form-data">
#csrf
<input type="file" name="image[]" id="image" multiple onchange="image_select()">
<button class="btn btn-sm btn-primary" type="submit">Submit</button>
</form>
</div>
<div class="card-body d-flex flex-wrap justify-content-start" id="container">
</div>
</div>
</div>
</body>
******* This is CSS code ********
<style>
.image_container {
height: 120px;
width: 200px;
border-radius: 6px;
overflow: hidden;
margin: 10px;
}
.image_container img {
height: 100%;
width: auto;
object-fit: cover;
}
.image_container span {
top: -6px;
right: 8px;
color: red;
font-size: 28px;
font-weight: normal;
cursor: pointer;
}
</style>
You may wish to create an array and use that instead of the read-only filelist.
var myReadWriteList = new Array();
// user selects files later...
// then as soon as convenient...
myReadWriteList = FileListReadOnly;
After that point do your uploading against your list instead of the built in list. I am not sure of the context you are working in but I am working with a jquery plugin I found and what I had to do was take the plugin's source and put it in the page using <script> tags. Then above the source I added my array so that it can act as a global variable and the plugin could reference it.
Then it was just a matter of swapping out the references.
I think this would allow you to also add drag & drop as again, if the built in list is read-only then how else could you get the dropped files into the list?
:))
I solve it this way
//position -> the position of the file you need to delete
this.fileImgs.forEach((item, index, object) => {
if(item.idColor === idC){
if(item.imgs.length === 1){
object.splice(index,1) }
else{
const itemFileImgs = [...item.imgs];
itemFileImgs.splice(position,1)
item.imgs = [...itemFileImgs]
}
}});
console.log(this.fileImgs)
In vue js :
self.$refs.inputFile.value = ''
I just change the type of input to the text and back to the file :D