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>
Related
I need to build a table in order to organize some data. I'm using the "onclick" function in order to call a separate function, which is supposed to increment a state variable up by one. My Chrome Devtools isn't giving me any errors but also isn't updating the stock variable. I'm not sure how to get the state to update and display. Here's my source code:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cars: [
{
manufacturer: "Toyota",
model: "Rav4",
year: 2008,
stock: 3,
price: 8500
},
{
manufacturer: "Toyota",
model: "Camry",
year: 2009,
stock: 2,
price: 6500
},
{
manufacturer: "Toyota",
model: "Tacoma",
year: 2016,
stock: 1,
price: 22000
},
{
manufacturer: "BMW",
model: "i3",
year: 2012,
stock: 5,
price: 12000
},
]
};
this.renderCar = this.renderRow.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(() => {
return {stock: this.stock + 1}
})
}
renderRow(car, index) {
return (
<tr key={index}>
<td key={car.manufacturer}>{car.manufacturer}</td>
<td key={car.model}>{car.model}</td>
<td key={car.year}>{car.year}</td>
<td key={car.stock}>{car.stock}</td>
<td key={car.price}>${car.price}.00</td>
<td key={index}><input type="button" onClick={car.handleClick} value="Increment" /></td>
</tr>
)
}
render() {
return (
<div>
<table>
<thead>
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th>Year</th>
<th>Stock</th>
<th>Price</th>
<th>Option</th>
</tr>
</thead>
<tbody>
{this.state.cars.map(this.renderRow)}
</tbody>
</table>
</div>
);
};
}
ReactDOM.render(<App />, document.getElementById("app"))
I'd make a separate component for the row, so that you can easily update that component to increment the stock value in state:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
cars: [
{
manufacturer: "Toyota",
model: "Rav4",
year: 2008,
stock: 3,
price: 8500
},
{
manufacturer: "Toyota",
model: "Camry",
year: 2009,
stock: 2,
price: 6500
},
{
manufacturer: "Toyota",
model: "Tacoma",
year: 2016,
stock: 1,
price: 22000
},
{
manufacturer: "BMW",
model: "i3",
year: 2012,
stock: 5,
price: 12000
},
]
};
}
render() {
return (
<div>
<table>
<thead>
<tr>
<th>Manufacturer</th>
<th>Model</th>
<th>Year</th>
<th>Stock</th>
<th>Price</th>
<th>Option</th>
</tr>
</thead>
<tbody>
{this.state.cars.map(
(car, i) => <Row car={car} key={i} />
)}
</tbody>
</table>
</div>
);
};
}
const Row = ({ car }) => {
const [stock, setStock] = React.useState(car.stock);
return (
<tr>
<td>{car.manufacturer}</td>
<td>{car.model}</td>
<td>{car.year}</td>
<td>{stock}</td>
<td>${car.price}.00</td>
<td><input type="button" onClick={() => setStock(stock + 1)} value="Increment" /></td>
</tr>
);
}
ReactDOM.render(<App />, document.getElementById("app"))
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id='app'></div>
You could put it all in one component if you had to, but it'd be a bit cumbersome. While rendering, you'd have to keep track of the render index of a row and pass that along to the click handler, then immutably update the stock property in the state array at that index. A separate component is easier.
handleClick(e){
const index = Number(e.currentTarget.value);
this.setState(this.state.cars.map(car, i)=> {
return i === index ? {...car, stock: car.stock + 1}: car
})
}
renderRow(){
....
<input type="button" onClick={this.handleClick} value={index} />
...
}
I have here a table in which a person's data will be displayed
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Username</th>
<th scope="col">Birthdate</th>
<th scope="col">Age</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<!-- existing data could optionally be included here -->
</tbody>
</table>
this template is will be use on putting the data and append it on the tbody
<template id="persons">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</template>
this is the JavaScript code that I have
let oCRUD = {
init: function() {
this.setDOMElements();
this.getPersons();
},
// Setting DOM Elements
setDOMElements: function() {
this.oTemplate = document.querySelector('#persons'); //persons row
this.oTbody = document.querySelector("tbody");
this.oClone = document.importNode(oCRUD.oTemplate.content, true);
this.oTd = oCRUD.oClone.querySelectorAll("td");
},
getPersons: function() {
axios.get('selectAll.php')
.then(function (response) {
response.data.forEach((element,index) => {
oCRUD.oTd[0].textContent = element.name;
oCRUD.oTd[1].textContent = element.username;
oCRUD.oTd[2].textContent = element.birthdate;
oCRUD.oTd[3].textContent = element.age;
oCRUD.oTd[4].textContent = element.email;
oCRUD.oTbody.appendChild(oCRUD.oClone);
});
})
.catch(function (error) {
console.log(error);
});
}
}
// call the init function
oCRUD.init();
How can I use the template put the data there after the successful response of axios and append it on the tbody. This is my first time using DOM templating I have no idea how to start it.
This is the successful response after axios get request
[
{
id: "1",
name: "john",
username: "john doe",
birthdate: "1999-05-21",
age: "20",
email: "test#gmail.com",
},
{
id: "2",
name: "sally",
username: "sally mcsalad",
birthdate: "1999-03-27",
age: "20",
email: "try#gmail.com",
},
]
EDIT: I SUCCESSFULLY SHOW THE DATA HOWEVER I ONLY GOT THE SECOND SET OF DATA (sally mcsalad) NOT THE WHOLE DATA
Your main problem is you only clone the node, and select the tds once. This counts as a single object, which will just update the existing elements on each iteration. You need to refresh the clone and the selected tds on each iteration
var data = [
{
id: "1",
name: "john",
username: "john doe",
birthdate: "1999-05-21",
age: "20",
email: "test#gmail.com",
},
{
id: "2",
name: "sally",
username: "sally mcsalad",
birthdate: "1999-03-27",
age: "20",
email: "try#gmail.com",
},
];
let oCRUD = {
init: function() {
this.setDOMElements();
this.getPersons();
},
// Setting DOM Elements
setDOMElements: function() {
this.oTemplate = document.querySelector('#persons'); //persons row
this.oTbody = document.querySelector("tbody");
this.oClone = document.importNode(oCRUD.oTemplate.content, true);
this.oTd = oCRUD.oClone.querySelectorAll("td");
},
refreshClone: function() {
this.oClone = document.importNode(oCRUD.oTemplate.content, true);
this.oTd = oCRUD.oClone.querySelectorAll("td");
},
getPersons: function() {
/*axios.get('selectAll.php')
.then(function (response) {*/
data.forEach((element,index) => {
oCRUD.refreshClone();
oCRUD.oTd[0].textContent = element.name;
oCRUD.oTd[1].textContent = element.username;
oCRUD.oTd[2].textContent = element.birthdate;
oCRUD.oTd[3].textContent = element.age;
oCRUD.oTd[4].textContent = element.email;
oCRUD.oTbody.appendChild(oCRUD.oClone);
});
/*})
.catch(function (error) {
console.log(error);
});*/
}
}
// call the init function
oCRUD.init();
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Username</th>
<th scope="col">Birthdate</th>
<th scope="col">Age</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<!-- existing data could optionally be included here -->
</tbody>
</table>
<template id="persons">
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</template>
add the function generateTable to your code and call it after success your request
function generateTable(persons){
let oTbody = document.querySelector("tbody");
if(!persons) return;
persons.forEach( person=>{
let tr =
`<tr id=${person.id}>
<td>${person.name}</td>
<td>${person.username}</td>
<td>${person.birthday}</td>
<td>${person.age}</td>
<td>${person.email}</td>
</tr>`
oTbody.insertAdjacentHTML('beforeend', tr);
})
}
let persons = [
{
id: "1",
name: "john",
username: "john doe",
birthdate: "1999-05-21",
age: "20",
email: "test#gmail.com",
},
{
id: "2",
name: "sally",
username: "sally mcsalad",
birthdate: "1999-03-27",
age: "20",
email: "try#gmail.com",
},
]
generateTable(persons)
<table class="table">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Username</th>
<th scope="col">Birthdate</th>
<th scope="col">Age</th>
<th scope="col">Email</th>
</tr>
</thead>
<tbody>
<!-- existing data could optionally be included here -->
</tbody>
</table>
Use template function in general, such as lodash.template or jQuery.tmpl, you can also implement a simple template function.
Step1: define a template function to transit each data object to html string.
# use ES2015 string template feature
function transitData(data){
return '<tr>' +
`<td class="record">${data.name}</td>` +
`<td>${data.email}</td>` +
'</tr>';
}
Step2: get Server response and render your data collection(such as array).
axios.get('selectAll.php').then(response => {
let html = response.data.map(transitData).join("\n");
oTbody.insertAdjacentHTML('beforeend', html);
});
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);
}
};
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;
});
},
}
}
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>