Minor performance changes in the core JS and CSS. Table widget, in progress.
This commit is contained in:
parent
228253fdf6
commit
4e921aab7a
|
|
@ -10610,20 +10610,17 @@ html.cssanimations .cursor-loading-indicator.hide {
|
|||
}
|
||||
.stripe-loading-indicator .stripe-loaded {
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
filter: alpha(opacity=0);
|
||||
display: none;
|
||||
}
|
||||
.stripe-loading-indicator.loaded {
|
||||
opacity: 0;
|
||||
filter: alpha(opacity=0);
|
||||
display: none;
|
||||
-webkit-transition: opacity 0.4s linear;
|
||||
transition: opacity 0.4s linear;
|
||||
-webkit-transition-delay: 0.3s;
|
||||
transition-delay: 0.3s;
|
||||
}
|
||||
.stripe-loading-indicator.loaded .stripe-loaded {
|
||||
opacity: 1;
|
||||
filter: alpha(opacity=100);
|
||||
display: block;
|
||||
-webkit-transition: width 0.3s linear;
|
||||
transition: width 0.3s linear;
|
||||
width: 100% !important;
|
||||
|
|
|
|||
|
|
@ -173,15 +173,22 @@ html.cssanimations {
|
|||
|
||||
.stripe-loaded {
|
||||
width: 0;
|
||||
.opacity(0);
|
||||
|
||||
// Opacity replaced with display. Leaving the animated indicator element
|
||||
// in the rendering tree, even invisible, affects the performance.
|
||||
// .opacity(0);
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.loaded {
|
||||
.opacity(0);
|
||||
// .opacity(0);
|
||||
display: none;
|
||||
|
||||
.transition(opacity .4s linear);
|
||||
.transition-delay(.3s);
|
||||
.stripe-loaded {
|
||||
.opacity(1);
|
||||
display: block;
|
||||
// .opacity(1);
|
||||
.transition(width .3s linear);
|
||||
width: 100% !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<script src="<?= Backend::skinAsset('assets/js/vendor/jquery-2.0.3.min.js') ?>"></script>
|
||||
<script src="<?= URL::asset('modules/system/assets/js/framework.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/vendor/modernizr.min.js') ?>"></script>
|
||||
<!--
|
||||
<script src="<?= Backend::skinAsset('assets/js/vendor/mousewheel.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/vendor/jquery.touchwipe.min.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/vendor/moment.min.js') ?>"></script>
|
||||
|
|
@ -41,6 +42,7 @@
|
|||
<script src="<?= Backend::skinAsset('assets/js/october.dragscroll.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.toolbar.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.verticalmenu.js') ?>"></script>
|
||||
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.navbar.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.sidenav.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.tab.js') ?>"></script>
|
||||
|
|
@ -48,10 +50,13 @@
|
|||
<script src="<?= Backend::skinAsset('assets/js/october.popup.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.goalmeter.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.scrollbar.js') ?>"></script>
|
||||
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.filelist.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.hotkey.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.loadindicator.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.stripeloadindicator.js') ?>"></script>
|
||||
|
||||
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.flashmessage.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.inputpreset.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.layout.js') ?>"></script>
|
||||
|
|
@ -61,16 +66,19 @@
|
|||
<script src="<?= Backend::skinAsset('assets/js/october.inspector.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.dropdown.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.changemonitor.js') ?>"></script>
|
||||
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.chartutils.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.chartpie.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.chartbar.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.chartline.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.balloonselector.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.rowlink.js') ?>"></script>
|
||||
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.treelist.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.autocomplete.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.callout.js') ?>"></script>
|
||||
<script src="<?= Backend::skinAsset('assets/js/october.sidenav-tree.js') ?>"></script>
|
||||
-->
|
||||
|
||||
<script>
|
||||
<!--
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class Table extends WidgetBase
|
|||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->columns = $this->getConfig('columns', []);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -48,7 +49,7 @@ class Table extends WidgetBase
|
|||
*/
|
||||
public function prepareVars()
|
||||
{
|
||||
|
||||
$this->vars['columns'] = $this->prepareColumnsArray();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -64,5 +65,26 @@ class Table extends WidgetBase
|
|||
$this->addJs('js/table.js', 'core');
|
||||
$this->addJs('js/table.datasource.base.js', 'core');
|
||||
$this->addJs('js/table.datasource.client.js', 'core');
|
||||
$this->addJs('js/table.processor.base.js', 'core');
|
||||
$this->addJs('js/table.processor.string.js', 'core');
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the columns associative array to a regular array.
|
||||
* Working with regular arrays is much faster in JavaScript.
|
||||
* References:
|
||||
* - http://www.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
|
||||
* - http://jsperf.com/performance-of-array-vs-object/3
|
||||
*/
|
||||
protected function prepareColumnsArray()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->columns as $key=>$data) {
|
||||
$data['key'] = $key;
|
||||
$result[] = $data;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
# Documentation drafr for the Table widget
|
||||
|
||||
# Client-side table widget (table.xxx.js)
|
||||
|
||||
## Code organization
|
||||
|
||||
### OOP pattern
|
||||
|
||||
The data source and cell processor JavaScript classes use the simple parasitic combination inheritance pattern described here:
|
||||
|
||||
- http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/
|
||||
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript
|
||||
|
||||
```
|
||||
|
||||
// Parent class with a method
|
||||
var SuperClass = function(params) {}
|
||||
SuperClass.prototype.someMethod = function() {}
|
||||
|
||||
// Child class
|
||||
var SubClass = function(params) {
|
||||
// Call the parent constructor
|
||||
SuperClass.call(this, params)
|
||||
}
|
||||
|
||||
SubClass.prototype = Object.create(SuperClass.prototype)
|
||||
SubClass.prototype.constructor = SubClass
|
||||
|
||||
// Child class methods can be defined only after the prototype
|
||||
// is updated in the two previous lines
|
||||
|
||||
SubClass.prototype.someMethod = function() {
|
||||
// Call the parent method
|
||||
SuperClass.prototype.someMethod.call(this)
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Namespaces
|
||||
|
||||
All classes for the table widget are be defined in the **$.oc.table** namespace. Cell processors are be defined in the **$.oc.table.processor** namespace. The client and server memory data sources are defined in the **$.oc.table.datasource** namespace.
|
||||
|
||||
### Client-side performance and memory usage considerations
|
||||
|
||||
The classes defined for the Table widget should follow the best practices in order to achieve the high performance and avoid memory leaks:
|
||||
|
||||
* All references to JavaScript objects and DOM elements should be cleared with the `dispose()` methods.
|
||||
* All event handlers registered in the control should be unregistered with the `dispose()` method.
|
||||
* DOM manipulations should only be performed in the detached tree with the `DocumentFragment` objects.
|
||||
* The number of registered event handlers should be kept as low as possible. Cell processors should rely to delegated events registered for the table.
|
||||
* Cell processors should have the `dispose()` method that unregisters the event handlers and does all required cleanup actions.
|
||||
* Do not use closures for event handlers. This gives more control over the variable scope and simplifies the cleanup operations.
|
||||
* If closures are used for anything, use named closures to simplify the profiling process with Chrome dev tools.
|
||||
|
||||
There are several articles that provide a good insight into the high performance JavaScript code and efficient memory management:
|
||||
|
||||
* http://www.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
|
||||
* http://www.toptal.com/javascript/javascript-prototypes-scopes-and-performance-what-you-need-to-know
|
||||
* http://developer.nokia.com/community/wiki/JavaScript_Performance_Best_Practices (the suggestion about the code comments is doubtful JS engines now compile code).
|
||||
|
||||
## Widget usage
|
||||
|
||||
Any `DIV` elements that have the `data-control="table"` attributes are automatically picked up by the control.
|
||||
|
||||
```
|
||||
<div data=control="table" data-columns="{...}"></div>
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
The options below are listed in the JavaScript notation. Corresponding data attribtues would look like `data-client-data-source-class`.
|
||||
|
||||
- `clientDataSourceСlass` (default is **client**)- specifies the client-side data source class. There are two data source classes supported on the client side - **client** and **server**.
|
||||
- `data` - specifies the data in JSON format for the **client**.
|
||||
- `recordsPerPage` - specifies how many records per page to display. If the value is not defined or `false` or `null`, the pagination feature is disabled and all records are displayed.
|
||||
- `columns` - column definitions in JSON format, see the server-side column definition format below.
|
||||
|
||||
## Data sources ($.oc.table.datasource)
|
||||
|
||||
### Client memory data source ($.oc.table.datasource.client)
|
||||
|
||||
The client memory data sources keeps the data in the client memory. The data is loaded from the control element's `data` property (`data-data` attribute) and posted back with the form data.
|
||||
|
||||
### Server memory data source ($.oc.table.datasource.server)
|
||||
|
||||
**TODO:** document this
|
||||
|
||||
## Cell processors ($.oc.table.processor)
|
||||
|
||||
Cell processors are responsible for rendering the cell content, creating the cell data editors and updating the cell value in the grid control. There is a single cell processor per the table column. All rows in a specific column are handled with a same cell processor.
|
||||
|
||||
Cell processors should use the table's `setCellValue()` method to update the value in the table. The table class, in turn, will commit the changes to the data source when the user navigates to another row, on the pagination event, search or form submit. The `setCellValue()` should be the only way to update the table data by cell processors.
|
||||
|
||||
Cell processors should register delegated events to detect user's interaction with the cells they responsible for. The processors should unregister any event handlers in the `dispose()` method. The even handlers should be registered for the widgwet's top element, not for the table, as the table could be rebuilt completely on pagination, search, and other cases.
|
||||
|
||||
### Removing editors from the table
|
||||
|
||||
The table keeps a reference to the currently active cell processor. The cell processor objects have the `activeCell` property that is a reference to the cell which currently has an editor. The table calls the `hideEditor()` method of the active cell processor and the cell processor removes the editor from the active cell.
|
||||
|
||||
### Showing editors
|
||||
|
||||
The table object calls the `onFocus()` method of the cell processors when a cell is clicked or navigated (with the keyboard). The cell processor can build a cell editor when this method is called, if it's required.
|
||||
|
||||
# Server-side table widget (Backend\Widgets\Table)
|
||||
|
||||
## Column definitions
|
||||
|
||||
Columns are defined as array with the `columns` property. The array keys correspond the column identifiers. The array elements are associative arrays with the following keys:
|
||||
|
||||
- title
|
||||
- type (string, checkbox, dropdown, autocomplete)
|
||||
- width
|
||||
- readonly
|
||||
- options (for drop-down elements and autocomplete types)
|
||||
|
|
@ -21,44 +21,45 @@
|
|||
//
|
||||
|
||||
this.tableObj = tableObj
|
||||
};
|
||||
}
|
||||
|
||||
Base.prototype.dispose = function() {
|
||||
this.tableObj = null
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns count records starting from the offset.
|
||||
* Fetches records from the underlying data source and
|
||||
* passes them to the onSuccess callback function.
|
||||
*/
|
||||
Base.prototype.getRecords = function(offset, count) {
|
||||
return [];
|
||||
};
|
||||
Base.prototype.getRecords = function(offset, count, onSuccess) {
|
||||
onSuccess([])
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the total number of records in the underlying set
|
||||
*/
|
||||
Base.prototype.count = function() {
|
||||
return 0;
|
||||
};
|
||||
return 0
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a record with the passed data and returns the new record index.
|
||||
*/
|
||||
Base.prototype.createRecord = function(recordData) {
|
||||
return 0;
|
||||
};
|
||||
return 0
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates a record with the specified index with the passed data
|
||||
*/
|
||||
Base.prototype.updateRecord = function(index, recordData) {
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Deletes a record with the specified index
|
||||
*/
|
||||
Base.prototype.updateRecord = function(index) {
|
||||
};
|
||||
Base.prototype.deleteRecord = function(index) {
|
||||
}
|
||||
|
||||
$.oc.table.datasource.base = Base
|
||||
$.oc.table.datasource.base = Base;
|
||||
}(window.jQuery);
|
||||
|
|
@ -16,10 +16,17 @@
|
|||
// ============================
|
||||
|
||||
var Base = $.oc.table.datasource.base,
|
||||
BaseProto = $.oc.table.datasource.base.prototype
|
||||
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)
|
||||
|
|
@ -27,7 +34,37 @@
|
|||
|
||||
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.
|
||||
*/
|
||||
Client.prototype.getRecords = function(offset, count, onSuccess) {
|
||||
onSuccess(this.data)
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the total number of records in the underlying set
|
||||
*/
|
||||
Client.prototype.count = function() {
|
||||
return this.data.length
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates a record with the passed data and returns the new record index.
|
||||
*/
|
||||
Client.prototype.createRecord = function(recordData) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates a record with the specified index with the passed data
|
||||
*/
|
||||
Client.prototype.updateRecord = function(index, recordData) {
|
||||
// console.log('Update recird', index, recordData)
|
||||
}
|
||||
|
||||
$.oc.table.datasource.client = Client
|
||||
}(window.jQuery);
|
||||
|
|
@ -16,14 +16,24 @@
|
|||
// ============================
|
||||
|
||||
var Table = function(element, options) {
|
||||
this.$el = $(element)
|
||||
this.el = element
|
||||
this.options = options
|
||||
|
||||
//
|
||||
// State properties
|
||||
//
|
||||
|
||||
this.dataSource = this.createDataSource()
|
||||
// The data source object
|
||||
this.dataSource = null
|
||||
|
||||
// The cell processors array
|
||||
this.cellProcessors = []
|
||||
|
||||
// A reference to the currently active cell processor
|
||||
this.activeCellProcessor = null
|
||||
|
||||
// A reference to the currently active table cell
|
||||
this.activeCell = null
|
||||
|
||||
// The current first record index.
|
||||
// This index is zero based and has nothing to do
|
||||
|
|
@ -32,39 +42,349 @@
|
|||
|
||||
// The index of the row which is being edited at the moment.
|
||||
// This index corresponds the data source row index which
|
||||
// uniquely identifies the row in the data set. The negative
|
||||
// value means that no record is edited at the moment. When the
|
||||
// uniquely identifies the row in the data set. When the
|
||||
// table grid notices that a cell in another row is edited it commits
|
||||
// the previously edited record to the data source.
|
||||
this.editedRowIndex = -1
|
||||
this.editedRowIndex = null
|
||||
|
||||
// A reference to the data table.
|
||||
this.dataTable = null
|
||||
|
||||
// Event handlers
|
||||
this.clickHandler = this.onClick.bind(this)
|
||||
this.keydownHandler = this.onKeydown.bind(this)
|
||||
|
||||
//
|
||||
// Initialization
|
||||
//
|
||||
|
||||
this.init()
|
||||
};
|
||||
|
||||
// INTERNAL METHODS
|
||||
// ============================
|
||||
|
||||
Table.prototype.init = function() {
|
||||
// Create the data source object
|
||||
this.createDataSource()
|
||||
|
||||
// Create cell processors
|
||||
this.initCellProcessors()
|
||||
|
||||
// Create header and data tables
|
||||
this.buildTables()
|
||||
|
||||
// Register event handlers
|
||||
this.registerHandlers()
|
||||
}
|
||||
|
||||
Table.prototype.disposeCellProcessors = function() {
|
||||
// For the performance reasons cell processors are stored
|
||||
// in an object structure with keys matching the column names.
|
||||
// We can iterate through then with the for cycle if we know
|
||||
// the column names. We use the for cycle for the performance
|
||||
// reasons: http://jsperf.com/for-vs-foreach/37,
|
||||
// http://jonraasch.com/blog/10-javascript-performance-boosting-tips-from-nicholas-zakas
|
||||
|
||||
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 ($.oc.table.datasource === undefined || $.oc.table.datasource[dataSourceClass] == undefined)
|
||||
throw new Error('The table client-side data source class "'+dataSourceClass+'" is not ' +
|
||||
'found in the $.oc.table.datasource namespace.')
|
||||
|
||||
this.dataSource = new $.oc.table.datasource[dataSourceClass](this)
|
||||
}
|
||||
|
||||
Table.prototype.registerHandlers = function() {
|
||||
this.el.addEventListener('click', this.clickHandler)
|
||||
this.el.addEventListener('keydown', this.keydownHandler)
|
||||
}
|
||||
|
||||
Table.prototype.unregisterHandlers = function() {
|
||||
this.el.removeEventListener('click', this.clickHandler);
|
||||
this.el.removeEventListener('keydown', this.clickHandler);
|
||||
}
|
||||
|
||||
Table.prototype.initCellProcessors = function() {
|
||||
for (var i = 0, len = this.options.columns.length; i < len; i++) {
|
||||
var column = this.options.columns[i].key,
|
||||
columnType = this.options.columns[i].type
|
||||
|
||||
// Resolve the default column type string
|
||||
if (columnType === undefined) {
|
||||
columnType = 'string'
|
||||
this.options.columns[i].type = columnType
|
||||
}
|
||||
|
||||
if ($.oc.table.processor === undefined || $.oc.table.processor[columnType] == undefined)
|
||||
throw new Error('The table cell processor for the column type "'+columnType+'" is not ' +
|
||||
'found in the $.oc.table.processor namespace.')
|
||||
|
||||
this.cellProcessors[column] = new $.oc.table.processor[columnType](this, column)
|
||||
}
|
||||
}
|
||||
|
||||
Table.prototype.getCellProcessor = function(columnName) {
|
||||
return this.cellProcessors[columnName]
|
||||
}
|
||||
|
||||
Table.prototype.buildTables = function() {
|
||||
// Build the headers table
|
||||
this.el.appendChild(this.buildHeaderTable())
|
||||
|
||||
// Build the data table
|
||||
this.updateDataTable()
|
||||
}
|
||||
|
||||
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')
|
||||
header.textContent !== undefined ?
|
||||
header.textContent = this.options.columns[i].title :
|
||||
header.innerText = this.options.columns[i].title
|
||||
|
||||
row.appendChild(header)
|
||||
}
|
||||
|
||||
return headersTable
|
||||
}
|
||||
|
||||
Table.prototype.updateDataTable = function() {
|
||||
var self = this;
|
||||
|
||||
this.getRecords(function onSuccess(records){
|
||||
self.buildDataTable(records)
|
||||
})
|
||||
}
|
||||
|
||||
Table.prototype.buildDataTable = function(records) {
|
||||
// Completely remove the existing data table. By convention there should
|
||||
// be no event handlers or references bound to it.
|
||||
if (this.dataTable !== null)
|
||||
this.dataTable.parentNode.removeChild(this.dataTable);
|
||||
|
||||
this.dataTable = document.createElement('table')
|
||||
|
||||
for (var i = 0, len = records.length; i < len; i++) {
|
||||
var row = document.createElement('tr')
|
||||
|
||||
row.setAttribute('data-row', i)
|
||||
|
||||
for (var j = 0, colsLen = this.options.columns.length; j < colsLen; j++) {
|
||||
var cell = document.createElement('td'),
|
||||
dataContainer = document.createElement('input'),
|
||||
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 = records[i][columnName]
|
||||
|
||||
cellProcessor.renderCell(records[i][columnName], cell)
|
||||
|
||||
cell.appendChild(dataContainer)
|
||||
row.appendChild(cell)
|
||||
}
|
||||
|
||||
this.dataTable.appendChild(row)
|
||||
}
|
||||
|
||||
// Build the data table
|
||||
this.el.appendChild(this.dataTable)
|
||||
}
|
||||
|
||||
Table.prototype.getRecords = function(onSuccess) {
|
||||
return this.dataSource.getRecords(
|
||||
this.offset,
|
||||
this.options.recordsPerPage,
|
||||
onSuccess)
|
||||
}
|
||||
|
||||
/*
|
||||
* Makes a cell processor active and hides the previously
|
||||
* active editor.
|
||||
*/
|
||||
Table.prototype.setActiveProcessor = function(processor) {
|
||||
if (this.activeCellProcessor)
|
||||
this.activeCellProcessor.onUnfocus()
|
||||
|
||||
this.activeCellProcessor = processor
|
||||
}
|
||||
|
||||
Table.prototype.commitEditedRow = function() {
|
||||
if (this.editedRowIndex === null)
|
||||
return
|
||||
|
||||
var editedRow = this.dataTable.querySelector('tr[data-row="'+this.editedRowIndex+'"]')
|
||||
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.editedRowIndex, data)
|
||||
editedRow.setAttribute('data-dirty', 0)
|
||||
}
|
||||
|
||||
/*
|
||||
* Calls the onFocus() method for the cell processor responsible for the
|
||||
* newly focused cell. Commit the previous edited row to the data source
|
||||
* if needed.
|
||||
*/
|
||||
Table.prototype.focusCell = function(cellElement) {
|
||||
var columnName = cellElement.getAttribute('data-column')
|
||||
if (columnName === null)
|
||||
return
|
||||
|
||||
var processor = this.getCellProcessor(columnName)
|
||||
if (!processor)
|
||||
throw new Error("Cell processor not found for the column "+columnName)
|
||||
|
||||
if (this.activeCell !== cellElement) {
|
||||
this.setActiveProcessor(processor)
|
||||
this.activeCell = cellElement
|
||||
}
|
||||
|
||||
// If the cell belongs to other row than the currently edited,
|
||||
// commit currently edited row to the data source. Update the
|
||||
// currently edited row index.
|
||||
var rowIndex = this.getCellRowIndex(cellElement)
|
||||
|
||||
if (this.editedRowIndex !== null && rowIndex != this.editedRowIndex)
|
||||
this.commitEditedRow()
|
||||
|
||||
this.editedRowIndex = rowIndex
|
||||
|
||||
processor.onFocus(cellElement, true)
|
||||
}
|
||||
|
||||
Table.prototype.markCellRowDirty = function(cellElement) {
|
||||
cellElement.parentNode.setAttribute('data-dirty', 1)
|
||||
}
|
||||
|
||||
// EVENT HANDLERS
|
||||
// ============================
|
||||
|
||||
Table.prototype.onClick = function(ev) {
|
||||
var target = this.getEventTarget(ev, 'TD')
|
||||
|
||||
if (!target)
|
||||
return
|
||||
|
||||
if (target.tagName != 'TD')
|
||||
return
|
||||
|
||||
this.focusCell(target)
|
||||
}
|
||||
|
||||
Table.prototype.onKeydown = function(ev) {
|
||||
|
||||
}
|
||||
|
||||
// PUBLIC METHODS
|
||||
// ============================
|
||||
|
||||
Table.prototype.dispose = function() {
|
||||
// Delete the reference to the HTML element.
|
||||
// Dispose the data source and clean up the reference
|
||||
this.dataSource.dispose()
|
||||
this.dataSource = null
|
||||
|
||||
// Unregister event handlers
|
||||
this.unregisterHandlers()
|
||||
|
||||
// Remove references to DOM elements
|
||||
this.dataTable = null
|
||||
|
||||
// Dispose cell processors
|
||||
this.disposeCellProcessors()
|
||||
|
||||
// Delete the reference to the control HTML element.
|
||||
// The script doesn't remove any DOM elements themselves.
|
||||
// If it's needed it should be done by the outer script,
|
||||
// we only make sure that the table widget doesn't hold
|
||||
// references to the detached DOM tree so that the garbage
|
||||
// collector can delete the elements if needed.
|
||||
this.$el = null
|
||||
this.el = null
|
||||
|
||||
// Dispose the data source and clean up the reference
|
||||
this.dataSource.dispose()
|
||||
this.dataSource = null
|
||||
// Delete references to other DOM elements
|
||||
this.activeCell = null
|
||||
}
|
||||
|
||||
// TODO: unbind event handlers,
|
||||
// remove references to objects,
|
||||
// remove references to DOM elements
|
||||
};
|
||||
|
||||
Table.prototype.createDataSource = function() {
|
||||
var dataSourceClass = this.options.clientDataSourceClass;
|
||||
// HELPER METHODS
|
||||
// ============================
|
||||
|
||||
if ($.oc.table.datasource === undefined || $.oc.table.datasource[dataSourceClass] == undefined)
|
||||
throw new Error('The table client-side data source class "'+dataSourceClass+'" is not \
|
||||
found in the .oc.table.datasource namespace.')
|
||||
Table.prototype.getElement = function() {
|
||||
return this.el
|
||||
}
|
||||
|
||||
this.dataSource = new $.oc.table.datasource[dataSourceClass](this);
|
||||
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.getCellValue = function(cellElement) {
|
||||
return cellElement.querySelector('[data-container]').value
|
||||
}
|
||||
|
||||
Table.prototype.getCellRowIndex = function(cellElement) {
|
||||
return cellElement.parentNode.getAttribute('data-row')
|
||||
}
|
||||
|
||||
Table.prototype.setCellValue = function(cellElement, value) {
|
||||
var dataContainer = cellElement.querySelector('[data-container]')
|
||||
|
||||
if (dataContainer.value != value) {
|
||||
dataContainer.value = value
|
||||
|
||||
this.markCellRowDirty(cellElement)
|
||||
}
|
||||
}
|
||||
|
||||
Table.DEFAULTS = {
|
||||
|
|
@ -96,6 +416,8 @@
|
|||
|
||||
$.fn.table.Constructor = Table
|
||||
|
||||
$.oc.table.table = Table
|
||||
|
||||
// TABLE NO CONFLICT
|
||||
// =================
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Base class for the table cell processors.
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
// PROCESSOR NAMESPACES
|
||||
// ============================
|
||||
|
||||
if ($.oc.table === undefined)
|
||||
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
|
||||
|
||||
if ($.oc.table.processor === undefined)
|
||||
$.oc.table.processor = {}
|
||||
|
||||
// CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Base = function(tableObj, columnName) {
|
||||
//
|
||||
// State properties
|
||||
//
|
||||
|
||||
this.tableObj = tableObj
|
||||
|
||||
this.columnName = columnName
|
||||
|
||||
this.activeCell = null
|
||||
|
||||
// Register event handlers
|
||||
this.registerHandlers()
|
||||
}
|
||||
|
||||
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, cellElement) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
Base.prototype.onUnfocus = function() {
|
||||
}
|
||||
|
||||
/*
|
||||
* 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(cellElement, value) {
|
||||
var viewContainer = document.createElement('div')
|
||||
|
||||
viewContainer.setAttribute('data-view-container', 'data-view-container')
|
||||
viewContainer.textContent = value
|
||||
|
||||
cellElement.appendChild(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
|
||||
}
|
||||
|
||||
$.oc.table.processor.base = Base
|
||||
}(window.jQuery);
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 ($.oc.table === undefined)
|
||||
throw new Error("The $.oc.table namespace is not defined. Make sure that the table.js script is loaded.");
|
||||
|
||||
if ($.oc.table.processor === undefined)
|
||||
throw new Error("The $.oc.table.processor namespace is not defined. Make sure that the table.processor.base.js script is loaded.");
|
||||
|
||||
// CLASS DEFINITION
|
||||
// ============================
|
||||
|
||||
var Base = $.oc.table.processor.base,
|
||||
BaseProto = Base.prototype
|
||||
|
||||
var StringProcessor = function(tableObj, columnName) {
|
||||
//
|
||||
// State properties
|
||||
//
|
||||
|
||||
//
|
||||
// Parent constructor
|
||||
//
|
||||
|
||||
Base.call(this, tableObj, columnName)
|
||||
}
|
||||
|
||||
StringProcessor.prototype = Object.create(BaseProto)
|
||||
StringProcessor.prototype.constructor = StringProcessor
|
||||
|
||||
/*
|
||||
* Renders the cell in the normal (no edit) mode
|
||||
*/
|
||||
StringProcessor.prototype.renderCell = function(value, cellElement) {
|
||||
this.createViewContainer(cellElement, value)
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
this.buildEditor(cellElement)
|
||||
}
|
||||
|
||||
StringProcessor.prototype.buildEditor = function(cellElement) {
|
||||
// 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)
|
||||
cellElement.appendChild(input)
|
||||
|
||||
// Focus the element in the next frame.
|
||||
// http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful
|
||||
window.setTimeout(function focusInput(){
|
||||
input.focus()
|
||||
}, 0)
|
||||
}
|
||||
|
||||
/*
|
||||
* Forces the processor to hide the editor when the user navigates
|
||||
* away from the cell.
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
$.oc.table.processor.string = StringProcessor;
|
||||
}(window.jQuery);
|
||||
|
|
@ -1 +1,305 @@
|
|||
<p>This is a grid</p>
|
||||
<?php
|
||||
|
||||
$data = [
|
||||
[
|
||||
'name' => 'City',
|
||||
'type' => 'String',
|
||||
'default' => 'Vancouver',
|
||||
'description' => 'City name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'Country',
|
||||
'type' => 'String',
|
||||
'default' => 'Canada',
|
||||
'description' => 'Country name',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
],
|
||||
[
|
||||
'name' => 'State',
|
||||
'type' => 'String',
|
||||
'default' => 'BC',
|
||||
'description' => 'British columbia',
|
||||
'details' => 'Details string',
|
||||
'monitors' => 'Monitors string',
|
||||
'pencils' => 'Pencils string',
|
||||
'speakers' => 'Speakers string',
|
||||
'stickers' => 'Stickers string'
|
||||
]
|
||||
];
|
||||
|
||||
?>
|
||||
|
||||
<div data-control="table" data-columns="<?= e(json_encode($columns)) ?>" data-data="<?= e(json_encode($data)) ?>"></div>
|
||||
|
|
@ -346,10 +346,34 @@ if (window.jQuery === undefined)
|
|||
$(this).request()
|
||||
})
|
||||
|
||||
$(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function(){
|
||||
$(this).request()
|
||||
return false
|
||||
})
|
||||
document.addEventListener('click', function nativeClickListener(ev){
|
||||
// Faster native click listener. This implementation doesn't use
|
||||
// jQuery until it's really necessary.
|
||||
var target = ev.target ? ev.target : ev.srcElement
|
||||
|
||||
if (target.getAttribute('data-request') == null)
|
||||
return
|
||||
|
||||
var tagName = target.tagName
|
||||
if (tagName == 'A' || tagName == 'BUTTON') {
|
||||
$(target).request()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tagName == 'INPUT') {
|
||||
var type = target.getAttribute('type').toLowerCase()
|
||||
|
||||
if (type == 'button' || type == 'submit') {
|
||||
$(target).request()
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// $(document).on('click', 'a[data-request], button[data-request], input[type=button][data-request], input[type=submit][data-request]', function(){
|
||||
// $(this).request()
|
||||
// return false
|
||||
// })
|
||||
|
||||
$(document).on('keydown', 'input[type=text][data-request], input[type=submit][data-request], input[type=password][data-request]', function(e){
|
||||
if (e.keyCode == 13) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue