Building dynamic table with handlebars - javascript

I am trying to create a dynamic table with handlebars.js.
An example of an object I want to put in the table is:
Object { title: "The Room", director: "Tommy Wiseau", genre: "Drama?", rating: "1"}
The HTML I would like to look like:
<thead>
<th>Title</th>
<th>Director</th>
<th>Genre</th>
<th>Rating</th>
<th>Edited</th>
</thead>
<tbody>
<tr>
<td>The Room</td>
<td>Tommy Wiseau</td>
<td>Drama?</td>
<td>1</td>
<td>2017-05-16</td>
</tr>
</tbody>
My problem is that the user can also add their own table headers, so i can't for example just do:
<td>{{title}}</td>
It has to be done dynamically. I have looked at: {{#each}}, but just can't seem to wrap my head around around it. Also, how can I print the property name instead of just the value in an object (like I want in the table headers).
<thead>
{{#each data}}
<th>{{#key}}</th><!--property name here-->
{{/each}}
</thead>
<tbody>
{{#each data}}
<tr>
<td>{{this}}</td><!--property value here-->
</tr>
{{/each}}
</tbody>
This is my template. I feel like I am closing in on the right solution, but I am not sure where I am doing something wrong.
$.getJSON("php/loadItems.php?category=" + selected, function(data) {
let source = $("#itemTemplate").html();
let template = Handlebars.compile(source);
delete data[0].id
$("#tableContainer").append(template(data));
});
This is how I handle the data in my JS. Any help is greatly appreciated!

Based on what you are describing, I think you would have no option other than to build an array of table headers (object keys) by iterating over every object in your array and filtering out all of the duplicates. In other words, you must build an array of all of the unique keys from all of your data objects and these will serve as the column headings in your table. You could use an existing library to help yo do this, like Underscore's .uniq, but I will provide an example implementation:
var uniqueKeys = data.reduce(function (acc, obj) {
return acc.concat(Object.keys(obj).filter(key => acc.indexOf(key) === -1));
}, []);
Next, we must pass our array of unique keys to our template. The unique keys will be used to create the table headings as well as in lookup operations for each object in data.
template({
uniqueKeys: uniqueKeys,
data: data
});
Our template must be updated to the following:
<thead>
<tr>
{{#each uniqueKeys}}
<th>{{this}}</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each data}}
<tr>
{{#each ../uniqueKeys}}
<td>{{lookup .. this}}</td>
{{/each}}
</tr>
{{/each}}
</tbody>
Note that the line {{lookup .. this}} is saying, "render the value on the current object in data at the property with the name of this key in uniqueKeys.
Also note that I added <tr> tags within your <thead>. According to MDN, these are the only permissible tags within a <thead>.
I have created a fiddle for reference.

Would something like this work for what you're trying to accomplish?
{
"titles": [
"The Room",
"Test"
],
"tableObjs": [{
"director": "Tommy Wiseau",
"genre": "Drama?",
"rating": "1"
},
{
"director": "Tommy Wiseau",
"genre": "Drama?",
"rating": "1"
}
]
}
<thead>
{{#each titles}}
<th>{{#key}}</th>
<!--property name here-->
{{/each}}
</thead>
<tbody>
{{#each tableObjs}}
<tr>
<td>{{this.director}}</td>
<td>{{this.genre}}</td>
<td>{{this.rating}}</td>
<!--property value here-->
</tr>
{{/each}}
</tbody>

Related

Iterate over array of objects to populate handlebars template

I am trying to populate a table with data returned from an ajax call. I am using handlebars as the templating engine. I am obviously doing this on the client. I found a lot of questions (here, here and here) asking about populating templates from object arrays. I have tried all of them without success. The last version I tried is as follows.
The template is embedded in a script tag.
<script id="resultTableTpl" type="text/template">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Regd. No.</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{{#each this}}
<tr>
<td>{{id}}</td>
<td>{{regd_id}}</td>
<td>{{type}}</td>
</tr>
{{/each}}
</tbody>
</table>
</script>
And this is the call to populate the template.
<script>
$("#submitSearch").on('click', function(e) {
e.preventDefault();
$.getJSON(...)
.done(function(data) {
template = Handlebars.compile($("#resultTableTpl").html());
$("#myDiv").replaceWith(template(data));
})
.fail(function(){
alert("failure");
});
});
</script>
The JSON data that is obtained from the ajax call is:
[
{id: 123, regd_id: "KA-123", type: "3w"}
, {id: 345, regd_id: "KA-345", type: "4w"}
, {id: 567, regd_id: "KA-567", type: "5w"}
]
This should populate the div myDiv with a table. My test JSON data has an array of three objects. The generated table has empty three rows with three columns. What am I missing? I am using the express flavour of handlebars from exphbs.

Changing or editings Keys in objects, in order to show in view. Using Angular

Example JSON:
[{"name":"John", "date_of_birth":"01/12/1987","marital_status": "Open Relationship"}]
Example view:
<table>
<tr>
<th> Name </th>
<th>Date of Birth</th>
<th>Martial Status</th>
</tr>
<tr ng-repeat= "profile in profiles">
<td>{{profile.name}}</td>
<td>{{profile.date_of_birth}}</td>
<td>{{profile.marital_status}}</td>
</tr>
</table>
What I want is to ng-repeat the keys or table heading too. I know how to put ng-repeat and get keys and values. But what I will like is to change the keys and make them look nice, for e.g : date_of_birth should be Date of Birth, but with ng-repeat on it.
I do agree with the comments in under your question that it is not the best approach. Also, remember that in reality you shouldn't count on a particular order when iterating over object's params (see https://github.com/angular/angular.js/issues/6210).
However ;) in the spirit of solving interesting question…
If you really want to do this, you can do it this way:
<table>
<tr ng-repeat="(key, value) in profiles[0]">
<th>{{ key | snailToHuman }}</th>
</tr>
<tr ng-repeat= "profile in profiles">
<td>{{profile.name}}</td>
<td>{{profile.date_of_birth}}</td>
<td>{{profile.martial_status}}</td>
</tr>
</table>
And create a filter snailToHuman, e.g.:
app.filter('snailToHuman', function () {
return function (snail) {
return snail.split('_').map(function (word) {
word.charAt(0).toUpperCase() + word.slice(1);
}).join(' ');
};
})

Obtain data from Json using angular

Below is my code:
post.json
[
{
"client_id":"100",
"client_name":"MM Hope House",
"user_fname":"Betty",
"user_lname":"Johnson",
"user_id":"10",
"username":"bjohnson",
"total_web":"$500",
"campaigns":{
"campaign":[{
"id":"23",
"campaign_name":"MM Hope House",
"start_date":"4/15/2015",
"end_date":"6/13/2015",
"goal":"$20,000",
"total_pledges":"$1550",
"total_donations":"$1000"
}],
"pledgees":[{
"pledgee_fname":"Tavonia",
"pledgee_lname":"Evans",
"pledge_email":"tavonia#gmail.com",
"pledge_date":"4/22/2015",
"pledge_amount":"$50.00",
"paid":"$50.00",
"last_pledge":"$50.00"
}],
"donors":[{
"donor_fname":"Pat",
"donor_lname":"Smith",
"donor_email":"patsmith#onlinemediainteractive.com",
"total_cdonation":"$10.00",
"total_ldonation":"$450.00",
"last_donation":"$200.00",
"last_pledge":"$350.00"
}]
}
}
]
My HTML code:
<script>
function PostsCtrlAjax($scope, $http) {
$http({method: 'POST', url: 'assets/js/posts.json'})
.success(function(data) {
console.log(data);
$scope.posts = data;
})
.error(function(data, status) {
$scope.posts = data || "Request failed";
});
}
</script>
My HTML code where I want to populate data:
<thead>
<tr>
<th>Campaign ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Amount</th>
<th>Email ID.</th>
<th>Pledge Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="post in posts" >
<td>{{post.id}}</td>
<td>{{post.pledgee_fname}}</td>
<td>{{post.pledgee_lname}}</td>
<td>{{post.pledge_amount}}</td>
<td>{{post.pledge_email}}</td>
<td>{{post.pledge_date}}</td>
I am trying to get client_id pleg_fname from array, but do not know how to.
Your pledgess object is an array of objects so you should do this instead and your entire JSON is contained within an array so you need the first element which will be your entire object and you call it with posts[0] and then you specify the object you are trying to iterate : pledgees.
<tr ng-repeat="post in posts[0].campaigns.pledgees" >
<td>{{posts[0].campaigns.campaign[0].id}}</td>
<td>{{post.pledgee_fname}}</td>
<td>{{post.pledgee_lname}}</td>
<td>{{post.pledge_amount}}</td>
<td>{{post.pledge_email}}</td>
<td>{{post.pledge_date}}</td>
Edit
You're right, I totally missed that array. See this plunker where I tested it http://plnkr.co/edit/dvdnjv2mN2gljQGI9Lls?p=preview
Edit 2
If you want the campaign ID just add this (updated the original code with this change)
{{posts[0].campaigns.campaign[0].id}}
Because of your posts object contains an array of one element, you should use posts[0] to get the first (and the only one) element. Moreover, your pledgees array is inside a campaigns object, so posts[0] become posts[0].compaigns.pledgees.
You should try this :
<tr ng-repeat="post in posts[0].campaigns.pledgees" >
<td>{{post.id}}</td>
<td>{{post.pledgee_fname}}</td>
<td>{{post.pledgee_lname}}</td>
<td>{{post.pledge_amount}}</td>
<td>{{post.pledge_email}}</td>
<td>{{post.pledge_date}}</td>
</tr>
EDIT:
And, if you want the id from the campagin object at the same index than your pledgee item, you have to add track by $index to posts[0].campaigns.pledgees. So it's become posts[0].campaigns.pledgees track by $index.
So, now with $index you get the right index to get the right campaign ID in the campaign object with {{posts[0].campaigns.campaign[$index].id}}
And the result is :
<tr ng-repeat="post in posts[0].campaigns.pledgees track by $index" >
<td>{{posts[0].campaigns.campaign[$index].id}}</td>
<td>{{post.pledgee_fname}}</td>
<td>{{post.pledgee_lname}}</td>
<td>{{post.pledge_amount}}</td>
<td>{{post.pledge_email}}</td>
<td>{{post.pledge_date}}</td>
</tr>
You can see the result here : http://jsfiddle.net/Fieldset/y1asxm61/1/

AngularJS - Using variable name in JSON selector

I have a table using angularjs where I want to loop through an array to print specific headers from a json object. The header prints out fine, but the problem comes when I try to use a variable from my nested ng-repeat as a json selector. If you replace the inner ng-repeat with the commented section below it, it will work.
Table:
<table>
<thead>
<th ng-repeat="column in tableHeader">{{column}} <a ng-click="sort_by(column);"><i class="glyphicon glyphicon-sort"></i></a></th>
</thead>
<tbody>
<tr ng-repeat="data in filtered>
<td ng-repeat="column2 in tableHeader">{{data.column2}}</td>
<!-- <td>{{data.Environment}}</td>
<td>{{data.HostIP}}</td>
<td>{{data.ServiceName}}</td>
<td>{{data.Status}}</td>
<td>{{data.StartTime}}</td>
<td>{{data.Capacity}}</td>
<td>{{data.Txn}}</td>
<td>{{data.Errors}}</td>
<td>{{data.Build}}</td>
<td>{{data.Project}}</td>
<td>{{data.Author}}</td>
<td>{{data.ModifyDate}}</td>
<td>{{data.Port}}</td>
<td>{{data.BasePath}}</td> -->
</tr>
</tbody>
</table>
Array located in controller:
$scope.tableHeader = ['Environment', 'HostIP', 'Status', 'ServiceName', 'StartTime', 'Capacity', 'Txn', 'Errors', 'Build', 'Project', 'Author', 'ModifyDate', 'Port', 'BasePath'];
I think you're looking for {{data[column2]}}. Since column2 is just the string value of the property you want, treat data like an associative array in this case to get the property you're trying to display.
column2 was created by the ng-repeat and is what you want. Note {{column2}}:
<td ng-repeat="column2 in tableHeader">{{column2}}</td>

Ember double each loop. Using value of inner each loop variable to bind to property named the same in the variable of the outer each loop

To elaborate on the title, what I am trying to achieve is the following.
I am building an interactive table component in Ember. Here is the stripped template:
<table>
<thead>
<tr>
{{#each header in headers}}
<th>{{header}}</th>
{{/each}}
</tr>
</thead>
<tbody>
{{#each row in rows}}
<tr>
{{#each header in headers}}
<td>{{input value=row.[header]}}</td> <!-- can I bind to row.header here somehow? -->
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
I want each input field for a specific row and specific column, to be bound to that row's row object, specifically to a property named the way the header column is named.
Essentially, I want to use the value of the header variable to bind a property in the row object called by that value (If current header has value 'title' then I want to bind to row.title)
Here is an example of how I initialize these objects:
var headers = ['title','description'];
var rows = [],
row = {};
for(var i = 0; i < headers.length; i++) {
row[headers[i]] = ''; // this line does similar thing to what I am trying to achieve
}
rows.push(row);
/* This gives
rows = [
{
title: '',
description: ''
}
]
*/
After researching, I found this in the Handlebars documentation that says I can access properties like this:
{{#each articles.[10].[#comments]}}
...
{{/each}}
Which is, according to the docs, pretty much the same as:
articles[10]['#comments']
However, using:
rows.[header]
doesn't work for me because it tries to literally access the 'header' property of the rows object (i.e. rows.header) and not the value contained in the header variable.
You can always extend the textfield component and at least get the value you are looking for.
App.DynamicInputComponent = Ember.TextField.extend({
row: null,
col: null,
value: function(){
var row = this.get('row');
var col = this.get('col');
return row[col];
}.property('row', 'col')
});
Then, in your template you can do:
<table>
{{#each item in model}}
<tr>
{{#each col in columns}}
<td> {{ dynamic-input type='text' row=item col=col }} </td>
{{/each}}
</tr>
{{/each}}
</table>
(Partially) working solution here
This functionality can now easily be achived in Ember (since 1.13) using the new and awesome inline helpers mut and get:
The way to achieve this is in two basic steps:
Use get to dynamically lookup a property from r named as whatever the value of h is at that each iteration. For example, if h = 'title', then this would return r['title'].
Use mut to specify that this extracted value is mutable by our input component (specifically its value property).
This is how the whole each looks:
{{#each rows as |r|}}
<tr>
{{#each headers as |h|}}
<td>
<input onkeyup={{action (mut (get r h)) value="target.value" }}>
</td>
{{/each}}
</tr>
{{/each}}
Detailed example on Ember Twiddle

Categories

Resources