NG-Table not working with nested data structure - javascript

I am trying to use the NG-Table library to display data to users in a web-app I'm creating, but it seems that the built in functions are not able to sort data structured like this:
var data = [
{
generalInfo:{
name: "Morty",
age: 20
}
},
{
generalInfo:{
name: "Ricky",
age: 20
}}];
The actual database I am using requires the data to be structured like this, so I can not change the structure. I tried changing my HTML to match this format, but the filter element does not allow dot notation.
My html looks like this:
<div ng-controller="appController as vm">
<table ng-table="vm.tableParams" class="table table-bordered table- striped table-condensed" show-filter="true">
<tr ng-repeat="user in $data">
<td title="'Name'" filter="{ generalFeatures.name: 'text'}" sortable="'generalFeatures.name'">{{user.generalFeatures.name}}</td>
<td title="'Age'" filter="{ generalFeatures.age: 'number'}" sortable="'generalFeatures.age'"> {{user.generalFeatures.age}}</td>
</tr>
</table>
</div>
Is there any way for me to sort data structured this way?

I found a temporary solution to this problem. I could create new properties in the parent object by looping through all of the properties in the nested object and adding them to the parent.
angular.forEach(data, function (user) {
if(user.generalInfo != null){
user.age = user.generalInfo.age;
user.name = user.generalInfo.name;
}
});

Related

ng repeat array in array

I am trying to repeat a child array of a multidimensional array with ng repeat in Angular.
My json object is this:
$scope.items = [{ "id":1,
"BasisA":"1",
"Basis":true,
"personSex":"m",
"isCollapsed":false,
"name":"Mark Polos",
"age":"1955",
"results":[{"1000":{"company_name":"***","model":"***","modelname":"***","pr":222,"rating":4.5,"priority":9,"matching":1},
"1001":{"company_name":"***","model":"***","modelname":"***","pr":228.7,"rating":5.7,"priority":7,"matching":2},
"1002":{"company_name":"***","model":"***","modelname":"***","pr":241.7,"rating":1.9,"priority":4,"matching":3}
}]
}]
Itried somthing like this:
... data-ng-repeat="item in items">
And then in the table of this child:
<tr data-ng-repeat="i in item | orderBy:'insItem.pr'">
It doesn't look like that results property is actually an "array." If that's just a typo in your example, then disregard. If not ... read on.
It looks like an array with a single item, and that Item is a set of properties which are, in turn, objects. In other words, you would reference the property "pr" for the result named "1000" by with code that looks like item.results[0]["1000"].pr NOT with code that looks the way your ng-repeat is expecting(item.results[0].pr).
Can you transform your items when you get them so that results is a true array?
OR - can you use a function inside of your controller that returns the array you are looking for?
View Code:
<... data-ng-repeat="result in resultsFromItem(item)" >
Controller Code:
$scope.resultsFromItem = function (item) {
if(item==undefined || item.results==undefined || item.results.length==0) {
return [];
}
var myResults = [];
for (var key in item.results[0]) {
if(item.results[0].hasOwnProperty(key)) {
myResults.push(item.results[0][key]);
}
}
return myResults;
}
You might even decide to hang that "transformed" results object off each item object (so you only have to go through the transform one time) if you wanted to.
You should access to the results field:
... data-ng-repeat="item in items">
<tr data-ng-repeat="i in item.results">
Since the nested array is in the results property of the main object.
I used three nested ng-repeat directives to get this rolling :-) The third ng-repeat uses ng-repeat="(key, value) in result" functionality to display all result object keys and values, which I got working with the help of this answer on how to iterate over keys and values in ng-repeat. The orderBy: part isn't yet working (if someone knows how to implement that then any help is welcomed).
<ul>
<li ng-repeat="item in items">
id: {{item.id}}, name: {{item.name}}, age: {{item.age}}, results:
<table>
<tr ng-repeat="result in item.results">
<td>
<table style="border: 1px solid black;">
<tr ng-repeat="(key, value) in result | orderBy: value.pr">
<td> {{key}} </td> <td> {{ value }} </td>
</tr>
</table>
</td>
</table>
</li>
</ul>
Plunker

Looping over an array of objects with mustache

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('');

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>
...

HTML not updating after successful use of $filter(orderBy) in custom directive

I'm creating a custom directive called ngSortable that can use the orderBy filter on an array when the element is clicked. I've stepped through and confirmed the array is being sorted correctly, but the view is not updating. The filter is wrapped in scope.$apply(), so I'm not sure why it doesn't update. I've also put a scope.$watch on the array to see if the change was being broadcast and the $watch doesn't catch anything. I'm using jquery click event listener if that makes any difference. If anyone can help me figure out what I'm doing wrong here I would greatly appreciate it.
HTML
<table class="table table-bordered table-condensed">
<thead>
<tr>
<th ng-sortable="policyAcordTypes" enable-multiple="true" data-field="description" style="width: 40%;">Acord Type</th>
.....
</tr>
</thead>
<tbody>
<tr ng-repeat="item in policyAcordTypes" >
<td style="width: 40%; min-width: 140px">
<div>{{item.description}}</div>
</td>
....
</tr>
</tbody>
Javascript
scope.$apply(function () {
$filter('orderBy')(scope.$eval(attr.ngSortable), 'description');
});
And I've tried this...
Javascript
scope.$apply(function () {
var list = scope.$eval(attr.ngSortable);
list = $filter('orderBy')(scope.$eval(attr.ngSortable), 'description');
});
$filter('orderBy') does not modify the passed in array, it returns a new ordered array. You need to assign the returned array back to the scope's property.
Instead of using scope.$eval(attr.ngSortable), you should use [] operator (standard javascript)
Try:
scope.$apply(function () {
scope[attr.ngSortable] = $filter('orderBy')(scope[attr.ngSortable], 'description');
});
DEMO (clicking on the header will sort the row)

HTML to JS Object

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));

Categories