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>
Related
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);
}
}
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
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?
I have the following fiddle where I am trying to display the data in key:value pairs,
i.e., key as header and followed by the information as rows .
I have the data in this format:
self.data = ko.observableArray([{
1:
{
name: 'Name 1',
lastLogin: '8/5/2012'
}
}
, {
2:
{
name: 'Name 2',
lastLogin: '2/8/2013'
}
}
]);
I have fiddle as :
https://jsfiddle.net/1988/z7nnf0fh/1/
I am expecting as:
1
name Name 1 lastLogin 8/5/2012
2
name Name 2 lastLogin 2/8/2013
I'd personally move all logic to your viewmodel. Then you could either use ko.toJSON to stringify the contents of each object or if you really want to have the output like above, you could do:
function DataModel() {
var self = this;
self.data = ko.observableArray([{
1: {
name: 'Name 1',
lastLogin: '8/5/2012'
}
}, {
2: {
name: 'Name 2',
lastLogin: '2/8/2013'
}
}
]);
self.formattedValues = ko.observableArray([]);
self.formatData = function() {
var tempRow = [];
ko.utils.arrayForEach(self.data(), function(item) {
for (var i in item) {
for (var j in item[i]) {
tempRow.push({
key: j,
value: item[i][j]
});
}
self.formattedValues.push({
key: i,
rows: tempRow
});
tempRow = [];
}
})
};
self.formatData();
}
var dataModel = new DataModel();
ko.applyBindings(dataModel);
.name {
color: #bbb;
}
.value {
fot-weight: bold
}
th {
width: 25px;
}
p {
margin-right: 10px;
display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="template: { name: 'template', data: formattedValues }"></div>
<script type="text/html" id="template">
<table>
<tbody data-bind="foreach: $data">
<tr>
<td data-bind="text: key"></td>
</tr>
<tr>
<td data-bind="foreach: rows">
<p>
<span class="name" data-bind="text: key + ': '"></span>
<span class="value" data-bind="text: value"></span>
</p>
</td>
</tr>
</tbody>
</table>
</script>
Hope that helps in some way
I have daily data for multiple employees and depending on the start time and end time that could mean a lot of data.
So with the mapping plugin i mapped them into one big list, but i will need them grouped by employee into smaller lists so i can make a tables per employee (like smaller view models) that has filtering and sorting for that subset of data.
Here is a basic example i created with static data.
$(function () {
var data = {
Employees: [{
Id: 1,
Name: "Employee1",
Day: new Date(),
Price: 12.54
}, {
Id: 2,
Name: "Employee2",
Day: new Date(),
Price: 112.54
}, {
Id: 1,
Name: "Employee1",
Day: new Date(),
Price: 12.54
}, {
Id: 3,
Name: "Employee3",
Day: new Date(),
Price: 12.54
}]
};
// simulate the model to json conversion. from now on i work with the json
var jsonModel = JSON.stringify(data);
function employeeModel(data) {
var employeeMapping = {
'copy': ["Id", "Name", "Day", "Price"]
};
ko.mapping.fromJS(data, employeeMapping, this);
}
function employeeViewModel(data) {
var self = this;
var employeesMapping = {
'Employees': {
create: function (options) {
return new employeeModel(options.data);
}
}
};
ko.mapping.fromJSON(data, employeesMapping, self);
}
var productsModel = new employeeViewModel(jsonModel);
ko.applyBindings(productsModel);
});
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
tr:nth-child(even) {
background-color: white;
}
tr:nth-child(odd) {
background-color: #C1C0C0;
}
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<table>
<tbody data-bind="foreach: Employees">
<tr>
<td><span data-bind="text:Id"></span>
</td>
<td><span data-bind="text:Name"></span>
</td>
<td><span data-bind="text:Day"></span>
</td>
<td><span data-bind="text:Price"></span>
</td>
</tr>
</tbody>
</table>
One possibility would be to use a computed value to group your data.
self.EmployeeGroups = ko.pureComputed(function () {
var employees = self.Employees(),
index = {},
group = [];
ko.utils.arrayForEach(employees, function(empl) {
var id = ko.unwrap(empl.Id);
if ( !index.hasOwnProperty(id) ) {
index[id] = {
grouping: {
Id: empl.Id,
Name: empl.Name
},
items: []
};
group.push(index[id]);
}
index[id].items.push(empl);
});
return group;
});
would turn your data from a flat array to this:
[{
grouping: {
Id: /* ... */,
Name: /* ... */
}
items: [/* references to all employee objects in this group */]
}, {
/* same */
}]
Expand the code snippet below to see it at work.
$(function () {
var data = {
Employees: [{
Id: 1,
Name: "Employee1",
Day: new Date(),
Price: 12.54
}, {
Id: 2,
Name: "Employee2",
Day: new Date(),
Price: 112.54
}, {
Id: 1,
Name: "Employee1",
Day: new Date(),
Price: 12.54
}, {
Id: 3,
Name: "Employee3",
Day: new Date(),
Price: 12.54
}]
};
var jsonModel = JSON.stringify(data);
function employeeModel(data) {
var employeeMapping = {
'copy': ["Id", "Name", "Day", "Price"]
};
ko.mapping.fromJS(data, employeeMapping, this);
}
function employeeViewModel(data) {
var self = this;
self.Employees = ko.observableArray();
self.EmployeeGroups = ko.pureComputed(function () {
var employees = self.Employees(),
index = {},
group = [];
ko.utils.arrayForEach(employees, function(empl) {
var id = ko.unwrap(empl.Id);
if ( !index.hasOwnProperty(id) ) {
index[id] = {
grouping: {
Id: empl.Id,
Name: empl.Name
},
items: []
};
group.push(index[id]);
}
index[id].items.push(empl);
});
return group;
});
// init
var employeesMapping = {
'Employees': {
create: function (options) {
return new employeeModel(options.data);
}
}
};
ko.mapping.fromJSON(data, employeesMapping, self);
}
var productsModel = new employeeViewModel(jsonModel);
ko.applyBindings(productsModel);
});
table {
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
tr:nth-child(even) {
background-color: #efefef;
}
tr:nth-child(odd) {
background-color: #CCCCCC;
}
tr.subhead {
background-color: #D6E3FF;
font-weight: bold;
}
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<table>
<!-- ko foreach: EmployeeGroups -->
<tbody>
<!-- ko with: grouping -->
<tr class="subhead">
<td colspan="2">
<span data-bind="text: Id"></span>
<span data-bind="text: Name"></span>
</td>
</tr>
<!-- /ko -->
<!-- ko foreach: items -->
<tr>
<td><span data-bind="text: Day"></span></td>
<td><span data-bind="text: Price"></span></td>
</tr>
<!-- /ko -->
</tbody>
<!-- /ko -->
</table>
<pre data-bind="text: ko.toJSON($root, null, 2)" style="font-size: smallest;"></pre>