Vue JS 2: Bind computed property to data attribute - javascript

I have a data object in Vue.js 2 that looks like this:
data: {
items: [
{
value1: 10,
value2: 10,
valuesum: ""
},
{
value1: 10,
value2: 100,
valuesum: "",
}
]
I render that data object in a table and run calculations on it. I want the valuesum property to be computed and stored in each object somehow. In other words, I want the code to essentially perform this:
data: {
items: [
{
value1: 10,
value2: 10,
valuesum: {{ value1 + value2 }} //20
},
{
value1: 10,
value2: 100,
valuesum: {{ value1 + value2 }} //110
}
]
The computed attribute doesn't seem to be able to accomplish this. I tried to use the following function but this does not work:
function (index) {
for (let i = 0; i < this.items.length; i++ ){
return this.items[index].value1 + this.items[index].value2;
}
}
The closest I have managed to get to an answer is by doing the calculation inline, but I can't bind its result to the items.total object. My HTML looks like this:
<table id="table">
<thead>
<tr>
<td>Value 1</td>
<td>Value 2</td>
<td>Sum</td>
</tr>
</thead>
<tbody>
<tr v-for="item in items">
<td><input type="number" v-model="item.value1"></td>
<td><input type="number" v-model="item.value2"></td>
<td> {{ item.value1 + item.value2 }} </td>
</tr>
</tbody>
</table>
But I can't add v-model to it, since it's not an input. I'd like to avoid adding a readonly <input> to the column, since that doesn't seem like the best solution and isn't very elegant.

Here are a few approaches: Binding to a method, binding to a computed property, and binding to a data property that is saved during the computed property call:
<table id="table">
<thead>
<tr>
<td>Value 1</td>
<td>Value 2</td>
<td>Sum</td>
<td>Sum</td>
<td>Sum</td>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in items">
<td><input type="number" v-model="item.value1"></td>
<td><input type="number" v-model="item.value2"></td>
<td> {{ sum(item) }} </td><!-- method -->
<td> {{ sums[index] }}</td><!-- computed -->
<td> {{ item.valuesum }}</td><!-- data property via computed -->
</tr>
</tbody>
</table>
The script:
var vm = new Vue({
el: "#table",
data: function () {
return {
items: [
{
value1: 10,
value2: 10,
valuesum: 0
},{
value1: 10,
value2: 100,
valuesum: 0,
}
]
}
},
computed: {
sums: function () {
var val = [];
for (var index in this.items) {
var sum = this.sum(this.items[index]);
val.push(sum);
// Update the local data if you want
this.items[index].valuesum = sum;
}
return val;
}
},
methods: {
sum: function (item) {
// Alternate, if you take valuesum out:
// for (var prop in item) {
// val += parseInt(item[prop]);
// }
return parseInt(item.value1) + parseInt(item.value2);
}
}
});
Is that the sort of thing you're looking for?

Related

Dynamic data-title with ng-repeat in AngularJS

I'm looking for a solution to show dynamic header in the angular table for some of its <td>
my data looks like
let data = [
{
id: 1,
name: 'name',
fields: {
field 1: { value: '123'},
field 2: {value: 'macx'}
}
},
{
id: 2,
name: 'name2',
fields: {
field 1: { value: '456'},
field 2: {value: '3333'}
}
}
]
it should show in one table, I mean fields attr's should show as extra columns in the same table
note: fields are dynamic and I can't know it exactly so I need to do something like this in code
if any idea how I can get that work or any other idea to get the view as explained
<tr ng-repeat="data in $data">
<td data-title="'id'|translate"
sortable="'id'">
{{data.id}}
</td>
<td ng-repeat="(key, value) in data.fields track by $index"
ng-show="columnsHash[key]"
data-title="customFieldsTitles[$index]"
filterable="{field:'fields', type:'text', align:'LEFT'}"
data-title-text="customFieldsTitles[$index]">
{{value && value.value || ''}}
</td>
<td ng-show="columnsHash.totalBenefitTarget"
data-title="'target_total_benefit' | translate"
sortable="'total_benefit_target'"
style="text-align:center;"
filterable="{field: 'total_benefit_target', type:'number_range', options: {min: Number.MIN_VALUE, max: Number.MAX_VALUE}}">
{{data.total_benefit_target | number: 0}}
</td>
<td ng-show="columnsHash.totalBenefitActual"
data-title="'actual_total_benefit' | translate"
sortable="'total_benefit_actual'"
style="text-align:center;"
filterable="{field: 'total_benefit_actual', type:'number_range',
options: {min: Number.MIN_VALUE, max: Number.MAX_VALUE}}">
{{data.total_benefit_actual | number: 0}}
</td>
<tr>
showing columns order is important so writing it like code above
thanks in advance
angular table use scope.$column to render tb cols so I solved that by using scope binding
<table ng-table="tableParams" ng-init="initTable()">
<td ng-repeat="(key, value) in data.fields"
data-title="'Custom Field'"
sortable="'fields'"
filterable="{field:'fields', type:'text', align:'LEFT'}">
{{value && value.value || ''}}
</td>
</table>
in controller
var tableColumns;
$scope.initTable = function(){
var scope = this;
$timeout(function(){
tableColumns = scope.$columns;
});
};
after loading data for table call this function to update columns title
function updateCustomFields(){
var columnTemplate, index;
var colCount = 0;
if (!tableColumns) {
return;
}
tableColumns.map(function(col, i){
if (col.title() === 'Custom Field'){
columnTemplate = col;
index = i;
tableColumns.splice(index, 1);
return true;
}
});
for(var fieldLabel in $scope.customFieldsHash){
(function (label) {
var column = angular.copy(columnTemplate);
column.id = column.id+colCount/10;
column.title = function(){ return label; };
tableColumns.splice(index+colCount, 0, column);
colCount++;
})(fieldLabel);
}
}

Format JSON to specific table

Given this data:
[
{
'Column': 'A',
'Value': 10,
'Color': 'red'
},
{
'Column': 'B',
'Value': 25,
'Color': 'blue'
},
{
'Column': 'A',
'Value': 4,
'Color': 'blue'
}
]
I would like to create this table
<table>
<thead><td>A</td><td>B</td></thead>
<tr>
<td><span color='red'>10</span></td>
<td><span color='red'></span></td>
</tr>
<tr>
<td><span color='blue'>4</span></td>
<td><span color='blue'>25</span></td>
</tr>
</table>
using KnockoutJS such that the values are data-bound.
I modified an example here but can't seem to figure out how to do it: http://jsfiddle.net/ktqcvj4x/
I suspect it will involve pure computed functions to get distinct values
You could group the array based on Column and a nested group based on color.
const array = [
{ Column: "A", Value: 10, Color: "red" },
{ Column: "B", Value: 25, Color: "blue" },
{ Column: "A", Value: 4, Color: "blue" },
]
const group = {}
for (const { Column, Value, Color } of array) {
if (!group[Column])
group[Column] = {};
group[Column][Color] = Value;
}
console.log(group)
This creates an object like this:
{
"A": {
"red": 10,
"blue": 4
},
"B": {
"blue": 25
}
}
You can assign this to a computed property. You can also use 2 Sets to get the all the unique colors and columns.
HTML:
For the headers, loop through the columns and create td. Straightforward.
For the body, you need nested loops. For each item in colors, create a tr and for each item in columns, create a td. Use an alias in foreach so that it's easier to access properties from grouped computed property.
<tbody data-bind="foreach: { data: colors, as: 'color' }">
<tr data-bind="foreach: { data: $root.columns, as: 'column' }">
<td>
<span data-bind="style: { color: color },
text: $root.grouped()[column][color]"></span>
</td>
</tr>
</tbody>
Here's a working snippet. This works for any number of columns and colors in your array.
function viewModel(array) {
this.array = ko.observableArray(array);
this.columns = ko.observableArray([]);
this.colors = ko.observableArray([]);
this.grouped = ko.computed(_ => {
const group = {},
columns = new Set,
colors = new Set;
for (const { Column, Value, Color } of array) {
columns.add(Column)
colors.add(Color)
if (!group[Column])
group[Column] = {};
group[Column][Color] = Value;
}
this.columns(Array.from(columns))
this.colors(Array.from(colors))
return group
})
}
const v = new viewModel([
{ Column: "A", Value: 10, Color: "red" },
{ Column: "B", Value: 25, Color: "blue" },
{ Column: "A", Value: 4, Color: "blue" },
])
ko.applyBindings(v)
table, td {
border: 1px solid black;
border-collapse: collapse;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr data-bind="foreach: columns">
<td data-bind="text: $data"></td>
</tr>
</thead>
<tbody data-bind="foreach: { data: colors, as: 'color' }">
<tr data-bind="foreach: { data: $root.columns, as: 'column' }">
<td>
<span data-bind="style: { color: color },
text: $root.grouped()[column][color]"></span>
</td>
</tr>
</tbody>
</table>

How to access multiple data elements of vue js in one tag of html file using v-for?

This is a part of my Vue project where I want to get some data elements in a table. I want to take data2 element like countryData in another column of the table. How can I do this? You can run the snippet for further reference.
In HTML code I have included the vue js library and simply made a table.
var app = new Vue({
el: "#app",
data(){
return {
countryData:[{name:"india", "1999":9537, "2000":10874},
{name:"china", "1999":7537, "2000":8874},
{name:"england", "1999":11537, "2000":12074}
],
data2: [
{ser1: 1, ser2: 7},
{ser1: 4, ser2: 1},
{ser1: 6, ser2: 8}
]}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<h3>Data Table</h3>
</div>
<table>
<thead>
<tr>
<th>Country</th>
<th>Sales</th>
</tr>
</thead>
<tbody>
<tr v-for="x in countryData">
<td>{{x.name}}</td>
<! -- I want to take data2 element in another column of my table -->
</tr>
</tbody>
</table>
</div>
You can either combine the data as you wish, before displaying in template, for example:
countryData: [
{ name: "india", "1999": 9537, "2000": 10874 },
{ name: "china", "1999": 7537, "2000": 8874 },
{ name: "england", "1999": 11537, "2000": 12074 }
],
data2: [{ ser1: 1, ser2: 7 }, { ser1: 4, ser2: 1 }, { ser1: 6, ser2: 8 }],
combinedData: []
// ...
created() {
this.countryData.forEach((x, i) => {
this.combinedData.push({ ...x, ...this.data2[i] });
});
}
Then you can access in template with:
<tr v-for="(x, i) in combinedData" :key="i">
<td>{{x.name}}</td>
<td>{{x.ser1}}</td>
<td>{{x.ser2}}</td>
</tr>
or you can utilize the index in the template:
<tr v-for="(x, i) in countryData" :key="i">
<td>{{x.name}}</td>
<td>{{data2[i].ser1}}</td>
<td>{{data2[i].ser2}}</td>
</tr>

bind multidimensional array with vuejs

I am building a dynamic table on my front end side, and at the end i need to know what was inserted on each cell of my table since it is editable, so i did this on my html:
<table class="table table-responsive">
<tbody>
<tr v-for="(row,idx1) in tableRows" :class="{headerRowDefault: checkHeader(idx1)}">
<td class="table-success" v-for="(col,idx2) in tableCols"><input v-model="items[idx1][idx2]" type="text" class="borderTbl" value="HEY"/></td>
</tr>
</tbody>
</table>
as you guys can see. i set inside the input a v-model with items[idx1][idx2] so i can pass the value to that line and columns, it is not working like this, i don't know how to set it.
This is my javascript:
export default {
name: 'app',
data () {
return {
table: {
rows: 1,
cols: 1,
key: 'Table',
tableStyle: 1,
caption: '',
colx: []
},
hasHeader: true,
hasCaption: true,
insert: 1,
idx2: 1,
items: []
}
},
computed: {
tableStyles () {
return this.$store.getters.getTableStyles
},
tableRows () {
return parseInt(this.table.rows)
},
tableCols () {
return parseInt(this.table.cols)
}
expected items array:
items:[
["john","Micheal"]
["john","Micheal"]
["john","Micheal"]
["john","Micheal"]
]
So, I think you're not pointing your models correctly.
Template:
<tr v-for="(row, idx1) in items">
<td class="table-success" v-for="(col, idx2) in row">
<input v-model="items[idx1][idx2]" type="text" />
</td>
</tr>
Script:
data () {
return {
items:[
["john","Micheal"],
["john","Micheal"],
["john","Micheal"],
["john","Micheal"]
];
};
}
Here's a working fiddle of it

Display data in a table by grouping them horizontally

I have some data that has the following format:
[name:'Name1', speed:'Val1', color:'Val2']
[name:'Name2', speed:'Val4', color:'Val5']
[name:'Name3', speed:'Val6', color:'Val7']
That I want to display in a table like this:
|Name1|Name2|Name3|
______|_____|______
speed |Val1 |Val4 |Val6
color |Val2 |Val5 |Val7
What I tried to do is group my data like this in the controller:
$scope.data = {
speeds: [{
...
},{
...
},{
...
}],
colors: [{
...
},{
...
},{
...
}],
};
But I am not sure what to put inside the empty areas, because all values there represent the values of the 'val1' variable for all Names (Accounts), and my tests until now keep failing.
You can imagine this as some sort of a comparisons matrix, that is used in order to see the all the values of the same variable across different accounts.
How can I represent the data in my model in order for me to successfully display them in a table as explained?
Edit
My difficulty lies in the fact that you create a table by going from row to row, so my html looks something like this:
<table md-data-table class="md-primary" md-progress="deferred">
<thead>
<tr>
<th ng-repeat="header in headers">
{{header.value}}
</th>
</tr>
</thead>
<tbody>
<tr md-auto-select ng-repeat="field in data">
<td ng-repeat="var in field">{{var.value}}</td>
</tr>
</tbody>
</table>
So as you can see I have a loop for each row, and a loop for each value of each row. This would be easier if I wanted to display the data horizontally, but I want the data vertically. So if we where talking about cars, we would have the car models as headers, and their respective characteristics(speed, color, etc) in each row.
If this is your basic structure:
var cols = [{name:'Name1', val1:'Val1', val2:'Val2'},
{name:'Name2', val1:'Val4', val2:'Val5'},
{name:'Name3', val1:'Val6', val2:'Val7'}];
This code
$scope.table = cols.reduce(function(rows, col) {
rows.headers.push({ value: col.name });
rows.data[0].push({ value: col.speed });
rows.data[1].push({ value: col.color });
return rows;
}, {headers:[], data:[[], []]});
will give you this structure for $scope.table:
$scope.table = {
headers : [{
value : "Name1"
}, {
value : "Name2"
}, {
value : "Name3"
}
],
data : [
[{
value : 'val1'
}, {
value : 'val4'
}, {
value : 'val6'
}
],
[{
value : 'val2'
}, {
value : 'val5'
}, {
value : 'val17'
}
]
]
};
<table md-data-table class="md-primary" md-progress="deferred">
<thead>
<tr>
<th ng-repeat="header in table.headers">
{{header.value}}
</th>
</tr>
</thead>
<tbody>
<tr md-auto-select ng-repeat="field in table.data">
<td ng-repeat="var in field">{{var.value}}</td>
</tr>
</tbody>
</table>
You could try this:
HTML
<table ng-app="myTable" ng-controller="myTableCtrl">
<thead>
<tr>
<th ng-repeat="car in cars">{{car.name}}</th>
</tr>
</thead>
<tbody>
<tr>
<td ng-repeat="car in cars">{{car.speed}}</td>
</tr>
<tr>
<td ng-repeat="car in cars">{{car.color}}</td>
</tr>
</tbody>
</table>
JS
angular.module("myTable",[])
.controller("myTableCtrl", function($scope) {
$scope.cars = [
{
name:'Name1',
speed:'Val1',
color:'Val2'
},
{
name:'Name2',
speed:'Val4',
color:'Val5'
},
{
name:'Name3',
speed:'Val6',
color:'Val7'
}
]
});
https://jsfiddle.net/ABr/ms91jezr/

Categories