diff --git a/modules/backend/widgets/Grid.php b/modules/backend/widgets/Grid.php index 472d9fa77..11de95c86 100644 --- a/modules/backend/widgets/Grid.php +++ b/modules/backend/widgets/Grid.php @@ -51,20 +51,25 @@ class Grid extends WidgetBase protected $disableToolbar = false; /** - * @var array Provided data set + * @var array Provided data set, cannot use with dataLocker or useDataSource. */ protected $data; /** - * @var boolean Use a remote data source - */ - protected $useDataSource; - - /** - * @var string HTML element that can [re]store the grid data. + * @var string HTML element that can [re]store the grid data, cannot use with data or useDataSource. */ protected $dataLocker; + /** + * @var boolean Get data from AJAX callback (onDataSource), cannot use with data or dataLocker. + */ + protected $useDataSource = false; + + /** + * @var boolean Sends an AJAX callback (onDataChanged) any time a field is changed. + */ + protected $monitorChanges = true; + /** * Initialize the widget, called by the constructor and free from its parameters. */ @@ -78,6 +83,7 @@ class Grid extends WidgetBase $this->data = $this->getConfig('data', $this->data); $this->dataLocker = $this->getConfig('dataLocker', $this->dataLocker); $this->useDataSource = $this->getConfig('useDataSource', $this->useDataSource); + $this->monitorChanges = $this->getConfig('monitorChanges', $this->monitorChanges); } /** @@ -106,6 +112,7 @@ class Grid extends WidgetBase $this->vars['data'] = $this->data; $this->vars['dataLocker'] = $this->dataLocker; $this->vars['useDataSource'] = $this->useDataSource; + $this->vars['monitorChanges'] = $this->monitorChanges; } protected function makeToolbarWidget() @@ -137,6 +144,19 @@ class Grid extends WidgetBase return ['result' => $result]; } + public function onDataChanged() + { + if (!$this->monitorChanges) + return; + + /* + * Changes array, each array item will contain: + * ['rowData' => [...], 'keyName' => 'changedColumn', 'oldValue' => 'was', 'newValue' => 'is'] + */ + $changes = post('changes'); + $this->fireEvent('grid.dataChanged', [$changes]); + } + public function onDataSource() { if (!$this->useDataSource) diff --git a/modules/backend/widgets/grid/assets/js/datagrid.js b/modules/backend/widgets/grid/assets/js/datagrid.js index d65817e21..bf1728ee4 100644 --- a/modules/backend/widgets/grid/assets/js/datagrid.js +++ b/modules/backend/widgets/grid/assets/js/datagrid.js @@ -3,13 +3,16 @@ * * Data attributes: * - data-control="datagrid" - enables the plugin on an element - * - data-option="value" - an option with a value + * - data-allow-remove="true" - allow rows to be removed + * - data-autocomplete-handler="onAutocomplete" - AJAX handler for autocomplete values + * - data-data-locker="input#locker" - Input element to store and restore grid data as JSON + * - data-source-handler="onGetData" - AJAX handler for obtaining grid data * * JavaScript API: - * $('a#someElement').dataGrid({ option: 'value' }) + * $('div#someElement').dataGrid({ option: 'value' }) * - * Dependences: - * - Some other plugin (filename.js) + * Dependences: + * - Handsontable (handsontable.js) */ +function ($) { "use strict"; @@ -43,6 +46,19 @@ // rowHeaders: false, // manualColumnMove: true, // manualRowMove: true, + afterChange: function(changes, source) { + if (source === 'loadData') + return + + /* + * changes - is a 2D array containing information about each of the edited cells + * [ [row, prop, oldVal, newVal], ... ]. + * + * source - is one of the strings: "alter", "empty", "edit", "populateFromArray", + * "loadData", "autofill", "paste". + */ + self.$el.trigger('datagrid.change', [changes, source]) + }, fillHandle: false, multiSelect: false, removeRowPlugin: this.options.allowRemove @@ -51,18 +67,24 @@ if (this.options.autoInsertRows) handsontableOptions.minSpareRows = 1 + /* + * Data provided + */ if (this.options.data) { handsontableOptions.data = this.options.data } + /* + * Data from a data locker + */ else if (this.options.dataLocker) { /* * Event to update the data locker */ this.$dataLocker = $(this.options.dataLocker) - handsontableOptions.afterChange = function(changes, source) { + self.$el.on('datagrid.change', function(event, eventData) { if (!self.gridInstance) return self.$dataLocker.val(JSON.stringify(self.getData())) - } + }) /* * Populate existing data @@ -74,10 +96,44 @@ delete handsontableOptions.data } } + /* + * Data from an AJAX data source + */ else if (this.options.sourceHandler) { self.refreshDataSource() } + /* + * Monitor for data changes + */ + if (this.options.changeHandler) { + self.$el.on('datagrid.change', function(event, changes, source) { + var changeData = []; + + $.each(changes, function(index, change){ + var changeObj = {} + changeObj.keyName = change[1] + changeObj.oldValue = change[2] + changeObj.newValue = change[3] + + if (changeObj.oldValue == changeObj.newValue) + return; // continue + + changeObj.rowData = self.getDataAtRow(change[0]) + changeData.push(changeObj) + }) + + if (changeData.length > 0) { + self.$el.request(self.options.changeHandler, { + data: { changes: changeData } + }) + } + }) + } + + /* + * Create up Handson table and validate columns + */ this.$el.handsontable(handsontableOptions) this.gridInstance = this.$el.handsontable('getInstance') @@ -105,6 +161,9 @@ return columns } + /* + * Auto complete + */ var autocompleteLastQuery = '', autocompleteInterval = 300, autocompleteInputTimer @@ -142,6 +201,7 @@ data: null, dataLocker: null, sourceHandler: null, + changeHandler: null, startRows: 1, minRows: 1, autoInsertRows: false, diff --git a/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.css b/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.css index d77fc1609..c47269b32 100644 --- a/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.css +++ b/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.css @@ -124,9 +124,6 @@ .handsontable tbody th:last-of-type { border-right: 1px solid #e2e2e2 !important; } -.handsontable tbody td:first-of-type.currentRow { - border-left: 3px solid #ff9933; -} .handsontable th.active { /*background-color: #CCC;*/ color: #666; diff --git a/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.less b/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.less index 7d297cb7f..717f5186b 100644 --- a/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.less +++ b/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.less @@ -147,9 +147,13 @@ border-right: 1px solid @color-handsontable-border !important; } - td:first-of-type.currentRow { - border-left: 3px solid @color-list-stripe-active; - } + //td:first-of-type { + // border-left: 3px solid transparent; + //} + + // td:first-of-type.currentRow { + // border-left: 3px solid @color-list-stripe-active; + // } } th.active { diff --git a/modules/backend/widgets/grid/partials/_grid.htm b/modules/backend/widgets/grid/partials/_grid.htm index cc33b2967..b295084c0 100644 --- a/modules/backend/widgets/grid/partials/_grid.htm +++ b/modules/backend/widgets/grid/partials/_grid.htm @@ -12,6 +12,7 @@ data-autocomplete-handler="getEventHandler('onAutocomplete') ?>" data-data-locker="" data-source-handler="getEventHandler('onDataSource') ?>" + data-change-handler="getEventHandler('onDataChanged') ?>" >