Created
September 26, 2013 16:29
-
-
Save kyeotic/6716633 to your computer and use it in GitHub Desktop.
A grid for durandal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<table class="paging-container grid-table" data-bind="grid: gridConfig"> | |
<tbody class="grid-body" data-part="body" data-bind="foreach: { data: currentPageRows, as: 'row' }" > | |
<tr class="grid-row" data-bind="css: { 'grid-row-odd': $index() % 2 == 1 }"> | |
<td class="grid-column-details" data-bind="click: $root.showJobDetails"><img class="info-btn" src="/Content/images/locationMoreInfoIcon.png"/></td> | |
<td class="grid-column-isNew" data-bind="if: isNew"><img src="Content/images/newJobStarHH.png"/></td> | |
<td class="grid-column-startDate" data-bind="text: startDate().format('{MM}/{dd}/{yyyy}')"></td> | |
<td class="grid-column-category" data-bind="text: jobCategory"></td> | |
<td class="grid-column-term" data-bind="text: term"></td> | |
<td class="grid-column-shift" data-bind="text: shiftStart"></td> | |
<td class="grid-column-type" data-bind="text: jobType"></td> | |
<td class="grid-column-spec" data-bind="text: specialty"></td> | |
<td class="grid-column-site" data-bind="text: hospital"></td> | |
<td class="grid-column-unit" data-bind="text: unit"></td> | |
<td class="grid-column-loc" data-bind="text: location"></td> | |
<td class="grid-column-rate" data-bind="text: '$' + bestRate() + '/hr'"></td> | |
<td class="grid-column-status" data-bind="command: action"> | |
<button class="btn" data-bind="css: status().toLowerCase() + '_btn'"></button> | |
</td> | |
</tr> | |
</tbody> | |
</table> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(['durandal/app', 'knockout', 'services/jobs', 'job/jobDetail'], function (app, ko, jobService, JobDetail) { | |
return function JobsGrid() { | |
var self = this; | |
self.jobs = ko.observableArray(); | |
self.activate = function() { | |
return jobService.getAgencyNurseJobs().then(function(jobs) { | |
//app.log('jobs page setup', jobs); | |
self.jobs(jobs); | |
}) | |
.fail(function(error) { | |
app.log(error); | |
}).done(); | |
}; | |
self.showJobDetails = function(job) { | |
JobDetail.show(job); | |
}; | |
self.gridConfig = { | |
pageSize: 13, | |
columns: [ | |
{ header: '', property: '', canSort: false }, | |
{ header: '', property: 'isNew', canSort: false }, | |
{ header: 'Start Date', property: 'startDate' }, | |
{ header: 'Category', property: 'jobCategory' }, | |
{ header: 'Term', property: 'term' }, | |
{ header: 'Shift Start', property: 'shiftStart' }, | |
{ header: 'Job Type', property: 'jobType' }, | |
{ header: 'Specialty', property: 'specialty' }, | |
{ header: 'Work Site', property: 'hospital' }, | |
{ header: 'Unit', property: 'unit' }, | |
{ header: 'Location', property: 'location' }, | |
{ header: 'Best Rate', property: 'bestRate' }, | |
{ header: 'Status', property: 'status', sort: function(a, b) { return a.jobStatus() < b.jobStatus() ? -1 : 1; } } | |
], | |
data: self.jobs | |
}; | |
}; | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<thead class="grid-columns" data-part="header"> | |
<tr class="grid-column" data-part="headerRow" data-bind="foreach: columns"> | |
<th class="grid-header-cell" data-bind="click: $parent.setSortColumn"> | |
<span data-bind="text: header"></span> | |
<!-- ko if: $data == $parent.sortColumn() --> | |
<img class="sort-btn" data-bind="attr: { src: 'Content/images/' + ($parent.sortDesc() ? 'sort-asc.gif' : 'sort-desc.gif') }" alt=""/> | |
<!-- /ko --> | |
</th> | |
</tr> | |
</thead> | |
<tbody class="grid-body" data-bind="foreach: { data: currentPageRows, as: 'row' }" data-part="body"> | |
<tr data-bind="foreach: $parent.columns" data-part="bodyRow"> | |
<td data-bind="text: $parents[1].getColumnText($data, row)"></td> | |
</tr> | |
</tbody> | |
<tfoot> | |
<tr data-part="footer"> | |
<td class="grid-footer" data-bind="attr: { colspan: columns().length }"> | |
<button class="btn-page" data-bind="click: pageToFirst">First</button> | |
<button class="btn-page" data-bind="command: pageBackward">Prev</button> | |
<!-- ko foreach: pageButtons --> | |
<button class="btn-page" data-bind="css: { 'btn-page-active' : isActive }, click: $parent.goToPage, text: name"></button> | |
<!-- /ko --> | |
<button class="btn-page" data-bind="command: pageForward">Next</button> | |
<button class="btn-page" data-bind="click: pageToLast">Last</button> | |
</td> | |
</tr> | |
</tfoot> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define(['durandal/app', 'knockout'], function (app, ko) { | |
/* | |
This widget can only be bound on a <Table> element in the DOM | |
Overridding it's parts can only be done if the un-"processed" <Table> would have legal HTML structure | |
Otherwise it will not render correctly in IE | |
*/ | |
return function Grid() { | |
var self = this, | |
rows = ko.observableArray(); | |
self.columns = ko.observableArray(); | |
self.activate = function(config) { | |
//app.log('grid setup', config); | |
var columns = config.columns, | |
pageSize = config.pageSize || 10, | |
alwaysShowPaging = config.alwaysShowPaging || true, | |
pageSizeOptions = config.pageSizeOptions || [25, 50, 75, 100], | |
showPageSizeOptions = config.showPageSizeOptions || false; | |
self.columns(columns); | |
rows(config.data); | |
self.pageSize(pageSize); | |
self.alwaysShowPaging(alwaysShowPaging); | |
self.pageSizeOptions(pageSizeOptions); | |
self.showPageSizeOptions(showPageSizeOptions); | |
}; | |
self.getColumnText = function(column, row) { | |
if (!column.property) | |
return ''; | |
return ko.unwrap(row[column.property]); | |
}; | |
//sorting | |
var customSort; | |
self.sortDesc = ko.observable(true); | |
self.sortColumn = ko.observable({}); | |
self.setSortColumn = function (column) { | |
if (column.canSort === false) | |
return; | |
//If column.sort is undefined, it will clear the customSort, which is what we want in that case | |
customSort = column.sort; | |
//Switch if column is same, otherwise set to true | |
self.sortDesc(column == self.sortColumn() ? !self.sortDesc() : true); | |
self.sortColumn(column); | |
}; | |
var standardSort = function(a, b, sortProperty) { | |
var propA = ko.unwrap(a[sortProperty]), | |
propB = ko.unwrap(b[sortProperty]); | |
if (propA == propB) | |
return 0; | |
return propA < propB ? -1 : 1; | |
}; | |
self.sortedRows = ko.computed(function () { | |
//The reason we have to read AND unwrap the rows is because rows is observable | |
//But the data is was passed through activate is its content, which may ALSO be observable | |
//It's admittedly a bit strange, but it makes the external API the simplest | |
//If a layer before sorting every gets introduced (like filtering), this "double" needs to go there | |
var sorted = ko.unwrap(rows()), | |
sortDirection = self.sortDesc() ? 1 : -1, | |
sortProperty = self.sortColumn().property || ''; | |
if (sortProperty === '' ) | |
return sorted; | |
var sort; | |
if (customSort) | |
sort = function(a, b) { return customSort(a, b) * sortDirection; }; | |
else | |
sort = function (a, b) { return standardSort(a, b, sortProperty) * sortDirection; }; | |
return sorted.sort(sort); | |
}).extend({ throttle: 10 }); //Throttle so that sortColumn and direction don't cause double update, it flickers | |
//pagination | |
self.pageSize = ko.observable(20); | |
self.pageIndex = ko.observable(0); | |
self.pageSizeOptions = ko.observableArray(); | |
self.alwaysShowPaging = ko.observable(true); | |
self.showPageSizeOptions = ko.observable(false); | |
self.lastPageIndex = ko.computed(function () { | |
return Math.max(Math.ceil(self.sortedRows().length / self.pageSize()) - 1, 0); | |
}); | |
self.pageCurrentNumber = ko.computed(function () { | |
return self.pageIndex() + 1; | |
}); | |
self.pageToFirst = function() { | |
self.pageIndex(0); | |
}; | |
self.pageToLast = function() { | |
self.pageIndex(self.lastPageIndex()); | |
}; | |
self.pageForward = ko.command({ | |
execute: function() { | |
self.pageIndex(self.pageIndex() + 1); | |
}, | |
canExecute: function() { | |
return self.pageIndex() < self.lastPageIndex(); | |
} | |
}); | |
self.pageBackward = ko.command({ | |
execute: function () { | |
self.pageIndex(self.pageIndex() - 1); | |
}, | |
canExecute: function () { | |
return self.pageIndex() > 0; | |
} | |
}); | |
self.currentPageRows = ko.computed({ | |
read: function () { | |
var pageSize = self.pageSize(), | |
pageStartIndex = self.pageIndex() * self.pageSize(), | |
sortedRows = self.sortedRows(); | |
if (self.pageIndex() == self.lastPageIndex()) | |
return sortedRows.slice(pageStartIndex); | |
else | |
return sortedRows.slice(pageStartIndex, pageStartIndex + pageSize - 1); | |
}, | |
deferEvaluation: true | |
}); | |
var pageCount = 5; //Max index, 5 pages | |
//The buttons for paginiation | |
//Will be buttons for up to 5 pages, with a selected page | |
//Selected page will be in the middle, when possible | |
self.pageButtons = ko.computed(function() { | |
var current = self.pageIndex().toNumber(), | |
last = self.lastPageIndex().toNumber(), | |
top = last, | |
bottom = 0; | |
if (current === 0) { | |
//Get current to either the last page, or pageCount from current | |
top = Math.min(pageCount - 1, last); | |
} else if (current === last) { | |
//Get from either the first page, or pageCount less than current, to current | |
bottom = Math.max(0, current - pageCount + 1); | |
} else { | |
//If it fits, we want pageCount buttons with current in the middle | |
//If it won't fit, we want the smaller of pageCount or the total number of pages | |
//Because we don't want the number of buttons to shrink in the latter case | |
var padding = Math.floor(pageCount / 2); | |
bottom = Math.max(0, current - padding); | |
top = Math.min(last, current + padding); | |
//There is room to pad more, and we don't have pageCount buttons | |
while (top - bottom !== pageCount - 1 && (last > padding || bottom > 0)) { | |
if (top < last) | |
top++; | |
else | |
bottom--; | |
} | |
} | |
return (bottom).upto(top).map(function(n) { | |
return { name: n + 1, isActive: n === current }; | |
}); | |
}); | |
self.goToPage = function(page) { | |
self.pageIndex(parseInt(page.name, 10) - 1); | |
}; | |
}; | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
the command: action bit... what are your thoughts on having drop-down lists here? For some of my stuff, i'm in need of providing a menu for "advanced" features.
KOGrid looked dead, and I didn't really want to use a "framework" looking grid like Wijmo or whatever... and I really, REALLY wanted to use OData and support as much of those bits as available on the UI.