I am hearing so much about populating a table. When I look it up at google there is a whole universe of post how to do that. But let's talk about populating a view!
Yes, you heard right! I think it's such as the simplest and most normal way everybody would do the first time.
I want to create a list! In this simple list, I want to show sentences. Something looks like this: https://www.dropbox.com/s/swm1n9ezled0ppd/img.jpg?dl=0
And my idea was it to use for every single item a div, with a title in it and so on. But how would I populate that div with help of javascript? And the most important thing how to set the titles of the divs to a different value?
How to do that via JavaScript or is there another much better way i did not know?
EDIT:
db.collection("classes").doc(data.readGrades).get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
const data = doc.data();
const members = data.members;
var permission_table = document.getElementById("noten_tabelle_permission");
var permission_table_container = document.getElementById("main_padding");
members.forEach(el => {
console.log(el)
table_number++;
clone = permission_table.cloneNode(true); // true means clone all childNodes and all event handlers
clone.id = "layout_table" + table_number;
permission_table_container.appendChild(clone);
db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.id, " => ", doc.data());
const data = doc.data();
//addToTable("grade_table" + table_number, doc.id, data.mdl, data.klu);
//window.alert("Table name: " + ["grade_table" + table_number])
//____________________________________________________________________________
const html = fillTemplate("grade_table" + table_number, "test", data.mdl, data.klu);
// Join the array of html and add it as the `innerHTML` of `main`
document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);
addToTable("grade_table" + table_number, doc.id, data.mdl, data.klu);
});
});
})
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
here is your writte function edited:
function fillTemplate({ table_name, id, subject, mdl, klu }) {
return `
<div class="noten_tabelle_permission" id="noten_tabelle_permission">
<h1 id="member_name">${id}</h1>
<table id="${table_name}" style="width:100%">
<tr>
<th>Fach</th>
<th>mündlich</th>
<th>Klausur</th>
</tr>
<!-- Make content with js code -->
</table>
</div>
`;
}
And here the other function:
function addToTable(table_name, subject, mdl, klu) {
var subject_name = getSubjectByNumber(subject);
var short_subject = getSubjectShortByNumber(subject);
//Zeile erstellen
var y = document.createElement([short_subject]);
y.setAttribute("id", [short_subject]);
document.getElementById([table_name]).appendChild(y);
//Spalten in einer Zeile
var y = document.createElement("TR");
y.setAttribute("id", [short_subject]);
//Spalten in einer Zeile
var cE = document.createElement("TD");
var tE = document.createTextNode([subject_name]);
cE.appendChild(tE);
y.appendChild(cE);
var a = document.createElement("TD");
var b = document.createTextNode([mdl]);
a.appendChild(b);
y.appendChild(a);
var c = document.createElement("TD");
var d = document.createTextNode([klu]);
c.appendChild(d);
y.appendChild(c);
document.getElementById(table_name).appendChild(y);
}
And here the HTML:
<div class='main_has_permission' id="main_has_permission" style="display: none;">
<div class='main_padding' id="main_padding">
<div class="noten_tabelle_permission" id="noten_tabelle_permission">
<h1 id="member_name"></h1>
<table id="grades_table" style="width:100%">
<tr>
<th>Fach</th>
<th>mündlich</th>
<th>Klausur</th>
</tr>
<!-- Make content with js code -->
</table>
</div>
</div>
</div>
Thanks in advance.
~carl
OK, so I was a little bored :) so I cooked up an example to help you out. It contains:
1) An array of objects containing your data. Each object has a title, an array of genres, and a year.
2) It uses map to iterate over the data array and call fillTemplate for each object.
3) fillTemplate returns a completed template literal using each array object's data. Note: because genre is an array of genres we use join to join the array elements together into a list.
4) It uses the CSS flex model to style the HTML.
const data = [{ title: 'Mad Max: Fury Road', genre: ['Action & Adventure'], year: 2015 }, { title: 'Inside Out', genre: ['Animation', 'Kids & Family'], year: 2015 }, { title: 'Star Wars: Episode VII - The Force Awakens', genre: ['Action'], year: 2015 }];
function fillTemplate({ title, genre, year }) {
return `
<section class="panel">
<div class="left">
<div class="title">${title}</div>
<ul class="genres">
<li class="genre">${genre.join('</li><li class="genre">')}</li>
</ul>
</div>
<div class="year">${year}</div>
</section>
`;
}
// For each array object call `fillTemplate` which returns a new
// array element of HTML
const html = data.map(obj => fillTemplate(obj));
// Join the array of html and add it as the `innerHTML` of `main`
document.querySelector('main').innerHTML = html.join('');
main { display: flex; flex-direction: column; }
.panel { width: 60%; display: flex; padding: 0.5em; margin: 0.2em 0 0.2em 0; border: 1px solid #565656; background-color: #efefef; flex-direction: row; align-items: flex-start; }
.genres { margin: 0.3em 0 0 0; padding-left: 0; list-style-type: none; }
.panel div:first-child { width: 100%; }
.genre { display: inline-flex; margin-right: 0.4em; }
.title { font-weight: 600; font-size: 1.1em; }
.year { text-align: right; }
<main></main>
JSFiddle
Related
Problem
I am trying to split some returned data into two separate columns as it displays only as a single column of data by default.
The script is as follows:
console.log(prop.toUpperCase() + ': ', obj[prop]);
const markup = `<li class="list-group-item">${(prop.toUpperCase() + ': ', obj[prop])}</li>`;
document.getElementById("result-list").insertAdjacentHTML('beforeend', markup);
ul {
color: white;
text-align: left;
}
<ul id="result-list" class="list-group"></ul>
Which gives the following in the console:
DESCRIPTION: The Harvester
TYPE: Restaurant
DESCRIPTION: McDonalds
TYPE: Fast food
etc etc...
However, in the front end I get:
The Harvester
Restaurant
McDonalds
Fast food
etc etc...
I would like to maintain the first column (effectively) shown by the console, which would have a DESCRIPTION heading (and hold all descriptions beneath), with the second column heading set as TYPE, which would hold all types beneath, e.g.
What I've tried
I've tried the following in my css, as well as a few other variations, but they don't seem to work...
ul {
columns: 2;
-webkit-columns: 2;
-moz-columns: 2;
color: white;
text-align: left;
}
I think you have an issue in this part const markup = `<li class="list-group-item">${(prop.toUpperCase() + ': ', obj[prop])}</li>`; Please change it to markup += `<li class="list-group-item">${(prop.toUpperCase() + ': '+obj[prop])}</li>`
According to my understanding, my full code is as follow:
let data = [
{"dscripiton": "The Harvester", "type":"Restaurant"},
{"dscripiton": "McDonalds", "type":"Fast food"},
{"dscripiton": "ok", "type":"ok?"}
]
var markup = "";
data.forEach((obj) => {
Object.keys(obj).forEach((prop) => {
console.log(prop.toUpperCase() + ': ', obj[prop]);
markup += `<li class="list-group-item">${(prop.toUpperCase() + ': '+obj[prop])}</li>`;
});
});
document.getElementById("result-list").insertAdjacentHTML('afterend', markup);
I think this is what you've trid. What I've tried
let data = [
{"dscripiton": "The Harvester", "type":"Restaurant"},
{"dscripiton": "McDonalds", "type":"Fast food"},
{"dscripiton": "etc etc", "type":"etc etc"}
]
data.forEach((obj) => {
let el = document.createElement('li');
el.className = "list-group-item";
let arr = [];
for(prop in obj) {
el.innerHTML += `<span style="margin-left: 30px">${obj[prop]}</span>`;
}
console.log(el)
document.getElementById("result-list").insertAdjacentHTML('beforeend', el.outerHTML);
});
Use a table rather than a list.
const data = [
{description: "McDonalds", type: "Fast Food"},
{description: "The Harvester", type: "Restaurant"}
];
data.forEach(({description, type}) => {
const markup = `<tr class="list-group-item"><td>${description.toUpperCase()}</td><td>${type}</td></th>`;
document.getElementById("result-table-body").insertAdjacentHTML('beforeend', markup);
});
#result-table td, #result-table th {
border: 1px solid black;
}
<table id="result-table" class="list-group">
<thead>
<tr>
<th>Description</th>
<th>Type</th>
</thead>
<tbody id="result-table-body">
</tbody>
</table>
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'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>