diff --git a/modules/backend/classes/TableClientMemoryDataSource.php b/modules/backend/classes/TableClientMemoryDataSource.php index 1e8813c5f..781e34da8 100644 --- a/modules/backend/classes/TableClientMemoryDataSource.php +++ b/modules/backend/classes/TableClientMemoryDataSource.php @@ -43,7 +43,8 @@ class TableClientMemoryDataSource extends TableDataSourceBase * Return records from the data source. * @param integer $offset Specifies the offset of the first record to return, zero-based. * @param integer $count Specifies the number of records to return. - * @return array + * @return array Returns the records. + * If there are no more records, returns an empty array. */ public function getRecords($offset, $count) { diff --git a/modules/backend/classes/TableDataSourceBase.php b/modules/backend/classes/TableDataSourceBase.php index 273d598e8..867747b54 100644 --- a/modules/backend/classes/TableDataSourceBase.php +++ b/modules/backend/classes/TableDataSourceBase.php @@ -6,10 +6,15 @@ abstract class TableDataSourceBase { /** - * @var Specifies a name of record's key column + * @var string Specifies a name of record's key column */ protected $keyColumn; + /** + * @var integer Internal record offset + */ + protected $offset = 0; + /** * Class constructor. * @param string $keyColumn Specifies a name of the key column. @@ -43,7 +48,31 @@ abstract class TableDataSourceBase * Return records from the data source. * @param integer $offset Specifies the offset of the first record to return, zero-based. * @param integer $count Specifies the number of records to return. - * @return array + * @return array Returns the records. + * If there are no more records, returns an empty array. */ abstract public function getRecords($offset, $count); + + /** + * Rewinds the the data source to the first record. + * Use this method with the readRecords() method. + */ + public function reset() + { + $this->offset = 0; + } + + /** + * Returns a set of records from the data source. + * @param integer $count Specifies the number of records to return. + * @return array Returns the records. + * If there are no more records, returns an empty array. + */ + public function readRecords($count = 10) + { + $result = $this->getRecords($this->offset, $count); + $this->offset += count($result); + + return $result; + } } \ No newline at end of file diff --git a/modules/backend/widgets/Table.php b/modules/backend/widgets/Table.php index 3d99210c0..36f81a646 100644 --- a/modules/backend/widgets/Table.php +++ b/modules/backend/widgets/Table.php @@ -1,6 +1,7 @@ dataSource = new $dataSourceClass($this->recordsKeyColumn); + + if (Request::method() == 'POST' && $this->isClientDataSource()) { + $requestDataField = $this->alias.'TableData'; + + if (Request::exists($requestDataField)) { + // Load data into the client memory data source on POST + $this->dataSource->purge(); + $this->dataSource->initRecords(Request::input($requestDataField)); + } + } } /** @@ -85,7 +96,7 @@ class Table extends WidgetBase $this->vars['columns'] = $this->prepareColumnsArray(); $this->vars['recordsKeyColumn'] = $this->recordsKeyColumn; - $isClientDataSource = $this->dataSource instanceof \Backend\Classes\TableClientMemoryDataSource; + $isClientDataSource = $this->isClientDataSource(); $this->vars['clientDataSourceClass'] = $isClientDataSource ? 'client' : 'server'; $this->vars['data'] = $isClientDataSource ? @@ -132,6 +143,11 @@ class Table extends WidgetBase return $result; } + protected function isClientDataSource() + { + return $this->dataSource instanceof \Backend\Classes\TableClientMemoryDataSource; + } + /* * Event handlers */ diff --git a/modules/backend/widgets/table/README.md b/modules/backend/widgets/table/README.md index 420f59814..25128113c 100644 --- a/modules/backend/widgets/table/README.md +++ b/modules/backend/widgets/table/README.md @@ -78,6 +78,8 @@ The options below are listed in the JavaScript notation. Corresponding data attr - `columns` - column definitions in JSON format, see the server-side column definition format below. - `rowSorting` - enables the drag & drop row sorting. The sorting cannot be used with the pagination (`recordsPerPage` is not `null` or `false`). - `keyColumn` - specifies the name of the key column. The default value is **id**. +- `postback` - post the client-memory data source data to the server automatically when the parent form gets submitted. The default value is `true`. The option is used only with client-memory data sources. When enabled, the data source data is available in the widget's server-side data source: `$table->getDataSource()->getRecords();` The data postback occurs only of the request handler name matches the `postbackHandlerName` option value. +- `postbackHandlerName` - AJAX data handler name for the automatic data postback. The data will be posted only when the AJAX requests posts data to this handler. The default value is **onSave**. ## Client-side helper classes @@ -233,4 +235,22 @@ $dataSource->purge(); ## Reading data from the data source -TODO \ No newline at end of file +The server-side data sources (PHP) automatically maintain the actual data, but that mechanism for the client-memory and server-memory data sources is different. + +In case of the client-memory data source, the table widget adds the data records to the POST, when the form is saved (see `postback` and `postbackHandlerName` options). On the server side the data is inserted to the data source by the table widget. + +The server-memory data source always automatically maintain its contents in synch with the client using AJAX, and POSTing data is not required. + +In PHP reading data from a data source of any type looks like this (it should be in the AJAX handler that saves the data, for the client-memory data source the handler name should match the `postbackHandlerName` option value): + +``` +public function onSave() +{ + // Assuming that the widget was initialized in the + // controller constructor with the "table" alias. + $dataSource = $this->widget->table->getDataSource(); + + while ($records = $dataSource->readRecords(5)) { + traceLog($records); + } +``` \ No newline at end of file diff --git a/modules/backend/widgets/table/assets/js/table.datasource.client.js b/modules/backend/widgets/table/assets/js/table.datasource.client.js index 1d9b90a96..edde54f1b 100644 --- a/modules/backend/widgets/table/assets/js/table.datasource.client.js +++ b/modules/backend/widgets/table/assets/js/table.datasource.client.js @@ -136,5 +136,9 @@ }).indexOf(parseInt(key)) } + Client.prototype.getAllData = function() { + return this.data + } + $.oc.table.datasource.client = Client }(window.jQuery); \ No newline at end of file diff --git a/modules/backend/widgets/table/assets/js/table.js b/modules/backend/widgets/table/assets/js/table.js index 7053989b3..23dbe28a9 100644 --- a/modules/backend/widgets/table/assets/js/table.js +++ b/modules/backend/widgets/table/assets/js/table.js @@ -57,6 +57,9 @@ this.clickHandler = this.onClick.bind(this) this.keydownHandler = this.onKeydown.bind(this) + if (this.options.postback && this.options.clientDataSourceClass == 'client') + this.formSubmitHandler = this.onFormSubmit.bind(this) + // Navigation helper this.navigation = null @@ -122,6 +125,9 @@ Table.prototype.registerHandlers = function() { this.el.addEventListener('click', this.clickHandler) this.el.addEventListener('keydown', this.keydownHandler) + + if (this.options.postback && this.options.clientDataSourceClass == 'client') + this.$el.closest('form').bind('oc.beforeRequest', this.formSubmitHandler) } Table.prototype.unregisterHandlers = function() { @@ -130,6 +136,11 @@ this.el.removeEventListener('keydown', this.keydownHandler); this.keydownHandler = null + + if (this.formSubmitHandler) { + this.$el.closest('form').unbind('oc.beforeRequest', this.formSubmitHandler) + this.formSubmitHandler = null + } } Table.prototype.initCellProcessors = function() { @@ -331,6 +342,10 @@ this.commitEditedRow() this.activeCellProcessor = null + + if (this.activeCell) + this.activeCell.setAttribute('class', '') + this.activeCell = null } @@ -466,6 +481,17 @@ ) } + 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]) + } + } + // EVENT HANDLERS // ============================ @@ -522,14 +548,10 @@ return } - 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.onFormSubmit = function(ev, data) { + if (data.handler == this.options.postbackHandlerName) { + this.unfocusTable() + data.options.data[this.options.alias + 'TableData'] = this.dataSource.getAllData() } } @@ -683,7 +705,9 @@ clientDataSourceClass: 'client', keyColumn: 'id', recordsPerPage: false, - data: null + data: null, + postback: true, + postbackHandlerName: 'onSave' } // TABLE PLUGIN DEFINITION diff --git a/modules/backend/widgets/table/assets/js/table.processor.dropdown.js b/modules/backend/widgets/table/assets/js/table.processor.dropdown.js index 6287f453c..85efe3881 100644 --- a/modules/backend/widgets/table/assets/js/table.processor.dropdown.js +++ b/modules/backend/widgets/table/assets/js/table.processor.dropdown.js @@ -75,9 +75,8 @@ this.fetchOptions(cellContentContainer.parentNode, function renderCellFetchOptions(options) { if (options[value] !== undefined) - value = options[value] + viewContainer.textContent = options[value] - viewContainer.textContent = value cellContentContainer.setAttribute('tabindex', 0) }) } @@ -113,7 +112,6 @@ this.hideDropdown() this.itemListElement = null - this.activeCell = null } @@ -178,6 +176,13 @@ 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()