Angularjs using Rowspan in Collections - javascript

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.

Related

Data tables not responding to provided json result from api call to fill table with data

This is the JSON result being return from the API call.
{
"LoanOfficers": [{
"Id": 13,
"FirstName": "Smith",
"LastName": "Kerry",
"EmailAddress": "kerry.smith#test.com",
"LoanOfficerUrlParameterId": 312,
"UrlParameter": "/smithkerry123"
}, {
"Id": 713,
"FirstName": "Kerry",
"LastName": "Smith",
"EmailAddress": "kerry.smith#test.com",
"LoanOfficerUrlParameterId": 654,
"UrlParameter": "/kerrysmith1234578"
}]
}
I implemented the data table below. The call works and fetches the data and puts it into an object. From there the table reads no data in the table. I use a console.log() to see if the data was being passed back but it is but I don't know why it's not returning to the datable.
$(document).ready(function () {
$.ajax({
url: '/CallAPI',
type: 'GET',
dataType: 'json',
success: function (data) {
assignToEventsColumns(data);
}
});
function assignToEventsColumns(data) {
console.log(data);
var table = $('#table_id').dataTable({
"bAutoWidth": false,
"aaData": data,
"columns": [
{ "data": "Id" },
{ "data": "FirstName" },
{ "data": "LastName" },
{ "data": "EmailAddress" },
{ "data": "LoanOfficerUrlParameterId" },
{ "data": "UrlParameter" }
],
"order": [[1, 'asc']]
})
}
});
Console log of the JSON object return from API call
I am new to data tables with JSON data don't understand fully understand why this issue is occurring. Any explanation would be much appreciated. All is I know that is returning a valid JSON format because I already tested that. I provided as much detail as possible. I provided an example of the JSON result and an example of console.log.
HTML
<table class="dataTable table table-striped text-center table-hover" id="table_id">
<thead>
<tr class="bg-green">
<th>Loan Officer ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email Address</th>
<th>LoanOfficerUrlParameterId</th>
<th>Url</th>
#*<th></th>*#
</tr>
</thead>
<tfoot>
<tr class="bg-green">
<th>Loan Officer ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email Address</th>
<th>LoanOfficerUrlParameterId</th>
<th>Url</th>
#*<th></th>*#
</tr>
</tfoot>
</table>

Multiple v-for loops in same vue file

I'm trying to populate a table using three for loops from my api.
const events = await app.$axios.get('/api/events/')
const markets = await app.$axios.get('/api/markets/')
const markets = await app.$axios.get('/api/runners/')
I need to make sure that the table rows index is runners since the runners column will have more runners than markets and markets will have more rows than events.
Like so.
Event Market
Runner
Runner
Runner
Market
Runner
Runner
When I try to do multiple for for loops in the same vue file I get this error.
duplicate attribute key
Why do I get this error from using id in separate loops?
My Question is how do I populate the table based on runner index for each row?
Here's my code so far.
<template>
<div class="course-list-row">
<th style="width:5%"> Start date </th>
<th style="width:5%"> Event name</th>
<th scope="col">Market</th>
<th scope="col">Runner</th>
<tr v-for="(event, index) in events" :key=id
v-for="(market, index) in markets" :key=id
v-for="(runner, index) in runners" :key=id>
<td>{{ event.start_time }} </td>
<td>{{ event.event_name }} </td>
<td>{{ market.name }} </td>
<td>{{ runner.name }} </td>
<td />
</tr>
</div>
</template>
<script>
export default {
async asyncData({
app
}) {
try {
const events = await app.$axios.get('/api/events/')
const markets = await app.$axios.get('/api/markets/')
const runners = await app.$axios.get('/api/runners/')
return {
events: events.data.results,
markets: markets.data.results,
runners: runners.data.results,
error: false
}
} catch (e) {
console.log('error', e)
return {
events: [],
markets: [],
runners: [],
error: true
}
}
},
};
</script>
<style>
th,
td {
font-family: ‘Lato’, sans-serif;
font-size: 12px;
font-weight: 400;
padding: 10px;
text-align: left;
width: 0%;
border-bottom: 1px solid black;
}
</style>
The api responses have key of id as below.
/api/events
{
"count": 80,
"next": null,
"previous": null,
"results": [{
"id": 103,
"sport_id": "9",
"event_name": "Guilherme Clezar vs Uladzimir Ignatik",
"start_date": "12-11-19",
"status": "open",
"event_id": 1273804660340017
}]
}
/api/markets
{
"count": 128,
"next": "http://localhost:8000/api/markets/?page=2",
"previous": null,
"results": [{
"id": 151,
"market_id": 1273804670840017,
"market_name": "Moneyline",
"status": "open",
"volume": 1107.5453,
"event": 103
}]
}
Key is used to calculate differences in DOM when re rendering. Keys have to be unique in scope a parent DOM node (I guess).
Try prefixing your keys with something.
v-for="(event, index) in events" :key="`event_${id}`"
EDIT:
Actually the reason is probably that your id is not defined anywhere. Replace it with index.
Besides, are you sure you can use three loops on the same element? Never tried it, im always concatenating things in a computed prop... Interesting approach!
As pointed out in the comments its not possible to have multiple v-for statements on the same element, you would need something like this instead:
<div class="course-list-row">
<th style="width:5%"> Start date </th>
<th style="width:5%"> Event name</th>
<th scope="col">Market</th>
<th scope="col">Runner</th>
<tr v-for="(event, index) in events" :key=id>
<td>{{ event.start_time }} </td>
<td>{{ event.event_name }} </td>
<td></td>
<td></td>
</tr>
<tr v-for="(market, index) in markets" :key=id>
<td></td>
<td></td>
<td>{{ market.name }} </td>
<td></td>
</tr>
<tr v-for="(runner, index) in runners" :key=id>
<td></td>
<td></td>
<td></td>
<td>{{ runner.name }} </td>
</tr>
</div>
OR
To avoid using verbose templating as above, you could merge all three arrays into a single array, where each object has an id and a type and render as follows:
<tr v-for="(entry, index) in combined" :key=id>
<td>{{entry.type == 'event' ? event.start_time : ''}} </td>
<td>{{ entry.type == 'event' ? event.event_name : ''}} </td>
<td>{{ entry.type = 'market' ? market.name : ''}}</td>
<td>{{ entry.type == 'runner' ? runner.name : ''}}</td>
</tr>
Though I think this has already been covered in the comments I'll quickly recap some of the problems with the original code.
An HTML element can't have the same attribute multiple times. While Vue templates are not technically HTML they do try to conform to roughly the same parsing rules, so duplicates are discarded. The original code has both v-for and key attributes duplicated on the <tr>.
Another problem is the key value. Writing :key=id will try to set the key to equal the value of a property called id. But where is this property defined? By default Vue will be looking for it on the Vue instance itself. I suspect what you meant was for it to use the id property of the event, market or runner. For that you would need to write :key="event.id", etc..
The question does leave some ambiguity as to what precisely it is you're trying to achieve but the code below is my best guess for what you want:
new Vue({
template: `
<table border>
<thead>
<th>Start date</th>
<th>Event name</th>
<th>Market</th>
<th>Runner</th>
</thead>
<tbody>
<template v-for="event in events">
<tr :key="'event' + event.id">
<td>{{ event.start_date }}</td>
<td>{{ event.event_name }}</td>
</tr>
<template v-for="market in marketsFor[event.id]">
<tr :key="'market' + market.id">
<td></td>
<td></td>
<td>{{ market.market_name }} </td>
</tr>
<tr v-for="runner in runnersFor[market.id]" :key="'runner' + runner.id">
<td></td>
<td></td>
<td></td>
<td>{{ runner.runner_name }}</td>
</tr>
</template>
</template>
</tbody>
</table>
`,
el: '#app',
data () {
return {
events: [
{
"id": 103,
"event_name": "Guilherme Clezar vs Uladzimir Ignatik",
"start_date": "12-11-19"
}, {
"id": 104,
"event_name": "Event 2",
"start_date": "13-11-19"
}
],
markets: [
{
"id": 151,
"market_name": "Moneyline",
"event": 103
}, {
"id": 152,
"market_name": "Market 2",
"event": 103
}, {
"id": 153,
"market_name": "Market 3",
"event": 104
}
],
runners: [
{
"id": 1,
"runner_name": "Runner 1",
"market": 151
}, {
"id": 2,
"runner_name": "Runner 2",
"market": 151
}, {
"id": 3,
"runner_name": "Runner 3",
"market": 152
}, {
"id": 4,
"runner_name": "Runner 4",
"market": 153
}
]
}
},
computed: {
marketsFor () {
const markets = {}
for (const market of this.markets) {
const event = market.event
markets[event] = markets[event] || []
markets[event].push(market)
}
return markets
},
runnersFor () {
const runners = {}
for (const runner of this.runners) {
const market = runner.market
runners[market] = runners[market] || []
runners[market].push(runner)
}
return runners
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app"></div>
I've had to rename some properties, e.g. start_time to start_date and market.name to market.market_name. The properties in the template didn't seem to match the example data.
The core of my solution is a pair of computed properties that generate simple object maps that can be used to find the corresponding children at each level. There are various alternatives to this but it seems that the heart of any solution is finding a way to navigate the hierarchy from within the template. The template in my example could be simplified but at the expense of moving the complexity into the JavaScript portion of the component.

AngularJS: Read Json data from HTML table

Using angularjs here:
I am creating a table which has 2 static columns (ID & Comment) and rest columns are dynamically created by clicking a button. User can enter data in the rows of this table. I want to read/extract the data from the table that the user has entered.
I have created a jsfiddle at: http://jsfiddle.net/ajnh8bo5/7/
Click on add tab, give a name, and then click add columns
Reading json as:
$scope.read = function() {
return JSON.stringify($scope.targetTable);
};
Currently my generated json just gives information about the dynamic columns that was added. See json below:
{
"id":0,
"name":"Table 1",
"columns":[
{
"id":0,
"label":"Column 0",
"$$hashKey":"object:11"
}
],
"rows":[
{
"0":"2",
"$$hashKey":"object:9",
"undefined":"1",
"id":1037
}]
The above json is generated when I add just one dynamic columns. Here under rows array "0":"2" means that the first dynamic column has a value of "2". But it does not shows the data that was entered for ID & Comment column. I know I am missing something here but not sure how to proceed.
Another thing which is less important at this moment is also the json to spit out the column name as well.
---updated---
Adding desired output:
{
"columns": [
{
"id": 0,
"label": "ID"
},
{
"id": 1,
"label": "Column 1"
},
{
"id": 2,
"label": "Column 2"
},
{
"id": 4,
"label": "Comment"
}
],
"rows": [
{
"Id": "1",
"Column 1": "2",
"Column 2": "3",
"Comment": "user comment"
}
]
}
The above json shows I have 2 static columns Id & Comment. Column1 & Column2 are the dynamic ones.
If anyone cant provide me any solution based on the above JSON. Can anyone just let me know how can I just output static columns ID & Comment in my json.
--Updated with relevant code---
Below is the HTML table:
<table class="table table-bordered" ng-if="targetTable">
<thead>
<tr>
<th>ID #</th>
<th contenteditable="true" ng-repeat="c in targetTable.columns" ng-model="c.label"></th>
<th class="comment-fixed-width" ng-model="comment">Comment</th>
<td class="center add-column fixed-width"><a ng-href ng-click="addColumn()">+ Add Column</a></td>
<td class="comment-fixed-width" contenteditable="true" ></td>
</tr>
</thead>
<tbody>
<tr ng-repeat="r in targetTable.rows">
<td class="fixed-width" contenteditable="true" ng-model="r[column.id]"></td>
<td class="fixed-width" contenteditable="true" ng-repeat="column in targetTable.columns" ng-model="r[column.id]" ng-blur="!r.id? addNewRow(r[column.id], r): undefined"></td>
<td class="comment-fixed-width" contenteditable="true" ></td>
<td class="blank fixed-width" colspan="2" ng-model="r[column.id]"></td>
</tr>
</tbody>
</table>
AngularJs Code:
function createTable() {
tableCounter++;
return {
id: currentTableId++,
name: `Table ${currentTableId}`,
columns: [],
rows: [{}],
uniqueIdCounter: 1037
}
While creating a new tab I am creating an instance of table as:
$scope.tables.push(createTable());
$scope.tables = [];
$scope.targetTable = null;
//When I click add dynamic column button I use the below code.
$scope.addColumn = function() {
if (tableCounter) {
var columns = $scope.targetTable.columns,
id = columns.length;
$scope.targetTable.columns.push({
id: columns.length,
label: `Column ${id}`
});
}
};
//Below code is for adding a new row
$scope.addNewRow = function(value, row) {
if (tableCounter) {
if (!value || value === "" || typeof value !== 'string') return;
$scope.targetTable.rows.push({});
row.id = $scope.targetTable.uniqueIdCounter++;
}
};
Anyone with inputs?
I didn't want to overhaul your code, so I just made some small tweaks in your HTML code:
JSFiddle: http://jsfiddle.net/0oerpd5u/
<tbody>
<tr ng-repeat="r in targetTable.rows">
<td class="fixed-width" contenteditable="true" ng-model="r.id"></td>
<td class="fixed-width" contenteditable="true" ng-repeat="column in targetTable.columns" ng-model="r[column.id]" ng-blur="!targetTable.rows[$index+1].id? addNewRow(r[column.id], r): ''"></td>
<td class="comment-fixed-width" contenteditable="true" ng-blur="!targetTable.rows[$index+1].id?addNewRow(r.comment, r):''" ng-model="r.comment">></td>
<td class="blank fixed-width" colspan="2" ng-model="r[column.id]"></td>
</tr>
</tbody>
and in your JS:
var uniqueIdCounter =1037;
function createTable() {
tableCounter++;
return {
id: currentTableId++,
name: `Table ${currentTableId}`,
columns: [],
rows: [{id: uniqueIdCounter}],
uniqueIdCounter: uniqueIdCounter
}
}
$scope.addNewRow = function(value, row) {
if (tableCounter) {
if (!value || value === "" || typeof value !== 'string') return;
$scope.targetTable.rows.push({id :++$scope.targetTable.uniqueIdCounter});
}
};

Vue: Displaying nested data in a table

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.

how to pass object data to table using ng-repeat

I have this object named 'SEG-Data as follows. I am trying to put this data into a table form using ng-repeat.
SEG_Data
Object {ImportValues: Array[2]}
ImportValues: Array[2]
0: Object
ImportArray: "0045614"
Name: "dean"
Type: "xyz"
1: Object
ImportArray: "2567741"
Name: "dean"
Type: "abc"
length: 2
The table used is as below and i am using ng-repeat where i mentiond 'field in SEG_data.ImportValues' to get the values.... But somehow i am not getting the data at the UI.
<table style="width:100%" border:"1px">
<tr>
<th>ImportArray</th>
<th>Name</th>
<th>Type</th>
</tr>
<tr ng-repeat="field in SEG_Data.ImportValues">
<td>{{field.ImportArray}}</td>
<td>{{field.Name}}</td>
<td>{{field.Type}}</td>
</tr>
</table>
Any advice if i am doing it wrong for displaying ??
Your object is called SEG_Data but you're referencing SEG_data with a lowercase 'd' in your template. Data displays properly with that one change.
Object
$scope.SEG_Data = {
ImportValues: [{
ImportArray: "0045614",
Name: "dean",
Type: "xyz"
}, {
ImportArray: "2567741",
Name: "dean",
Type: "abc"
}]
};
Template
<table style="width:100%; border:1px">
<tr>
<th>ImportArray</th>
<th>Name</th>
<th>Type</th>
</tr>
<tr ng-repeat="field in SEG_Data.ImportValues">
<td>{{field.ImportArray}}</td>
<td>{{field.Name}}</td>
<td>{{field.Type}}</td>
</tr>
</table>
See plunker example
Working Example :
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.SEG_Data = {
ImportValues: [{
ImportArray: "0045614",
Name: "dean",
Type: "xyz"
}, {
ImportArray: "2567741",
Name: "dean",
Type: "abc"
}]
};
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<table>
<tr>
<th>ImportArray</th>
<th>Name</th>
<th>Type</th>
</tr>
<tr ng-repeat="field in SEG_Data.ImportValues">
<td>{{field.ImportArray}}</td>
<td>{{field.Name}}</td>
<td>{{field.Type}}</td>
</tr>
</table>
</div>

Categories

Resources