How to create a table with indents from nested JSON in angularjs - javascript

I get a nested JSON object back from an API call that looks something along the lines of this:
{
"name": “Main “Folder”,
"children": [
{
"name": “Child Folder 1”,
"children": []
},
{
"name": “Child Folder 2”,
"children": [
{
"name": “Sub Folder 1”,
"children": [
{
“name”: “Sub Sub Folder 1”,
“children”: []
}
]
},
{
"name": “Sub Folder 2” ,
"children": []
}
]
}
]
}
There is no limit on how far the JSON object can be nested so that is unknown to me. I need to have all of the children of the folders to be indented under the parent in the table. I'm not really even sure how to go about doing this. The first thing I tried was doing something like this in my HTML file, but I quickly realized it wasn't going to work.
folders.html
<table>
<thead>
<tr><strong>{{ this.tableData.name }}</strong></tr>
</thead>
<tbody ng-repeat="b in this.tableData.children">
<tr>
<td>{{ b.name }}</td>
<td ng-repeat="c in b.children">{{ c.name }}</td>
</tr>
</tbody>
</table>
folders.js
export default class FoldersController {
constructor($rootScope, $scope, $uibModal) {
this.tableData = {Example Data from top}
}
}
Is there a not too complicated way to go about doing this? Thanks!

You should create a component with a template that contains a table, then you can nest your component inside itself to follow the tree structure logical path:
Your root controller should contain your table data:
angular.module('app').controller('RootCtrl', ['$scope', function($scope) {
// assigning the data to $scope to make it available in the view
$scope.tableData = {Example Data from top};
}]);
Your tree component could be something on this lines:
angular.module('app').component('treeComponent', {
controller: 'TreeCtrl',
bindings: {
tree: '<',
},
templateUrl: 'tree-view.html'
});
your root template should load the first instance of the component:
<div>
<tree-component tree="tableData"></tree-component>
</div>
then the component template should take care of the the recursion when required;
tree-view.html:
<table class="record-table">
<thead>
<tr>
<th>
<strong>{{ $ctrl.tableData.name }}</strong>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="node in $ctrl.tableData.children">
<td>{{node.name}}</td>
<td ng-if="node.children.length > 0">
<tree-component tree="node.children"></tree-component>
</td>
</tr>
</tbody>
</table>
creating indentation then becomes easy using basic css:
.record-table .record-table {
padding-left: 20px
}

I was able to figure out a solution of my own using recursion in the js file. I implemented mindthefrequency's answer as well and it seems to be working just fine. I'm marking it as the best answer because it seems to be the cleaner solution, but I'm posting what I have in case someone wants to take a more js oriented approach.
First, in the js file, use recursion to add all of the nodes and how far each needs to be indented to the table data variable.
folders.js
export default class FoldersController {
constructor($rootScope, $scope, $uibModal) {
this.resp = {Example Data from top}
this.tableData = []
this.createTable(this.resp.children, 0, [
{
name: this.resp.name,
indent: '0px',
},
]);
}
createTable(children, count, data) {
count += 1;
// base case
if (!children || children.length === 0) {
return;
}
for (const child of children) {
const { name } = child;
const returnData = data;
returnData.push({
name: name,
indent: `${count * 25}px`,
});
this.tableData = returnData;
this.createTable(child.children, count, returnData);
}
}
}
Then, in the html file, use angularjs to properly indent each node
folders.html
<table>
<thead>
<tr><strong>Table Header</strong></tr>
</thead>
<tbody ng-repeat="b in vm.tableData">
<tr>
<td ng-style="{'padding-left': b.indent}">{{ b.name }}</td>
</tr>
</tbody>
</table>

Related

how to pass json data into query string and use that data in another page

Here table contains book details which contains book name, author, pric, ISBN and category. When user click on Book Name it should pass the data to another page using querystring
<script type="text/javascript" src="book.js">
<body ng-app="mymodule" >
<div ng-controller="myController" >
<table border=2>
<thead>
<tr>
<th>ISBN</th>
<th>NAME</th>
<th>AUTHOR</th>
<th>CATEGORY</th>
<th>PRICE</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="book in books">
<td>{{ book.ISBN }}</td>
<td >{{ book.Name }}</td>
<td>{{ book.Author }}</td>
<td>{{ book.Category }}</td>
<td>{{ book.price }}</td>
</tr>
</tbody>
</table>
books.js
var myapp = angular.module('mymodule', []);
myapp.controller("myController", function($scope, $http,$window) {
$http.get("https://api.myjson.com/bins/p4ujn").then(function(response) {
$scope.books = response.data;
$scope.getdetail=function(){
$scope.getbookdetail=this.book;
$window.location.href = "orderpage.html";
}
});
});
orderpage.html
<script type="text/javascript" src="book.js"></script>
<body ng-app="mymodule" >
<div ng-controller="myController" >
{{getbookdetail.Name}}<br>
{{getbookdetail.Author}}
{{getbookdetail.price }}<br>
</div>
</body
So you said this: 'When user click on Book Name it should pass the data to another page using querystring'
Querystring is not the best method to use for something like this. You're better off learning about ui-router and setting up routes that handle this. You have your initial state, then you can create another state to display each book. Something like this:
.state('initial', {
url: 'some/initial',
template: 'some/initial/template.html',
params: {
name: null,
price: null,
author: null,
isbn: null,
category: null
}
})
.state('read-book-details', {
parent: 'initial',
url: 'some/url',
template: 'some/template.html',
params: {
name: null,
price: null,
author: null,
isbn: null,
category: null
}
})
Then when you're transitioning from one 'state' to another, you do it like so passing along the parameters you want:
$state.go('read-book-details',
{ name: book.name, price: book.price, author: book.author });
On the 'other' page's controller (ie the controller for the 'read-book-details' state) you can inject $state and get the parameters that are passed in via $state.params (ie., $state.params.price)
A second option for you is to have a service that you can store the data in, then retrieve from anywhere else. This obviously becomes useful when you start to pass around larger amounts of data rather than simpler smaller pieces (like name, price).

Rendering a table with dynamic headers

I have to render a table with dynamic headers, I mean, I don't want to do something like this in the HTML
<table>
<tr>
// THIS TABLE ROW IS WHAT I SAY
<th>here info</th>
<th>something here</th>
<th>another header</th>
</tr>
<tr ng-repeat="thing in things">
<td>{{thing.asfs}}</td>
<td>{{thing.asx}}</td>
<td>{{person.dsf}}</td>
</tr>
</table>
I want something like this
<table>
<tr ng-repeat="head in heads">
{{head}}
</tr>
<tr ng-repeat="bar in bars">
<td ng-repeat="foo in foos"></td>
</tr>
</table>
that is only an example, I need to do it with this data:
{
"55f6de98f0a50c25f7be4db0":{
"clicks":{
"total":144,
"real":1
},
"conversions":{
"total":4,
"amount":229
},
"cost":{
"cpc":0.1999999999999995,
"ecpc":1145.0000000000027,
"total":28.79999999999993
},
"revenue":{
"total":4,
"epc":0.027777777777777776
},
"net":{
"roi":-1.1612903225806457,
"total":4
},
"name":"Traffic Source #2",
},
"55f6de98f0a50c25f7be4dbOTHER":{
"clicks":{
"total":144,
"real":1
},
"conversions":{
"total":4,
"amount":229
},
"cost":{
"cpc":0.1999999999999995,
"ecpc":1145.0000000000027,
"total":28.79999999999993
},
"revenue":{
"total":4,
"epc":0.027777777777777776
},
"net":{
"roi":-1.1612903225806457,
"total":4
}
"name":"Traffic Source #3"
},
}
every key, like clicks, conversions, cost, etc, should be a td, it is just that I don't want static HTML.
Any suggestions?
EDIT
And also, sometimes that object will grow, could come up with some more keys like this one 55f6de98f0a50c25f7be4db0
I did this fiddle with the exact same data I am receiving
http://jsfiddle.net/wLkz45qj/
UPDATE:
What you need to do is first convert you inconvenient object to array of objects with simple structure, and then use my code , i.e.
{
a: {
b:{
c: 'x'
}
}
}
will turn into
[[ a, { 'b.c' : 'x' }], ...]
or just
[{ _id : a , 'b.c' :'x'}, ...]
easiest way to do that is to use lodash or underscore ( check map, flatMap, pairs etc)
#jperezov showed you core idea, little bit detailed example:
$scope.peopleKeys = Object.keys(people[0])
and
<table>
<tr>
<th></th>
<th ng-repeat="personKey in peopleKeys">
{{ personKey }}
</th>
</tr>
<tr ng-repeat='p in people'>
<th>{{ $index }}</th>
<td ng-repeat="personKey in peopleKeys">
{{ p[personKey] }}
</td>
</tr>
</table>
You may also have some dictionary with display names:
$scope.displayNames = {
id: 'ID',
firstName: 'First Name'
...
}
and then your header going to be:
<tr>
<th></th>
<th ng-repeat="personKey in peopleKeys">
{{ displayNames[personKey] }}
</th>
</tr>
PS: OR you can just use ui-grid
var app = angular.module('myApp', []);
function PeopleCtrl($scope, $http) {
$scope.headers=[];
$scope.data = [];
$scope.LoadMyJson = function() {
for (var s in myJson){
$scope.data.push(s);
if ($scope.headers.length < 1)
for (var prop in myJson[s]){
prop.data = [];
$scope.headers.push({th:prop, td: []});
}
}
for (var s in $scope.data){
for (var prop in $scope.headers){
var header = $scope.headers[prop].th;
var data = myJson[$scope.data[s]][header];
$scope.headers[prop].td.push(data);
}
}
};
}
What you're looking for is something like this, I think:
http://jsfiddle.net/wLkz45qj/8/
Maybe iterate another time over "inner" for formatting.

AngularJS select with ng-options not updating referenced object property in the parent scope

My select is populating with the contents of the model, but when I select an option, the model does not update.
I'm using ng-options, not ng-repeat and my ng-model is an object on the parent scope, not a primitive, so I think I've avoided the "child-scope" issues I've seen on similar posts. I've recreated the problem on jsfiddle:
http://jsfiddle.net/bobweil/wfdjrej5/
When the user clicks on a row in the table, a small form shows up below that row, permitting a new status value to be selected for that row for posting to the backend service.
Here's my javascript:
angular.module('myApp', [])
.controller('TaskCtrl', function HomeController($scope, $filter) {
$scope.statusMasters = [{
"Id": 1,
"DisplayOrder": 100,
"Text": "Review"
}, {
"Id": 2,
"DisplayOrder": 200,
"Text": "New"
}, {
"Id": 3,
"DisplayOrder": 300,
"Text": "Working"
}, {
"Id": 4,
"DisplayOrder": 400,
"Text": "Complete"
}]
$scope.tasks = [{
"taskId": 1000,
"Descr": "My first task",
"statusId": 1
}, {
"taskId": 2000,
"Descr": "My second task",
"statusId": 1
}, {
"taskId": 3000,
"Descr": "My third task",
"statusId": 1
}];
$scope.selectedTask = null;
$scope.newTaskStatus = {};
$scope.opGroup = "A";
$scope.selectTask = function (thisTask) {
$scope.selectedTask = thisTask;
$scope.newTaskStatus = {};
$scope.newTaskStatus.taskId = thisTask.taskId;
$scope.newTaskStatus.statusId = thisTask.statusId;
};
$scope.isSelected = function (thisTask) {
if (thisTask.hasOwnProperty('taskId')) {
return $scope.selectedTask.taskId === thisTask.taskId;
} else return false;
};
});
And here's my html:
<div ng-controller="TaskCtrl">
<table class="table table-bordered">
<thead>
<tr>
<th>Task #</th>
<th>Description</th>
<th>StatusId</th>
<th>Status Text</th>
</tr>
</thead>
<tbody ng-repeat="item in tasks" ng-click="selectTask(item)" ng-switch on="isSelected(item)">
<tr>
<td>{{item.taskId }}</td>
<td>{{item.Descr}}</td>
<td>{{item.statusId}}</td>
<td>{{statusMasters[item.statusId - 1].Text}}</td>
</tr>
<tr ng-switch-when="true">
<td colspan="10">
<div>Debug: contents of new task status object: <pre>{{newTaskStatus | json}}</pre>
</div>
<label>Select a new status for task {{newTaskStatus.taskId}}:</label>
<select ng-model="newTaskStatus.taskId" ng-show="(opGroup == 'A')" class="form-control" ng-options="rec.Id as rec.Text for rec in statusMasters | orderBy : 'DisplayOrder'"></select>
<select ng-model="newTaskStatus.taskId" ng-show="(opGroup == 'B')" class="form-control" ng-options="rec.Id as rec.Text for rec in statusMasters | orderBy : 'DisplayOrder'"></select>
</td>
</tr>
</tbody>
</table>
Part of the issue is you are trying to set a click event on the tbody, you need to set the ng-click on the row (tr).
Secondly, unless it is needed for another reason, I wouldn't duplicate the values from the "selectedTask" into a "newTaskStatus" when you are planning on changing the status and sending back that value, it can all be done with one object on the scope.
Third, you could clean up your .js a little by changing the 'ng-switch on' to do the check if it is selected. It replaces an entire function with a comparison.
I would do something like this.
<tbody ng-repeat="item in tasks" ng-switch on="selectedTask.taskId == item.taskId">
<tr ng-click="selectTask(item)">
<td>{{item.taskId }}</td>
<td>{{item.Descr}}</td>
<td>{{item.statusId}}</td>
<td>{{statusMasters[item.statusId - 1].Text}}</td>
</tr>
<tr ng-switch-when="true">
<td colspan="10">
<div>Debug: contents of new task status object: <pre>{{selectedTask | json}}</pre>
</div>
<label>Select a new status for task {{selectedTask.taskId}}:</label>
<select ng-model="selectedTask.statusId" ng-show="(opGroup == 'A')" class="form-control" ng-options="rec.Id as rec.Text for rec in statusMasters | orderBy : 'DisplayOrder'"></select>
<select ng-model="selectedTask.statusId" ng-show="(opGroup == 'B')" class="form-control" ng-options="rec.Id as rec.Text for rec in statusMasters | orderBy : 'DisplayOrder'"></select>
</td>
</tr>
</tbody>
With the .js I would remove the unnecessary items:
$scope.selectedTask = null;
$scope.opGroup = "A";
$scope.selectTask = function (thisTask) {
$scope.selectedTask = thisTask;
};
$scope.isSelected = function (thisTask) {
if (thisTask.hasOwnProperty('taskId')) {
return $scope.selectedTask.taskId === thisTask.taskId;
} else return false;
};
I forked your jsfiddle here to demonstrate what I mean. Good Luck!

Flexible table ng-repeat

Is the following possible and if so how do I change my HTML to allow it?
I have the following model;
prospect = [{"name":"jamie",
"phones": [{
"type":"home",
"number":"01275"},
{
"type":"mobile",
"number":"0788"}]},
{"name":"peter",
"phones": [{
"type":"mobile",
"number":"07852"}]}
]
and I would like to display - in an angularjs table - like this
name home mobile
jamie 01275 0788
peter 07852
My current HTML
<table>
<tbody ng-repeat='person in prospect'>
<th>Name</th>
<th ng-repeat="phone in person.phones">{{phone.type}}</th>
<tr>
<td>
{{person.name}}
</td>
<td ng-repeat='phone in person.phones'>
{{phone.number}}
</td>
</tr>
</tbody>
</table>
produces
Name home mobile
jamie 01275 0788
Name mobile
peter 07852
http://jsfiddle.net/jaydubyasee/D7f2k/
To do this in html, without modifying your json, I'd first add an array that indicates what type of phone goes into each column:
$scope.types= ["home","mobile"];
Then use it in the header:
<th ng-repeat="type in types">{{type}}</th>
Then to print out the phone numbers we iterate over each phone within each column, using ngIf to conditionally show any phones that match that column's type:
<td ng-repeat='type in types'>
<span ng-repeat='pphone in person.phones' ng-if="pphone.type == type">
{{pphone.number}}
</span>
</td>
updated fiddle
A variation would be to replace the nested ngRepeats with a custom directive that displays the correct phone for the given column and row.
I hope you will like this solution :)
I did you this dependency
bower install angular
bower install ng-tasty
bower install bootstrap
And here the full solution
<div tasty-table bind-resource="resource">
<table class="table table-striped table-condensed">
<thead tasty-thead></thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-bind="row.name"></td>
<td ng-bind="row.phones | filterTypeColumn:'home'"></td>
<td ng-bind="row.phones | filterTypeColumn:'mobile'"></td>
</tr>
</tbody>
</table>
</div>
<script src="bower_components/angular/angular.min.js"></script>
<script src="bower_components/ng-tasty/ng-tasty-tpls.min.js"></script>
<script>
angular.module('stackOverflowAnswer', ['ngTasty'])
.filter('filterTypeColumn', function() {
return function (input, typeColumn) {
var phoneNumber;
input.forEach(function (phone) {
if (phone.type === typeColumn) {
phoneNumber = phone.number;
}
})
return phoneNumber;
};
})
.controller('StackOverflowController', function ($scope) {
$scope.resource = {
"header": [
{ "name": "Name" },
{ "home": "Home" },
{ "mobile": "Mobile" }
],
"rows": [
{
"name":"jamie",
"phones": [
{ "type":"home","number":"01275" },
{ "type":"mobile", "number":"0788"}
]
},
{
"name":"peter",
"phones": [
{ "type":"mobile","number":"07852"}
]
}
]
};
});
</script>
</body>
</html>
If you want know more about ngTasty you can find all the doc here http://zizzamia.com/ng-tasty/directive/table .
For your specific case, the solution was make a custom filter.
Ciao

How to use EMBER.SORTABLEMIXIN?

My FIXTURES contains array of products which i want to sort based on ID.
Astcart.Application.FIXTURES=[
{
"name" : "astr",
"home_products": [
{
"id": 3,
"name": "Mobiles & Accessories"
},
{
"id": 2,
"name": "Mobiles & Accessories"
},
{
"id": 1,
"name": "Mobiles & Accessories"
}
]
}
];
I am not getting complete example of EMBER.SORTABLEMIXIN.I don't have any idea about sorting in ember.
Can anyone explain me how to do sorting in ember using my this example(Not working)?
The sortable feature is provided by Ember.SortableMixin. This mixin expose two properties: sortAscending and sortProperties.
The sortAscending accepts a boolean value determining if the sort is ascendant or not.
And the sortProperties expect an array with the properties to sort.
For instance:
Controller
App.IndexController = Ember.Controller.extend(Ember.SortableMixin, {
sortAscending: false,
sortProperties: ['id'],
});
These properties can be changed and the order will be updated, here is a sample with dynamic sort:
Controller
App.IndexController = Ember.Controller.extend(Ember.SortableMixin, {
sortProperties: ['firstName'], // or whatever property you want to initially sort the data by
sortAscending: false, // defaults to "true"
actions: {
sortBy: function(property) {
this.set('sortProperties', [property]);
}
}
});
To access the arranged content, you should refer to arrangedContent in your template instead of the regular model property. Like this:
Template
<script type="text/x-handlebars" data-template-name="index">
<h2>Index Content:</h2>
<table>
<thead>
<th {{action "sortBy" "id"}}>ID</th>
<th {{action "sortBy" "firstName"}}>First Name</th>
<th {{action "sortBy" "lastName"}}>Last Name</th>
</thead>
<tbody>
{{#each arrangedContent as |prop|}}
<tr>
<td>{{prop.id}}</td>
<td>{{prop.firstName}}</td>
<td>{{prop.lastName}}</td>
</tr>
{{/each}}
</tbody>
</table>
</script>
You can see this working here http://emberjs.jsbin.com/gunagoceyu/1/edit?html,js,output
I hope it helps
Since Ember.SortableMixin is going to be deprecated in Ember 2.0 (as well as the ArrayController), the recommended way to sort will be using Ember.computed.sort(), as illustrated here: https://stackoverflow.com/a/31614050/525338

Categories

Resources