feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run

- Base: wintercms/winter branch 1.2 (full framework)
- Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS
- Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication
- Partials: hero (offline-first), features, modes (offline/nube toggle),
  screenshots, pricing (3 planes), comparison, FAQ, CTA
- Plugin VivesPOS.Site with ContactForm
- Dockerfile: PHP 8.2 Apache, port 80, healthcheck
- Added winter/wn-pages, blog, sitemap, seo plugins
- Active theme set to vivespos
This commit is contained in:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
.control-table .table-container{border:1px solid #e0e0e0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;overflow:hidden;margin-bottom:15px}
.control-table .table-container:last-child{margin-bottom:0}
.control-table:not([data-records-per-page="false"]) .table-container{border-bottom-right-radius:0;border-bottom-left-radius:0}
.control-table.active .table-container{border-color:#e0e0e0}
.control-table table{width:100%;border-collapse:collapse;table-layout:fixed}
.control-table table td,
.control-table table th{padding:0;font-size:13px;color:#555}
.control-table table [data-view-container]{padding:5px 10px;width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-height:28px}
.control-table table.headers:after{content:' ';display:block;position:absolute;left:1px;right:1px;margin-top:-1px;border-bottom:1px solid #e0e0e0}
.control-table table.headers th{padding:7px 10px;font-weight:normal;text-transform:uppercase;font-size:11px;color:#333;background:white;border-right:1px solid #ecf0f1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.control-table table.headers th [data-view-container]{padding-bottom:6px}
.control-table table.headers th:last-child{border-right:none}
.control-table.active table.headers:after{border-bottom-color:#e0e0e0}
.control-table table.data td{border:1px solid #ecf0f1}
.control-table table.data td .content-container{position:relative;padding:1px;outline:none}
.control-table table.data td .content-container.readonly{background:#f7f7f7}
.control-table table.data td.active{border-color:#103141 !important}
.control-table table.data td.active .content-container{padding:0;border:1px solid #103141}
.control-table table.data td.active .content-container:before,
.control-table table.data td.active .content-container:after{content:' ';background:#103141;position:absolute;left:-2px;top:-2px}
.control-table table.data td.active .content-container:before{width:1px;bottom:-2px}
.control-table table.data td.active .content-container:after{right:-2px;height:1px}
.control-table table.data tr{background-color:#fff}
.control-table table.data tr.error{background-color:#fbecec !important}
.control-table table.data tr.error td.active.error{border-color:#ec0000 !important}
.control-table table.data tr.error td.active.error .content-container{border-color:#ec0000 !important}
.control-table table.data tr.error td.active.error .content-container:before,
.control-table table.data tr.error td.active.error .content-container:after{background-color:#ec0000 !important}
.control-table table.data tr:nth-child(2n){background-color:#fafafa}
.control-table table.data tr:first-child td{border-top:none}
.control-table table.data tr:last-child td{border-bottom:none}
.control-table table.data td:first-child{border-left:none}
.control-table table.data td:last-child{border-right:none}
.control-table .control-scrollbar>div{border-bottom-right-radius:4px;border-bottom-left-radius:4px;overflow:hidden}
.control-table .control-scrollbar table.data tr:last-child td{border-bottom:1px solid #ecf0f1}
.control-table .toolbar{background:white;border-bottom:1px solid #e0e0e0}
.control-table .toolbar:before,
.control-table .toolbar:after{content:" ";display:table}
.control-table .toolbar:after{clear:both}
.control-table .toolbar a.btn{color:#323e50;padding:8px 10px;opacity:0.5;filter:alpha(opacity=50);-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none}
.control-table .toolbar a.btn:hover{opacity:1;filter:alpha(opacity=100)}
.control-table .toolbar .table-search{float:right;margin:3px 3px 3px 0}
.control-table .toolbar .table-search .table-search-input{height:auto;padding:5px 13px 5px}
.control-table .toolbar a.table-icon:before{display:inline-block;content:' ';width:16px;height:16px;margin-right:8px;position:relative;top:3px;background:transparent url(../images/table-icons.gif) no-repeat 0 0;background-size:32px auto}
.control-table .toolbar a.table-icon.add-table-row-above:before{background-position:0 -56px}
.control-table .toolbar a.table-icon.delete-table-row:before{background-position:0 -113px}
.control-table.active .toolbar{border-bottom-color:#e0e0e0}
.control-table .pagination{margin:-15px 0 15px;padding:7px 10px;background:white;border:1px solid #e0e0e0;border-top:none;border-bottom-right-radius:4px;border-bottom-left-radius:4px}
.control-table .pagination ul{padding:0;margin:0}
.control-table .pagination ul li{list-style:none;display:inline-block;margin-right:5px;font-size:12px;line-height:100%}
.control-table .pagination ul li a{display:inline-block;text-decoration:none;color:#95a5a6;padding:4px 6px;background:#ecf0f1;-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;outline:none;line-height:100%}
.control-table .pagination ul li a:focus-visible{outline:auto}
.control-table .pagination ul li.active a{background:#6cc551;color:#fff}
@media only screen and (-moz-min-device-pixel-ratio:1.5),only screen and (-o-min-device-pixel-ratio:1.5),only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min-devicepixel-ratio:1.5),only screen and (min-resolution:1.5dppx){.control-table .toolbar a:before{background-position:0 -9px;background-size:16px auto}.control-table .toolbar a.add-table-row-above:before{background-position:0 -39px}.control-table .toolbar a.delete-table-row:before{background-position:0 -66px}}.control-table td[data-column-type=string] input[type=text],
.control-table td[data-column-type=autocomplete] input[type=text]{width:100%;height:100%;display:block;outline:none;border:none;padding:6px 10px 7px}
html.chrome .control-table td[data-column-type=string] input[type=text],
html.chrome .control-table td[data-column-type=autocomplete] input[type=text]{padding:6px 10px 7px!important}
html.safari .control-table td[data-column-type=string] input[type=text],
html.gecko .control-table td[data-column-type=string] input[type=text],
html.safari .control-table td[data-column-type=autocomplete] input[type=text],
html.gecko .control-table td[data-column-type=autocomplete] input[type=text]{padding:5px 10px 5px}
ul.table-widget-autocomplete{background:white;font-size:13px;margin-top:0;border:1px solid #808c8d;border-top:1px solid #ecf0f1;border-bottom-right-radius:4px;border-bottom-left-radius:4px}
ul.table-widget-autocomplete li a{padding:5px 10px}.control-table td[data-column-type=checkbox] div[data-checkbox-element]{width:16px;height:16px;border-radius:3px;background-color:#FFF;border:1px solid #999;margin:6px 5px 6px 10px;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.control-table td[data-column-type=checkbox] div[data-checkbox-element]:hover{border-color:#808080;color:#4d4d4d}
.control-table td[data-column-type=checkbox] div[data-checkbox-element].checked{border-width:2px}
.control-table td[data-column-type=checkbox] div[data-checkbox-element].checked:before{font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f00c";font-size:10px;position:relative;left:1px;top:-4px}
.control-table td[data-column-type=checkbox] div[data-checkbox-element]:focus{border-color:#103141;outline:none}.control-table td[data-column-type=dropdown] .content-container:not(.readonly){-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}
.control-table td[data-column-type=dropdown] .content-container:not(.readonly) [data-view-container]{padding-right:20px;position:relative;cursor:pointer}
.control-table td[data-column-type=dropdown] .content-container:not(.readonly) [data-view-container]:after{font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f107";font-size:13px;line-height:100%;color:#95a5a6;position:absolute;top:8px;right:10px}
.control-table td[data-column-type=dropdown] .content-container:not(.readonly) [data-view-container]:hover:after{color:#2da7c7}
.control-table td[data-column-type=dropdown] [data-dropdown-open=true]{background:white}
.control-table td[data-column-type=dropdown] [data-dropdown-open=true] [data-view-container]:after{font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f106"}.widget-field.frameless .control-table .table-container{border-top:none;border-left:none;border-right:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}
.widget-field.frameless .control-table .toolbar{background:transparent}html.cssanimations .control-table td[data-column-type=dropdown] [data-view-container].loading:after{background:url('../../../../../../modules/system/assets/ui/images/loader-transparent.svg') 50% 50%;background-size:15px 15px;position:absolute;width:15px;height:15px;top:6px;right:5px;content:' ';-webkit-animation:spin 1s linear infinite;animation:spin 1s linear infinite}
.table-control-dropdown-list{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;position:absolute;background:white;border:1px solid #e0e0e0;border-top:none;padding-top:1px;overflow:hidden;z-index:1000;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-bottom-right-radius:4px;border-bottom-left-radius:4px}
.table-control-dropdown-list ul{border-top:1px solid #ecf0f1;padding:0;margin:0;max-height:200px;overflow:auto}
.table-control-dropdown-list li{list-style:none;font-size:13px;color:#555;padding:5px 10px;cursor:pointer;outline:none}
.table-control-dropdown-list li:focus{background:#103141;color:white}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,773 @@
+function($){"use strict";if($.wn===undefined)$.wn={}
if($.oc===undefined)$.oc=$.wn
if($.wn.table===undefined)$.wn.table={}
var Table=function(element,options){this.el=element
this.$el=$(element)
this.options=options
this.disposed=false
this.dataSource=null
this.cellProcessors={}
this.activeCellProcessor=null
this.activeCell=null
this.tableContainer=null
this.dataTableContainer=null
this.editedRowKey=null
this.dataTable=null
this.headerTable=null
this.toolbar=null
this.clickHandler=this.onClick.bind(this)
this.keydownHandler=this.onKeydown.bind(this)
this.documentClickHandler=this.onDocumentClick.bind(this)
this.toolbarClickHandler=this.onToolbarClick.bind(this)
if(this.options.postback&&this.options.clientDataSourceClass=='client'){if(!this.options.postbackHandlerName.length){var formHandler=this.$el.closest('form').data('request')
this.options.postbackHandlerName=[formHandler||'onSave']}else if(typeof this.options.postbackHandlerName==='string'){this.options.postbackHandlerName=this.options.postbackHandlerName.split(',')}this.formSubmitHandler=this.onFormSubmit.bind(this)}this.navigation=null
this.search=null
this.recordsAddedOrDeleted=0
this.disposeBound=this.dispose.bind(this)
this.init()
$.wn.foundation.controlUtils.markDisposable(element)}
Table.prototype.init=function(){this.createDataSource()
this.initCellProcessors()
this.navigation=new $.wn.table.helper.navigation(this)
this.search=new $.wn.table.helper.search(this)
this.buildUi()
this.registerHandlers()}
Table.prototype.disposeCellProcessors=function(){for(var i=0,len=this.options.columns.length;i<len;i++){var column=this.options.columns[i].key
this.cellProcessors[column].dispose()
this.cellProcessors[column]=null}this.cellProcessors=null
this.activeCellProcessor=null}
Table.prototype.createDataSource=function(){var dataSourceClass=this.options.clientDataSourceClass
if($.wn.table.datasource===undefined||$.wn.table.datasource[dataSourceClass]==undefined)throw new Error('The table client-side data source class "'+dataSourceClass+'" is not '+'found in the $.wn.table.datasource namespace.')
this.dataSource=new $.wn.table.datasource[dataSourceClass](this)}
Table.prototype.registerHandlers=function(){this.el.addEventListener('click',this.clickHandler)
this.el.addEventListener('keydown',this.keydownHandler)
this.$el.one('dispose-control',this.disposeBound)
document.addEventListener('click',this.documentClickHandler)
if(this.options.postback&&this.options.clientDataSourceClass=='client')this.$el.closest('form').bind('oc.beforeRequest',this.formSubmitHandler)
var toolbar=this.getToolbar()
if(toolbar)toolbar.addEventListener('click',this.toolbarClickHandler);}
Table.prototype.unregisterHandlers=function(){this.el.removeEventListener('click',this.clickHandler);document.removeEventListener('click',this.documentClickHandler)
this.clickHandler=null
this.el.removeEventListener('keydown',this.keydownHandler);this.keydownHandler=null
var toolbar=this.getToolbar()
if(toolbar)toolbar.removeEventListener('click',this.toolbarClickHandler);this.toolbarClickHandler=null
if(this.formSubmitHandler){this.$el.closest('form').unbind('oc.beforeRequest',this.formSubmitHandler)
this.formSubmitHandler=null}}
Table.prototype.initCellProcessors=function(){for(var i=0,len=this.options.columns.length;i<len;i++){var columnConfiguration=this.options.columns[i],column=columnConfiguration.key,columnType=columnConfiguration.type
if(columnType===undefined){columnType='string'
this.options.columns[i].type=columnType}if($.wn.table.processor===undefined||$.wn.table.processor[columnType]==undefined)throw new Error('The table cell processor for the column type "'+columnType+'" is not '+'found in the $.wn.table.processor namespace.')
this.cellProcessors[column]=new $.wn.table.processor[columnType](this,column,columnConfiguration)}}
Table.prototype.getCellProcessor=function(columnName){return this.cellProcessors[columnName]}
Table.prototype.buildUi=function(){this.tableContainer=document.createElement('div')
this.tableContainer.setAttribute('class','table-container')
if(this.options.toolbar){this.buildToolbar()}this.tableContainer.appendChild(this.buildHeaderTable())
this.el.insertBefore(this.tableContainer,this.el.children[0])
if(!this.options.height){this.dataTableContainer=this.tableContainer}else{this.dataTableContainer=this.buildScrollbar()}this.updateDataTable()}
Table.prototype.buildToolbar=function(){if(!this.options.adding&&!this.options.deleting&&!this.options.searching){return}this.toolbar=$($('[data-table-toolbar]',this.el).html()).appendTo(this.tableContainer).get(0)
if(!this.options.adding){$('[data-cmd^="record-add"]',this.toolbar).remove()}else{if(this.navigation.paginationEnabled()||!this.options.rowSorting){$('[data-cmd=record-add-below], [data-cmd=record-add-above]',this.toolbar).remove()}else{$('[data-cmd=record-add]',this.toolbar).remove()}}if(!this.options.deleting){$('[data-cmd="record-delete"]',this.toolbar).remove()}}
Table.prototype.buildScrollbar=function(){var scrollbar=document.createElement('div'),scrollbarContent=document.createElement('div')
scrollbar.setAttribute('class','control-scrollbar')
if(this.options.dynamicHeight)scrollbar.setAttribute('style','max-height: '+this.options.height+'px')
else scrollbar.setAttribute('style','height: '+this.options.height+'px')
scrollbar.appendChild(scrollbarContent)
this.tableContainer.appendChild(scrollbar)
$(scrollbar).scrollbar({animation:false})
return scrollbarContent}
Table.prototype.buildHeaderTable=function(){var headersTable=document.createElement('table'),row=document.createElement('tr')
headersTable.className='headers'
headersTable.appendChild(row)
for(var i=0,len=this.options.columns.length;i<len;i++){var header=document.createElement('th')
if(this.options.columns[i].width)header.setAttribute('style','width: '+this.options.columns[i].width)
header.textContent!==undefined?header.textContent=this.options.columns[i].title:header.innerText=this.options.columns[i].title
row.appendChild(header)}this.headerTable=headersTable
return headersTable}
Table.prototype.updateDataTable=function(onSuccess){var self=this
this.unfocusTable()
this.fetchRecords(function onUpdateDataTableSuccess(records,totalCount){self.buildDataTable(records,totalCount)
if(onSuccess)onSuccess()
if(totalCount==0)self.addRecord('above',true)
self.$el.trigger('oc.tableUpdateData',[records,totalCount])
self=null})}
Table.prototype.updateColumnWidth=function(){var headerCells=this.headerTable.querySelectorAll('th'),dataCells=this.dataTable.querySelectorAll('tr:first-child td')
for(var i=0,len=headerCells.length;i<len;i++){if(dataCells[i])dataCells[i].setAttribute('style',headerCells[i].getAttribute('style'))}}
Table.prototype.buildDataTable=function(records,totalCount){var dataTable=document.createElement('table'),tbody=document.createElement('tbody'),keyColumn=this.options.keyColumn
dataTable.setAttribute('class','data')
for(var i=0,len=records.length;i<len;i++){var row=document.createElement('tr')
if(records[i][keyColumn]===undefined)throw new Error('The row attribute '+keyColumn+' is not set for the row #'+i);row.setAttribute('data-row',records[i][keyColumn])
for(var j=0,colsLen=this.options.columns.length;j<colsLen;j++){var cell=document.createElement('td'),dataContainer=document.createElement('input'),cellContentContainer=document.createElement('div'),column=this.options.columns[j],columnName=column.key,cellProcessor=this.getCellProcessor(columnName)
cell.setAttribute('data-column',columnName)
cell.setAttribute('data-column-type',column.type)
dataContainer.setAttribute('type','hidden')
dataContainer.setAttribute('data-container','data-container')
dataContainer.value=this.formatDataContainerValue(records[i][columnName])
cellContentContainer.setAttribute('class','content-container')
cell.appendChild(cellContentContainer)
row.appendChild(cell)
cell.appendChild(dataContainer)
cellProcessor.renderCell(records[i][columnName],cellContentContainer)}tbody.appendChild(row)}dataTable.appendChild(tbody)
if(this.dataTable!==null)this.dataTableContainer.replaceChild(dataTable,this.dataTable)
else this.dataTableContainer.appendChild(dataTable)
this.dataTable=dataTable
this.updateColumnWidth()
this.updateScrollbar()
this.navigation.buildPagination(totalCount)
this.search.buildSearchForm()}
Table.prototype.formatDataContainerValue=function(value){if(value===undefined){return''}if(typeof value==='boolean'){return value?1:''}return value}
Table.prototype.fetchRecords=function(onSuccess){if(this.search.hasQuery()){this.dataSource.searchRecords(this.search.getQuery(),this.navigation.getPageFirstRowOffset(),this.options.recordsPerPage,onSuccess)}else{this.dataSource.getRecords(this.navigation.getPageFirstRowOffset(),this.options.recordsPerPage,onSuccess)}}
Table.prototype.updateScrollbar=function(){if(!this.options.height)return
$(this.dataTableContainer.parentNode).data('oc.scrollbar').update()}
Table.prototype.scrollCellIntoView=function(){if(!this.options.height||!this.activeCell)return
$(this.dataTableContainer.parentNode).data('oc.scrollbar').gotoElement(this.activeCell)}
Table.prototype.disposeScrollbar=function(){if(!this.options.height)return
$(this.dataTableContainer.parentNode).data('oc.scrollbar').dispose()
$(this.dataTableContainer.parentNode).data('oc.scrollbar',null)}
Table.prototype.setActiveProcessor=function(processor){if(this.activeCellProcessor)this.activeCellProcessor.onUnfocus()
this.activeCellProcessor=processor}
Table.prototype.commitEditedRow=function(){if(this.editedRowKey===null)return
var editedRow=this.dataTable.querySelector('tr[data-row="'+this.editedRowKey+'"]')
if(!editedRow)return
if(editedRow.getAttribute('data-dirty')!=1)return
var cells=editedRow.children,data={}
for(var i=0,len=cells.length;i<len;i++){var cell=cells[i]
data[cell.getAttribute('data-column')]=this.getCellValue(cell)}this.dataSource.updateRecord(this.editedRowKey,data)
editedRow.setAttribute('data-dirty',0)}
Table.prototype.unfocusTable=function(){this.elementRemoveClass(this.el,'active')
if(this.activeCellProcessor)this.activeCellProcessor.onUnfocus()
this.commitEditedRow()
this.activeCellProcessor=null
if(this.activeCell)this.activeCell.setAttribute('class','')
this.activeCell=null}
Table.prototype.focusTable=function(){this.elementAddClass(this.el,'active')}
Table.prototype.focusCell=function(cellElement,isClick){var columnName=cellElement.getAttribute('data-column')
if(columnName===null)return
this.focusTable()
var processor=this.getCellProcessor(columnName)
if(!processor)throw new Error("Cell processor not found for the column "+columnName)
if(this.activeCell!==cellElement){if(this.activeCell)this.elementRemoveClass(this.activeCell,'active')
this.setActiveProcessor(processor)
this.activeCell=cellElement
if(processor.isCellFocusable())this.elementAddClass(this.activeCell,'active')}var rowKey=this.getCellRowKey(cellElement)
if(this.editedRowKey!==null&&rowKey!=this.editedRowKey)this.commitEditedRow()
this.editedRowKey=rowKey
processor.onFocus(cellElement,isClick)
this.scrollCellIntoView()}
Table.prototype.markCellRowDirty=function(cellElement){cellElement.parentNode.setAttribute('data-dirty',1)}
Table.prototype.addRecord=function(placement,noFocus){if(!this.activeCell||this.navigation.paginationEnabled()||!this.options.rowSorting)placement='bottom'
var relativeToKey=null,currentRowIndex=null
if(placement=='above'||placement=='below'){relativeToKey=this.getCellRowKey(this.activeCell)
currentRowIndex=this.getCellRowIndex(this.activeCell)}this.unfocusTable()
if(this.navigation.paginationEnabled()){var newPageIndex=this.navigation.getNewRowPage(placement,currentRowIndex)
if(newPageIndex!=this.navigation.pageIndex){if(!this.validate())return}this.navigation.pageIndex=newPageIndex}this.recordsAddedOrDeleted++
var keyColumn=this.options.keyColumn,recordData={},self=this
recordData[keyColumn]=-1*this.recordsAddedOrDeleted
this.$el.trigger('oc.tableNewRow',[recordData])
this.dataSource.createRecord(recordData,placement,relativeToKey,this.navigation.getPageFirstRowOffset(),this.options.recordsPerPage,function onAddRecordDataTableSuccess(records,totalCount){self.buildDataTable(records,totalCount)
var row=self.findRowByKey(recordData[keyColumn])
if(!row)throw new Error('New row is not found in the updated table: '+recordData[keyColumn])
if(!noFocus)self.navigation.focusCell(row,0)
self=null})}
Table.prototype.deleteRecord=function(){if(!this.activeCell)return
var currentRowIndex=this.getCellRowIndex(this.activeCell),key=this.getCellRowKey(this.activeCell),self=this,paginationEnabled=this.navigation.paginationEnabled(),currentPageIndex=this.navigation.pageIndex,currentCellIndex=this.activeCell.cellIndex
if(paginationEnabled)this.navigation.pageIndex=this.navigation.getPageAfterDeletion(currentRowIndex)
this.recordsAddedOrDeleted++
var keyColumn=this.options.keyColumn,newRecordData={}
newRecordData[keyColumn]=-1*this.recordsAddedOrDeleted
this.dataSource.deleteRecord(key,newRecordData,this.navigation.getPageFirstRowOffset(),this.options.recordsPerPage,function onDeleteRecordDataTableSuccess(records,totalCount){self.buildDataTable(records,totalCount)
if(!paginationEnabled)self.navigation.focusCellInReplacedRow(currentRowIndex,currentCellIndex)
else{if(currentPageIndex!=self.navigation.pageIndex)self.navigation.focusCell('bottom',currentCellIndex)
else self.navigation.focusCellInReplacedRow(currentRowIndex,currentCellIndex)}self=null})}
Table.prototype.notifyRowProcessorsOnChange=function(cellElement){var columnName=cellElement.getAttribute('data-column'),row=cellElement.parentNode
for(var i=0,len=row.children.length;i<len;i++){var column=this.options.columns[i].key
this.cellProcessors[column].onRowValueChanged(columnName,row.children[i])}}
Table.prototype.getToolbar=function(){return this.tableContainer.querySelector('div.toolbar')}
Table.prototype.validate=function(){var rows=this.dataTable.querySelectorAll('tbody tr[data-row]')
for(var i=0,len=rows.length;i<len;i++){var row=rows[i]
this.elementRemoveClass(row,'error')}for(var i=0,rowsLen=rows.length;i<rowsLen;i++){var row=rows[i],rowData=this.getRowData(row)
for(var j=0,colsLen=row.children.length;j<colsLen;j++)this.elementRemoveClass(row.children[j],'error')
for(var columnName in rowData){var cellProcessor=this.getCellProcessor(columnName),message=cellProcessor.validate(rowData[columnName],rowData)
if(message!==undefined){var cell=row.querySelector('td[data-column="'+columnName+'"]'),self=this
this.elementAddClass(row,'error')
this.elementAddClass(cell,'error')
$.wn.flashMsg({text:message,'class':'error'})
window.setTimeout(function(){self.focusCell(cell,false)
cell=null
self=null
cellProcessor=null},100)
return false}}}return true}
Table.prototype.onClick=function(ev){this.focusTable()
if(this.navigation.onClick(ev)===false)return
if(this.search.onClick(ev)===false)return
for(var i=0,len=this.options.columns.length;i<len;i++){var column=this.options.columns[i].key
this.cellProcessors[column].onClick(ev)}var target=this.getEventTarget(ev,'TD')
if(!target){this.unfocusTable();return;}if(target.tagName!='TD'){this.unfocusTable();return;}this.focusCell(target,true)}
Table.prototype.onKeydown=function(ev){if((ev.key==='a'||ev.key==='A')&&ev.altKey&&this.options.adding){if(!ev.shiftKey){this.addRecord('below')}else{this.addRecord('above')}this.stopEvent(ev)
return}if((ev.key==='d'||ev.key==='D')&&ev.altKey&&this.options.deleting){this.deleteRecord()
this.stopEvent(ev)
return}for(var i=0,len=this.options.columns.length;i<len;i++){var column=this.options.columns[i].key
if(this.cellProcessors[column].onKeyDown(ev)===false){return}}if(this.navigation.onKeydown(ev)===false){return}if(this.search.onKeydown(ev)===false){return}}
Table.prototype.onFormSubmit=function(ev,data){if(this.options.postbackHandlerName.indexOf(data.handler)>-1){this.unfocusTable()
if(!this.validate()){ev.preventDefault()
return}data.options.data[this.options.fieldName]=this.dataSource.getAllData()}}
Table.prototype.onToolbarClick=function(ev){var target=this.getEventTarget(ev),cmd=target.getAttribute('data-cmd')
if(!cmd){return}switch(cmd){case'record-add':case'record-add-below':this.addRecord('below')
break
case'record-add-above':this.addRecord('above')
break
case'record-delete':this.deleteRecord()
break}this.stopEvent(ev)}
Table.prototype.onDocumentClick=function(ev){var target=this.getEventTarget(ev)
if(this.parentContainsElement(this.el,target))return
if(this.activeCellProcessor&&this.activeCellProcessor.elementBelongsToProcessor(target))return
this.unfocusTable()}
Table.prototype.dispose=function(){if(this.disposed){return}this.disposed=true
this.disposeBound=true
this.unfocusTable()
this.dataSource.dispose()
this.dataSource=null
this.unregisterHandlers()
this.dataTable=null
this.headerTable=null
this.toolbar=null
this.disposeCellProcessors()
this.navigation.dispose()
this.navigation=null
this.disposeScrollbar()
this.el=null
this.tableContainer=null
this.$el=null
this.dataTableContainer=null
this.activeCell=null}
Table.prototype.setRowValues=function(rowIndex,rowValues){var row=this.findRowByIndex(rowIndex)
if(!row){return false}var dataUpdated=false
for(var i=0,len=row.children.length;i<len;i++){var cell=row.children[i],cellColumnName=this.getCellColumnName(cell)
for(var rowColumnName in rowValues){if(rowColumnName==cellColumnName){this.setCellValue(cell,rowValues[rowColumnName],true)
dataUpdated=true}}}if(dataUpdated){var originalEditedRowKey=this.editedRowKey
this.editedRowKey=this.getRowKey(row)
this.commitEditedRow()
this.editedRowKey=originalEditedRowKey}return true}
Table.prototype.getElement=function(){return this.el}
Table.prototype.getAlias=function(){return this.options.alias}
Table.prototype.getTableContainer=function(){return this.tableContainer}
Table.prototype.getDataTableBody=function(){return this.dataTable.children[0]}
Table.prototype.getEventTarget=function(ev,tag){var target=ev.target?ev.target:ev.srcElement
if(tag===undefined)return target
var tagName=target.tagName
while(tagName!=tag){target=target.parentNode
if(!target)return null
tagName=target.tagName}return target}
Table.prototype.stopEvent=function(ev){if(ev.stopPropagation)ev.stopPropagation()
else ev.cancelBubble=true
if(ev.preventDefault)ev.preventDefault()
else ev.returnValue=false}
Table.prototype.elementHasClass=function(el,className){if(el.classList)return el.classList.contains(className);return new RegExp('(^| )'+className+'( |$)','gi').test(el.className);}
Table.prototype.elementAddClass=function(el,className){if(this.elementHasClass(el,className))return
if(el.classList)el.classList.add(className);else el.className+=' '+className;}
Table.prototype.elementRemoveClass=function(el,className){if(el.classList)el.classList.remove(className);else el.className=el.className.replace(new RegExp('(^|\\b)'+className.split(' ').join('|')+'(\\b|$)','gi'),' ');}
Table.prototype.parentContainsElement=function(parent,element){while(element&&element!=parent){element=element.parentNode}return element?true:false}
Table.prototype.getCellValue=function(cellElement){return cellElement.querySelector('[data-container]').value}
Table.prototype.getCellRowKey=function(cellElement){return parseInt(cellElement.parentNode.getAttribute('data-row'))}
Table.prototype.getRowKey=function(rowElement){return parseInt(rowElement.getAttribute('data-row'))}
Table.prototype.findRowByKey=function(key){return this.dataTable.querySelector('tbody tr[data-row="'+key+'"]')}
Table.prototype.findRowByIndex=function(index){return this.getDataTableBody().children[index]}
Table.prototype.getCellRowIndex=function(cellElement){return parseInt(cellElement.parentNode.rowIndex)}
Table.prototype.getRowCellValueByColumnName=function(row,columnName){var cell=row.querySelector('td[data-column="'+columnName+'"]')
if(!cell)return cell
return this.getCellValue(cell)}
Table.prototype.getRowData=function(row){var result={}
for(var i=0,len=row.children.length;i<len;i++){var cell=row.children[i]
result[cell.getAttribute('data-column')]=this.getCellValue(cell)}return result}
Table.prototype.getCellColumnName=function(cellElement){return cellElement.getAttribute('data-column')}
Table.prototype.setCellValue=function(cellElement,value,suppressEvents){var dataContainer=cellElement.querySelector('[data-container]')
if(dataContainer.value!=value){dataContainer.value=value
this.markCellRowDirty(cellElement)
this.notifyRowProcessorsOnChange(cellElement)
if(suppressEvents===undefined||!suppressEvents){this.$el.trigger('oc.tableCellChanged',[this.getCellColumnName(cellElement),value,this.getCellRowIndex(cellElement)])}}}
Table.DEFAULTS={clientDataSourceClass:'client',keyColumn:'id',recordsPerPage:false,data:null,postback:true,postbackHandlerName:[],adding:true,deleting:true,toolbar:true,searching:false,rowSorting:false,height:false,dynamicHeight:false}
var old=$.fn.table
$.fn.table=function(option){var args=Array.prototype.slice.call(arguments,1),result=undefined
this.each(function(){var $this=$(this)
var data=$this.data('oc.table')
var options=$.extend({},Table.DEFAULTS,$this.data(),typeof option=='object'&&option)
if(!data)$this.data('oc.table',(data=new Table(this,options)))
if(typeof option=='string')result=data[option].apply(data,args)
if(typeof result!='undefined')return false})
return result?result:this}
$.fn.table.Constructor=Table
$.wn.table.table=Table
$.fn.table.noConflict=function(){$.fn.table=old
return this}
$(document).on('render',function(){$('div[data-control=table]').table()})}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.helper===undefined)$.wn.table.helper={}
var Navigation=function(tableObj){this.tableObj=tableObj
this.pageIndex=0
this.pageCount=0
this.init()};Navigation.prototype.init=function(){}
Navigation.prototype.dispose=function(){this.tableObj=null}
Navigation.prototype.paginationEnabled=function(){return this.tableObj.options.recordsPerPage>0;}
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)}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{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){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){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
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}
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{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{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{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{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)}}
Navigation.prototype.onKeydown=function(ev){if(ev.key==='ArrowDown')return this.navigateDown(ev)
else if(ev.key==='ArrowUp')return this.navigateUp(ev)
else if(ev.key==='ArrowLeft')return this.navigateLeft(ev)
if(ev.key==='ArrowRight')return this.navigateRight(ev)
if(ev.key==='Tab')return this.navigateNext(ev)}
Navigation.prototype.onClick=function(ev){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}
$.wn.table.helper.navigation=Navigation;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.helper===undefined)$.wn.table.helper={}
var Search=function(tableObj){this.tableObj=tableObj
this.searchForm=null
this.searchInput=null
this.inputTrackTimer=null
this.activeQuery=null
this.isActive=false
this.init()};Search.prototype.init=function(){}
Search.prototype.dispose=function(){this.tableObj=null
this.searchForm=null
this.searchInput=null}
Search.prototype.buildSearchForm=function(){if(!this.searchEnabled())return
var el=this.tableObj.getElement(),toolbar=this.tableObj.getToolbar(),searchForm=toolbar.querySelector('.table-search')
if(!searchForm){this.searchForm=$($('[data-table-toolbar-search]',el).html()).appendTo(toolbar).get(0)
this.searchInput=$('.table-search-input',this.searchForm).get(0)}}
Search.prototype.getQuery=function(){return $.trim(this.activeQuery)}
Search.prototype.hasQuery=function(){return this.searchEnabled()&&$.trim(this.activeQuery).length>0}
Search.prototype.searchEnabled=function(){return this.tableObj.options.searching}
Search.prototype.getSearchableColumns=function(){const columns=[];this.tableObj.options.columns.forEach(function(column){if(column.type==='checkbox'){return;}if(!column.searchable){return;}columns.push(column.key);});return columns;}
Search.prototype.performSearch=function(query,onSuccess){var isDirty=this.activeQuery!=query
this.activeQuery=query
if(isDirty){this.tableObj.updateDataTable(onSuccess)}}
Search.prototype.onKeydown=function(ev){if(ev.key==='Tab'){this.onClick(ev)
return}if(!this.isActive){return}var self=this
this.inputTrackTimer=window.setTimeout(function(){self.performSearch(self.searchInput.value)},300)}
Search.prototype.onClick=function(ev){var target=this.tableObj.getEventTarget(ev,'INPUT')
this.isActive=target&&$(target).hasClass('table-search-input')}
$.wn.table.helper.search=Search;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.datasource===undefined)$.wn.table.datasource={}
var Base=function(tableObj){this.tableObj=tableObj}
Base.prototype.dispose=function(){this.tableObj=null}
Base.prototype.getRecords=function(offset,count,onSuccess){onSuccess([])}
Base.prototype.searchRecords=function(query,offset,count,onSuccess){onSuccess([])}
Base.prototype.createRecord=function(recordData,placement,relativeToKey,offset,count,onSuccess){onSuccess([],0)}
Base.prototype.updateRecord=function(key,recordData){}
Base.prototype.deleteRecord=function(key,newRecordData,offset,count,onSuccess){onSuccess([],0)}
$.wn.table.datasource.base=Base;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.datasource===undefined)throw new Error("The $.wn.table.datasource namespace is not defined. Make sure that the table.datasource.base.js script is loaded.");var Base=$.wn.table.datasource.base,BaseProto=Base.prototype
var Client=function(tableObj){Base.call(this,tableObj)
var dataString=tableObj.getElement().getAttribute('data-data')
if(dataString===null||dataString===undefined)throw new Error('The required data-data attribute is not found on the table control element.')
this.data=JSON.parse(dataString)};Client.prototype=Object.create(BaseProto)
Client.prototype.constructor=Client
Client.prototype.dispose=function(){BaseProto.dispose.call(this)
this.data=null}
Client.prototype.getRecords=function(offset,count,onSuccess){if(!count){onSuccess(this.data,this.data.length)}else{onSuccess(this.data.slice(offset,offset+count),this.data.length)}}
Client.prototype.createRecord=function(recordData,placement,relativeToKey,offset,count,onSuccess){if(placement==='bottom'){this.data.push(recordData)}else if(placement=='above'||placement=='below'){var recordIndex=this.getIndexOfKey(relativeToKey)
if(placement=='below')recordIndex++
this.data.splice(recordIndex,0,recordData)}this.getRecords(offset,count,onSuccess)}
Client.prototype.searchRecords=function(query,offset,count,onSuccess){const searchFields=this.tableObj.search.getSearchableColumns();const matched=this.data.filter(function(record){for(let i=0;i<searchFields.length;i++){const value=record[searchFields[i]];if(value===undefined){continue;}if(value.toString().toLowerCase().includes(query.toLowerCase())){return true;}}return false;});if(matched.length===0){onSuccess([]);return;}if(!count){onSuccess(matched,matched.length);}else{onSuccess(matched.slice(offset,offset+count),matched.length);}}
Client.prototype.updateRecord=function(key,recordData){var recordIndex=this.getIndexOfKey(key)
if(recordIndex!==-1){recordData[this.tableObj.options.keyColumn]=key
this.data[recordIndex]=recordData}else{throw new Error('Record with they key '+key+' is not found in the data set')}}
Client.prototype.deleteRecord=function(key,newRecordData,offset,count,onSuccess){var recordIndex=this.getIndexOfKey(key)
if(recordIndex!==-1){this.data.splice(recordIndex,1)
if(this.data.length==0)this.data.push(newRecordData)
this.getRecords(offset,count,onSuccess)}else{throw new Error('Record with they key '+key+' is not found in the data set')}}
Client.prototype.getIndexOfKey=function(key){var keyColumn=this.tableObj.options.keyColumn
return this.data.map(function(record){return record[keyColumn]+""}).indexOf(key+"")}
Client.prototype.getAllData=function(){return this.data}
$.wn.table.datasource.client=Client}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.datasource===undefined)throw new Error("The $.wn.table.datasource namespace is not defined. Make sure that the table.datasource.base.js script is loaded.");var Base=$.wn.table.datasource.base,BaseProto=Base.prototype
var Server=function(tableObj){Base.call(this,tableObj)
var dataString=tableObj.getElement().getAttribute('data-data')
if(dataString===null||dataString===undefined)throw new Error('The required data-data attribute is not found on the table control element.')
this.data=JSON.parse(dataString)};Server.prototype=Object.create(BaseProto)
Server.prototype.constructor=Server
Server.prototype.dispose=function(){BaseProto.dispose.call(this)
this.data=null}
Server.prototype.getRecords=function(offset,count,onSuccess){var handlerName=this.tableObj.getAlias()+'::onServerGetRecords'
this.tableObj.$el.request(handlerName,{data:{offset:offset,count:count}}).done(function(data){onSuccess(data.records,data.count)})}
Server.prototype.searchRecords=function(query,offset,count,onSuccess){var handlerName=this.tableObj.getAlias()+'::onServerSearchRecords'
this.tableObj.$el.request(handlerName,{data:{query:query,offset:offset,count:count}}).done(function(data){onSuccess(data.records,data.count)})}
Server.prototype.createRecord=function(recordData,placement,relativeToKey,offset,count,onSuccess){var handlerName=this.tableObj.getAlias()+'::onServerCreateRecord'
this.tableObj.$el.request(handlerName,{data:{recordData:recordData,placement:placement,relativeToKey:relativeToKey,offset:offset,count:count}}).done(function(data){onSuccess(data.records,data.count)})}
Server.prototype.updateRecord=function(key,recordData){var handlerName=this.tableObj.getAlias()+'::onServerUpdateRecord'
this.tableObj.$el.request(handlerName,{data:{key:key,recordData:recordData}})}
Server.prototype.deleteRecord=function(key,newRecordData,offset,count,onSuccess){var handlerName=this.tableObj.getAlias()+'::onServerDeleteRecord'
this.tableObj.$el.request(handlerName,{data:{key:key,offset:offset,count:count}}).done(function(data){onSuccess(data.records,data.count)})}
$.wn.table.datasource.server=Server}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)$.wn.table.processor={}
var Base=function(tableObj,columnName,columnConfiguration){this.tableObj=tableObj
this.columnName=columnName
this.columnConfiguration=columnConfiguration
this.activeCell=null
this.validators=[]
this.registerHandlers()
this.initValidators()}
Base.prototype.dispose=function(){this.unregisterHandlers()
this.tableObj=null
this.activeCell=null}
Base.prototype.renderCell=function(value,cellContentContainer){}
Base.prototype.registerHandlers=function(){}
Base.prototype.unregisterHandlers=function(){}
Base.prototype.onFocus=function(cellElement,isClick){}
Base.prototype.onUnfocus=function(){}
Base.prototype.onKeyDown=function(ev){}
Base.prototype.onClick=function(ev){}
Base.prototype.onRowValueChanged=function(columnName,cellElement){}
Base.prototype.keyNavigationAllowed=function(ev,direction){return true}
Base.prototype.isCellFocusable=function(){return true}
Base.prototype.getCellContentContainer=function(cellElement){return cellElement.querySelector('.content-container')}
Base.prototype.createViewContainer=function(cellContentContainer,value){var viewContainer=document.createElement('div')
viewContainer.setAttribute('data-view-container','data-view-container')
viewContainer.textContent=value===undefined?'':value
cellContentContainer.appendChild(viewContainer)
return viewContainer}
Base.prototype.getViewContainer=function(cellElement){return cellElement.querySelector('[data-view-container]')}
Base.prototype.showViewContainer=function(cellElement){return this.getViewContainer(cellElement).setAttribute('class','')}
Base.prototype.hideViewContainer=function(cellElement){return this.getViewContainer(cellElement).setAttribute('class','hide')}
Base.prototype.setViewContainerValue=function(cellElement,value){return this.getViewContainer(cellElement).textContent=value}
Base.prototype.elementBelongsToProcessor=function(element){return false}
Base.prototype.initValidators=function(){if(this.columnConfiguration.validation===undefined)return
for(var validatorName in this.columnConfiguration.validation){if($.wn.table.validator===undefined||$.wn.table.validator[validatorName]==undefined)throw new Error('The table cell validator "'+validatorName+'" for the column "'+this.columnName+'" is not '+'found in the $.wn.table.validator namespace.')
var validator=new $.wn.table.validator[validatorName](this.columnConfiguration.validation[validatorName])
this.validators.push(validator)}}
Base.prototype.validate=function(value,rowData){for(var i=0,len=this.validators.length;i<len;i++){var message=this.validators[i].validate(value,rowData)
if(message!==undefined)return message}}
$.wn.table.processor.base=Base}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.wn.table.processor.base,BaseProto=Base.prototype
var StringProcessor=function(tableObj,columnName,columnConfiguration){Base.call(this,tableObj,columnName,columnConfiguration)}
StringProcessor.prototype=Object.create(BaseProto)
StringProcessor.prototype.constructor=StringProcessor
StringProcessor.prototype.dispose=function(){BaseProto.dispose.call(this)}
StringProcessor.prototype.renderCell=function(value,cellContentContainer){this.createViewContainer(cellContentContainer,value);if(this.columnConfiguration.readonly||this.columnConfiguration.readOnly){cellContentContainer.classList.add('readonly');cellContentContainer.setAttribute('tabindex',0);}}
StringProcessor.prototype.onFocus=function(cellElement,isClick){if(this.activeCell===cellElement)return
this.activeCell=cellElement
if(!this.columnConfiguration.readonly&&!this.columnConfiguration.readOnly){this.buildEditor(cellElement,this.getCellContentContainer(cellElement))}else{this.getCellContentContainer(cellElement).focus()}}
StringProcessor.prototype.onUnfocus=function(){if(!this.activeCell)return
var editor=this.activeCell.querySelector('.string-input')
if(editor){this.tableObj.setCellValue(this.activeCell,editor.value)
this.setViewContainerValue(this.activeCell,editor.value)
editor.parentNode.removeChild(editor)}this.showViewContainer(this.activeCell)
this.activeCell=null}
StringProcessor.prototype.buildEditor=function(cellElement,cellContentContainer){this.hideViewContainer(this.activeCell)
var input=document.createElement('input')
input.setAttribute('type','text')
input.setAttribute('class','string-input')
input.value=this.tableObj.getCellValue(cellElement)
cellContentContainer.appendChild(input)
input.focus();this.setCaretPosition(input,0);}
StringProcessor.prototype.keyNavigationAllowed=function(ev,direction){if(direction!='left'&&direction!='right')return true
if(!this.activeCell)return true
var editor=this.activeCell.querySelector('.string-input')
if(!editor)return true
var caretPosition=this.getCaretPosition(editor)
if(direction=='left')return caretPosition==0
if(direction=='right')return caretPosition==editor.value.length
return true}
StringProcessor.prototype.onRowValueChanged=function(columnName,cellElement){if(columnName!=this.columnName){return}var value=this.tableObj.getCellValue(cellElement)
this.setViewContainerValue(cellElement,value)}
StringProcessor.prototype.getCaretPosition=function(input){if(document.selection){var selection=document.selection.createRange()
selection.moveStart('character',-input.value.length)
return selection.text.length}if(input.selectionStart!==undefined)return input.selectionStart
return 0}
StringProcessor.prototype.setCaretPosition=function(input,position){if(document.selection){var range=input.createTextRange()
setTimeout(function(){range.collapse(true)
range.moveStart("character",position)
range.moveEnd("character",0)
range.select()},0)}if(input.selectionStart!==undefined){setTimeout(function(){input.selectionStart=position
input.selectionEnd=position},0)}return 0}
$.wn.table.processor.string=StringProcessor;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.wn.table.processor.base,BaseProto=Base.prototype
var CheckboxProcessor=function(tableObj,columnName,columnConfiguration){Base.call(this,tableObj,columnName,columnConfiguration)}
CheckboxProcessor.prototype=Object.create(BaseProto)
CheckboxProcessor.prototype.constructor=CheckboxProcessor
CheckboxProcessor.prototype.dispose=function(){BaseProto.dispose.call(this)}
CheckboxProcessor.prototype.isCellFocusable=function(){return false}
CheckboxProcessor.prototype.renderCell=function(value,cellContentContainer){var checkbox=document.createElement('div')
checkbox.setAttribute('data-checkbox-element','true')
checkbox.setAttribute('tabindex','0')
if(value&&value!=0&&value!="false"){checkbox.setAttribute('class','checked')}cellContentContainer.appendChild(checkbox)
if(this.columnConfiguration.readonly||this.columnConfiguration.readOnly){cellContentContainer.classList.add('readonly');}}
CheckboxProcessor.prototype.onFocus=function(cellElement,isClick){cellElement.querySelector('div[data-checkbox-element]').focus()}
CheckboxProcessor.prototype.onKeyDown=function(ev){if(ev.key==='(Space character)'||ev.key==='Spacebar'||ev.key===' ')this.onClick(ev)}
CheckboxProcessor.prototype.onClick=function(ev){if(this.columnConfiguration.readonly||this.columnConfiguration.readOnly){return}var target=this.tableObj.getEventTarget(ev,'DIV')
if(target.getAttribute('data-checkbox-element')){var container=this.getCheckboxContainerNode(target)
if(container.getAttribute('data-column')!==this.columnName){return}this.changeState(target)
$(ev.target).trigger('change')}}
CheckboxProcessor.prototype.changeState=function(divElement){var cell=divElement.parentNode.parentNode
if(divElement.getAttribute('class')=='checked'){divElement.setAttribute('class','')
this.tableObj.setCellValue(cell,0)}else{divElement.setAttribute('class','checked')
this.tableObj.setCellValue(cell,1)}}
CheckboxProcessor.prototype.getCheckboxContainerNode=function(checkbox){return checkbox.parentNode.parentNode}
CheckboxProcessor.prototype.onRowValueChanged=function(columnName,cellElement){if(columnName!=this.columnName){return}var checkbox=cellElement.querySelector('div[data-checkbox-element]'),value=this.tableObj.getCellValue(cellElement)
if(value&&value!=0&&value!="false"){checkbox.setAttribute('class','checked')}else{checkbox.setAttribute('class','')}}
$.wn.table.processor.checkbox=CheckboxProcessor;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.wn.table.processor.base,BaseProto=Base.prototype
var DropdownProcessor=function(tableObj,columnName,columnConfiguration){this.itemListElement=null
this.cachedOptionPromises={}
this.searching=false
this.searchQuery=null
this.searchInterval=null
this.itemClickHandler=this.onItemClick.bind(this)
this.itemKeyDownHandler=this.onItemKeyDown.bind(this)
this.itemMouseMoveHandler=this.onItemMouseMove.bind(this)
Base.call(this,tableObj,columnName,columnConfiguration)}
DropdownProcessor.prototype=Object.create(BaseProto)
DropdownProcessor.prototype.constructor=DropdownProcessor
DropdownProcessor.prototype.dispose=function(){this.unregisterListHandlers()
this.itemClickHandler=null
this.itemKeyDownHandler=null
this.itemMouseMoveHandler=null
this.itemListElement=null
this.cachedOptionPromises=null
BaseProto.dispose.call(this)}
DropdownProcessor.prototype.unregisterListHandlers=function(){if(this.itemListElement){this.itemListElement.removeEventListener('click',this.itemClickHandler)
this.itemListElement.removeEventListener('keydown',this.itemKeyDownHandler)
this.itemListElement.removeEventListener('mousemove',this.itemMouseMoveHandler)}}
DropdownProcessor.prototype.renderCell=function(value,cellContentContainer){let viewContainer;if(this.columnConfiguration.readonly||this.columnConfiguration.readOnly){viewContainer=this.createViewContainer(cellContentContainer,value)
cellContentContainer.classList.add('readonly');cellContentContainer.setAttribute('tabindex',0);return;}viewContainer=this.createViewContainer(cellContentContainer,'...')
this.fetchOptions(cellContentContainer.parentNode,function renderCellFetchOptions(options){if(options[value]!==undefined)viewContainer.textContent=options[value]
cellContentContainer.setAttribute('tabindex',0)})}
DropdownProcessor.prototype.onFocus=function(cellElement,isClick){if(this.activeCell===cellElement){this.showDropdown()
return}this.activeCell=cellElement
if(!this.columnConfiguration.readonly&&!this.columnConfiguration.readOnly){var cellContentContainer=this.getCellContentContainer(cellElement)
this.buildEditor(cellElement,cellContentContainer,isClick)
if(!isClick){cellContentContainer.focus()}}else{this.getCellContentContainer(cellElement).focus()}}
DropdownProcessor.prototype.onUnfocus=function(){if(!this.activeCell)return
this.unregisterListHandlers()
this.hideDropdown()
this.itemListElement=null
this.activeCell=null}
DropdownProcessor.prototype.buildEditor=function(cellElement,cellContentContainer,isClick){var currentValue=this.tableObj.getCellValue(cellElement),containerPosition=this.getAbsolutePosition(cellContentContainer),self=this
this.itemListElement=document.createElement('div')
this.itemListElement.addEventListener('click',this.itemClickHandler)
this.itemListElement.addEventListener('keydown',this.itemKeyDownHandler)
this.itemListElement.addEventListener('mousemove',this.itemMouseMoveHandler)
this.itemListElement.setAttribute('class','table-control-dropdown-list')
this.itemListElement.style.width=cellContentContainer.offsetWidth+'px'
this.itemListElement.style.left=containerPosition.left+'px'
this.itemListElement.style.top=containerPosition.top-2+cellContentContainer.offsetHeight+'px'
this.fetchOptions(cellElement,function renderCellFetchOptions(options){var listElement=document.createElement('ul')
for(var value in options){var itemElement=document.createElement('li')
itemElement.setAttribute('data-value',value)
itemElement.textContent=options[value]
itemElement.setAttribute('tabindex',0)
if(value==currentValue)itemElement.setAttribute('class','selected')
listElement.appendChild(itemElement)}self.itemListElement.appendChild(listElement)
if(isClick)self.showDropdown()
self=null})}
DropdownProcessor.prototype.hideDropdown=function(){if(this.itemListElement&&this.activeCell&&this.itemListElement.parentNode){var cellContentContainer=this.getCellContentContainer(this.activeCell)
cellContentContainer.setAttribute('data-dropdown-open','false')
this.itemListElement.parentNode.removeChild(this.itemListElement)
cellContentContainer.focus()}}
DropdownProcessor.prototype.showDropdown=function(){if(this.itemListElement&&this.itemListElement.parentNode!==document.body){this.getCellContentContainer(this.activeCell).setAttribute('data-dropdown-open','true')
document.body.appendChild(this.itemListElement)
var activeItemElement=this.itemListElement.querySelector('ul li.selected')
if(!activeItemElement){activeItemElement=this.itemListElement.querySelector('ul li:first-child')
if(activeItemElement)activeItemElement.setAttribute('class','selected')}if(activeItemElement){window.setTimeout(function(){activeItemElement.focus()},0)}}}
DropdownProcessor.prototype.fetchOptions=function(cellElement,onSuccess){if(this.columnConfiguration.options){onSuccess(this.columnConfiguration.options)}else{var row=cellElement.parentNode,cachingKey=this.createOptionsCachingKey(row),viewContainer=this.getViewContainer(cellElement)
viewContainer.setAttribute('class','loading')
if(!this.cachedOptionPromises[cachingKey]){var requestData={column:this.columnName,rowData:this.tableObj.getRowData(row)},handlerName=this.tableObj.getAlias()+'::onGetDropdownOptions'
this.cachedOptionPromises[cachingKey]=this.tableObj.$el.request(handlerName,{data:requestData})}this.cachedOptionPromises[cachingKey].done(function onDropDownLoadOptionsSuccess(data){onSuccess(data.options)}).always(function onDropDownLoadOptionsAlways(){viewContainer.setAttribute('class','')})}}
DropdownProcessor.prototype.createOptionsCachingKey=function(row){var cachingKey='non-dependent',dependsOn=this.columnConfiguration.dependsOn
if(dependsOn){if(typeof dependsOn=='object'){for(var i=0,len=dependsOn.length;i<len;i++)cachingKey+=dependsOn[i]+this.tableObj.getRowCellValueByColumnName(row,dependsOn[i])}else cachingKey=dependsOn+this.tableObj.getRowCellValueByColumnName(row,dependsOn)}return cachingKey}
DropdownProcessor.prototype.getAbsolutePosition=function(element){var top=document.body.scrollTop,left=0
do{top+=element.offsetTop||0;top-=element.scrollTop||0;left+=element.offsetLeft||0;element=element.offsetParent;}while(element)return{top:top,left:left}}
DropdownProcessor.prototype.updateCellFromFocusedItem=function(focusedItem){if(!focusedItem){focusedItem=this.findFocusedItem();}this.setSelectedItem(focusedItem);}
DropdownProcessor.prototype.findSelectedItem=function(){if(this.itemListElement)return this.itemListElement.querySelector('ul li.selected')
return null}
DropdownProcessor.prototype.setSelectedItem=function(item){if(!this.itemListElement)return null;if(item.tagName=='LI'&&this.itemListElement.contains(item)){this.itemListElement.querySelectorAll('ul li').forEach(function(option){option.removeAttribute('class');});item.setAttribute('class','selected');}this.tableObj.setCellValue(this.activeCell,item.getAttribute('data-value'))
this.setViewContainerValue(this.activeCell,item.textContent)}
DropdownProcessor.prototype.findFocusedItem=function(){if(this.itemListElement)return this.itemListElement.querySelector('ul li:focus')
return null}
DropdownProcessor.prototype.onItemClick=function(ev){var target=this.tableObj.getEventTarget(ev)
if(target.tagName=='LI'){target.focus();this.updateCellFromFocusedItem(target)
this.hideDropdown()}}
DropdownProcessor.prototype.onItemKeyDown=function(ev){if(!this.itemListElement)return
if(ev.key==='ArrowDown'||ev.key==='ArrowUp'){var focused=this.findFocusedItem(),newFocusedItem=focused.nextElementSibling
if(ev.key==='ArrowUp')newFocusedItem=focused.previousElementSibling
if(newFocusedItem){newFocusedItem.focus()}return}if(ev.key==='Enter'||ev.key==='(Space character)'||ev.key==='Spacebar'||ev.key===' '){this.updateCellFromFocusedItem()
this.hideDropdown()
return}if(ev.key==='Tab'){this.updateCellFromFocusedItem()
this.tableObj.navigation.navigateNext(ev)
this.tableObj.stopEvent(ev)
return}if(ev.key==='Escape'){this.hideDropdown()
return}this.searchByTextInput(ev,true);}
DropdownProcessor.prototype.onItemMouseMove=function(ev){if(!this.itemListElement)return
var target=this.tableObj.getEventTarget(ev)
if(target.tagName=='LI'){target.focus();}}
DropdownProcessor.prototype.onKeyDown=function(ev){if(!this.itemListElement)return
if((ev.key==='(Space character)'||ev.key==='Spacebar'||ev.key===' ')&&!this.searching){this.showDropdown()}else if(ev.key==='ArrowDown'||ev.key==='ArrowUp'){var selected=this.findSelectedItem(),newSelectedItem;if(!selected){if(ev.key==='ArrowUp'){return false}newSelectedItem=this.itemListElement.querySelector('ul li:first-child')}else{newSelectedItem=selected.nextElementSibling
if(ev.key==='ArrowUp')newSelectedItem=selected.previousElementSibling}if(newSelectedItem){this.setSelectedItem(newSelectedItem);}return false}else{this.searchByTextInput(ev);}}
DropdownProcessor.prototype.onRowValueChanged=function(columnName,cellElement){if(!this.columnConfiguration.dependsOn)return
var dependsOnColumn=false,dependsOn=this.columnConfiguration.dependsOn
if(typeof dependsOn=='object'){for(var i=0,len=dependsOn.length;i<len;i++){if(dependsOn[i]==columnName){dependsOnColumn=true
break}}}else{dependsOnColumn=dependsOn==columnName}if(!dependsOnColumn)return
var currentValue=this.tableObj.getCellValue(cellElement),viewContainer=this.getViewContainer(cellElement)
this.fetchOptions(cellElement,function rowValueChangedFetchOptions(options){var value=options[currentValue]!==undefined?options[currentValue]:'...'
viewContainer.textContent=value
viewContainer=null})}
DropdownProcessor.prototype.elementBelongsToProcessor=function(element){if(!this.itemListElement)return false
return this.tableObj.parentContainsElement(this.itemListElement,element)}
DropdownProcessor.prototype.searchByTextInput=function(ev,focusOnly){if(focusOnly===undefined){focusOnly=false;}var character=ev.key;if(character.length===1||character==='Space'){if(!this.searching){this.searching=true;this.searchQuery='';}this.searchQuery+=(character==='Space')?' ':character;var validItem=null;var query=this.searchQuery;this.itemListElement.querySelectorAll('ul li').forEach(function(item){if(validItem===null&&item.dataset.value&&item.dataset.value.toLowerCase().indexOf(query.toLowerCase())===0){validItem=item;}});if(validItem){if(focusOnly===true){validItem.focus();}else{this.setSelectedItem(validItem);}if(this.searchInterval){clearTimeout(this.searchInterval);}this.searchInterval=setTimeout(this.cancelTextSearch.bind(this),1000);}else{this.cancelTextSearch();}}}
DropdownProcessor.prototype.cancelTextSearch=function(){this.searching=false;this.searchQuery=null;this.searchInterval=null;}
$.wn.table.processor.dropdown=DropdownProcessor;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.processor===undefined)throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");var Base=$.wn.table.processor.string,BaseProto=Base.prototype
var AutocompleteProcessor=function(tableObj,columnName,columnConfiguration){this.cachedOptionPromises={}
Base.call(this,tableObj,columnName,columnConfiguration)}
AutocompleteProcessor.prototype=Object.create(BaseProto)
AutocompleteProcessor.prototype.constructor=AutocompleteProcessor
AutocompleteProcessor.prototype.dispose=function(){this.cachedOptionPromises=null
BaseProto.dispose.call(this)}
AutocompleteProcessor.prototype.onUnfocus=function(){if(!this.activeCell)return
this.removeAutocomplete()
BaseProto.onUnfocus.call(this)}
AutocompleteProcessor.prototype.renderCell=function(value,cellContentContainer){BaseProto.renderCell.call(this,value,cellContentContainer)}
AutocompleteProcessor.prototype.buildEditor=function(cellElement,cellContentContainer,isClick){BaseProto.buildEditor.call(this,cellElement,cellContentContainer,isClick)
var self=this
this.fetchOptions(cellElement,function autocompleteFetchOptions(options){self.buildAutoComplete(options)
self=null})}
AutocompleteProcessor.prototype.fetchOptions=function(cellElement,onSuccess){if(this.columnConfiguration.options){if(onSuccess!==undefined){onSuccess(this.columnConfiguration.options)}}else{if(this.triggerGetOptions(onSuccess)===false){return}var row=cellElement.parentNode,cachingKey=this.createOptionsCachingKey(row),viewContainer=this.getViewContainer(cellElement)
$.wn.foundation.element.addClass(viewContainer,'loading')
if(!this.cachedOptionPromises[cachingKey]){var requestData={column:this.columnName,rowData:this.tableObj.getRowData(row)},handlerName=this.tableObj.getAlias()+'::onGetAutocompleteOptions'
this.cachedOptionPromises[cachingKey]=this.tableObj.$el.request(handlerName,{data:requestData})}this.cachedOptionPromises[cachingKey].done(function onAutocompleteLoadOptionsSuccess(data){if(onSuccess!==undefined){onSuccess(data.options)}}).always(function onAutocompleteLoadOptionsAlways(){$.wn.foundation.element.removeClass(viewContainer,'loading')})}}
AutocompleteProcessor.prototype.createOptionsCachingKey=function(row){var cachingKey='non-dependent',dependsOn=this.columnConfiguration.dependsOn
if(dependsOn){if(typeof dependsOn=='object'){for(var i=0,len=dependsOn.length;i<len;i++)cachingKey+=dependsOn[i]+this.tableObj.getRowCellValueByColumnName(row,dependsOn[i])}else cachingKey=dependsOn+this.tableObj.getRowCellValueByColumnName(row,dependsOn)}return cachingKey}
AutocompleteProcessor.prototype.triggerGetOptions=function(callback){var tableElement=this.tableObj.getElement()
if(!tableElement){return}var optionsEvent=$.Event('autocompleteitems.oc.table'),values={}
$(tableElement).trigger(optionsEvent,[{values:values,callback:callback,column:this.columnName,columnConfiguration:this.columnConfiguration}])
if(optionsEvent.isDefaultPrevented()){return false}return true}
AutocompleteProcessor.prototype.getInput=function(){if(!this.activeCell){return null}return this.activeCell.querySelector('.string-input')}
AutocompleteProcessor.prototype.buildAutoComplete=function(items){if(!this.activeCell){return}var input=this.getInput()
if(!input){return}if(items===undefined){items=[]}$(input).autocomplete({source:this.prepareItems(items),matchWidth:true,menu:'<ul class="autocomplete dropdown-menu table-widget-autocomplete"></ul>',bodyContainer:true})}
AutocompleteProcessor.prototype.prepareItems=function(items){var result={}
if($.isArray(items)){for(var i=0,len=items.length;i<len;i++){result[items[i]]=items[i]}}else{result=items}return result}
AutocompleteProcessor.prototype.removeAutocomplete=function(){var input=this.getInput()
$(input).autocomplete('destroy')}
$.wn.table.processor.autocomplete=AutocompleteProcessor;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)$.wn.table.validator={}
var Base=function(options){this.options=options}
Base.prototype.validate=function(value,rowData){if(this.options.requiredWith!==undefined&&!this.rowHasValue(this.options.requiredWith,rowData))return
return this.validateValue(value,rowData)}
Base.prototype.validateValue=function(value,rowData){}
Base.prototype.trim=function(value){if(String.prototype.trim)return value.trim()
return value.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,'')}
Base.prototype.getMessage=function(defaultValue){if(this.options.message!==undefined)return this.options.message
return defaultValue}
Base.prototype.rowHasValue=function(columnName,rowData){if(rowData[columnName]===undefined)return false
if(typeof rowData[columnName]=='boolean')return rowData[columnName]
var value=this.trim(String(rowData[columnName]))
return value.length>0}
$.wn.table.validator.base=Base;}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");var Base=$.wn.table.validator.base,BaseProto=Base.prototype
var Required=function(options){Base.call(this,options)};Required.prototype=Object.create(BaseProto)
Required.prototype.constructor=Required
Required.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length===0)return this.getMessage("The value should not be empty.")
return}
$.wn.table.validator.required=Required}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");var Base=$.wn.table.validator.base,BaseProto=Base.prototype
var BaseNumber=function(options){Base.call(this,options)};BaseNumber.prototype=Object.create(BaseProto)
BaseNumber.prototype.constructor=BaseNumber
BaseNumber.prototype.doCommonChecks=function(value){if(this.options.min!==undefined||this.options.max!==undefined){if(this.options.min!==undefined){if(this.options.min.value===undefined)throw new Error('The min.value parameter is not defined in the table validator configuration')
if(value<this.options.min.value){return this.options.min.message!==undefined?this.options.min.message:"The value should not be less than "+this.options.min.value}}if(this.options.max!==undefined){if(this.options.max.value===undefined)throw new Error('The max.value parameter is not defined in the table validator configuration')
if(value>this.options.max.value){return this.options.max.message!==undefined?this.options.max.message:"The value should not be more than "+this.options.max.value}}}return}
$.wn.table.validator.baseNumber=BaseNumber}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");if($.wn.table.validator.baseNumber===undefined)throw new Error("The $.wn.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");var Base=$.wn.table.validator.baseNumber,BaseProto=Base.prototype
var Integer=function(options){Base.call(this,options)};Integer.prototype=Object.create(BaseProto)
Integer.prototype.constructor=Integer
Integer.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length==0)return
var testResult=this.options.allowNegative?/^\-?[0-9]*$/.test(value):/^[0-9]*$/.test(value)
if(!testResult){var defaultMessage=this.options.allowNegative?'The value should be an integer.':'The value should be a positive integer';return this.getMessage(defaultMessage)}return this.doCommonChecks(parseInt(value))}
$.wn.table.validator.integer=Integer}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");if($.wn.table.validator.baseNumber===undefined)throw new Error("The $.wn.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");var Base=$.wn.table.validator.baseNumber,BaseProto=Base.prototype
var Float=function(options){Base.call(this,options)};Float.prototype=Object.create(BaseProto)
Float.prototype.constructor=Float
Float.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length==0)return
var testResult=this.options.allowNegative?/^[-]?([0-9]+\.[0-9]+|[0-9]+)$/.test(value):/^([0-9]+\.[0-9]+|[0-9]+)$/.test(value)
if(!testResult){var defaultMessage=this.options.allowNegative?'The value should be a floating point number.':'The value should be a positive floating point number';return this.getMessage(defaultMessage)}return this.doCommonChecks(parseFloat(value))}
$.wn.table.validator.float=Float}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");var Base=$.wn.table.validator.base,BaseProto=Base.prototype
var Length=function(options){Base.call(this,options)};Length.prototype=Object.create(BaseProto)
Length.prototype.constructor=Length
Length.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length==0)return
if(this.options.min!==undefined||this.options.max!==undefined){if(this.options.min!==undefined){if(this.options.min.value===undefined)throw new Error('The min.value parameter is not defined in the Length table validator configuration')
if(value.length<this.options.min.value){return this.options.min.message!==undefined?this.options.min.message:"The string should not be shorter than "+this.options.min.value}}if(this.options.max!==undefined){if(this.options.max.value===undefined)throw new Error('The max.value parameter is not defined in the Length table validator configuration')
if(value.length>this.options.max.value){return this.options.max.message!==undefined?this.options.max.message:"The string should not be longer than "+this.options.max.value}}}return}
$.wn.table.validator.length=Length}(window.jQuery);+function($){"use strict";if($.wn.table===undefined)throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");if($.wn.table.validator===undefined)throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");var Base=$.wn.table.validator.base,BaseProto=Base.prototype
var Regex=function(options){Base.call(this,options)};Regex.prototype=Object.create(BaseProto)
Regex.prototype.constructor=Regex
Regex.prototype.validateValue=function(value,rowData){value=this.trim(value)
if(value.length==0)return
if(this.options.pattern===undefined)throw new Error('The pattern parameter is not defined in the Regex table validator configuration')
var regexObj=new RegExp(this.options.pattern,this.options.modifiers)
if(!regexObj.test(value))return this.getMessage("Invalid value format.")
return}
$.wn.table.validator.regex=Regex}(window.jQuery);

View File

@@ -0,0 +1,28 @@
/*
* This is a bundle file, you can compile this by running
*
* php artisan winter:util compile assets
*
* @see build-min.js
*
=require table.js
=require table.helper.navigation.js
=require table.helper.search.js
=require table.datasource.base.js
=require table.datasource.client.js
=require table.datasource.server.js
=require table.processor.base.js
=require table.processor.string.js
=require table.processor.checkbox.js
=require table.processor.dropdown.js
=require table.processor.autocomplete.js
=require table.validator.base.js
=require table.validator.required.js
=require table.validator.basenumber.js
=require table.validator.integer.js
=require table.validator.float.js
=require table.validator.length.js
=require table.validator.regex.js
*/

View File

@@ -0,0 +1,92 @@
/*
* Base class for the table data sources.
*/
+function ($) { "use strict";
// DATASOURCE NAMESPACES
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.datasource === undefined)
$.wn.table.datasource = {}
// CLASS DEFINITION
// ============================
var Base = function(tableObj) {
//
// State properties
//
this.tableObj = tableObj
}
Base.prototype.dispose = function() {
this.tableObj = null
}
/*
* Fetches records from the underlying data source and
* passes them to the onSuccess callback function.
* The onSuccess callback parameters: records, totalCount.
* Each record contains the key field which uniquely identifies
* the record. The name of the key field is defined with the table
* widget options.
*/
Base.prototype.getRecords = function(offset, count, onSuccess) {
onSuccess([])
}
/*
* Identical to getRecords except using a search query.
*/
Base.prototype.searchRecords = function(query, offset, count, onSuccess) {
onSuccess([])
}
/*
* Creates a record with the passed data and returns the updated page records
* to the onSuccess callback function.
*
* - recordData - the record fields
* - placement - "bottom" (the end of the data set), "above", "below"
* - relativeToKey - a row key, required if the placement is not "bottom"
* - offset - the current page's first record index (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Base.prototype.createRecord = function(recordData, placement, relativeToKey, offset, count, onSuccess) {
onSuccess([], 0)
}
/*
* Updates a record with the specified key with the passed data
*
* - key - the record key in the dataset (primary key, etc)
* - recordData - the record fields.
*/
Base.prototype.updateRecord = function(key, recordData) {
}
/*
* Deletes a record with the specified key.
*
* - key - the record key in the dataset (primary key, etc).
* - newRecordData - replacement record to add to the dataset if the deletion
* empties it.
* - offset - the current page's first record key (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Base.prototype.deleteRecord = function(key, newRecordData, offset, count, onSuccess) {
onSuccess([], 0)
}
$.wn.table.datasource.base = Base;
}(window.jQuery);

View File

@@ -0,0 +1,184 @@
/*
* Client memory data source for the table control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.datasource === undefined)
throw new Error("The $.wn.table.datasource namespace is not defined. Make sure that the table.datasource.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.datasource.base,
BaseProto = Base.prototype
var Client = function(tableObj) {
Base.call(this, tableObj)
var dataString = tableObj.getElement().getAttribute('data-data')
if (dataString === null || dataString === undefined)
throw new Error('The required data-data attribute is not found on the table control element.')
this.data = JSON.parse(dataString)
};
Client.prototype = Object.create(BaseProto)
Client.prototype.constructor = Client
Client.prototype.dispose = function() {
BaseProto.dispose.call(this)
this.data = null
}
/*
* Fetches records from the underlying data source and
* passes them to the onSuccess callback function.
* The onSuccess callback parameters: records, totalCount.
* Each record contains the key field which uniquely identifies
* the record. The name of the key field is defined with the table
* widget options.
*/
Client.prototype.getRecords = function(offset, count, onSuccess) {
if (!count) {
// Return all records
onSuccess(this.data, this.data.length)
}
else {
// Return a subset of records
onSuccess(this.data.slice(offset, offset+count), this.data.length)
}
}
/*
* Creates a record with the passed data and returns the updated page records
* to the onSuccess callback function.
*
* - recordData - the record fields
* - placement - "bottom" (the end of the data set), "above", "below"
* - relativeToKey - a row key, required if the placement is not "bottom"
* - offset - the current page's first record index (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Client.prototype.createRecord = function(recordData, placement, relativeToKey, offset, count, onSuccess) {
if (placement === 'bottom') {
// Add record to the bottom of the dataset
this.data.push(recordData)
}
else if (placement == 'above' || placement == 'below') {
// Add record above or below the passed record key
var recordIndex = this.getIndexOfKey(relativeToKey)
if (placement == 'below')
recordIndex ++
this.data.splice(recordIndex, 0, recordData)
}
this.getRecords(offset, count, onSuccess)
}
/*
* Identical to getRecords except using a search query.
*/
Client.prototype.searchRecords = function(query, offset, count, onSuccess) {
const searchFields = this.tableObj.search.getSearchableColumns();
const matched = this.data.filter(function(record) {
for (let i = 0; i < searchFields.length; i++) {
const value = record[searchFields[i]];
if (value === undefined) {
continue;
}
if (value.toString().toLowerCase().includes(query.toLowerCase())) {
return true;
}
}
return false;
});
if (matched.length === 0) {
onSuccess([]);
return;
}
if (!count) {
// Return all records
onSuccess(matched, matched.length);
} else {
// Return a subset of records
onSuccess(matched.slice(offset, offset + count), matched.length);
}
}
/*
* Updates a record with the specified key with the passed data
*
* - key - the record key in the dataset (primary key, etc)
* - recordData - the record fields.
*/
Client.prototype.updateRecord = function(key, recordData) {
var recordIndex = this.getIndexOfKey(key)
if (recordIndex !== -1) {
recordData[this.tableObj.options.keyColumn] = key
this.data[recordIndex] = recordData
}
else {
throw new Error('Record with they key '+key+ ' is not found in the data set')
}
}
/*
* Deletes a record with the specified key.
*
* - key - the record key in the dataset (primary key, etc).
* - newRecordData - replacement record to add to the dataset if the deletion
* empties it.
* - offset - the current page's first record key (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Client.prototype.deleteRecord = function(key, newRecordData, offset, count, onSuccess) {
var recordIndex = this.getIndexOfKey(key)
if (recordIndex !== -1) {
this.data.splice(recordIndex, 1)
if (this.data.length == 0)
this.data.push(newRecordData)
this.getRecords(offset, count, onSuccess)
}
else {
throw new Error('Record with they key '+key+ ' is not found in the data set')
}
}
Client.prototype.getIndexOfKey = function(key) {
var keyColumn = this.tableObj.options.keyColumn
return this.data.map(function(record) {
return record[keyColumn] + ""
}).indexOf(key + "")
}
Client.prototype.getAllData = function() {
return this.data
}
$.wn.table.datasource.client = Client
}(window.jQuery);

View File

@@ -0,0 +1,146 @@
/*
* Server memory data source for the table control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.datasource === undefined)
throw new Error("The $.wn.table.datasource namespace is not defined. Make sure that the table.datasource.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.datasource.base,
BaseProto = Base.prototype
var Server = function(tableObj) {
Base.call(this, tableObj)
var dataString = tableObj.getElement().getAttribute('data-data')
if (dataString === null || dataString === undefined)
throw new Error('The required data-data attribute is not found on the table control element.')
this.data = JSON.parse(dataString)
};
Server.prototype = Object.create(BaseProto)
Server.prototype.constructor = Server
Server.prototype.dispose = function() {
BaseProto.dispose.call(this)
this.data = null
}
/*
* Fetches records from the underlying data source and
* passes them to the onSuccess callback function.
* The onSuccess callback parameters: records, totalCount.
* Each record contains the key field which uniquely identifies
* the record. The name of the key field is defined with the table
* widget options.
*/
Server.prototype.getRecords = function(offset, count, onSuccess) {
var handlerName = this.tableObj.getAlias()+'::onServerGetRecords'
this.tableObj.$el.request(handlerName, {
data: {
offset: offset,
count: count
}
}).done(function(data) {
onSuccess(data.records, data.count)
})
}
/*
* Identical to getRecords except using a search query.
*/
Server.prototype.searchRecords = function(query, offset, count, onSuccess) {
var handlerName = this.tableObj.getAlias()+'::onServerSearchRecords'
this.tableObj.$el.request(handlerName, {
data: {
query: query,
offset: offset,
count: count
}
}).done(function(data) {
onSuccess(data.records, data.count)
})
}
/*
* Creates a record with the passed data and returns the updated page records
* to the onSuccess callback function.
*
* - recordData - the record fields
* - placement - "bottom" (the end of the data set), "above", "below"
* - relativeToKey - a row key, required if the placement is not "bottom"
* - offset - the current page's first record index (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Server.prototype.createRecord = function(recordData, placement, relativeToKey, offset, count, onSuccess) {
var handlerName = this.tableObj.getAlias()+'::onServerCreateRecord'
this.tableObj.$el.request(handlerName, {
data: {
recordData: recordData,
placement: placement,
relativeToKey: relativeToKey,
offset: offset,
count: count
}
}).done(function(data) {
onSuccess(data.records, data.count)
})
}
/*
* Updates a record with the specified key with the passed data
*
* - key - the record key in the dataset (primary key, etc)
* - recordData - the record fields.
*/
Server.prototype.updateRecord = function(key, recordData) {
var handlerName = this.tableObj.getAlias()+'::onServerUpdateRecord'
this.tableObj.$el.request(handlerName, {
data: {
key: key,
recordData: recordData
}
})
}
/*
* Deletes a record with the specified key.
*
* - key - the record key in the dataset (primary key, etc).
* - newRecordData - replacement record to add to the dataset if the deletion
* empties it.
* - offset - the current page's first record key (zero-based)
* - count - number of records to return
* - onSuccess - a callback function to execute when the updated data gets available.
*
* The onSuccess callback parameters: records, totalCount.
*/
Server.prototype.deleteRecord = function(key, newRecordData, offset, count, onSuccess) {
var handlerName = this.tableObj.getAlias()+'::onServerDeleteRecord'
this.tableObj.$el.request(handlerName, {
data: {
key: key,
offset: offset,
count: count
}
}).done(function(data) {
onSuccess(data.records, data.count)
})
}
$.wn.table.datasource.server = Server
}(window.jQuery);

View File

@@ -0,0 +1,424 @@
/*
* Navigation helper for the table widget.
* Implements the keyboard navigation within the current page
* and pagination.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.helper === undefined)
$.wn.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 > 0;
}
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.key === 'ArrowDown')
return this.navigateDown(ev)
else if (ev.key === 'ArrowUp')
return this.navigateUp(ev)
else if (ev.key === 'ArrowLeft')
return this.navigateLeft(ev)
if (ev.key === 'ArrowRight')
return this.navigateRight(ev)
if (ev.key === 'Tab')
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
}
$.wn.table.helper.navigation = Navigation;
}(window.jQuery);

View File

@@ -0,0 +1,134 @@
/*
* Search helper for the table widget.
* Implements searching within the table.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.helper === undefined)
$.wn.table.helper = {}
// SEARCH CLASS DEFINITION
// ============================
var Search = function(tableObj) {
// Reference to the table object
this.tableObj = tableObj
// The search form element
this.searchForm = null
this.searchInput = null
// Timer used for tracking input changes
this.inputTrackTimer = null
// Event handlers
// Active search query
this.activeQuery = null
this.isActive = false
this.init()
};
Search.prototype.init = function() {
}
Search.prototype.dispose = function() {
// Remove the reference to the table object
this.tableObj = null
this.searchForm = null
this.searchInput = null
}
Search.prototype.buildSearchForm = function() {
if (!this.searchEnabled())
return
var el = this.tableObj.getElement(),
toolbar = this.tableObj.getToolbar(),
searchForm = toolbar.querySelector('.table-search')
if (!searchForm) {
this.searchForm = $($('[data-table-toolbar-search]', el).html()).appendTo(toolbar).get(0)
this.searchInput = $('.table-search-input', this.searchForm).get(0)
}
}
Search.prototype.getQuery = function() {
return $.trim(this.activeQuery)
}
Search.prototype.hasQuery = function() {
return this.searchEnabled() && $.trim(this.activeQuery).length > 0
}
Search.prototype.searchEnabled = function() {
return this.tableObj.options.searching
}
Search.prototype.getSearchableColumns = function() {
const columns = [];
this.tableObj.options.columns.forEach(function(column) {
if (column.type === 'checkbox') {
return;
}
if (!column.searchable) {
return;
}
columns.push(column.key);
});
return columns;
}
Search.prototype.performSearch = function(query, onSuccess) {
var isDirty = this.activeQuery != query
this.activeQuery = query
if (isDirty) {
this.tableObj.updateDataTable(onSuccess)
}
}
// EVENT HANDLERS
// ============================
Search.prototype.onKeydown = function(ev) {
// The navigation object uses the table's keydown handler
// and doesn't register own handler.
// Tab pressed
if (ev.key === 'Tab') {
this.onClick(ev)
return
}
if (!this.isActive) {
return
}
var self = this
this.inputTrackTimer = window.setTimeout(function() {
self.performSearch(self.searchInput.value)
}, 300)
}
Search.prototype.onClick = function(ev) {
var target = this.tableObj.getEventTarget(ev, 'INPUT')
this.isActive = target && $(target).hasClass('table-search-input')
}
$.wn.table.helper.search = Search;
}(window.jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
/*
* Autocomplete cell processor for the table control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.processor.string,
BaseProto = Base.prototype
var AutocompleteProcessor = function(tableObj, columnName, columnConfiguration) {
//
// State properties
//
this.cachedOptionPromises = {}
//
// Parent constructor
//
Base.call(this, tableObj, columnName, columnConfiguration)
}
AutocompleteProcessor.prototype = Object.create(BaseProto)
AutocompleteProcessor.prototype.constructor = AutocompleteProcessor
AutocompleteProcessor.prototype.dispose = function() {
this.cachedOptionPromises = null
BaseProto.dispose.call(this)
}
/*
* Forces the processor to hide the editor when the user navigates
* away from the cell. Processors can update the sell value in this method.
* Processors must clear the reference to the active cell in this method.
*/
AutocompleteProcessor.prototype.onUnfocus = function() {
if (!this.activeCell)
return
this.removeAutocomplete()
BaseProto.onUnfocus.call(this)
}
/*
* Renders the cell in the normal (no edit) mode
*/
AutocompleteProcessor.prototype.renderCell = function(value, cellContentContainer) {
BaseProto.renderCell.call(this, value, cellContentContainer)
}
AutocompleteProcessor.prototype.buildEditor = function(cellElement, cellContentContainer, isClick) {
BaseProto.buildEditor.call(this, cellElement, cellContentContainer, isClick)
var self = this
this.fetchOptions(cellElement, function autocompleteFetchOptions(options) {
self.buildAutoComplete(options)
self = null
})
}
AutocompleteProcessor.prototype.fetchOptions = function(cellElement, onSuccess) {
if (this.columnConfiguration.options) {
if (onSuccess !== undefined) {
onSuccess(this.columnConfiguration.options)
}
} else {
// If options are not provided and not found in the cache,
// request them from the server. For dependent autocomplete editors
// the caching key contains the master column values.
if (this.triggerGetOptions(onSuccess) === false) {
return
}
var row = cellElement.parentNode,
cachingKey = this.createOptionsCachingKey(row),
viewContainer = this.getViewContainer(cellElement)
// Request options from the server. When the table widget builds,
// multiple cells in the column could require loading the options.
// The AJAX promises are cached here so that we have a single
// request per caching key.
$.wn.foundation.element.addClass(viewContainer, 'loading')
if (!this.cachedOptionPromises[cachingKey]) {
var requestData = {
column: this.columnName,
rowData: this.tableObj.getRowData(row)
},
handlerName = this.tableObj.getAlias()+'::onGetAutocompleteOptions'
this.cachedOptionPromises[cachingKey] = this.tableObj.$el.request(handlerName, {data: requestData})
}
this.cachedOptionPromises[cachingKey].done(function onAutocompleteLoadOptionsSuccess(data){
if (onSuccess !== undefined) {
onSuccess(data.options)
}
}).always(function onAutocompleteLoadOptionsAlways(){
$.wn.foundation.element.removeClass(viewContainer, 'loading')
})
}
}
AutocompleteProcessor.prototype.createOptionsCachingKey = function(row) {
var cachingKey = 'non-dependent',
dependsOn = this.columnConfiguration.dependsOn
if (dependsOn) {
if (typeof dependsOn == 'object') {
for (var i = 0, len = dependsOn.length; i < len; i++ )
cachingKey += dependsOn[i] + this.tableObj.getRowCellValueByColumnName(row, dependsOn[i])
} else
cachingKey = dependsOn + this.tableObj.getRowCellValueByColumnName(row, dependsOn)
}
return cachingKey
}
AutocompleteProcessor.prototype.triggerGetOptions = function(callback) {
var tableElement = this.tableObj.getElement()
if (!tableElement) {
return
}
var optionsEvent = $.Event('autocompleteitems.oc.table'),
values = {} // TODO - implement loading values from the current row.
$(tableElement).trigger(optionsEvent, [{
values: values,
callback: callback,
column: this.columnName,
columnConfiguration: this.columnConfiguration
}])
if (optionsEvent.isDefaultPrevented()) {
return false
}
return true
}
AutocompleteProcessor.prototype.getInput = function() {
if (!this.activeCell) {
return null
}
return this.activeCell.querySelector('.string-input')
}
AutocompleteProcessor.prototype.buildAutoComplete = function(items) {
if (!this.activeCell) {
return
}
var input = this.getInput()
if (!input) {
return
}
if (items === undefined) {
items = []
}
$(input).autocomplete({
source: this.prepareItems(items),
matchWidth: true,
menu: '<ul class="autocomplete dropdown-menu table-widget-autocomplete"></ul>',
bodyContainer: true
})
}
AutocompleteProcessor.prototype.prepareItems = function(items) {
var result = {}
if ($.isArray(items)) {
for (var i = 0, len = items.length; i < len; i++) {
result[items[i]] = items[i]
}
}
else {
result = items
}
return result
}
AutocompleteProcessor.prototype.removeAutocomplete = function() {
var input = this.getInput()
$(input).autocomplete('destroy')
}
$.wn.table.processor.autocomplete = AutocompleteProcessor;
}(window.jQuery);

View File

@@ -0,0 +1,210 @@
/*
* Base class for the table cell processors.
*/
+function ($) { "use strict";
// PROCESSOR NAMESPACES
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
$.wn.table.processor = {}
// CLASS DEFINITION
// ============================
var Base = function(tableObj, columnName, columnConfiguration) {
//
// State properties
//
this.tableObj = tableObj
this.columnName = columnName
this.columnConfiguration = columnConfiguration
this.activeCell = null
this.validators = []
// Register event handlers
this.registerHandlers()
// Initialize validators
this.initValidators()
}
Base.prototype.dispose = function() {
// Register event handlers
this.unregisterHandlers()
// Remove references to the DOM
this.tableObj = null
this.activeCell = null
}
/*
* Renders the cell in the normal (no edit) mode
*/
Base.prototype.renderCell = function(value, cellContentContainer) {
}
/*
* Registers event handlers required for the cell processor.
* Event handers should be bound to the container control element
* (not to the table element).
*/
Base.prototype.registerHandlers = function() {
}
/*
* Unregisters event handlers previously registered with
* registerHandlers().
*/
Base.prototype.unregisterHandlers = function() {
}
/*
* This method is called when the cell managed by the processor
* is focused (clicked or navigated with the keyboard).
*/
Base.prototype.onFocus = function(cellElement, isClick) {
}
/*
* Forces the processor to hide the editor when the user navigates
* away from the cell. Processors can update the sell value in this method.
* Processors must clear the reference to the active cell in this method.
*/
Base.prototype.onUnfocus = function() {
}
/*
* Event handler for the keydown event. The table class calls this method
* for all processors.
*/
Base.prototype.onKeyDown = function(ev) {
}
/*
* Event handler for the click event. The table class calls this method
* for all processors.
*/
Base.prototype.onClick = function(ev) {
}
/*
* This method is called when a cell value in the row changes.
*/
Base.prototype.onRowValueChanged = function(columnName, cellElement) {
}
/*
* Determines if the keyboard navigation in the specified direction is allowed
* by the cell processor. Some processors could reject the navigation, for example
* the string processor could cancel the left array navigation if the caret
* in the text input is not in the beginning of the text.
*/
Base.prototype.keyNavigationAllowed = function(ev, direction) {
return true
}
/*
* Determines if the processor's cell is focusable.
*/
Base.prototype.isCellFocusable = function() {
return true
}
/*
* Returns the content container element of a cell
*/
Base.prototype.getCellContentContainer = function(cellElement) {
return cellElement.querySelector('.content-container')
}
/*
* Creates a cell view data container (a DIV element that contains
* the current cell value). This functionality is required for most
* of the processors, perhaps except the checkbox cell processor.
*/
Base.prototype.createViewContainer = function(cellContentContainer, value) {
var viewContainer = document.createElement('div')
viewContainer.setAttribute('data-view-container', 'data-view-container')
viewContainer.textContent = value === undefined ? '' : value
cellContentContainer.appendChild(viewContainer)
return viewContainer
}
/*
* Returns the cell's view container element.
*/
Base.prototype.getViewContainer = function(cellElement) {
return cellElement.querySelector('[data-view-container]')
}
/*
* Displays the view container
*/
Base.prototype.showViewContainer = function(cellElement) {
return this.getViewContainer(cellElement).setAttribute('class', '')
}
/*
* Hides the view container
*/
Base.prototype.hideViewContainer = function(cellElement) {
return this.getViewContainer(cellElement).setAttribute('class', 'hide')
}
/*
* Sets visual value for the view container
*/
Base.prototype.setViewContainerValue = function(cellElement, value) {
return this.getViewContainer(cellElement).textContent = value
}
/*
* Determines whether the specified element is some element created by the
* processor.
*/
Base.prototype.elementBelongsToProcessor = function(element) {
return false
}
Base.prototype.initValidators = function() {
if (this.columnConfiguration.validation === undefined)
return
for (var validatorName in this.columnConfiguration.validation) {
if ($.wn.table.validator === undefined || $.wn.table.validator[validatorName] == undefined)
throw new Error('The table cell validator "'+validatorName+'" for the column "'+this.columnName+'" is not ' +
'found in the $.wn.table.validator namespace.')
var validator = new $.wn.table.validator[validatorName](
this.columnConfiguration.validation[validatorName]
)
this.validators.push(validator)
}
}
Base.prototype.validate = function(value, rowData) {
for (var i=0, len=this.validators.length; i<len; i++) {
var message = this.validators[i].validate(value, rowData)
if (message !== undefined)
return message
}
}
$.wn.table.processor.base = Base
}(window.jQuery);

View File

@@ -0,0 +1,140 @@
/*
* Checkbox cell processor for the table control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.processor.base,
BaseProto = Base.prototype
var CheckboxProcessor = function(tableObj, columnName, columnConfiguration) {
//
// Parent constructor
//
Base.call(this, tableObj, columnName, columnConfiguration)
}
CheckboxProcessor.prototype = Object.create(BaseProto)
CheckboxProcessor.prototype.constructor = CheckboxProcessor
CheckboxProcessor.prototype.dispose = function() {
BaseProto.dispose.call(this)
}
/*
* Determines if the processor's cell is focusable.
*/
CheckboxProcessor.prototype.isCellFocusable = function() {
return false
}
/*
* Renders the cell in the normal (no edit) mode
*/
CheckboxProcessor.prototype.renderCell = function(value, cellContentContainer) {
var checkbox = document.createElement('div')
checkbox.setAttribute('data-checkbox-element', 'true')
checkbox.setAttribute('tabindex', '0')
if (value && value != 0 && value != "false") {
checkbox.setAttribute('class', 'checked')
}
cellContentContainer.appendChild(checkbox)
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
cellContentContainer.classList.add('readonly');
}
}
/*
* This method is called when the cell managed by the processor
* is focused (clicked or navigated with the keyboard).
*/
CheckboxProcessor.prototype.onFocus = function(cellElement, isClick) {
cellElement.querySelector('div[data-checkbox-element]').focus()
}
/*
* Event handler for the keydown event. The table class calls this method
* for all processors.
*/
CheckboxProcessor.prototype.onKeyDown = function(ev) {
if (ev.key === '(Space character)' || ev.key === 'Spacebar' || ev.key === ' ')
this.onClick(ev)
}
/*
* Event handler for the click event. The table class calls this method
* for all processors.
*/
CheckboxProcessor.prototype.onClick = function(ev) {
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
return
}
var target = this.tableObj.getEventTarget(ev, 'DIV')
if (target.getAttribute('data-checkbox-element')) {
// The method is called for all processors, but we should
// update only the checkbox in the clicked column.
var container = this.getCheckboxContainerNode(target)
if (container.getAttribute('data-column') !== this.columnName) {
return
}
this.changeState(target)
$(ev.target).trigger('change')
}
}
CheckboxProcessor.prototype.changeState = function(divElement) {
var cell = divElement.parentNode.parentNode
if (divElement.getAttribute('class') == 'checked') {
divElement.setAttribute('class', '')
this.tableObj.setCellValue(cell, 0)
}
else {
divElement.setAttribute('class', 'checked')
this.tableObj.setCellValue(cell, 1)
}
}
CheckboxProcessor.prototype.getCheckboxContainerNode = function(checkbox) {
return checkbox.parentNode.parentNode
}
/*
* This method is called when a cell value in the row changes.
*/
CheckboxProcessor.prototype.onRowValueChanged = function(columnName, cellElement) {
if (columnName != this.columnName) {
return
}
var checkbox = cellElement.querySelector('div[data-checkbox-element]'),
value = this.tableObj.getCellValue(cellElement)
if (value && value != 0 && value != "false") {
checkbox.setAttribute('class', 'checked')
}
else {
checkbox.setAttribute('class', '')
}
}
$.wn.table.processor.checkbox = CheckboxProcessor;
}(window.jQuery);

View File

@@ -0,0 +1,538 @@
/*
* Drop-down cell processor for the table control.
*/
/*
* TODO: implement the search
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.processor.base,
BaseProto = Base.prototype
var DropdownProcessor = function(tableObj, columnName, columnConfiguration) {
//
// State properties
//
this.itemListElement = null
this.cachedOptionPromises = {}
this.searching = false
this.searchQuery = null
this.searchInterval = null
// Event handlers
this.itemClickHandler = this.onItemClick.bind(this)
this.itemKeyDownHandler = this.onItemKeyDown.bind(this)
this.itemMouseMoveHandler = this.onItemMouseMove.bind(this)
//
// Parent constructor
//
Base.call(this, tableObj, columnName, columnConfiguration)
}
DropdownProcessor.prototype = Object.create(BaseProto)
DropdownProcessor.prototype.constructor = DropdownProcessor
DropdownProcessor.prototype.dispose = function() {
this.unregisterListHandlers()
this.itemClickHandler = null
this.itemKeyDownHandler = null
this.itemMouseMoveHandler = null
this.itemListElement = null
this.cachedOptionPromises = null
BaseProto.dispose.call(this)
}
DropdownProcessor.prototype.unregisterListHandlers = function() {
if (this.itemListElement)
{
// This processor binds custom click handler to the item list,
// the standard registerHandlers/unregisterHandlers functionality
// can't be used here because the element belongs to the document
// body, not to the table.
this.itemListElement.removeEventListener('click', this.itemClickHandler)
this.itemListElement.removeEventListener('keydown', this.itemKeyDownHandler)
this.itemListElement.removeEventListener('mousemove', this.itemMouseMoveHandler)
}
}
/*
* Renders the cell in the normal (no edit) mode
*/
DropdownProcessor.prototype.renderCell = function(value, cellContentContainer) {
let viewContainer;
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
viewContainer = this.createViewContainer(cellContentContainer, value)
cellContentContainer.classList.add('readonly');
cellContentContainer.setAttribute('tabindex', 0);
return;
}
viewContainer = this.createViewContainer(cellContentContainer, '...')
this.fetchOptions(cellContentContainer.parentNode, function renderCellFetchOptions(options) {
if (options[value] !== undefined)
viewContainer.textContent = options[value]
cellContentContainer.setAttribute('tabindex', 0)
})
}
/*
* This method is called when the cell managed by the processor
* is focused (clicked or navigated with the keyboard).
*/
DropdownProcessor.prototype.onFocus = function(cellElement, isClick) {
if (this.activeCell === cellElement) {
this.showDropdown()
return
}
this.activeCell = cellElement
if (!this.columnConfiguration.readonly && !this.columnConfiguration.readOnly) {
var cellContentContainer = this.getCellContentContainer(cellElement)
this.buildEditor(cellElement, cellContentContainer, isClick)
if (!isClick) {
cellContentContainer.focus()
}
} else {
this.getCellContentContainer(cellElement).focus()
}
}
/*
* Forces the processor to hide the editor when the user navigates
* away from the cell. Processors can update the sell value in this method.
* Processors must clear the reference to the active cell in this method.
*/
DropdownProcessor.prototype.onUnfocus = function() {
if (!this.activeCell)
return
this.unregisterListHandlers()
this.hideDropdown()
this.itemListElement = null
this.activeCell = null
}
DropdownProcessor.prototype.buildEditor = function(cellElement, cellContentContainer, isClick) {
// Create the select control
var currentValue = this.tableObj.getCellValue(cellElement),
containerPosition = this.getAbsolutePosition(cellContentContainer),
self = this
this.itemListElement = document.createElement('div')
this.itemListElement.addEventListener('click', this.itemClickHandler)
this.itemListElement.addEventListener('keydown', this.itemKeyDownHandler)
this.itemListElement.addEventListener('mousemove', this.itemMouseMoveHandler)
this.itemListElement.setAttribute('class', 'table-control-dropdown-list')
this.itemListElement.style.width = cellContentContainer.offsetWidth + 'px'
this.itemListElement.style.left = containerPosition.left + 'px'
this.itemListElement.style.top = containerPosition.top - 2 + cellContentContainer.offsetHeight + 'px'
this.fetchOptions(cellElement, function renderCellFetchOptions(options) {
var listElement = document.createElement('ul')
for (var value in options) {
var itemElement = document.createElement('li')
itemElement.setAttribute('data-value', value)
itemElement.textContent = options[value]
itemElement.setAttribute('tabindex', 0)
if (value == currentValue)
itemElement.setAttribute('class', 'selected')
listElement.appendChild(itemElement)
}
self.itemListElement.appendChild(listElement)
if (isClick)
self.showDropdown()
self = null
})
}
/*
* Hide the drop-down, but don't delete it.
*/
DropdownProcessor.prototype.hideDropdown = function() {
if (this.itemListElement && this.activeCell && this.itemListElement.parentNode) {
var cellContentContainer = this.getCellContentContainer(this.activeCell)
cellContentContainer.setAttribute('data-dropdown-open', 'false')
this.itemListElement.parentNode.removeChild(this.itemListElement)
cellContentContainer.focus()
}
}
DropdownProcessor.prototype.showDropdown = function() {
if (this.itemListElement && this.itemListElement.parentNode !== document.body) {
this.getCellContentContainer(this.activeCell).setAttribute('data-dropdown-open', 'true')
document.body.appendChild(this.itemListElement)
var activeItemElement = this.itemListElement.querySelector('ul li.selected')
if (!activeItemElement) {
activeItemElement = this.itemListElement.querySelector('ul li:first-child')
if (activeItemElement)
activeItemElement.setAttribute('class', 'selected')
}
if (activeItemElement) {
window.setTimeout(function(){
activeItemElement.focus()
}, 0)
}
}
}
DropdownProcessor.prototype.fetchOptions = function(cellElement, onSuccess) {
if (this.columnConfiguration.options) {
onSuccess(this.columnConfiguration.options)
}
else {
// If options are not provided and not found in the cache,
// request them from the server. For dependent drop-downs
// the caching key contains the master column values.
var row = cellElement.parentNode,
cachingKey = this.createOptionsCachingKey(row),
viewContainer = this.getViewContainer(cellElement)
// Request options from the server. When the table widget builds,
// multiple cells in the column could require loading the options.
// The AJAX promises are cached here so that we have a single
// request per caching key.
viewContainer.setAttribute('class', 'loading')
if (!this.cachedOptionPromises[cachingKey]) {
var requestData = {
column: this.columnName,
rowData: this.tableObj.getRowData(row)
},
handlerName = this.tableObj.getAlias()+'::onGetDropdownOptions'
this.cachedOptionPromises[cachingKey] = this.tableObj.$el.request(handlerName, {data: requestData})
}
this.cachedOptionPromises[cachingKey].done(function onDropDownLoadOptionsSuccess(data){
onSuccess(data.options)
}).always(function onDropDownLoadOptionsAlways(){
viewContainer.setAttribute('class', '')
})
}
}
DropdownProcessor.prototype.createOptionsCachingKey = function(row) {
var cachingKey = 'non-dependent',
dependsOn = this.columnConfiguration.dependsOn
if (dependsOn) {
if (typeof dependsOn == 'object') {
for (var i = 0, len = dependsOn.length; i < len; i++ )
cachingKey += dependsOn[i] + this.tableObj.getRowCellValueByColumnName(row, dependsOn[i])
} else
cachingKey = dependsOn + this.tableObj.getRowCellValueByColumnName(row, dependsOn)
}
return cachingKey
}
DropdownProcessor.prototype.getAbsolutePosition = function(element) {
// TODO: use the foundation library
var top = document.body.scrollTop,
left = 0
do {
top += element.offsetTop || 0;
top -= element.scrollTop || 0;
left += element.offsetLeft || 0;
element = element.offsetParent;
} while(element)
return {
top: top,
left: left
}
}
DropdownProcessor.prototype.updateCellFromFocusedItem = function(focusedItem) {
if (!focusedItem) {
focusedItem = this.findFocusedItem();
}
this.setSelectedItem(focusedItem);
}
DropdownProcessor.prototype.findSelectedItem = function() {
if (this.itemListElement)
return this.itemListElement.querySelector('ul li.selected')
return null
}
DropdownProcessor.prototype.setSelectedItem = function(item) {
if (!this.itemListElement)
return null;
if (item.tagName == 'LI' && this.itemListElement.contains(item)) {
this.itemListElement.querySelectorAll('ul li').forEach(function (option) {
option.removeAttribute('class');
});
item.setAttribute('class', 'selected');
}
this.tableObj.setCellValue(this.activeCell, item.getAttribute('data-value'))
this.setViewContainerValue(this.activeCell, item.textContent)
}
DropdownProcessor.prototype.findFocusedItem = function() {
if (this.itemListElement)
return this.itemListElement.querySelector('ul li:focus')
return null
}
DropdownProcessor.prototype.onItemClick = function(ev) {
var target = this.tableObj.getEventTarget(ev)
if (target.tagName == 'LI') {
target.focus();
this.updateCellFromFocusedItem(target)
this.hideDropdown()
}
}
DropdownProcessor.prototype.onItemKeyDown = function(ev) {
if (!this.itemListElement)
return
if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp')
{
// Up or down keys - find previous/next list item and select it
var focused = this.findFocusedItem(),
newFocusedItem = focused.nextElementSibling
if (ev.key === 'ArrowUp')
newFocusedItem = focused.previousElementSibling
if (newFocusedItem) {
newFocusedItem.focus()
}
return
}
if (ev.key === 'Enter' || ev.key === '(Space character)' || ev.key === 'Spacebar' || ev.key === ' ') {
// Return or space keys - update the selected value and hide the editor
this.updateCellFromFocusedItem()
this.hideDropdown()
return
}
if (ev.key === 'Tab') {
// Tab - update the selected value and pass control to the table navigation
this.updateCellFromFocusedItem()
this.tableObj.navigation.navigateNext(ev)
this.tableObj.stopEvent(ev)
return
}
if (ev.key === 'Escape') {
// Esc - hide the drop-down
this.hideDropdown()
return
}
this.searchByTextInput(ev, true);
}
/*
* Event handler for mouse movements over options in the dropdown menu
*/
DropdownProcessor.prototype.onItemMouseMove = function(ev) {
if (!this.itemListElement)
return
var target = this.tableObj.getEventTarget(ev)
if (target.tagName == 'LI') {
target.focus();
}
}
/*
* Event handler for the keydown event. The table class calls this method
* for all processors.
*/
DropdownProcessor.prototype.onKeyDown = function(ev) {
if (!this.itemListElement)
return
if ((ev.key === '(Space character)' || ev.key === 'Spacebar' || ev.key === ' ') && !this.searching) { // Spacebar
this.showDropdown()
} else if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') { // Up and down arrow keys
var selected = this.findSelectedItem(),
newSelectedItem;
if (!selected) {
if (ev.key === 'ArrowUp') {
// Only show an initial item when the down array key is pressed
return false
}
newSelectedItem = this.itemListElement.querySelector('ul li:first-child')
} else {
newSelectedItem = selected.nextElementSibling
if (ev.key === 'ArrowUp')
newSelectedItem = selected.previousElementSibling
}
if (newSelectedItem) {
this.setSelectedItem(newSelectedItem);
}
return false // Stop propogation of event
} else {
this.searchByTextInput(ev);
}
}
/*
* This method is called when a cell value in the row changes.
*/
DropdownProcessor.prototype.onRowValueChanged = function(columnName, cellElement) {
// Determine if this drop-down depends on the changed column
// and update the option list if necessary
// TODO: setting drop-down values with table.setRowValues() is not implemented currently
if (!this.columnConfiguration.dependsOn)
return
var dependsOnColumn = false,
dependsOn = this.columnConfiguration.dependsOn
if (typeof dependsOn == 'object') {
for (var i = 0, len = dependsOn.length; i < len; i++ ) {
if (dependsOn[i] == columnName) {
dependsOnColumn = true
break
}
}
}
else {
dependsOnColumn = dependsOn == columnName
}
if (!dependsOnColumn)
return
var currentValue = this.tableObj.getCellValue(cellElement),
viewContainer = this.getViewContainer(cellElement)
this.fetchOptions(cellElement, function rowValueChangedFetchOptions(options) {
var value = options[currentValue] !== undefined
? options[currentValue]
: '...'
viewContainer.textContent = value
viewContainer = null
})
}
/*
* Determines whether the specified element is some element created by the
* processor.
*/
DropdownProcessor.prototype.elementBelongsToProcessor = function(element) {
if (!this.itemListElement)
return false
return this.tableObj.parentContainsElement(this.itemListElement, element)
}
/*
* Provides auto-complete like functionality for typing in a query and selecting
* a matching list option
*/
DropdownProcessor.prototype.searchByTextInput = function(ev, focusOnly) {
if (focusOnly === undefined) {
focusOnly = false;
}
var character = ev.key;
if (character.length === 1 || character === 'Space') {
if (!this.searching) {
this.searching = true;
this.searchQuery = '';
}
this.searchQuery += (character === 'Space') ? ' ' : character;
// Search for a valid option in dropdown
var validItem = null;
var query = this.searchQuery;
this.itemListElement.querySelectorAll('ul li').forEach(function(item) {
if (validItem === null && item.dataset.value && item.dataset.value.toLowerCase().indexOf(query.toLowerCase()) === 0) {
validItem = item;
}
});
if (validItem) {
// If a valid item is found, select item and allow for fine-tuning the search query
if (focusOnly === true) {
validItem.focus();
} else {
this.setSelectedItem(validItem);
}
if (this.searchInterval) {
clearTimeout(this.searchInterval);
}
this.searchInterval = setTimeout(this.cancelTextSearch.bind(this), 1000);
} else {
this.cancelTextSearch();
}
}
}
DropdownProcessor.prototype.cancelTextSearch = function() {
this.searching = false;
this.searchQuery = null;
this.searchInterval = null;
}
$.wn.table.processor.dropdown = DropdownProcessor;
}(window.jQuery);

View File

@@ -0,0 +1,187 @@
/*
* String cell processor for the table control.
* The string processor allows to edit cell values with a simple
* input control.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.processor === undefined)
throw new Error("The $.wn.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.processor.base,
BaseProto = Base.prototype
var StringProcessor = function(tableObj, columnName, columnConfiguration) {
//
// Parent constructor
//
Base.call(this, tableObj, columnName, columnConfiguration)
}
StringProcessor.prototype = Object.create(BaseProto)
StringProcessor.prototype.constructor = StringProcessor
StringProcessor.prototype.dispose = function() {
BaseProto.dispose.call(this)
}
/*
* Renders the cell in the normal (no edit) mode
*/
StringProcessor.prototype.renderCell = function(value, cellContentContainer) {
this.createViewContainer(cellContentContainer, value);
if (this.columnConfiguration.readonly || this.columnConfiguration.readOnly) {
cellContentContainer.classList.add('readonly');
cellContentContainer.setAttribute('tabindex', 0);
}
}
/*
* This method is called when the cell managed by the processor
* is focused (clicked or navigated with the keyboard).
*/
StringProcessor.prototype.onFocus = function(cellElement, isClick) {
if (this.activeCell === cellElement)
return
this.activeCell = cellElement
if (!this.columnConfiguration.readonly && !this.columnConfiguration.readOnly) {
this.buildEditor(cellElement, this.getCellContentContainer(cellElement))
} else {
this.getCellContentContainer(cellElement).focus()
}
}
/*
* Forces the processor to hide the editor when the user navigates
* away from the cell. Processors can update the sell value in this method.
* Processors must clear the reference to the active cell in this method.
*/
StringProcessor.prototype.onUnfocus = function() {
if (!this.activeCell)
return
var editor = this.activeCell.querySelector('.string-input')
if (editor) {
// Update the cell value and remove the editor
this.tableObj.setCellValue(this.activeCell, editor.value)
this.setViewContainerValue(this.activeCell, editor.value)
editor.parentNode.removeChild(editor)
}
this.showViewContainer(this.activeCell)
this.activeCell = null
}
StringProcessor.prototype.buildEditor = function(cellElement, cellContentContainer) {
// Hide the view container
this.hideViewContainer(this.activeCell)
// Create the input control
var input = document.createElement('input')
input.setAttribute('type', 'text')
input.setAttribute('class', 'string-input')
input.value = this.tableObj.getCellValue(cellElement)
cellContentContainer.appendChild(input)
input.focus();
this.setCaretPosition(input, 0);
}
/*
* Determines if the keyboard navigation in the specified direction is allowed
* by the cell processor. Some processors could reject the navigation, for example
* the string processor could cancel the left array navigation if the caret
* in the text input is not in the beginning of the text.
*/
StringProcessor.prototype.keyNavigationAllowed = function(ev, direction) {
if (direction != 'left' && direction != 'right')
return true
if (!this.activeCell)
return true
var editor = this.activeCell.querySelector('.string-input')
if (!editor)
return true
var caretPosition = this.getCaretPosition(editor)
if (direction == 'left')
return caretPosition == 0
if (direction == 'right')
return caretPosition == editor.value.length
return true
}
/*
* This method is called when a cell value in the row changes.
*/
StringProcessor.prototype.onRowValueChanged = function(columnName, cellElement) {
if (columnName != this.columnName) {
return
}
var value = this.tableObj.getCellValue(cellElement)
this.setViewContainerValue(cellElement, value)
}
StringProcessor.prototype.getCaretPosition = function(input) {
// TODO: use the foundation library
if (document.selection) {
var selection = document.selection.createRange()
selection.moveStart('character', -input.value.length)
return selection.text.length
}
if (input.selectionStart !== undefined)
return input.selectionStart
return 0
}
StringProcessor.prototype.setCaretPosition = function(input, position) {
// TODO: use the foundation library
if (document.selection) {
var range = input.createTextRange()
setTimeout(function() {
// Asynchronous layout update, better performance
range.collapse(true)
range.moveStart("character", position)
range.moveEnd("character", 0)
range.select()
}, 0)
}
if (input.selectionStart !== undefined) {
setTimeout(function() {
// Asynchronous layout update
input.selectionStart = position
input.selectionEnd = position
}, 0)
}
return 0
}
$.wn.table.processor.string = StringProcessor;
}(window.jQuery);

View File

@@ -0,0 +1,77 @@
/*
* Base class for the table validators.
*/
+function ($) { "use strict";
// VALIDATOR NAMESPACES
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
$.wn.table.validator = {}
// CLASS DEFINITION
// ============================
var Base = function(options) {
//
// State properties
//
this.options = options
}
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Base.prototype.validate = function(value, rowData) {
if (this.options.requiredWith !== undefined && !this.rowHasValue(this.options.requiredWith, rowData))
return
return this.validateValue(value, rowData)
}
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined. This method should be redefined
* in descendant classes.
* The rowData parameter is an object containing all values in the
* target row.
*/
Base.prototype.validateValue = function(value, rowData) {
}
Base.prototype.trim = function(value) {
if (String.prototype.trim)
return value.trim()
return value.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
}
Base.prototype.getMessage = function(defaultValue) {
if (this.options.message !== undefined)
return this.options.message
return defaultValue
}
Base.prototype.rowHasValue = function(columnName, rowData) {
if (rowData[columnName] === undefined)
return false
if (typeof rowData[columnName] == 'boolean')
return rowData[columnName]
var value = this.trim(String(rowData[columnName]))
return value.length > 0
}
$.wn.table.validator.base = Base;
}(window.jQuery);

View File

@@ -0,0 +1,57 @@
/*
* Base class for number validators.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.base,
BaseProto = Base.prototype
var BaseNumber = function(options) {
Base.call(this, options)
};
BaseNumber.prototype = Object.create(BaseProto)
BaseNumber.prototype.constructor = BaseNumber
BaseNumber.prototype.doCommonChecks = function(value) {
if (this.options.min !== undefined || this.options.max !== undefined) {
if (this.options.min !== undefined) {
if (this.options.min.value === undefined)
throw new Error('The min.value parameter is not defined in the table validator configuration')
if (value < this.options.min.value) {
return this.options.min.message !== undefined ?
this.options.min.message :
"The value should not be less than " + this.options.min.value
}
}
if (this.options.max !== undefined) {
if (this.options.max.value === undefined)
throw new Error('The max.value parameter is not defined in the table validator configuration')
if (value > this.options.max.value) {
return this.options.max.message !== undefined ?
this.options.max.message :
"The value should not be more than " + this.options.max.value
}
}
}
return
}
$.wn.table.validator.baseNumber = BaseNumber
}(window.jQuery);

View File

@@ -0,0 +1,59 @@
/*
* Float table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
if ($.wn.table.validator.baseNumber === undefined)
throw new Error("The $.wn.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.baseNumber,
BaseProto = Base.prototype
var Float = function(options) {
Base.call(this, options)
};
Float.prototype = Object.create(BaseProto)
Float.prototype.constructor = Float
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Float.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
var testResult = this.options.allowNegative ?
/^[-]?([0-9]+\.[0-9]+|[0-9]+)$/.test(value) :
/^([0-9]+\.[0-9]+|[0-9]+)$/.test(value)
if (!testResult) {
var defaultMessage = this.options.allowNegative ?
'The value should be a floating point number.' :
'The value should be a positive floating point number';
return this.getMessage(defaultMessage)
}
return this.doCommonChecks(parseFloat(value))
}
$.wn.table.validator.float = Float
}(window.jQuery);

View File

@@ -0,0 +1,59 @@
/*
* Integer table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
if ($.wn.table.validator.baseNumber === undefined)
throw new Error("The $.wn.table.validator.baseNumber namespace is not defined. Make sure that the table.validator.baseNumber.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.baseNumber,
BaseProto = Base.prototype
var Integer = function(options) {
Base.call(this, options)
};
Integer.prototype = Object.create(BaseProto)
Integer.prototype.constructor = Integer
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Integer.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
var testResult = this.options.allowNegative ?
/^\-?[0-9]*$/.test(value) :
/^[0-9]*$/.test(value)
if (!testResult) {
var defaultMessage = this.options.allowNegative ?
'The value should be an integer.' :
'The value should be a positive integer';
return this.getMessage(defaultMessage)
}
return this.doCommonChecks(parseInt(value))
}
$.wn.table.validator.integer = Integer
}(window.jQuery);

View File

@@ -0,0 +1,68 @@
/*
* String length table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.base,
BaseProto = Base.prototype
var Length = function(options) {
Base.call(this, options)
};
Length.prototype = Object.create(BaseProto)
Length.prototype.constructor = Length
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Length.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
if (this.options.min !== undefined || this.options.max !== undefined) {
if (this.options.min !== undefined) {
if (this.options.min.value === undefined)
throw new Error('The min.value parameter is not defined in the Length table validator configuration')
if (value.length < this.options.min.value) {
return this.options.min.message !== undefined ?
this.options.min.message :
"The string should not be shorter than " + this.options.min.value
}
}
if (this.options.max !== undefined) {
if (this.options.max.value === undefined)
throw new Error('The max.value parameter is not defined in the Length table validator configuration')
if (value.length > this.options.max.value) {
return this.options.max.message !== undefined ?
this.options.max.message :
"The string should not be longer than " + this.options.max.value
}
}
}
return
}
$.wn.table.validator.length = Length
}(window.jQuery);

View File

@@ -0,0 +1,52 @@
/*
* Regex length table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.base,
BaseProto = Base.prototype
var Regex = function(options) {
Base.call(this, options)
};
Regex.prototype = Object.create(BaseProto)
Regex.prototype.constructor = Regex
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Regex.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length == 0)
return
if (this.options.pattern === undefined)
throw new Error('The pattern parameter is not defined in the Regex table validator configuration')
var regexObj = new RegExp(this.options.pattern, this.options.modifiers)
if (!regexObj.test(value))
return this.getMessage("Invalid value format.")
return
}
$.wn.table.validator.regex = Regex
}(window.jQuery);

View File

@@ -0,0 +1,44 @@
/*
* Required table validator.
*/
+function ($) { "use strict";
// NAMESPACE CHECK
// ============================
if ($.wn.table === undefined)
throw new Error("The $.wn.table namespace is not defined. Make sure that the table.js script is loaded.");
if ($.wn.table.validator === undefined)
throw new Error("The $.wn.table.validator namespace is not defined. Make sure that the table.validator.base.js script is loaded.");
// CLASS DEFINITION
// ============================
var Base = $.wn.table.validator.base,
BaseProto = Base.prototype
var Required = function(options) {
Base.call(this, options)
};
Required.prototype = Object.create(BaseProto)
Required.prototype.constructor = Required
/*
* Validates a value and returns the error message. If there
* are no errors, returns undefined.
* The rowData parameter is an object containing all values in the
* target row.
*/
Required.prototype.validateValue = function(value, rowData) {
value = this.trim(value)
if (value.length === 0)
return this.getMessage("The value should not be empty.")
return
}
$.wn.table.validator.required = Required
}(window.jQuery);

View File

@@ -0,0 +1,509 @@
@import "../../../../../backend/assets/less/core/boot.less";
/*
* General control styling
*/
@table-active-border: #e0e0e0;
@table-inactive-border: #e0e0e0;
@table-odd-row: #ffffff;
@table-even-row: #fafafa;
@table-disabled-field: #f7f7f7;
.control-table {
.table-container {
border: 1px solid @table-inactive-border;
.border-radius(4px);
overflow: hidden;
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
&:not([data-records-per-page="false"]) .table-container {
.border-bottom-radius(0);
}
&.active .table-container {
border-color: @table-active-border;
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
td, th {
padding: 0;
font-size: 13px;
color: #555555;
}
[data-view-container] {
padding: 5px 10px;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-height: 28px;
}
}
table.headers {
&:after {
content: ' ';
display: block;
position: absolute;
left: 1px;
right: 1px;
margin-top: -1px;
border-bottom: 1px solid @table-inactive-border;
}
th {
padding: 7px 10px;
font-weight: normal;
text-transform: uppercase;
font-size: @font-size-base - 3;
color: #333333;
background: white;
border-right: 1px solid #ecf0f1;
[data-view-container] {
padding-bottom: 6px;
}
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:last-child {
border-right: none;
}
}
}
&.active table.headers:after {
border-bottom-color: @table-active-border;
}
table.data {
td {
border: 1px solid #ecf0f1;
.content-container {
position: relative;
padding: 1px;
outline: none;
&.readonly {
background: @table-disabled-field;
}
}
&.active {
border-color: @color-focus!important;
.content-container {
padding: 0;
border: 1px solid @color-focus;
&:before, &:after {
content: ' ';
background: @color-focus;
position: absolute;
left: -2px;
top: -2px;
}
&:before {
width: 1px;
bottom: -2px;
}
&:after {
right: -2px;
height: 1px;
}
}
}
}
tr {
background-color: @table-odd-row;
&.error {
background-color: #fbecec!important;
td.active.error {
border-color: #ec0000!important;
.content-container {
border-color: #ec0000!important;
&:before, &:after {
background-color: #ec0000!important;
}
}
}
}
}
tr:nth-child(2n) {
background-color: @table-even-row;
}
}
table.data {
tr:first-child td {
border-top: none;
}
tr:last-child td {
border-bottom: none;
}
td:first-child {
border-left: none;
}
td:last-child {
border-right: none;
}
}
.control-scrollbar {
> div {
.border-bottom-radius(4px);
overflow: hidden;
}
table.data {
tr:last-child td {
border-bottom: 1px solid #ecf0f1;
}
}
}
.toolbar {
background: white;
border-bottom: 1px solid @table-inactive-border;
.clearfix();
a.btn {
color: #323e50;
padding: 8px 10px;
.opacity(0.5);
.box-shadow(none) !important;
text-shadow: none;
&:hover {
.opacity(1);
}
}
.table-search {
float: right;
margin: 3px 3px 3px 0;
.table-search-input {
height: auto;
padding: 5px 13px 5px;
}
}
a.table-icon {
&:before {
display: inline-block;
content: ' ';
width: 16px;
height: 16px;
margin-right: 8px;
position: relative;
top: 3px;
background: transparent url(../images/table-icons.gif) no-repeat 0 0;
background-size: 32px auto;
}
&.add-table-row-above:before {
background-position: 0 -56px;
}
&.delete-table-row:before {
background-position: 0 -113px;
}
}
}
&.active .toolbar {
border-bottom-color: @table-active-border;
}
.pagination {
margin: -15px 0 15px;
padding: 7px 10px;
background: white;
border: 1px solid @table-inactive-border;
border-top: none;
.border-bottom-radius(4px);
ul {
padding: 0;
margin: 0;
li {
list-style: none;
display: inline-block;
margin-right: 5px;
font-size: 12px;
line-height: 100%;
a {
display: inline-block;
text-decoration: none;
color: #95a5a6;
padding: 4px 6px;
background: #ecf0f1;
.border-radius(2px);
outline: none;
line-height: 100%;
}
a:focus-visible {
outline: auto;
}
&.active a {
background: @brand-accent;
color: #ffffff;
}
}
}
}
}
@media only screen and (-moz-min-device-pixel-ratio: 1.5), only screen and (-o-min-device-pixel-ratio: 3/2), only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-devicepixel-ratio: 1.5), only screen and (min-resolution: 1.5dppx) {
.control-table .toolbar {
a {
&:before {
background-position: 0px -9px;
background-size: 16px auto;
}
&.add-table-row-above:before {
background-position: 0 -39px;
}
&.delete-table-row:before {
background-position: 0 -66px;
}
}
}
}
/*
* String and autocomplete editors
*/
.control-table {
td[data-column-type=string],
td[data-column-type=autocomplete] {
input[type=text] {
width: 100%;
height: 100%;
display: block;
outline: none;
border: none;
padding: 6px 10px 7px;
}
}
}
html.chrome {
.control-table {
td[data-column-type=string],
td[data-column-type=autocomplete] {
input[type=text] {
padding: 6px 10px 7px!important;
}
}
}
}
html.safari, html.gecko {
.control-table {
td[data-column-type=string],
td[data-column-type=autocomplete] {
input[type=text] {
padding: 5px 10px 5px;
}
}
}
}
ul.table-widget-autocomplete {
background: white;
font-size: 13px;
margin-top: 0;
border: 1px solid #808c8d;
border-top: 1px solid #ecf0f1;
.border-bottom-radius(4px);
li a {
padding: 5px 10px;
}
}
/*
* Checkbox editor
*/
.control-table {
td[data-column-type=checkbox] {
div[data-checkbox-element] {
width: 16px;
height: 16px;
border-radius: @border-radius-base;
background-color: #FFFFFF;
border: 1px solid @color-custom-input-border;
margin: 6px 5px 6px 10px;
cursor: pointer;
.user-select(none);
&:hover {
border-color: darken(@color-custom-input-border, 10%);
color: darken(@color-custom-input-icon, 10%);
}
&.checked {
border-width: 2px;
&:before {
.icon(@check);
font-size: 10px;
position: relative;
left: 1px;
top: -4px;
}
}
&:focus {
border-color: @color-focus;
outline: none;
}
}
}
}
/*
* Dropdown editor
*/
.control-table {
td[data-column-type=dropdown] {
.dropdown-arrow() {
.icon(@angle-down);
font-size: 13px;
line-height: 100%;
color: #95a5a6;
position: absolute;
top: 8px;
right: 10px;
}
.content-container:not(.readonly) {
.user-select(none);
[data-view-container] {
padding-right: 20px;
position: relative;
cursor: pointer;
&:after {
.dropdown-arrow();
}
&:hover:after {
color: @link-color;
}
}
}
[data-dropdown-open=true] {
background: white;
[data-view-container]:after {
.icon(@angle-up);
}
}
}
}
/* Frameless control styles start */
.widget-field.frameless .control-table {
.table-container {
border-top: none;
border-left: none;
border-right: none;
.border-radius(0);
}
.toolbar {
background: transparent;
}
}
/* Frameless control styles end */
html.cssanimations {
.control-table td[data-column-type=dropdown] {
[data-view-container].loading:after {
background: url('../../../../../../modules/system/assets/ui/images/loader-transparent.svg') 50% 50%;
background-size: 15px 15px;
position: absolute;
width: 15px;
height: 15px;
top: 6px;
right: 5px;
content: ' ';
.animation(spin 1s linear infinite);
}
}
}
.table-control-dropdown-list {
.user-select(none);
position: absolute;
background: white;
border: 1px solid @table-active-border;
border-top: none;
padding-top: 1px;
overflow: hidden;
z-index: 1000;
.box-sizing(border-box);
.border-bottom-radius(4px);
ul {
border-top: 1px solid #ecf0f1;
padding: 0;
margin: 0;
max-height: 200px;
overflow: auto;
}
li {
list-style: none;
font-size: 13px;
color: #555555;
padding: 5px 10px;
cursor: pointer;
outline: none;
&:focus {
background: @color-focus;
color: white;
}
}
}