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>
Related
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 just got this code working after hours of stress. I am new to Javascript so I am not sure if I did this the most efficient way possible. I am using an API that is provided by IEX. The goal of this code is to output out news when there is some. This isn't completely working as you can tell, but I did get the headline to work. So if I am doing anything wrong let me know please.
<html>
<head>
<style>
/* Outter Table <Tbody> Settings*/
.outtertable tbody {
vertical-align: top;
}
/* Innertable Table data settings */
.innertable tr > td {
vertical-align: top;
}
/* Div Article Holder Settings */
.divBorder {
margin-bottom: 10px;
border: solid;
border-color: #c4ef8b;
border-width: 4px 0px 0px 0px;
}
/* Article Image settings */
.articleImg {
height:50px;
width: 50px;
}
/* DivBorder Mouse Hover */
.divBorder:hover {
cursor: pointer;
background-color: #f3ffe5;
}
</style>
</head>
<body>
<table class="outterTable" id="newsContent"></table>
</body>
<script>
var request = new XMLHttpRequest();
request.open ('GET', 'https://api.iextrading.com/1.0/stock/spy/news')
//on request load
request.onload = function() {
//VARIABLES
var newsContainer = document.getElementById("newsContent");
var JSONData = JSON.parse(request.responseText);
var articleAmount = JSONData.length;
var rowAmount = articleAmount / 3;
var rowAmountRoundDown= Math.trunc(rowAmount);
var rowAmountRoundUp = (Math.trunc(rowAmount) + 1);
var remainder = (rowAmount - Math.floor(rowAmount)).toFixed(2); //.00, .67, or .33;
//=== TABLE CREATOR =============================================
//Create an "<tbody>" element
let tbodyHTML = document.createElement('tbody');
//"Assembler" inside is "createTable()"
tbodyHTML.innerHTML = createTable();
//FUNCTION Create Table
function createTable() {
var tData = '';
var index = 0;
//========= First Table Part Row Loop ===========================================================
for (var i = 1; i <= rowAmountRoundDown; i++) {
//Row Start
tData = tData + `
<tr>
`;
//Content: <td> <div> <table> <tr> <td>
for (var c = 1 + index; c < 4 + index; c++) {
tData = tData + `
<td style="width: 33.33%; padding: 0px 25px">
<div class="divBorder">
<table class="innerTable">
<tbody>
<tr>
<td>
<img class="articleImg" src="images/seeking-alpha-badge.png" id="image${c}">
</td>
<td style="padding-left: 5px">
<h3 id="headline${c}"></h3>
</td>
</tr>
</tbody>
</table>
</div>
</td>
`;
}
//Row End
tData = tData + `
</tr>
`;
index = index + 3;
}
//========= Second table part =====================================================================
//If remainder is .67 create 2 <td>
if (remainder == 0.67) {
//Row Start
tData = tData + `
<tr>
`;
//Content: <td> <div> <table> <tr> <td>
for (var c2 = (1 + index); c2 < (3 + index); c2++){
tData = tData + `
<td style="width: 33.33%; padding: 0px 25px">
<div class="divBorder">
<table class="innerTable">
<tbody>
<tr>
<td>
<img class="articleImg" src="images/seeking-alpha-badge.png" id="image${c2}">
</td>
<td style="padding-left: 5px">
<h3 id="headline${c2}"></h3>
</td>
</tr>
</tbody>
</table>
</div>
</td>
`;
}
//row End
tData = tData + `
</tr>
`;
//If remainder is .33 create 1 <Td>
} else if (remainder == 0.33) {
//Row Start
tData = tData + `
<tr>
`;
//Content: <td> <div> <table> <tr> <td>
for (var c = (1 + index); c < (2 + index); c++){
tData = tData + `
<td style="width: 33.33%; padding: 0px 25px">
<div class="divBorder">
<table class="innerTable">
<tbody>
<tr>
<td>
<img class="articleImg" src="images/seeking-alpha-badge.png" id="image${c}">
</td>
<td style="padding-left: 5px">
<h3 id="headline${c}"></h3>
</td>
</tr>
</tbody>
</table>
</div>
</td>
`;
}
//row End
tData = tData + `
</tr>
`;
//Anything else dont do anything
} else {
tData = tData;
}
return tData;
}
//Inject into the HTML
newsContainer.appendChild(tbodyHTML);
//===============================================================
var red = (JSONData.length + 1)
console.log(red);
//Output data to html
for (var l = 1; l < red; l++){
console.log("l: " + l);
spyOutputToHTML(JSONData, l);
}
};
function spyOutputToHTML(data, i) {
//get current variables in this HTML page x3
var offset = i - 1;
var headline = document.getElementById(`headline${i}`);
//Get Content From the JSON file ex: ".latestPrice"
var JSONHeadline = data[offset].headline;
//Inject data into HTML
headline.innerHTML = JSONHeadline;
}
request.send()
</script>
First of all, great job! This is impressive work for a newbie in javascript
Few things could definitely be improved, but I don't see you're doing anything wrong. Maybe, only the logic with remainder is too confusing. I bet there should be an easier way
Readability
Your code would be undoubtedly easier to read and understand if you had the view (templating) logic, the request logic, and the data "massaging" logic separated
The view logic
Generally, constructing HTML structures "by hand" (createElement, appendChild) takes more effort and is, arguably, more confusing as opposed to rendering a string with a template function (kind of like you did) and injecting the result where you need it. Mixing these approaches is even more error-prone than doing everything "by hand". So, I would suggest you have one view/ template function that would take data and return a string
function renderTable(data) {
var result = '<div>';
// build the result string...
result += '</div>';
return result;
}
// and then...
targetEl.innerHTML = renderTable(data);
You may also want to leverage micro-templating. One or another kind of templating would be a must-have for a larger application. Make yourself familiar with templating engines. For your project, building strings with javascript is fine. Although, there is a more advanced technique you can consider
The data "massaging" logic
Well, that comes down to having your template function not being "smart" about its context (basic separation of concerns), and only consuming the data. Not "cooking" it, only eating it :)
So, instead of doing this
function renderTable(data) {
var result = '<div>';
var something = data.abc * data.xyz;
// do something with something here
result += '</div>';
return result;
}
targetEl.innerHTML = renderTable(data);
... you do this
function adaptResponsePayloadData(data) {
var result = { ...data };
result.something = result.abc * result.xyz;
return result;
}
function renderTable(data) {
// ...
}
targetEl.innerHTML = renderTable(adaptResponsePayloadData(data));
This is an example of the so-called Adapter design pattern. This is the right case for using it. There are many other design patterns and I strongly recommend to dedicate time to make yourself familiar with them
The request logic
Another concern separation here. You can have the request logic in a separate function, similarly how we separated "massaging" from the view above
const API_ENDPOINT_URL = 'https://api.iextrading.com/1.0/stock/spy/news';
function fetchData(callback) {
var request = new XMLHttpRequest();
request.open('GET', API_ENDPOINT_URL);
request.onLoad(() => {
var data = JSON.parse(request.responseText);
callback(data);
});
request.send();
}
// ... and then
fetchData(data => {
// ...
targetEl.innerHTML = renderTable(adaptResponsePayloadData(data));
});
Note on execution order
There is the rule of thumb to make it clear when your code is run. This is totally a nit-pick, but it is possible to separate what from when on the code level
function goOn() { // there are many conventional names for this, like `main`
fetchData(data => {
document.body.innerHTML = renderTable(adaptResponsePayloadData(data));
});
}
window.onload = goOn;
Notes on HTML and CSS
You don't really need <tbody>. It is not needed unless you want to highlight something with CSS
Avoid using inline styles, like <td style="width: 33.33%; padding: 0px 25px">. You can express that with CSS
You don't need the divBorder class. Add padding to the parent td and border to the child table
Other minor notes
Conventionally, names with the first capital letter are object constructors or classes. Simply, make regular var names lowerCamelCase, like jsonHeadline
JSON is a term for notation we know. When we parse a string of that notation, it simply becomes data or contextData, you got it... Then, what's inside that data becomes simply headline
Do your best at naming variables so that you don't have to write comments to understand what you meant. Find other good tips here
Make sure your production code has no console.log statements
let keyword is safer than var. You'd never pollute the global scope with it
Please mind that there is Code Review on StackExchange where you can learn many other aspects of how to write great code
You did really great. Good luck to you on your journey! :)
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
Here i have an initial table which i have created using rcm.create() method.Then i have to create another in which rows will will be ordered according to the descending summation of the rows from first table.That means the row having higher sum will be placed first in second table.For creating the second table i have rcm.generateTab2() method .Which works as following;
1.call rcm.create() method to create the second table.
2.create the sum of each rows from first table and push it inside a rank array which contains an array of objects.
3.rank array is sorted according to descending value
now rank array contains object with three element.
first td value from each row.
sum of rows
and the complete row which will be used to insert rows in tbody of second table
4.tbody element from second table is deleted.
5.then created a new one and attempted to insert sorted rows form table 1 to table 2.
but all i am getting is table 2 is pushed above table 1 in browser and no rows are inserted.
full code : jsfiddle
main problem is inside rcm.generateTab2 method.So i am posting it here separately .
rcm.generateTab2 method:
generateTab2:function(){
var power=0;
this.create(machine,process); //create the second table
var tbody=document.getElementsByTagName('tbody')[0];
var trow=tbody.getElementsByTagName('tr');
for(i=0;i<trow.length;i++){ //get summation
var td=trow[i].getElementsByTagName('td');
var sum=0;
for(j=td.length-1;j>0;j--){
if(td[j].innerHTML==''){
sum+=0*Math.pow(2,power);
}else{
sum+=parseInt(td[j].innerHTML)*Math.pow(2,power);
}
power++;
}
var first=parseInt(td[0].innerHTML);
rank.push({rowNo:first,sum:sum,row:trow[i]}); //pushed to rank array
power=0;
}
rank.sort(function (a,b){ //rank array is sorted
if(a.sum>b.sum){
return -1;
}else if(a.sum<b.sum){
return 1;
}else{
return 0;
}
});
console.log(rank);
var parent=document.getElementsByTagName('table')[1];
parent.removeChild(parent.childNodes[1]);//delete existing tbody from second table
var newTbody=document.createElement('tbody'); //create a new tbody
parent.appendChild(newTbody); //append it to second table
for(i=0;i<rank.length;i++){
newTbody.appendChild(rank[i].row); //insert rows to tbody of second table
}
}
Not sure if I've understood the ranking math correctly.
Please have a look at the demo below and here at jsfiddle.
I've re-coded your js because I've thought that's easier. (But if you don't like using jQuery, I could have a look at your code and check if I can find the issue.)
I'm using these js libs:
jQuery for DOM manipulation
Underscore for array creation with _.range (could be also done with a for loop, so Underscore is not really needed)
Tinysort jQuery plugin for sorting the table
For the sorting I've added the sorting rank (sum of the row) as data attribute to each row so tinysort can use this to order the table.
The CSS here at SO is a bit different then at jsFiddle (not centered text) in table header. Not sure why.
The default values (3 & 2) in the form inputs is just for easier debugging. Just remove the value attribute from the inputs later.
Update 07.04.2015
I've found the issue with your code. The problem was that you've stored the reference to table1 inside your rank object. The tr elements in the object.
So you've overriden the table1 because of that reference.
You can fix this with using rank[i].row.cloneNode(true) to clone the contents of the row. Then you can append it to your new table with-out the problem.
See the updated fiddle here.
var ROC = {
init: function (element) {
this.$el = $(element);
this.$table1wrap = $('<div/>').attr('id', 'table1wrapper');
this.$table2wrap = $('<div/>').attr('id', 'table2wrapper');
this.$el.append([this.$table1wrap, this.$table2wrap]);
},
create: function (machine, process) {
var self = this,
$tableHeading = $('<tr/>'),
$table = $('<table/>').attr('id', 'mainTable');
this.$table1wrap.html($table.append($('<thead/>').html($tableHeading)));
this.processes = this.createCols(process);
this.machines = this.createRows(machine);
//var addRow = function() {
// this.$el.append($('tr').html(this.processes));
//this.$el.append($('<tr/>').html($('<td/>').text('test')));
$(this.machines).each(function (index, row) {
//console.log(index, $(row));
var $curRow = $(row);
//console.log($tableHeading.length);
$(self.processes).each(function (i, col) {
if (index == 0) {
var letter = String.fromCharCode(97 + i).toUpperCase();
if (i == 0) $tableHeading.append($('<th/>').text('~'));
$tableHeading.append($('<th/>').text(letter));
}
//console.log(i, $(col));
// self.$el.append(($(row).clone()).html($(col).clone()));
if (i == 0) $curRow.append($('<td/>')
.text(index + 1)
.addClass('rowIndex'));
$curRow.append($(col).attr('contentEditable', 'true'));
});
$table.append($curRow.clone());
});
//console.log(this.processes, this.machines);
},
createCols: function (cols) {
var rCols = _.range(cols).map(function (num, index) {
return $('<td/>').text(0);
}); // [td, td, ...];
return rCols;
},
createRows: function (rows) {
var rRows = _.range(rows).map(function (num, index) {
return $('<tr/>');
}); // [tr, tr, ...];
return rRows;
},
copy: function (sel) {
//console.log($(sel));
var $newTable = $(sel).clone().attr('id', 'copy');
var $sortedBody = $($newTable)
.find('tbody')
.html(this.calcRank($newTable));
//console.log($sortedBody, this.calcRank($newTable));
//console.log('sorted', $sortedTable);
$(this.$table2wrap).html($($newTable, 'tbody').append($sortedBody));
},
calcRank: function (newTable) {
var sum, $col;
newTable.find('tr').each(function (index, item) {
//console.log(index, $(item).children());
$col = $(item).children();
sum = 0;
if (index > 0) { // skip heading
$col.each(function (i, cell) {
if (i > 0) sum += parseInt($(cell).text()); // skip first col
});
$(item).attr('data-rank', sum);
}
//console.log(index, sum, $(item));
//$(item).attr('data-rank', sum);
});
//console.log($(newTable));
return tinysort($(newTable).find('tbody>tr'), {
attr: 'data-rank',
order: 'desc'
});
},
reset: function () {
this.$table1wrap.empty();
this.$table2wrap.empty();
}
};
ROC.init('#out');
$('#btnCreate').click(function () {
var proc = $('#process').val(),
machine = $('#machine').val();
ROC.create(machine, proc);
});
$('#btnCreate2').click(function () {
ROC.copy('#mainTable');
});
$('#btnRst').click(function () {
ROC.reset();
});
body {
padding: 1em;
}
input[type='number'] {
background:lightblue;
color:crimson;
margin-left:20px;
}
table {
border-collapse: initial !important;
border-spacing: 10px !important;
}
th {
background:black;
color:white;
width:40px;
height:40px;
border:1px solid white;
text-align:center;
box-shadow:0px 0px 7px black;
}
td {
box-shadow:0px 0px 7px black;
background:white;
width:40px;
height:40px;
border:1px solid black;
text-align:center;
}
td.rowIndex {
background: black;
color: white;
}
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinysort/2.1.1/tinysort.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>Rank Order Clustering</h1>
<fieldset>
<legend style='font-size:30px;background:lightblue;'>insert items</legend>
<label for='process'>process :</label>
<input type='number' id='process' placeholder='processes' value="3" />
<br/>
<label for='machine'>machines :</label>
<input type='number' id='machine' placeholder='machines' value="2" />
<br/>
<input type='button' value='create table' id='btnCreate' />
<input type='button' value=' reset' id='btnRst' />
<input type='button' value='generate table2' id='btnCreate2' />
</fieldset>
<div id="out"></div>