ORIENT/modules/backend/widgets/table/assets/js/table.helper.navigation.js

424 lines
13 KiB
JavaScript

/*
* Navigation helper for the table widget.
* Implements the keyboard navigation within the current page
* and pagination.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.oc.table === undefined)
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.oc.table.helper === undefined)
$.oc.table.helper = {}
// NAVIGATION CLASS DEFINITION
// ============================
var Navigation = function(tableObj) {
// Reference to the table object
this.tableObj = tableObj
// The current page index
this.pageIndex = 0
// Event handlers
// Number of pages in the pagination
this.pageCount = 0
this.init()
};
Navigation.prototype.init = function() {
}
Navigation.prototype.dispose = function() {
// Remove the reference to the table object
this.tableObj = null
}
// PAGINATION
// ============================
Navigation.prototype.paginationEnabled = function() {
return this.tableObj.options.recordsPerPage != null &&
this.tableObj.options.recordsPerPage != false
}
Navigation.prototype.getPageFirstRowOffset = function() {
return this.pageIndex * this.tableObj.options.recordsPerPage
}
Navigation.prototype.buildPagination = function(recordCount) {
if (!this.paginationEnabled())
return
var paginationContainer = this.tableObj.getElement().querySelector('.pagination'),
newPaginationContainer = false,
curRecordCount = 0
this.pageCount = this.calculatePageCount(recordCount, this.tableObj.options.recordsPerPage)
if (!paginationContainer) {
paginationContainer = document.createElement('div')
paginationContainer.setAttribute('class', 'pagination')
newPaginationContainer = true
}
else {
curRecordCount = this.getRecordCount(paginationContainer)
}
// Generate the new page list only if the record count has changed
if (newPaginationContainer || curRecordCount != recordCount) {
paginationContainer.setAttribute('data-record-count', recordCount)
var pageList = this.buildPaginationLinkList(
recordCount,
this.tableObj.options.recordsPerPage,
this.pageIndex
)
if (!newPaginationContainer) {
paginationContainer.replaceChild(pageList, paginationContainer.children[0])
}
else {
paginationContainer.appendChild(pageList)
this.tableObj.getElement().appendChild(paginationContainer)
}
}
else {
// Do not re-generate the pages if the record count hasn't changed,
// but mark the new active item in the pagination list
this.markActiveLinkItem(paginationContainer, this.pageIndex)
}
}
Navigation.prototype.calculatePageCount = function(recordCount, recordsPerPage) {
var pageCount = Math.ceil(recordCount/recordsPerPage)
if (!pageCount)
pageCount = 1
return pageCount
}
Navigation.prototype.getRecordCount = function(paginationContainer) {
var container = paginationContainer ? paginationContainer : this.tableObj.getElement().querySelector('.pagination')
return parseInt(container.getAttribute('data-record-count'))
}
Navigation.prototype.buildPaginationLinkList = function(recordCount, recordsPerPage, pageIndex) {
// This method could be refactored and moved to a pagination
// helper if we want to support other pagination markup options.
var pageCount = this.calculatePageCount(recordCount, recordsPerPage),
pageList = document.createElement('ul')
for (var i=0; i < pageCount; i++) {
var item = document.createElement('li'),
link = document.createElement('a')
if (i == pageIndex)
item.setAttribute('class', 'active')
link.innerText = i+1
link.setAttribute('data-page-index', i)
link.setAttribute('href', '#')
item.appendChild(link)
pageList.appendChild(item)
$(link).addClass('pagination-link')
}
return pageList
}
Navigation.prototype.markActiveLinkItem = function(paginationContainer, pageIndex) {
// This method could be refactored and moved to a pagination
// helper if we want to support other pagination markup options.
var activeItem = paginationContainer.querySelector('.active'),
list = paginationContainer.children[0]
activeItem.setAttribute('class', '')
for (var i=0, len = list.children.length; i < len; i++) {
if (i == pageIndex) {
list.children[i].setAttribute('class', 'active')
}
}
}
Navigation.prototype.gotoPage = function(pageIndex, onSuccess) {
this.tableObj.unfocusTable()
if (!this.tableObj.validate())
return
this.pageIndex = pageIndex
this.tableObj.updateDataTable(onSuccess)
}
Navigation.prototype.getRowCountOnPage = function(cellElement) {
return this.tableObj.getDataTableBody().children.length
}
Navigation.prototype.getNewRowPage = function(placement, currentRowIndex) {
var curRecordCount = this.getRecordCount()
if (placement === 'bottom')
return this.calculatePageCount(curRecordCount + 1, this.tableObj.options.recordsPerPage) - 1
// When a row is added above a current row, the current row just moves down,
// so it's safe to return the current page index
if (placement == 'above')
return this.pageIndex
if (placement == 'below') {
if (currentRowIndex == (this.tableObj.options.recordsPerPage - 1))
return this.pageIndex + 1
return this.pageIndex
}
return this.pageIndex
}
Navigation.prototype.getPageAfterDeletion = function(currentRowIndex) {
if (currentRowIndex == 0 && this.getRowCountOnPage() == 1)
return this.pageIndex == 0 ? 0 : this.pageIndex - 1
return this.pageIndex
}
// KEYBOARD NAVIGATION
// ============================
Navigation.prototype.navigateDown = function(ev, forceCellIndex) {
if (!this.tableObj.activeCell)
return
if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'down'))
return
var row = this.tableObj.activeCell.parentNode,
newRow = !ev.shiftKey
? row.nextElementSibling
: row.parentNode.children[row.parentNode.children.length - 1],
cellIndex = forceCellIndex !== undefined
? forceCellIndex
: this.tableObj.activeCell.cellIndex
if (newRow) {
var cell = newRow.children[cellIndex]
if (cell)
this.tableObj.focusCell(cell)
}
else {
// Try to switch to the next page if that's possible
if (!this.paginationEnabled())
return
if (this.pageIndex < this.pageCount - 1) {
var self = this
this.gotoPage(this.pageIndex + 1, function navDownPageSuccess(){
self.focusCell('top', cellIndex)
self = null
})
}
}
}
Navigation.prototype.navigateUp = function(ev, forceCellIndex, isTab) {
if (!this.tableObj.activeCell)
return
if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'up'))
return
var row = this.tableObj.activeCell.parentNode,
newRow = (!ev.shiftKey || isTab)
? row.previousElementSibling
: row.parentNode.children[0],
cellIndex = forceCellIndex !== undefined
? forceCellIndex
: this.tableObj.activeCell.cellIndex
if (newRow) {
var cell = newRow.children[cellIndex]
if (cell)
this.tableObj.focusCell(cell)
}
else {
// Try to switch to the previous page if that's possible
if (!this.paginationEnabled())
return
if (this.pageIndex > 0) {
var self = this
this.gotoPage(this.pageIndex - 1, function navUpPageSuccess(){
self.focusCell('bottom', cellIndex)
self = null
})
}
}
}
Navigation.prototype.navigateLeft = function(ev, isTab) {
if (!this.tableObj.activeCell)
return
if (!isTab && this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'left'))
return
var row = this.tableObj.activeCell.parentNode,
newIndex = (!ev.shiftKey || isTab)
? this.tableObj.activeCell.cellIndex - 1
: 0
var cell = row.children[newIndex]
if (cell) {
this.tableObj.focusCell(cell)
}
else {
// Try to navigate up if that's possible
this.navigateUp(ev, row.children.length - 1, isTab)
}
}
Navigation.prototype.navigateRight = function(ev, isTab) {
if (!this.tableObj.activeCell)
return
if (!isTab && this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'right'))
return
var row = this.tableObj.activeCell.parentNode,
newIndex = !ev.shiftKey
? this.tableObj.activeCell.cellIndex + 1
: row.children.length - 1
var cell = row.children[newIndex]
if (cell) {
this.tableObj.focusCell(cell)
}
else {
// Try to navigate down if that's possible
this.navigateDown(ev, 0)
}
}
Navigation.prototype.navigateNext = function(ev) {
if (!this.tableObj.activeCell)
return
if (this.tableObj.activeCellProcessor && !this.tableObj.activeCellProcessor.keyNavigationAllowed(ev, 'tab'))
return
if (!ev.shiftKey)
this.navigateRight(ev, true)
else
this.navigateLeft(ev, true)
this.tableObj.stopEvent(ev)
}
Navigation.prototype.focusCell = function(rowReference, cellIndex) {
var row = null,
tbody = this.tableObj.getDataTableBody()
if (typeof rowReference === 'object') {
row = rowReference
}
else {
if (rowReference == 'bottom') {
row = tbody.children[tbody.children.length-1]
}
else if (rowReference == 'top') {
row = tbody.children[0]
}
}
if (!row)
return
var cell = row.children[cellIndex]
if (cell)
this.tableObj.focusCell(cell)
}
Navigation.prototype.focusCellInReplacedRow = function(rowIndex, cellIndex) {
if (rowIndex == 0) {
this.focusCell('top', cellIndex)
}
else {
var focusRow = this.tableObj.findRowByIndex(rowIndex)
if (!focusRow)
focusRow = this.tableObj.findRowByIndex(rowIndex-1)
if (focusRow)
this.focusCell(focusRow, cellIndex)
else
this.focusCell('top', cellIndex)
}
}
// EVENT HANDLERS
// ============================
Navigation.prototype.onKeydown = function(ev) {
// The navigation object uses the table's keydown handler
// and doesn't register own handler.
if (ev.keyCode == 40)
return this.navigateDown(ev)
else if (ev.keyCode == 38)
return this.navigateUp(ev)
else if (ev.keyCode == 37)
return this.navigateLeft(ev)
if (ev.keyCode == 39)
return this.navigateRight(ev)
if (ev.keyCode == 9)
return this.navigateNext(ev)
}
Navigation.prototype.onClick = function(ev) {
// The navigation object uses the table's click handler
// and doesn't register own click handler.
var target = this.tableObj.getEventTarget(ev, 'A')
if (!target || !$(target).hasClass('pagination-link'))
return
var pageIndex = parseInt(target.getAttribute('data-page-index'))
if (pageIndex === null)
return
this.gotoPage(pageIndex)
this.tableObj.stopEvent(ev)
return false
}
$.oc.table.helper.navigation = Navigation;
}(window.jQuery);