Vue: Displaying nested data in a table - javascript

I'm in the process of rewriting some older code, and using Vue as the replacement. It's all going great apart from changing one table that is templated using handlebars.
Using handlebars nested {{each}} I can work down through the nested data structure while displaying the relevant data in table rows: e.g. using handlebars: https://jsfiddle.net/6dsruo40/1/
I cant figure out how to do the same using Vue with v-for etc.
Fiddle with as mush as I have: https://jsfiddle.net/cj7go6bv/
I don't know how to work through the data structure while maintaining the <tr>s like in handlebars
This is the data structure I'm using, but it is flexible:
var data = [
{
key: "Region 1",
values: [
{
key: "Sub region 1",
values: [
{
site: "Site A",
timestamp: 1507246300935,
total: 3,
},
{
site: "Site B",
timestamp: 1507246300818,
total: 0,
},
{
site: "Site C",
timestamp: 1507246300936,
total: 0,
}
],
},
{
key: "Sub region 2",
values: [
{
site: "Site A",
timestamp: 1507246301442,
total: 20,
},
{
site: "Site B",
timestamp: 1507246301788,
total: 5,
}
]
},
{
key: "Sub region 3",
values: [
{
site: "Site A",
timestamp: 1507246302503,
total: 66,
},
{
site: "Site B",
timestamp: 1507246302783,
total: 2
}
]
}
]
},
{
key: "Region 2",
values: [
{
key: "Sub region 1",
values: [
{
site: "Site A",
timestamp: 1507246306789,
total: 0,
},
{
site: "Site B",
timestamp: 1507246307439,
total: 6,
}
]
},
{
key: "Sub region 2",
values: [
{
site: "Site A",
timestamp: 1507246308269,
total: 10,
},
{
site: "Site B",
timestamp: 1507246308683,
total: 30,
}
]
}
]
}
];
The Vue code I have is very modest so far:
Vue.component('project-table', {
template: '#table-template',
props: {
data: Array
}
});
var app = new Vue({
el: '#table-container',
data: {data: sites},
});
And the template:
<div id="table-container">
<project-table :data="data"></project-table>
</div>
<script id="table-template" type="text/x-template">
<table class="u-full-width">
<thead>
<tr>
<th>Project location</th>
<th>Total</th>
<th>Timestamp</th>
</tr>
</thead>
<tbody>
<tr class="name-row" v-for="item in data">
<th class="name" colspan="3">{{item.key}}</th>
</tr>
<tbody>
</table>
</script>
What is the mechanism in Vue for displaying a table like is done in Handlebars? Thanks!

Here is the relevant part of the template updated.
<tbody>
<template v-for="item in data">
<tr class="name-row" >
<th class="name" colspan="3">{{item.key}}</th>
</tr>
<template v-for="subregion in item.values">
<tr>
<th class="group-name" colspan="4">{{subregion.key}}</th>
</tr>
<tr v-for="site in subregion.values">
<td>{{site.site}}</td>
<td>{{site.total}}</td>
<td>
<span>{{site.timestamp}}</span>
</td>
</tr>
</template>
</template>
<tbody>
And the updated fiddle.
The main point is taking advantage of the template element to render more than one tr per iteration.

Related

I am looking for a way to do a multi-column Dropdown Listbox using Kendo in an Angular 7 project

I'm attempting to recreate the multi-column Kendo DropDownList found at the following link.
kendoDropDownList
I've been able to recreate most of it using the following code, but it will not display the data properly.
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'app-tablebox',
// templateUrl: './tablebox.component.html',
styleUrls: ['./tablebox.component.scss'],
template: `
<div>
<kendo-dropdownlist style="width:400px;"
[defaultItem]="defaultItem"
[data]= "data"
[textField]= "'band'"
[valueField]= "'id'"
>
<ng-template kendoDropDownListHeaderTemplate>
<table>
<tr class="combo-tr">
<td class="combo-hd-td">Band</td>
<td class="combo-hd-td">Song</td>
<td class="combo-hd-td">Album</td>
</tr>
</table>
</ng-template>
<ng-template kendoDropDownListValueTemplate let-dataItem>
<span>
<table>
<tr class="combo-tr">
<td class="combo-td">{{dataItem.band}}</td>
<td class="combo-td">{{dataItem.song}}</td>
<td class="combo-td">{{dataItem.album}}</td>
</tr>
</table>
</span>
</ng-template>
</kendo-dropdownlist>
</div> `
})
export class TableboxComponent {
public defaultItem: { text: string, value: number } = { text: "Select item...", value: null };
public data = [
{ id: 1, band: "Iron Maiden", song: "Wasted Years", album: "Ed Hunter" },
{ id: 2, band: "Metallica", song: "Enter Eandman", album: "Metallica" },
{ id: 3, band: "Mr. Big", song: "Seven Impossible Days", album: "Japandemonium" },
{ id: 4, band: "Unknown Band", song: "Some Song", album: "The Album" }
];
constructor() { }
}
The component displays as follows:
Screenshot
Thanks in advance.
Mike
You should use kendoDropDownListItemTemplate instead of kendoDropDownListValueTemplate.
kendoDropDownListValueTemplate is the template used for the item that appears in the dropdown after you've selected an item.
kendoDropDownListItemTemplate is the template used for the items in the list.

Binding array items in Vue.js

I have an arry which contains some array and proporties in it.
Now I want to iterate through the array and create new rows for each of items.
This is what I am doing.
var orderDetails = [{
"ID": "1",
"NAME": "Item1",
"Orders": [
"ORD001"
],
"Purchases": [
"RhynoMock",
"Ember"
],
"ISENABLED": "false"
},
{
"ID": "2",
"NAME": "Item2",
"Orders": [
"ORD002",
"ORD008"
],
"Purchases": [
"StufferShop"
],
"ISENABLED": "false"
}
];
var app = new Vue({
el: '#ordersDiv',
data: {
allOrders: orderDetails
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="ordersDiv">
<table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="order in allOrders">{{ order.NAME }}</tr>
</table>
</div>
Getting the error,
[Vue warn]: Error in render: "TypeError: Cannot read property 'NAME' of undefined"
Your HTML is invalid, and it seems that Vue's template compiler or DOM renderer has trouble with it: <tr> must only have <td> or <th> child elements, it cannot have text node children.
Try this instead:
<tr v-for="order in allOrders">
<td>{{ order.NAME }}</td>
</tr>

How to show/hide two views in one Marionette js region

I am having a LayoutView in which I am having two regions named filter region and main region (Content Region). Based on the selection over filter region, I need to show the view into the main region.
As of now, I have created one view to show into the main region i.e Current Year view, but I need inputs on how to create a view for the other Wireframe that I am attaching into my question panel now.
// Application Bootstrap
var App = new Marionette.Application();
// Add a region
App.addRegions({
main: "#app"
});
/*DATA*/
var data = {
"accounts": [{
"accountNumber": "AllAccounts",
"assets": [{
"assetName": "ASSET 1",
"isin": "GB0008533564",
"grossProfit": [{
"assetCost": "500",
"units": "10",
"netGainLoss": "20"
}]
},
{
"assetName": "ASSET 2",
"isin": "GB0008533565",
"grossProfit": [{
"assetCost": "500",
"units": "10",
"netGainLoss": "20"
}]
},
{
"assetName": "ASSET 3",
"isin": "GB0008533566",
"grossProfit": [{
"assetCost": "500",
"units": "10",
"netGainLoss": "20"
}]
}
]
}]
};
// AssetsModel
var AssetsModel = Backbone.Model.extend({});
//AssetsCollection - will contain the assets array
var AssetsCollection = Backbone.Collection.extend({
model: AssetsModel,
getAssetCost: function() {
var assetCost = 0;
this.each(function(model) {
_(model.get('grossProfit')).each(function(gross) {
assetCost += parseInt(gross.assetCost, 10);
});
});
return assetCost;
},
getUnits: function() {
var units = 0;
this.each(function(model) {
_(model.get('grossProfit')).each(function(gross) {
units += parseInt(gross.units, 10);
});
});
return units;
},
getNetGainLoss: function() {
var netGainLoss = 0;
this.each(function(model) {
_(model.get('grossProfit')).each(function(gross) {
netGainLoss += parseInt(gross.netGainLoss, 10);
});
});
return netGainLoss;
}
});
// AccountsModel - unique idAttribute will be accountNumber and setting the assets key of AccountsModel to AssetsCollection
var AccountsModel = Backbone.Model.extend({
idAttribute: "accountNumber",
initialize: function(response) {
this.set('assets', new AssetsCollection(response.assets));
}
});
// AccountsCollection - will be containg the Acccounts Array
var AccountsCollection = Backbone.Collection.extend({
model: AccountsModel
});
// Passing the data.accounts to accountsCollection instance
var accountsCollection = new AccountsCollection(data.accounts);
// AppLayoutView - mainlayoutview of the application
var AppLayoutView = Marionette.LayoutView.extend({
template: Handlebars.compile($("#layout-view-template").html()),
regions: {
filter: "#filterArea",
main: "#contentArea" // mainContentArea regiobn- will be containing two type of views:
// 1. High level view, with multiple assets as table row's and only one object inside grossProfit
// 2. Deep level view, with multiple objects inside assets array as table and mutliple objects inside grossProfit array
// (second point, need to be discussed for which i am raising question)
},
onRender: function() {
this.showChildView("filter", new FilterView());
this.showChildView("main", new TableCompositeView({
collection: accountsCollection.get('AllAccounts').get('assets')
}));
}
});
var FilterView = Marionette.ItemView.extend({
id: "filter-view",
template: Handlebars.compile($('#filter-view-template').html())
});
var RowView = Marionette.ItemView.extend({
tagName: "tr",
template: Handlebars.compile($('#report-row-template').html())
});
var TableCompositeView = Marionette.CompositeView.extend({
id: "report-area-view",
tagName: "section",
template: Handlebars.compile($("#report-view-template").html()),
childView: RowView,
childViewContainer: "#childWrapper",
templateHelpers: function() {
var totalAssetCost = this.collection.getAssetCost();
var totalUnits = this.collection.getUnits();
var totalNetGainLoss = this.collection.getNetGainLoss();
return {
totalAssetCost: totalAssetCost,
totalUnits: totalUnits,
totalNetGainLoss: totalNetGainLoss
}
}
});
var layoutView = new AppLayoutView();
// Render the layoutView to the main Region
App.getRegion('main').show(layoutView);
App.start();
.container {
padding-top: 10px;
}
<div class="container">
<div class="row">
<div class="col-md-12">
<div id="app"></div>
</div>
</div>
</div>
<!--TEMPLATES -->
<script id="layout-view-template" type="text/handlebars-template">
<section>
<div id="filterArea"> </div>
<div id="contentArea"> </div>
</section>
</script>
<script id="filter-view-template" type="text/handlebars-template">
<div class="well">
<input type="radio" name="currentYear" value="currentYear" /> Current year <br/><br/>
<input type="radio" name="twoYears" value="pastYears" /> Past 2 year
</div>
</script>
<script id="report-view-template" type="text/handlebars-template">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Asset Name</th>
<th>Asset Cost</th>
<th>Units</th>
<th>NetGainLoss</th>
</tr>
</thead>
<tbody id="childWrapper"></tbody>
<tfoot>
<tr>
<th>Total</th>
<th>₹ {{totalAssetCost}}</th>
<th>₹ {{totalUnits}}</th>
<th>₹ {{totalNetGainLoss}}</th>
</tr>
</tfoot>
</table>
</script>
<script id="report-row-template" type="text/handlebars-template">
<td>{{assetName}}</td>
{{#grossProfit}}
<td>{{assetCost}}</td>
<td>{{units}}</td>
<td>{{netGainLoss}}</td>
{{/grossProfit}}
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.marionette/2.4.2/backbone.marionette.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.0/handlebars.min.js"></script>
Please use the below data to show the Past 2 Years view, there is only minor changes i.e only the grossProfit array objects have been increased.
var data = {
"accounts": [{
"accountNumber": "AllAccounts",
"assets": [{
"assetName": "ASSET 1",
"isin": "GB0008533564",
"grossProfit": [{
"assetCost": "500",
"units": "10",
"netGainLoss": "20"
},
{
"assetCost": "500",
"units": "10",
"netGainLoss": "20"
}
]
},
{
"assetName": "ASSET 2",
"isin": "GB0008533565",
"grossProfit": [{
"assetCost": "500",
"units": "10",
"netGainLoss": "20"
},
{
"assetCost": "500",
"units": "10",
"netGainLoss": "20"
}
]
},
{
"assetName": "ASSET 3",
"isin": "GB0008533566",
"grossProfit": [{
"assetCost": "500",
"units": "10",
"netGainLoss": "20"
},
{
"assetCost": "1000",
"units": "10",
"netGainLoss": "20"
}
]
}
]
}]
};
Case 1:If you have to show completely different composite view within the main region,you have to create a separate layout 'ResultLayout' which should be rendered in Main Region.You can intialized both the compositeview's in ResultLayout and render the desired compositeview when the filter is changed.(This is the case when your data is static for different filters)
In case of dynamic data
Case 2: If you have to show completely different composite view within the main region, you have to create a separate layout 'ResultLayout' which should be rendered in Main Region.You can render the desired compositeview when the filter is changed.
For first time you can show default filter selected and corresponding compositeview is render within the layout(ResultLayout) of main region.
On filter change you can trigger event from FilterView which will be listened in ResultLayout (with filter params), fetch the collection according to the filters and then you can render the corresponding compositeview in the region of 'ResultLayout' The hierarchy will be as below:
LayoutView -> Filter Region and Main Region
Main Region -> ResultLayout
ResultLayout -> CompositeView1 or CompositeView2 (Depending upon the filters.)
Case 3: If you have to keep compositeview same then you just need to update the collection and render the view.
I hope this helps!

ng-repeat and ng-if not working

I want to do following:
Show all the html elements on the page based on some conditions,
my understanding was i can use ng-if.
If employeeList is empty then do i need to create another copy of the inner elements to make sure it shows on the page
ng-if prints both divs and spans which only one should be printed my html is this
html:
<div class="container" ng-controller="profileController" ng-init="loadProfilesData()">
<div ng-repeat="p in profileData">
<div>{{p.company}}</div>
<div>{{p.department}}</div>
<div ng-repeat="emp in p.employeeList"></div>
<div ng-if="emp.Tag== 'Devo100'" gauge-chart class="gauge" id="Devo100-{{p.Id}}" value=p.Value*100></div>
<div ng-if="emp.Tag!= 'Devo100'" gauge-chart class="gauge" id="Devo100-{{p.Id}}" value=0></div>
<span ng-if="emp.Tag== 'Devo102'">
{{ p.Value | date: "hh:mm:ss" }}
</span>
<span ng-if="emp.Tag== 'Devo102'">
0
</span>
</div>
</div>
</div>
my json is following
profileData: [
{
ID: "1",
metricStatList: [{"Value":0.003,"Stat":{"parameter":0,"Name":"test0","Tag":"Devo100"}},
{"Value":0.004,"Stat":{"parameter":0,"Name":"test1","Tag":"Devo101"}},
{"Value":0.005,"Stat":{"parameter":0,"Name":"test2","Tag":"Devo102"}}],
comapny: "MSDFT",
department: "Sales"
},
{
ID: "2",
metricStatList: null,
comapny: "MSDFT",
department: "HR"
},
{
ID: "3",
metricStatList: [{"Value":0.003,"Stat":{"parameter":0,"Name":"test0","Tag":"Devo100"}},
{"Value":0.004,"Stat":{"parameter":0,"Name":"test1","Tag":"Devo101"}}],
comapny: "MSDFT",
department: "Development"
},
{
ID: "4",
metricStatList: [{"Value":0.1,"Stat":{"parameter":0,"Name":"test2","Tag":"Devo102"}},
{"Value":0.25,"Stat":{"parameter":0,"Name":"test1","Tag":"Devo101"}}],
comapny: "MSDFT",
department: "Finance"
},
{
ID: "5",
metricStatList: [{"Value":0.233,"Stat":{"parameter":0,"Name":"test0","Tag":"Devo100"}}],
comapny: "MSDFT",
department: "Accounts"
}
]

Angularjs using Rowspan in Collections

I have collections of data coming from JSON request.. that i want to display with row span using angular.. but its not yet working properly.. hopefully you can help me with this.
$scope.data = [
{
order_number: "A-0001",
name: "computer 01-01",
status: 'new'
},
{
order_number: "A-0002",
name: "computer 02-01",
status: 'new'
},
{
order_number: "A-0001",
name: "computer 01-02",
status: 'new'
},
{
order_number: "A-0003",
name: "computer 03-01",
status: 'check'
},
{
order_number: "A-0001",
name: "computer 01-03",
status: 'new'
},
{
order_number: "A-0003",
name: "computer 03-02",
status: 'check'
}
];
I want my collections to display in a table like this.
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Order Number</th>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="3">A-0001</td>
<td>01-01</td>
<td>new</td>
</tr>
<tr>
<td>01-02</td>
<td>new</td>
</tr>
<tr>
<td>01-03</td>
<td>new</td>
</tr>
<tr>
<td>A-0002</td>
<td>02-01</td>
<td>new</td>
</tr>
</tbody>
</table>
demo plunker
For a true angular solution, you shouldn't need javascript to alter the dom. Angular's goal is to have the data itself dictate how controls are rendered.
Are you able to change your data structure? I find it a bit odd that you have id's that aren't unique. I took the liberty of changing it so that each order has a set of sub_orders, which consist of the name and status.
Here's a working plunker with an altered data structure.
http://plnkr.co/edit/lpfAJPejfVGaJqB8f6HR?p=preview
Data structure:
$scope.data = [
{
order_number: "A-0001",
sub_orders: [
{
name: "computer 01-01",
status: "new"
},
{
name: "computer 01-02",
status: "new"
},
{
name: "computer 01-03",
status: "new"
}
]
},
{
order_number: "A-0002",
sub_orders: [
{
name: "computer 02-01",
status: "new"
}
]
},
{
order_number: "A-0003",
sub_orders: [
{
name: "computer 03-01",
status: "new"
},
{
name: "computer 03-02",
status: "new"
}
]
}
];
Here's the angular code for rendering the table:
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Order Number</th>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody ng-repeat="order in data">
<tr>
<td rowspan="{{order.sub_orders.length + 1}}">{{order.order_number}}</td>
</tr>
<tr ng-repeat="suborder in order.sub_orders">
<td>{{suborder.name}}</td>
<td>{{suborder.status}}</td>
</tr>
</tbody>
</table>
Just made a quick and dirty solution to your problem using the data provided
http://plnkr.co/edit/fvVh73sXfIRLHw42Q2XM?p=preview
I ordered the data with ng-repeat and orderBy
ng-repeat="item in data | orderBy: 'order_number'"
And then using functions I checked if we needed a rowspan or not.
A Better solution for you would be to refactor the data,like MaskedTwilight's solution suggests, so that your order are already grouped per order number and that your items are a subcollection of this order. That whay you can reduce the code and watchers.

Categories

Resources