Looping over an array of objects with mustache - javascript

edit: updated this thread to clarify my question.
I make an ajax call that returns a dataset in json which looks like this:
Everything (including the correct column names) has already been taken care of via DB views so I wanted to write a script that just grabs a dataset and spits it out in a nicely formatted html table. This way the DB's table\view can be changed (columns added and removed) and the code will not have to be updated. I've been trying to get this to work with mustache but there doesn't seem to be a simple way of doing it. In the examples I find of people using mustache with an array of objects they are all explicitly referencing the objects properties in the template. I don't know the number or name of the objects' properties (the dataset's columns) will be a head of time so I can't enter them statically in the template.
Right now I'm using two templates, one for the headers and one just for the table rows:
<script id="datasetTable" type="text/template">
<table class="table table-hover table-bordered">
<thead>
<tr>
{{#headers}}
<th>{{.}}</th>
{{/headers}}
</tr>
</thead>
<tbody>
</tbody>
</table></script>
<script id="datasetTableRows" type="text/template">
<tr>
{{#rows}}
<td>{{.}}</td>
{{/rows}}
</tr>
</script>
And here is how I'm using it:
//Build table headers from dataset's columns
datasetCols = [];
for (var keyName in dataset[0]){
datasetCols.push(keyName);
};
//Build table rows from dataset rows
var renderedTableRows = '';
var tplRows = document.getElementById('datasetTableRows').innerHTML;
datasetLength = dataset.length;
for (var i=0; i<datasetLength; i++) {
var currentRow = dataset[i];
var rowValues = [];
for (var prop in currentRow){
rowValues.push(currentRow[prop]);
}
var renderedHtml = Mustache.render(tplRows, {rows: rowValues});
renderedTableRows += renderedHtml;
}
//render table with headers
var $renderedTable = $(Mustache.render('datasetTable', {headers: datasetCols}));
$renderedTable.find('tbody').html(renderedTableRows);
$(htmlContainer).html($renderedTable);
This works fine, but I really would like to simplify it further by using only one template. Can mustache process this in a more efficient way- without me having to explicitly reference the objects properties' names in the template?
I'd also like to add that I am already using mustache in a bunch of other places (code I don't feel like re-writing with a new engine right now) so if mustache can't do it I'll stick to pure js for the time being.

I've not personally used moustache, but they're all very similar.
Also, since it is logic-less you really want to return a more useful format. I.e an array of arrays would be better in this instance.
[["234", "ddg", "aa"], ["and, so on", "and so on", "and so on"]]
But if you know that there will always be three columns returned, you could do something like:
<table class="table table-hover table-bordered">
<thead>
<th> Whatever your headers are </th>
</thead>
<tbody>
{{#.}}
<tr>
<td>{{col1}}</td>
<td>{{col2}}</td>
<td>{{col3}}</td>
</tr>
{{/.}}
<tbody>
</table>
Or enumerate the object:
<table class="table table-hover table-bordered">
<thead>
<th> Whatever your headers are </th>
</thead>
<tbody>
{{#.}}
<tr>
{{#each dataSet}}
<td>{{this}}</td>
{{/each}}
</tr>
{{/.}}
<tbody>
</table>
Also, when creating HTML in javascript, use an array, it's faster.
var somehtml = [];
somehtml.push('something');
somehtml.push('something else');
somehtml = somehtml.join('');

Related

Get calling table from column option

I have a few Bootstrap table on one page. Each table has some data attributes (like data-link="test-page" and so on). Besides that, one column of each Bootstrap table uses a column formatter, using data-formatter="actionFormatter". However, I want to get the current table data attributes when actionFormatter is called, so based on the data attributes I can return a string.
Both this and $(this) return an Object, which doesn't work. $(this).closest('table').data() doesn't work either, while I expected that one to be the most true.
Here's the code I use:
<th data-field="actions" data-formatter="actionFormatter" data-events="actionEvents">Actions</th>
this returns a JSON object with the row properties, and $(this).closest('table').data(XXX) return undefined. I expected it to return an array with all the data attributes.
Is there any way to get the current processing table from within the formatter?
Example code:
<!-- table 1 -->
<table
data-actions="edit,remove"
data-url="some/url"
>
<thead>
<tr>
<th data-formatter="actionFormatter">Actions</th>
</tr>
</thead>
</table>
<!-- table 2 -->
<table
data-actions="edit,remove"
data-url="some/url"
>
<thead>
<tr>
<th data-formatter="actionFormatter">Actions</th>
</tr>
</thead>
</table>
// actionFormatter:
function actionFormatter(value, row, index, field) {
// get data-actions from the right table somehow,
// and return a string based on data-url/other
// data attributes
}
It seems that when the action formatter is called, the execution context this is an object with all the bootstrap table row associated data as well as all the data-* attributes of the row.
Taking that into account you can add an id to each table and a data-table-id attribute to your rows like:
<table
id="table-1"
data-actions="edit,remove"
data-url="some/url"
>
<thead>
<tr>
<th data-formatter="actionFormatter" data-table-id="table-1">Actions</th>
</tr>
</thead>
so that in your formatter you can retrieve the table DOM Element by using that id:
function actionFormatter(value, row, index, field) {
// get data-actions from the right table somehow,
// and return a string based on data-url/other
// data attributes
var data = $('#' + this.tableId).data();
}

NodeJS: How can I scrape two different tables, that are visually part of the same table, into one JSON Object?

Here's an example of the table of data I'm scraping:
The elements in red are in the <th> tags while the elements in green are in a <td> tag, the <tr> tag can be displayed according to how they're grouped (i.e. '1' is in it's own <tr>; HTML snippet:
EDIT: I forgot to add the surrounding div
<div class="table-cont">
<table class="tg-1">
<thead>
<tr>
<th class="tg-phtq">ID</td>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky">1</td>
<td class="tg-0pky">2</td>
<td class="tg-0pky">3</td>
</tr>
</tbody>
</table>
<table class="tg-2">
<thead>
<tr>
<th class="tg-phtq">Sample1</td>
<th class="tg-phtq">Sample2</td>
<...the rest of the table code matches the pattern...>
</tr>
</thead>
<tbody>
<tr>
<td class="tg-0pky">Swimm</td>
<td class="tg-dvpl">1:30</td>
<...>
</tr>
</tbody>
<...the rest of the table code...>
</table>
</div>
As you can see, in the HTML they're actually two different tables while they're displayed in the above example as only one. I want to generate a JSON object where the keys and values include the data from the two tables as if they were one, and output a single JSON Object.
How I'm scraping it right now is a bit of modified javascript code I found on a tutorial:
EDIT: In the below, I've been trying to find a way to select all relevant <th> tags from both tables and insert them into the same array as the rest of the <th> tag array and do the same for <tr> in the table body; I'm fairly sure for the th I can just insert the element separately before the rest but only because there's a single one - I've been having problems figuring out how to do that for both arrays and make sure all the items in the two arrays map correctly to each other
EDIT 2: Possible solution? I tried using XPath Selectors and I can use them in devTools to select everything I want, but page.evaluate doesn't accept them and page.$x('XPath') returns JSHandle#node since I'm trying to make an array, but I don't know where to go from there
let scrapeMemberTable = async (page) => {
await page.evaluate(() => {
let ths = Array.from(document.querySelectorAll('div.table-cont > table.tg-2 > thead > tr > th'));
let trs = Array.from(document.querySelectorAll('div.table-cont > table.tg-2 > tbody > tr'));
// the above two lines of code are the main problem area- I haven't been
//able to select all the head/body elements I want in just those two lines of code
// just removig the table id "tg-2" seems to deselect the whole thing
const headers = ths.map(th => th.textContent);
let results = [];
trs.forEach(tr => {
let r = {};
let tds = Array.from(tr.querySelectorAll('td')).map(td => td.textContent);
headers.forEach((k,i) => r[k] = tds[i]);
results.push(r);
});
return results; //results is OBJ in JSON format
}
}
...
results = results.concat( //merge into one array OBJ
await scrapeMemberTable(page)
);
...
Intended Result:
[
{
"ID": "1", <-- this is the goal
"Sample1": "Swimm",
"Sample2": "1:30",
"Sample3": "2:05",
"Sample4": "1:15",
"Sample5": "1:41"
}
]
Actual Result:
[
{
"Sample1": "Swimm",
"Sample2": "1:30",
"Sample3": "2:05",
"Sample4": "1:15",
"Sample5": "1:41"
}
]

Optimized plain JavaScript means to iterate over static HTML table, creating array of objects based on its row/columns

Recently I was asked to write some plain ol JS, that would iterate over a static html table, and allow me to sort the data therein respectively to the column. The idea I have currently is something that loops over the rows, getting the cells data. But I feel there has to be something I am overlooking. I feel I can optimize what I have thus far even further. I'm not really big on loops in loops, and the person who asked me this question is convinced it's possible as well. But I'm a little stumped thinking I can whittle it down further.
which what I have thus far is.
let table = document.getElementById( "table" );
let arr = [];
for(let i=1; i < table.rows.length; i++) {
let obj = {};
for(let j=0; j < table.rows[j].cells.length; j++) {
obj[j] = table.rows[i].cells[j].innerText;
}
arr.push(obj);
}
console.log(arr);
Heres the HTML for reference:
<table id="table">
<tr>
<th>ID</th>
<th>Name</th>
<th>Age</th>
</tr>
<tr>
<td>555</td>
<td>Mike</td>
<td>25</td>
</tr>
<tr>
<td>963</td>
<td>Christy</td>
<td>23</td>
</tr>
<tr>
<td>42</td>
<td>Bobby</td>
<td>22</td>
</tr>
</table>
You are using bubble sort, Use quick sort to make it even faster

How to handle an empty result set in tempojs?

The documentation is not quite clear on this, or I may not be understanding how to implement this in the HTML, but how do you handle a template in tempojs that has an empty list/array of items coming from JSON output? Is there a template directive that can be used to display something when the data list is empty (i.e. like the else empty in normal conditional code)?
Here's an example:
Javascript:
$(function() {
/*var data = [
{id:'1',name:'Test One',coordinates:'12.0012,-122.92'}
];*/
var data = [];
Tempo.prepare('userLocs').render(data);
});
HTML:
...
<tbody id="userLocs">
<tr data-template>
<td>{{name}}</td>
<td>{{coordinates}}</td>
<td>Delete</td>
</tr>
<tr data-template-fallback>
<td colspan="3">Javascript is not available.</td>
</tr>
</tbody>
...

how to convert/transform an HTML table tbody (with rowspans) TO json?

I have an HTML table with combined row td's, or how to say, I don't know how to express myself (I am not so good at English), so I show it! This is my table:
<table border="1">
<thead>
<tr>
<th>line</th>
<th>value1</th>
<th>value2</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="2">1</td>
<td>1.1</td>
<td>1.2</td>
</tr>
<tr>
<td>1.3</td>
<td>1.4</td>
</tr>
<tr>
<td rowspan="2">2</td>
<td>2.1</td>
<td>2.2</td>
</tr>
<tr>
<td>2.3</td>
<td>2.4</td>
</tr>
</tbody>
</table>
(you can check it here)
I want to convert this table to a JSON variable by jquery or javascript.
How should it look like, and how should I do it? Thank you, if you can help me!
if you want to convert only text use this one :
var array = [];
$('table').find('thead tr').each(function(){
$(this).children('th').each(function(){
array.push($(this).text());
})
}).end().find('tbody tr').each(function(){
$(this).children('td').each(function(){
array.push($(this).text());
})
})
var json = JSON.stringify(array);
To make a somehow representation of your table made no problem to me, but the problem is how to parse it back to HTML! Here a JSON with the first 6 tags:
{"table":{"border":1,"thead":{"th":{"textContent":"line","tr":"textContent":"value1",...}}}}}...
OR for better understanding:
{"tag":"table","border":1,"child":{"tag":"thead","child":{"tag":"th","textContent":"line",
"child":{"tag":"tr","textContent":"value1","child":...}}}}...
Closing tags are included.
For further explanations I need to know whether your table is a string or part of the DOM.
I belive this is what you want:
var jsonTable = {};
// add a new array property named: "columns"
$('table').find('thead tr').each(function() {
jsonTable.columns = $(this).find('th').text();
};
// now add a new array property which contains your rows: "rows"
$('table').find('tbody tr').each(function() {
var row = {};
// add data by colum names derived from "tbody"
for(var i = 0; i < jsonTable.columnsl.length; i++) {
row[ col ] = $(this).find('td').eq( i ).text();
}
// push it all to the results..
jsonTable.rows.push( row );
};
alert(JSON.stringify(jsonTable));
I think there should be some corrections, but this is it I think.

Categories