How to sort dates in table using vue.js - javascript

My table is based on the Grid Component Example in Vue.js' website
I'm having problem with sorting dates inside the table. I get all the table data from server side as JSON. So in the codes provided, I just mocked the data in var mockDataFromServerSide.
Here is the code: https://jsfiddle.net/5w1wzhvw/3/
HTML file:
<!-- component template -->
<script type="text/x-template" id="grid-template">
<table>
<thead>
<tr>
<th v-for="key in columns"
v-on:click="sortBy(key)"
:class="{active: sortKey == key}">
{{key | capitalize}}
<span class="arrow"
:class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="
entry in data
| filterBy filterKey
| orderBy sortKey sortOrders[sortKey]">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
</script>
<!-- demo root element -->
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid
:filter-key="searchQuery">
</demo-grid>
</div>
Js file:
var gridColumns = ['name', 'date'];
var mockDataFromServerSide = [
{ name: 'Chuck Norris', date: "01 Dec 2016" },
{ name: 'Bruce Lee', date: "23 Apr 2005" },
{ name: 'Jackie C', date: "30 Jan 2012" },
{ name: 'Jet Li', date: "20 Apr 2006" }
];
// register the grid component
Vue.component('demo-grid', {
template: '#grid-template',
props: {
filterKey: String
},
data: function () {
var sortOrders = {}
gridColumns.forEach(function (key) {
sortOrders[key] = 1
})
return {
sortKey: '',
sortOrders: sortOrders,
columns: gridColumns,
data: mockDataFromServerSide
}
},
methods: {
sortBy: function (key) {
this.sortKey = key
this.sortOrders[key] = this.sortOrders[key] * -1
}
}
})
// bootstrap the demo
var demo = new Vue({
el: '#demo',
data: {
searchQuery: ''
}
})
I also tried to add a filter to the date. The sort is correct but the displayed dates are shown as "Thu Apr 02 2016 00:00:00 GMT+0800 (China Standard Time)". I want the dates to be displayed as 02 Apr 2016.
Added filter Code: https://jsfiddle.net/kr1m5de5/1/
HTML file (added filter):
<!-- component template -->
<script type="text/x-template" id="grid-template">
<table>
<thead>
<tr>
<th v-for="key in columns"
v-on:click="sortBy(key)"
:class="{active: sortKey == key}">
{{key | capitalize}}
<span class="arrow"
:class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="
entry in data
| filterBy filterKey
| orderBy sortKey sortOrders[sortKey]
| datesFilter">
<td v-for="key in columns">
{{entry[key]}}
</td>
</tr>
</tbody>
</table>
</script>
<!-- demo root element -->
<div id="demo">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid
:filter-key="searchQuery">
</demo-grid>
</div>
JS file (added filter):
var gridColumns = ['name', 'date'];
var mockDataFromServerSide = [
{ name: 'Chuck Norris', date: "01 Dec 2016" },
{ name: 'Bruce Lee', date: "23 Apr 2005" },
{ name: 'Jackie C', date: "30 Jan 2012" },
{ name: 'Jet Li', date: "20 Apr 2006" }
];
// register the grid component
Vue.component('demo-grid', {
template: '#grid-template',
props: {
filterKey: String
},
filters: {
datesFilter: function (data) {
data.forEach(function (row) {
row.date = new Date(row.date);
});
return data;
}
},
data: function () {
var sortOrders = {}
gridColumns.forEach(function (key) {
sortOrders[key] = 1
})
return {
sortKey: '',
sortOrders: sortOrders,
columns: gridColumns,
data: mockDataFromServerSide
}
},
methods: {
sortBy: function (key) {
this.sortKey = key
this.sortOrders[key] = this.sortOrders[key] * -1
}
}
})
// bootstrap the demo
var demo = new Vue({
el: '#demo',
data: {
searchQuery: ''
}
})
Please let me know how to fix it or if there is a better way to do it.

I solved this by making a TableHeader component it says semantic cause i used semantic-ui... sorry for the spanglish in the code, must of 'em are cognates anyway. Also, this code is working, but if you see improvements to the code/answer let me know please!
As you can see, i really don't sort at front... i make a new request with the sorted items.
<template>
<th #click="cycleSort(sth, $event)">
<span><span>{{ sth.texto }} </span><i class="icon" :class="sth.icon"></i><sub v-if="sth.posicion > 0"><small>{{ sth.posicion }}</small></sub></span>
</th>
</template>
<script>
export default {
name: "SemanticTableHeader",
props: {
sth : {
type : Object,
default: () => {}
},
sths : {
type : Array,
default: () => { return [] }
},
filtrosOrder : {
type : Array,
default: () => { return [] }
},
isSearching : {
type : Boolean,
required : true
}
},
methods: {
cycleSort(sth, event) {
if(this.isSearching == true){
return false;
}
switch (sth.direction) {
case null:
sth.direction = 'asc';
sth.icon = 'sort ascending';
break;
case 'asc':
sth.direction = 'desc';
sth.icon = 'sort descending';
break;
case 'desc':
sth.direction = null;
sth.icon = 'sort disabled';
break;
default:
sth.direction = null;
sth.icon = 'sort disabled';
}
this.manejaCambioHeader(sth);
},
manejaCambioHeader: _.debounce(function (sth) {
var self = this;
console.log(this.filtrosOrder);
let auxUser = _.find(this.filtrosOrder, function(o) { return o.id == sth.id; });
if( auxUser != null ){
auxUser.direction = sth.direction;
if(auxUser.direction == null){
for (var i=0 ; i < this.filtrosOrder.length ; i++){
if (this.filtrosOrder[i].id === auxUser.id) {
let auxSths = _.find(self.sths, function(o) { return o.id == sth.id; });
auxSths.posicion = 0;
this.filtrosOrder.splice(i, 1);
}
}
}
}else{
this.filtrosOrder.push({ id: sth.id, direction: sth.direction });
}
for (var i=0 ; i < self.filtrosOrder.length; i++){
let auxSths = _.find(this.sths, function(o) { return o.id == self.filtrosOrder[i].id; });
auxSths.posicion = i + 1;
}
console.log(this.filtrosOrder);
this.$emit('sortHeaderChanged', sth);
}, 400),
},
}
</script>
<style lang="css" scoped>
th span{
cursor: pointer !important;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */
}
i.icon{
margin: 0em -0.2em 0em 0em;
}
</style>
In my Index views i just load the component and use it like this
<template>
<table>
<thead>
<tr>
<semantic-table-header v-for="sth in sths" :key="sth.key"
:sth="sth"
:sths="sths"
:isSearching="isSearching"
:filtrosOrder="filtros.orderBy"
#sortHeaderChanged="fetchIndex"
></semantic-table-header>
<th></th>
</tr>
</thead>
<tbody>
<tr v-for="contact in contacts" :key="contact.key" :class="[contact.justAdded ? 'justAdded' : '']">
</tr>
</tbody>
</table>
</template>
export default {
name: "ContactsIndex",
data:() => ({
filtros:{
orderBy:[
{ id: 'nombre', direction: 'asc' } // orderBy is calculated through the headers component
]
},
sths:[
{ id: 'nombre', texto: 'Nombre', icon: 'sort ascending', direction: 'asc', posicion: 1 },
{ id: 'telefonos', texto: 'Teléfono(s)', icon: 'sort disabled', direction: null, posicion: 0 },
{ id: 'emails', texto: 'Correo Electrónico(s)', icon: 'sort disabled', direction: null, posicion: 0 },
{ id: 'estatus', texto: 'Estatus', icon: 'sort disabled', direction: null, posicion: 0 }
],
contacts: [],
}),
created() {
this.fetchIndex();
},
methods: {
resetFilters() {
// this function is to reset filters and headers
Object.assign(this.$data.filtros, this.$options.data().filtros);
this.$data.sths = this.$options.data().sths;
this.fetchIndex();
},
fetchIndex() {
let self = this;
// this is a wrapper i made for an axios post call you can replace it with a normal call
singleIndexRequest('/api/v1/contacts/index', self).then(response => {
self.contacts = response.data.contacts;
});
},
}
}

Related

Fixing vue data map to group by date as well as employee

I currently have a vue component and template in which I'm listing employees and their hours/scans by date. The problem is my current map is totaling all hours and scans by the first record and it's date.
I need to modify this because my table headers are dates (today, tomorrow and the day after). So I need to be able to use a v-if statement for each to compare the date in the column header to the date of the record. In this instance, I should only have one record for employee A123 but I should have 2 records for employee D432 because the two records for that employee have different dates.
How can I also factor date into the unique mapping here?
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: "#app",
data: {
rows: [{
employee: "A123",
hours: "15",
date: "2021-08-31",
scans: "4"
},
{
employee: "A123",
hours: "25",
date: "2021-08-31",
scans: "4"
},
{
employee: "D432",
hours: "82",
date: "2021-09-02",
scans: "2"
},
{
employee: "D432",
hours: "40",
date: "2021-09-01",
scans: "5"
}
]
},
methods: {
groupByField(list, field) {
const result = {};
list.forEach(item => {
const value = item[field];
if (value) {
if (!result[value]) {
result[value] = [];
}
result[value].push(item);
}
});
return result;
}
},
computed: {
compRows() {
const a = this.groupByField(this.rows, 'employee');
let b = Object.values(a)
return b.map(item => {
return {
employee: item[0].employee,
hours: item.reduce((acc, _item) => (+acc) + (+_item.hours), 0),
scans: item.reduce((acc, _item) => (+acc) + (+_item.scans), 0),
date: item[0].date
}
})
}
}
});
th,td{
padding:8px
}
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app" class="container">
<table class="table">
<thead>
<tr>
<th>Employee</th>
<th>hours</th>
<th>scans</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in compRows">
<td>{{row.employee}}</td>
<td>{{row.hours}}</td>
<td>{{row.scans}}</td>
<td>{{row.date}}</td>
</tr>
</tbody>
</table>
</div>
You can group based on employee and then on date and sum up date and scans in array#reduce.
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: "#app",
data: {
rows: [{employee: "A123", hours: "15", date: "2021-08-31", scans: "4" }, { employee: "A123", hours: "25", date: "2021-08-31", scans: "4" }, { employee: "D432", hours: "82", date: "2021-09-02", scans: "2" }, { employee: "D432",hours: "40",date: "2021-09-01",scans: "5"}]
},
computed: {
compRows() {
const grouped = this.rows.reduce((r, o) => {
r[o.employee] ??= {};
r[o.employee][o.date] ??= {employee: o.employee, date: o.date, scans: 0, hours: 0};
r[o.employee][o.date].scans += +o.scans;
r[o.employee][o.date].hours += +o.hours;
return r;
}, {});
return Object.values(grouped).map(o => Object.values(o)).flat();
}
}
});
th,td{
padding:8px
}
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app" class="container">
<table class="table">
<thead>
<tr>
<th>Employee</th>
<th>hours</th>
<th>scans</th>
<th>date</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, index) in compRows">
<td>{{row.employee}}</td>
<td>{{row.hours}}</td>
<td>{{row.scans}}</td>
<td>{{row.date}}</td>
</tr>
</tbody>
</table>
</div>

Knockout Table : Highlight a Table Row

I have an Example Fiddle here. In this Table I wish to achieve Highlighting a Particular Row selected. If unselected Row should not be highlighted.
One of many sample I found Fiddle but I am unable to incorporate them inside my Example Fiddle Above.
Below is the HTML Code which shows basic Table.
<table id="devtable">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr data-bind=" click: $parent.select ">
<td data-bind="text: ID"></td>
<td data-bind="text: Name"></td>
<td data-bind="text: Status"></td>
</tr>
</tbody>
ID :
Name :
Status :
Here is the knockout function to do manipulations
<Script>
var rowModel = function (id, name, status) {
this.ID = ko.observable(id);
this.Name = ko.observable(name);
this.Status = ko.observable(status);
};
var myData = [{
id: "001",
name: "Jhon",
status: "Single"
}, {
id: "002",
name: "Mike",
status: "Married"
}, {
id: "003",
name: "Marrie",
status: "Complicated"
}];
function MyVM(data) {
var self = this;
self.items = ko.observableArray(data.map(function (i) {
return new rowModel(i.id, i.name, i.status);
}));
self.select = function(item) {
self.selected(item);
self.enableEdit(true);
};
self.flashCss = ko.computed(function () {
//just an example
return 'flash';
});
self.selected = ko.observable(self.items()[0]);
self.enableEdit = ko.observable(false);
self.changeTableData = function() {
// How do I change the Data here and it should also reflect on the Page.
// If I do binding depending on condition it gives me error
if(true){
var myData = [{
id: "001",
name: "Jhon",
status: "Single"
}, {
id: "002",
name: "Mike",
status: "Married"
}, {
id: "003",
name: "Marrie",
status: "Complicated"
}];
}
else{
myData = [{
id: "111",
name: "ABC",
status: "Single"
}, {
id: "222",
name: "XYZ",
status: "Married"
}, {
id: "3333",
name: "PQR",
status: "Complicated"
}];
}
}
}
ko.applyBindings(new MyVM(myData));
</script>
CSS code below
.flash { background-color: yellow; }
You can use the css binding to add the .flash class based on the currently selected value:
<tr data-bind="click: $parent.select,
css: { flash: $parent.selected() === $data }">
...
</tr>
If you don't like this logic being defined in the view, you can pass a reference to the selected observable and create a computed property inside your RowModel:
var RowModel = function( /* ... */ selectedRow) {
// ...
this.isSelected = ko.pureComputed(function() {
return selectedRow() === this;
}, this);
}
Here's the quick fix in your fiddle:
http://jsfiddle.net/wa78zoe4/
P.S. if you want toggle-behavior, update select to:
self.select = function(item) {
if (item === self.selected()) {
self.selected(null);
self.enableEdit(false);
} else {
self.selected(item);
self.enableEdit(true);
}
};

Dynamically Filter an Array of Objects in Vue.js

I have a Vue.js app. In this app, I'm trying to dynamically apply filter value to an Array of objects. Each object in the Array has fields. I'm trying to filter these objects by field values. Each field can be filtered by multiple values.
At this time, I have been unsuccessful in figuring out how to do this filtering. I've tried using JavaScript's baked-in filter function. However, that always returned an empty result set for me. I've put together this Fiddle, which includes this code:
new Vue({
el: '#app',
data: {
currentFilterProperty: '',
currentFilterValue: '',
cols: [
{ title: 'Name', prop:'name' },
{ title: 'Age', prop:'age' },
{ title: 'Birthday', prop:'birthday' },
],
dataFilters: [],
data: [
{ name:'Patricia Miller', age:69, birthday:'04-15-1948' },
{ name:'Bill Baggett', age:62, birthday:'05-07-1955' },
{ name:'Maxine Thies', age:21, birthday:'11-28-1995' },
{ name:'Alison Battle', age:65, birthday:'08-07-1952' },
{ name:'Dick Triplett', age:25, birthday:'08-27-1982' }
]
},
methods: {
addFilter: function() {
var f = this.dataFilters[this.currentFilterProperty];
if (!f) {
this.dataFilters = {};
this.dataFilters[this.currentFilterProperty] = [ this.currentFilterValue ];
} else {
this.dataFilters[this.currentFilterProperty].push(this.currentFilterValue);
}
// How to apply filter?
}
}
})
I'm not sure how to apply the filters to the data object.
Complete solution. Best test: add filter Age 62, then Birthday 04-15-1948, then 'tri' in Name Patricia.
new Vue({
el: '#app',
data: {
filteredProperty: 'name',
query: '',
activeFilters: [],
data: [
{name: 'Patricia Miller', age: 62, birthday: '04-15-1948'},
{name: 'Bill Baggett', age:62, birthday: '04-15-1948' },
{name:'Maxine Thies', age:62, birthday:'11-28-1948'},
{name:'Alison Battle', age:65, birthday:'08-07-1952'},
{name:'Dick Triplett', age:25, birthday:'08-27-1982'}
]
},
computed: {
filtered () {
var filtered = this.data
this.activeFilters.forEach(filter => {
filtered = filtered.filter(record => {
return filter.name === 'name'
? new RegExp(filter.value, 'i').test(record[filter.name])
: record[filter.name] == filter.value
})
})
return filtered
}
},
methods: {
addFilter () {
this.activeFilters.push({
name: this.filteredProperty,
value: this.query
})
this.query = ''
},
removeFilter (idx) {
this.activeFilters.splice(idx, 1)
}
}
})
<div id="app">
<div>
<select v-model="filteredProperty">
<option value="name">Name</option>
<option value="age">Age</option>
<option value="birthday">Birthdate</option>
</select>
<input placeholder="filter value" v-model="query">
<button #click="addFilter">add filter</button>
</div>
<hr>
<table v-if="activeFilters.length">
<tr style="width: 100px">
<th colspan="3">Filters in use:</th>
</tr>
<tr v-for="(filter, index) in activeFilters" :key="index">
<td>{{ _.capitalize(filter.name) }}:</td>
<td>{{ filter.value }}</td>
<td style="padding-left: 10px;">
<a href="#" #click.prevented=removeFilter(index)>
remove
</a>
</td>
</tr>
</table>
<hr v-if="activeFilters.length">
<table>
<tbody>
<tr v-for="(record, index) in filtered" :key="index">
<td style="padding-right:18px;">{{ record.name }}</td>
<td style="padding-right:18px;">{{ record.age }}</td>
<td>{{ record.birthday }}</td>
</tr>
</tbody>
</table>
</div>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/lodash"></script>
Take a look the below code. Change your method to a computed property and then the filter can happen automatically without pressing a button. The fiddle always filters by Name so you'll need to make a few adjustments to work for all filter criteria, but it should get you moving in the correct direction.
new Vue({
el: '#app',
data: {
currentFilterProperty: '',
currentFilterValue: '',
cols: [
{ title: 'Name', prop:'name' },
{ title: 'Age', prop:'age' },
{ title: 'Birthday', prop:'birthday' }
],
data: [
{ name:'Patricia Miller', age:69, birthday:'04-15-1948' },
{ name:'Bill Baggett', age:62, birthday:'05-07-1955' },
{ name:'Maxine Thies', age:21, birthday:'11-28-1995' },
{ name:'Alison Battle', age:65, birthday:'08-07-1952' },
{ name:'Dick Triplett', age:25, birthday:'08-27-1982' }
]
},
computed:{
filteredData(){
var self = this;
// Add condition for currentFilterProperty == 'Name'
if(this.currentFilterValue != undefined && this.currentFilterValue != ''){
return this.data.filter(function(d){
//alert(d.name + " " + this.currentFilterValue);
return d.name.indexOf(self.currentFilterValue) != -1;
});
}
// else if(currentFilterProperty == 'Date'){
// return this.data.filter(function(d){
//return d.birthday.indexOf(self.currentFilterValue) != -1;
// });
else{
return this.data;
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.4/vue.min.js"></script>
<div id="app">
<div>
<select v-model="currentFilterProperty">
<option v-for="c in cols" :value="c.prop">{{c.title}}</option>
</select>
<input placeholder="filter value" v-model="currentFilterValue" />
</div>
<hr />
<table>
<tbody>
<tr v-for="(record, index) in filteredData">
<td style="padding-right:18px;">{{ record.name }}</td>
<td style="padding-right:18px;">{{ record.age }}</td>
<td>{{ record.birthday }}</td>
</tr>
</tbody>
</table>
</div>
Here is working solution By checking captaining condition
new Vue({
el: '#app',
data: {
currentFilterProperty: 'name',
currentFilterValue: '',
filteredData:[],
cols: [
{ title: 'Name', prop:'name' },
{ title: 'Age', prop:'age' },
{ title: 'Birthday', prop:'birthday' },
],
dataFilters: [],
addFilters:[],
data: [
{ name:'Patricia Miller', age:69, birthday:'04-15-1948' },
{ name:'Bill Baggett', age:62, birthday:'05-07-1955' },
{ name:'Maxine Thies', age:21, birthday:'11-28-1995' },
{ name:'Alison Battle', age:65, birthday:'08-07-1952' },
{ name:'Dick Triplett', age:25, birthday:'08-27-1982' }
]
},
methods: {
addFilter: function() {
if(!this.currentFilterValue){
return false;
}
var obj = {};
this.addFilters.push({name:this.currentFilterProperty,value:this.currentFilterValue});
this.currentFilterValue = "";
var vm = this;
this.dataFilters = this.data
//var temp = [];
for(var i in vm.addFilters){
this.dataFilters = this.dataFilters.filter(function(a,b){
return ((a[vm.addFilters[i].name]).toString().toLowerCase()).indexOf((vm.addFilters[i].value).toString().toLowerCase()) !== -1;
});
}
// How to apply filter?
}
},
mounted(){
this.dataFilters = this.data;
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div>
<select v-model="currentFilterProperty">
<option value="name">Name</option>
<option value="age">Age</option>
<option value="birthday">Birthdate</option>
</select>
<input placeholder="filter value" v-model="currentFilterValue" />
<button v-on:click="addFilter">
add filter
</button>
</div>
<div v-for="(filter,index) in addFilters">{{filter.name}} : {{filter.value}}</div>
<hr />
<table>
<tbody>
<tr v-for="(record, index) in dataFilters">
<td style="padding-right:18px;">{{ record.name }}</td>
<td style="padding-right:18px;">{{ record.age }}</td>
<td>{{ record.birthday }}</td>
</tr>
</tbody>
</table>
</div>

Displaying json object details in knockout js

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

Knockout group list into smaller lists with objects

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>

Categories