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>
...
Related
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"
}
]
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('');
I am using handlebars to compile and get the html from the JS object. Is it possible to transform (html) back to JS object (using handlebars or anyother library)? To be more precise; Using handlebars, I have the following template:
<tr>
<td>{{qty}}</td>
<td>{{rate}}</td>
<td>{{gstPerc}}</td>
<td>{{discountPerc}}</td>
<td>{{amount}}</td>
</tr>
and following JS Object:
{
qty : 12,
rate : 1000,
gstPerc : 10,
discountPerc : 2,
amount: 1500
}
after compilation using handlebars, it gets transform to simple html i.e following, for example.
<tr>
<td>12</td>
<td>1000</td>
<td>10</td>
<td>2</td>
<td>1500</td>
</tr>
Now what I was wondering is, Is it possible (using handlebars), to transform the given HTML back to the object?
give give data-name as qty ,rate etc
var obj = getElementsByTagName('td');
$data = {};
for(var i=0;i<obj.length;i++)
{
$data[obj[i].dataset.name] = obj[i].innerHtml;
}
you can do reverse process you want to populate table with object data
You can use grid control like jqgrid for easy integration
For arbitrary templates and values, this is not possible. Consider the following template:
<td>{{cell1}}</td><td>{{cell2}}</td>
and the following result:
<td></td><td></td><td></td>
Which of cell1, cell2 is empty, and which contains </td><td>?
If you know the HTML inserted is valid and you know the template in advance, this is easy. For your specific template:
var table = document.createElement("table")
table.innerHTML = input
var tds = table.rows[0].cells
return {qty: tds[0].innerHTML, rate: tds[1].innerHTML ...}
If you know the values inserted should be numbers, you can convert them as such:
return {qty: +tds[0].innerHTML, rate: +tds[1].innerHTML ...}
Maybe you could use jQuery and end up with something like this
In your template: add classes to the <td> elements:
<table>
<tr id='someId'>
<td class='qty'>12</td>
<td class='rate'>1000</td>
<td class='gstPerc'>10</td>
<td class='discountPerc'>2</td>
<td class='amount'>1500</td>
</tr>
</table>
Create your object again:
var myObj = {};
$("#someId td").each(function() {
var td = $(this);
myObj[td.attr("class")] = td.text();
});
alert(JSON.stringify(myObj));
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.
I am new to the mustache template library and have the following scenario. I am retrieving both a template and the data from a web server. I am trying to combine the two using the following code:
function formatSearchResults(results)
{
dojo.xhrGet({
url:"search_result_template.mch",
handleAs:"text",
load: function(template)
{
//Load the template into a hidden div on the page
var templateNode = dojo.byId("templateArea")
templateNode.innerHTML = template;
}
});
var templateNode = dojo.byId("templateArea");
var formattedResults;
formattedResults = Mustache.to_html(templateNode.innerHTML,results);
var templateNode = dojo.byId("outputArea");
outputArea.innerHTML = formattedResults;
}
Retrieving the json and the template work fine. But the mustache never renders my data.
I have this json data:
{
"data": [
{
"ColumnValues": {
"_0": "Bubbles",
"_1": "Thompson%20Trucking",
"_2": "A633937432DF91212431251256D350",
"_3": "Eagleton",
"_4": "CA",
"_5": "555-555-5555",
"_6": "12121",
"_7": "Midatlantic",
"_8": "Thompson%20Trucking..<snip>,
"_9": ""
}
}, <snip>
]
}
and this mustache template:
<table id="orgInfo">
<tr>
<th>Search results: <br/><br/></th>
</tr>
<tr>
<td>Organization</td>
<td>City</td>
<td>State</td>
<td>Phone</td>
<td>Region</td>
<td>Industry</td>
<td>Description</td>
</tr>
{{data}}
{{#ColumnValues}}
<tr>
<td>{{_0}}</td>
<td></td>
<td></td>
<td></td>
<td>Region</td>
<td>Industry</td>
<td></td>
</tr>
{{/ColumnValues}}
{{/data}}
and this is the output I get:
{{/data}}
Search results:
Organization City State Phone NAICS Region Industry Description
NAICS Region Industry`
I have been over the docs and examples for hours. Can anybody tell me what I am doing wrong?
Thanks!
Kelvin
I think you need to change
{{data}}
into
{{#data}}
Ok. I now feel like a dope. I was not converting the json text to an object before passing it to the mustache library. All is well now. I hope this helps someone else who makes the same goofy mistake.