diff --git a/modules/backend/formwidgets/DataGrid.php b/modules/backend/formwidgets/DataGrid.php
index 249b7f0e3..1b405ca8c 100644
--- a/modules/backend/formwidgets/DataGrid.php
+++ b/modules/backend/formwidgets/DataGrid.php
@@ -1,5 +1,6 @@
columns = $this->getConfig('columns', []);
$this->size = $this->getConfig('size', $this->size);
+ $this->grid = $this->makeGridWidget();
+ $this->grid->bindToController();
}
/**
@@ -59,125 +52,10 @@ class DataGrid extends FormWidgetBase
*/
public function prepareVars()
{
+ $this->vars['grid'] = $this->grid;
$this->vars['name'] = $this->formField->getName();
- $this->vars['columnHeaders'] = $this->getColumnHeaders();
- $this->vars['columnDefinitions'] = $this->getColumnDefinitions();
- $this->vars['columnWidths'] = $this->getColumnWidths();
$this->vars['size'] = $this->size;
- $this->vars['toolbarWidget'] = $this->makeToolbarWidget();
- $this->vars['value'] = json_encode($this->model->{$this->columnName});
- }
-
- protected function makeToolbarWidget()
- {
- $toolbarConfig = $this->makeConfig([
- 'alias' => $this->alias . 'Toolbar',
- 'buttons' => $this->getViewPath('_toolbar.htm'),
- ]);
-
- $toolbarWidget = $this->makeWidget('Backend\Widgets\Toolbar', $toolbarConfig);
- return $toolbarWidget;
- }
-
- //
- // Getters
- //
-
- protected function getColumnHeaders()
- {
- $headers = [];
- foreach ($this->columns as $key => $column) {
- $headers[] = isset($column['title']) ? $column['title'] : '???';
- }
- return $headers;
- }
-
- protected function getColumnWidths()
- {
- $widths = [];
- foreach ($this->columns as $key => $column) {
- $widths[] = isset($column['width']) ? $column['width'] : '0';
- }
- return $widths;
- }
-
- protected function getColumnDefinitions()
- {
- $definitions = [];
- foreach ($this->columns as $key => $column) {
- $item = [];
- $item['data'] = $key;
-
- if (isset($column['readOnly']))
- $item['readOnly'] = $column['readOnly'];
-
- $item = $this->evalColumnType($column, $item);
- $definitions[] = $item;
- }
- return $definitions;
- }
-
- protected function evalColumnType($column, $item)
- {
- if (!isset($column['type']))
- return $item;
-
- switch ($column['type']) {
- case 'number':
- $item['type'] = 'numeric';
- break;
-
- case 'currency':
- $item['type'] = 'numeric';
- $item['format'] = '$0,0.00';
- break;
-
- case 'checkbox':
- $item['type'] = 'checkbox';
- break;
-
- case 'autocomplete':
- $item['type'] = 'autocomplete';
- if (isset($column['options'])) $item['source'] = $column['options'];
- if (isset($column['strict'])) $item['strict'] = $column['strict'];
- break;
- }
-
- return $item;
- }
-
- //
- // AJAX
- //
-
- public function onAutocomplete()
- {
- if (!$this->model->methodExists('getGridAutocompleteValues'))
- throw new ApplicationException('Model :model does not contain a method getGridAutocompleteValues()');
-
- $field = post('autocomplete_field');
- $value = post('autocomplete_value');
- $data = post('autocomplete_data', []);
- $result = $this->model->getGridAutocompleteValues($field, $value, $data);
- if (!is_array($result))
- $result = [];
-
- return ['result' => $result];
- }
-
- //
- // Internals
- //
-
- /**
- * {@inheritDoc}
- */
- public function loadAssets()
- {
- $this->addCss('vendor/handsontable/jquery.handsontable.css', 'core');
- $this->addCss('css/datagrid.css', 'core');
- $this->addJs('vendor/handsontable/jquery.handsontable.js', 'core');
- $this->addJs('js/datagrid.js', 'core');
+ $this->vars['value'] = json_encode($this->formField->value);
}
/**
@@ -187,4 +65,28 @@ class DataGrid extends FormWidgetBase
{
return json_decode($value);
}
+
+ protected function makeGridWidget()
+ {
+ $config = $this->makeConfig((array) $this->config);
+ $config->dataLocker = '#'.$this->getId('dataLocker');
+
+ $grid = new Grid($this->controller, $config);
+ $grid->alias = $this->alias . 'Grid';
+ $grid->bindEvent('grid.autocomplete', [$this, 'getAutocompleteValues']);
+
+ return $grid;
+ }
+
+ public function getAutocompleteValues($field, $value, $data)
+ {
+ if (!$this->model->methodExists('getGridAutocompleteValues'))
+ throw new ApplicationException('Model :model does not contain a method getGridAutocompleteValues()');
+
+ $result = $this->model->getGridAutocompleteValues($field, $value, $data);
+ if (!is_array($result))
+ $result = [];
+
+ return $result;
+ }
}
\ No newline at end of file
diff --git a/modules/backend/formwidgets/datagrid/partials/_datagrid.htm b/modules/backend/formwidgets/datagrid/partials/_datagrid.htm
index f8cb23918..7ec6fe954 100644
--- a/modules/backend/formwidgets/datagrid/partials/_datagrid.htm
+++ b/modules/backend/formwidgets/datagrid/partials/_datagrid.htm
@@ -2,16 +2,7 @@
id="= $this->getId() ?>"
class="field-datagrid size-= $size ?>">
- = $toolbarWidget->render() ?>
-
-
+ = $grid->render() ?>
-
\ No newline at end of file
diff --git a/modules/backend/formwidgets/datagrid/partials/_toolbar.htm b/modules/backend/formwidgets/datagrid/partials/_toolbar.htm
deleted file mode 100644
index ffa217aff..000000000
--- a/modules/backend/formwidgets/datagrid/partials/_toolbar.htm
+++ /dev/null
@@ -1,6 +0,0 @@
-
\ No newline at end of file
diff --git a/modules/backend/widgets/Grid.php b/modules/backend/widgets/Grid.php
new file mode 100644
index 000000000..8105149fd
--- /dev/null
+++ b/modules/backend/widgets/Grid.php
@@ -0,0 +1,226 @@
+columns = $this->getConfig('columns', []);
+ $this->showHeader = $this->getConfig('showHeader', $this->showHeader);
+ $this->allowInsert = $this->getConfig('allowInsert', $this->allowInsert);
+ $this->allowRemove = $this->getConfig('allowRemove', $this->allowRemove);
+ $this->disableToolbar = $this->getConfig('disableToolbar', $this->disableToolbar);
+ $this->dataLocker = $this->getConfig('dataLocker', $this->dataLocker);
+ $this->dataSource = $this->getConfig('dataSource', $this->dataSource);
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function render()
+ {
+ $this->prepareVars();
+ return $this->makePartial('grid');
+ }
+
+ /**
+ * Prepares the view data
+ */
+ public function prepareVars()
+ {
+ $this->vars['columnHeaders'] = $this->getColumnHeaders();
+ $this->vars['columnDefinitions'] = $this->getColumnDefinitions();
+ $this->vars['columnWidths'] = $this->getColumnWidths();
+ $this->vars['toolbarWidget'] = $this->makeToolbarWidget();
+
+ $this->vars['showHeader'] = $this->showHeader;
+ $this->vars['allowInsert'] = $this->allowInsert;
+ $this->vars['allowRemove'] = $this->allowRemove;
+ $this->vars['disableToolbar'] = $this->disableToolbar;
+ $this->vars['dataLocker'] = $this->dataLocker;
+ }
+
+ protected function makeToolbarWidget()
+ {
+ if ($this->disableToolbar)
+ return;
+
+ $toolbarConfig = $this->makeConfig([
+ 'alias' => $this->alias . 'Toolbar',
+ 'buttons' => $this->getViewPath('_toolbar.htm'),
+ ]);
+
+ $toolbarWidget = $this->makeWidget('Backend\Widgets\Toolbar', $toolbarConfig);
+ $toolbarWidget->vars['allowInsert'] = $this->allowInsert;
+ $toolbarWidget->vars['allowRemove'] = $this->allowRemove;
+ return $toolbarWidget;
+ }
+
+ //
+ // AJAX
+ //
+
+ public function onAutocomplete()
+ {
+ $field = post('autocomplete_field');
+ $value = post('autocomplete_value');
+ $data = post('autocomplete_data', []);
+ $result = $this->fireEvent('grid.autocomplete', [$field, $value, $data], true);
+ return ['result' => $result];
+ }
+
+ public function onDataSource()
+ {
+ if ($this->dataLocker)
+ return;
+
+ $result = $this->dataSource;
+ return ['result' => $result];
+ }
+
+ //
+ // Getters
+ //
+
+ protected function getColumnHeaders()
+ {
+ if (!$this->showHeader)
+ return false;
+
+ $headers = [];
+ foreach ($this->columns as $key => $column) {
+ $headers[] = isset($column['title']) ? $column['title'] : '???';
+ }
+ return $headers;
+ }
+
+ protected function getColumnWidths()
+ {
+ $widths = [];
+ foreach ($this->columns as $key => $column) {
+ $widths[] = isset($column['width']) ? $column['width'] : '0';
+ }
+ return $widths;
+ }
+
+ protected function getColumnDefinitions()
+ {
+ $definitions = [];
+ foreach ($this->columns as $key => $column) {
+ $item = [];
+ $item['data'] = $key;
+
+ if (isset($column['readOnly']))
+ $item['readOnly'] = $column['readOnly'];
+
+ $item = $this->evalColumnType($column, $item);
+ $definitions[] = $item;
+ }
+ return $definitions;
+ }
+
+ protected function evalColumnType($column, $item)
+ {
+ if (!isset($column['type']))
+ return $item;
+
+ switch ($column['type']) {
+ case 'number':
+ $item['type'] = 'numeric';
+ break;
+
+ case 'currency':
+ $item['type'] = 'numeric';
+ $item['format'] = '$0,0.00';
+ break;
+
+ case 'checkbox':
+ $item['type'] = 'checkbox';
+ break;
+
+ case 'autocomplete':
+ $item['type'] = 'autocomplete';
+ if (isset($column['options'])) $item['source'] = $column['options'];
+ if (isset($column['strict'])) $item['strict'] = $column['strict'];
+ break;
+ }
+
+ return $item;
+ }
+
+ //
+ // Internals
+ //
+
+ /**
+ * {@inheritDoc}
+ */
+ public function loadAssets()
+ {
+ $this->addCss('vendor/handsontable/jquery.handsontable.css', 'core');
+ $this->addCss('css/datagrid.css', 'core');
+ $this->addJs('vendor/handsontable/jquery.handsontable.js', 'core');
+ $this->addJs('js/datagrid.js', 'core');
+ }
+
+}
\ No newline at end of file
diff --git a/modules/backend/widgets/Toolbar.php b/modules/backend/widgets/Toolbar.php
index 57f75a6fe..e2abfb0b2 100644
--- a/modules/backend/widgets/Toolbar.php
+++ b/modules/backend/widgets/Toolbar.php
@@ -83,6 +83,6 @@ class Toolbar extends WidgetBase
if (!isset($this->config->buttons))
return false;
- return $this->controller->makePartial($this->config->buttons);
+ return $this->controller->makePartial($this->config->buttons, $this->vars);
}
}
\ No newline at end of file
diff --git a/modules/backend/formwidgets/datagrid/assets/css/datagrid.css b/modules/backend/widgets/grid/assets/css/datagrid.css
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/css/datagrid.css
rename to modules/backend/widgets/grid/assets/css/datagrid.css
diff --git a/modules/backend/formwidgets/datagrid/assets/js/datagrid.js b/modules/backend/widgets/grid/assets/js/datagrid.js
similarity index 95%
rename from modules/backend/formwidgets/datagrid/assets/js/datagrid.js
rename to modules/backend/widgets/grid/assets/js/datagrid.js
index 83dfd2971..a1609cc3d 100644
--- a/modules/backend/formwidgets/datagrid/assets/js/datagrid.js
+++ b/modules/backend/widgets/grid/assets/js/datagrid.js
@@ -32,17 +32,17 @@
colWidths: function(columnIndex) {
return self.staticWidths[columnIndex]
},
- height: 400,
+ // height: 400,
columns: this.columns,
startRows: this.options.startRows,
minRows: this.options.minRows,
currentRowClassName: 'currentRow',
- // rowHeaders: true,
+ // rowHeaders: false,
// manualColumnMove: true,
// manualRowMove: true,
fillHandle: false,
multiSelect: false,
- removeRowPlugin: true
+ removeRowPlugin: this.options.allowRemove
}
if (this.options.autoInsertRows)
@@ -68,6 +68,13 @@
delete handsontableOptions.data
}
}
+ else if (this.options.sourceHandler) {
+ $.request(self.options.sourceHandler, {
+ success: function(data, textStatus, jqXHR){
+ self.gridInstance.loadData(data.result)
+ }
+ })
+ }
this.$el.handsontable(handsontableOptions)
this.gridInstance = this.$el.handsontable('getInstance')
@@ -138,6 +145,8 @@
columnWidths: null,
columns: null,
autocompleteHandler: null,
+ sourceHandler: null,
+ allowRemove: true,
confirmMessage: 'Are you sure?'
}
diff --git a/modules/backend/formwidgets/datagrid/assets/less/datagrid.less b/modules/backend/widgets/grid/assets/less/datagrid.less
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/less/datagrid.less
rename to modules/backend/widgets/grid/assets/less/datagrid.less
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/columnautosize.js b/modules/backend/widgets/grid/assets/vendor/handsontable/columnautosize.js
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/columnautosize.js
rename to modules/backend/widgets/grid/assets/vendor/handsontable/columnautosize.js
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/columnmove.js b/modules/backend/widgets/grid/assets/vendor/handsontable/columnmove.js
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/columnmove.js
rename to modules/backend/widgets/grid/assets/vendor/handsontable/columnmove.js
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/columnresize.js b/modules/backend/widgets/grid/assets/vendor/handsontable/columnresize.js
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/columnresize.js
rename to modules/backend/widgets/grid/assets/vendor/handsontable/columnresize.js
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/columnsorting.js b/modules/backend/widgets/grid/assets/vendor/handsontable/columnsorting.js
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/columnsorting.js
rename to modules/backend/widgets/grid/assets/vendor/handsontable/columnsorting.js
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/jquery.handsontable.css b/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.css
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/jquery.handsontable.css
rename to modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.css
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/jquery.handsontable.js b/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.js
similarity index 97%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/jquery.handsontable.js
rename to modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.js
index 8b7ed63cb..42ba7132d 100644
--- a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/jquery.handsontable.js
+++ b/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.js
@@ -14,6 +14,7 @@
* - Removed contextMenu plugin
* - Use autocomplete plugin instead of typeahead
* - Custom checkboxes
+ * - Removed native scrollbars
*
*/
@@ -2797,9 +2798,7 @@ DefaultSettings.prototype = {
allowInvalid: true,
invalidCellClassName: 'htInvalid',
fragmentSelection: false,
- readOnly: false,
- scrollbarModelV: 'dragdealer',
- scrollbarModelH: 'dragdealer'
+ readOnly: false
};
$.fn.handsontable = function (action) {
@@ -3021,8 +3020,6 @@ Handsontable.TableView = function (instance) {
data: instance.getDataAtCell,
totalRows: instance.countRows,
totalColumns: instance.countCols,
- scrollbarModelV: this.settings.scrollbarModelV,
- scrollbarModelH: this.settings.scrollbarModelH,
offsetRow: 0,
offsetColumn: 0,
width: this.getWidth(),
@@ -3317,9 +3314,6 @@ Handsontable.TableView.prototype.maximumVisibleElementWidth = function (left) {
*/
Handsontable.TableView.prototype.maximumVisibleElementHeight = function (top) {
var rootHeight = this.wt.wtViewport.getWorkspaceHeight();
- if(this.wt.isNativeScroll) {
- return rootHeight;
- }
return rootHeight - top;
};
@@ -8004,25 +7998,6 @@ WalkontableScroll.prototype.scrollViewport = function (coords) {
, fixedRowsTop = this.instance.getSetting('fixedRowsTop')
, fixedColumnsLeft = this.instance.getSetting('fixedColumnsLeft');
- if (this.instance.isNativeScroll) {
- var TD = this.instance.wtTable.getCell(coords);
- if (typeof TD === 'object') {
- var offset = WalkontableDom.prototype.offset(TD);
- var outerHeight = WalkontableDom.prototype.outerHeight(TD);
- var scrollY = window.scrollY;
- var clientHeight = document.documentElement.clientHeight;
- if (outerHeight < clientHeight) {
- if (offset.top < scrollY) {
- TD.scrollIntoView(true);
- }
- else if (offset.top + outerHeight > scrollY + clientHeight) {
- TD.scrollIntoView(false);
- }
- }
- return;
- }
- }
-
if (coords[0] < 0 || coords[0] > totalRows - 1) {
throw new Error('row ' + coords[0] + ' does not exist');
}
@@ -8437,129 +8412,10 @@ WalkontableScrollbarNative.prototype.destroy = function () {
this.$scrollHandler.off('scroll.walkontable');
};
-///
-var WalkontableVerticalScrollbarNative = function (instance) {
- this.instance = instance;
- this.type = 'vertical';
- this.cellSize = 23;
- this.init();
-
- var that = this;
- WalkontableCellStrategy.prototype.isLastIncomplete = function () { //monkey patch needed. In future get rid of it to improve performance
- /*
- * this.remainingSize = window viewport reduced by sum of all rendered cells (also those before the visible part)
- * that.sumCellSizes(...) = sum of the sizes of cells that are before the visible part + 1 cell that is partially visible on top of the screen
- */
- return this.remainingSize > that.sumCellSizes(that.offset, that.offset + that.curOuts + 1);
- };
-};
-
-WalkontableVerticalScrollbarNative.prototype = new WalkontableScrollbarNative();
-
-WalkontableVerticalScrollbarNative.prototype.getLastCell = function () {
- return this.instance.getSetting('offsetRow') + this.instance.wtTable.tbodyChildrenLength - 1;
-};
-
-WalkontableVerticalScrollbarNative.prototype.getTableSize = function () {
- return this.instance.wtDom.outerHeight(this.TABLE);
-};
-
-var partialOffset = 0;
-
-WalkontableVerticalScrollbarNative.prototype.sumCellSizes = function (from, length) {
- var sum = 0;
- while (from < length) {
- sum += this.instance.getSetting('rowHeight', from);
- from++;
- }
- return sum;
-};
-
-WalkontableVerticalScrollbarNative.prototype.applyToDOM = function () {
- var headerSize = this.instance.wtViewport.getColumnHeaderHeight();
- this.fixedContainer.style.height = headerSize + this.sumCellSizes(0, this.total) + 'px';
- this.fixed.style.top = this.measureBefore + 'px';
- this.fixed.style.bottom = '';
-};
-
-WalkontableVerticalScrollbarNative.prototype.scrollTo = function (cell) {
- var newY = this.tableParentOffset + cell * this.cellSize;
- this.$scrollHandler.scrollTop(newY);
- this.onScroll(newY);
-};
-
-WalkontableVerticalScrollbarNative.prototype.readSettings = function () {
- var offset = this.instance.wtDom.offset(this.fixedContainer);
- this.tableParentOffset = offset.top;
- this.tableParentOtherOffset = offset.left;
- this.windowSize = this.$scrollHandler.height();
- this.windowScrollPosition = this.$scrollHandler.scrollTop();
- this.offset = this.instance.getSetting('offsetRow');
- this.total = this.instance.getSetting('totalRows');
-};
-
-///
-
-var WalkontableHorizontalScrollbarNative = function (instance) {
- this.instance = instance;
- this.type = 'horizontal';
- this.cellSize = 50;
- this.init();
-};
-
-WalkontableHorizontalScrollbarNative.prototype = new WalkontableScrollbarNative();
-
-WalkontableHorizontalScrollbarNative.prototype.getLastCell = function () {
- return this.instance.wtTable.getLastVisibleColumn();
-};
-
-WalkontableHorizontalScrollbarNative.prototype.getTableSize = function () {
- return this.instance.wtDom.outerWidth(this.TABLE);
-};
-
-WalkontableHorizontalScrollbarNative.prototype.applyToDOM = function () {
- this.fixedContainer.style.paddingLeft = this.measureBefore + 'px';
- this.fixedContainer.style.paddingRight = this.measureAfter + 'px';
-};
-
-WalkontableHorizontalScrollbarNative.prototype.scrollTo = function (cell) {
- this.$scrollHandler.scrollLeft(this.tableParentOffset + cell * this.cellSize);
-};
-
-WalkontableHorizontalScrollbarNative.prototype.readSettings = function () {
- var offset = this.instance.wtDom.offset(this.fixedContainer);
- this.tableParentOffset = offset.left;
- this.tableParentOtherOffset = offset.top;
- this.windowSize = this.$scrollHandler.width();
- this.windowScrollPosition = this.$scrollHandler.scrollLeft();
- this.offset = this.instance.getSetting('offsetColumn');
- this.total = this.instance.getSetting('totalColumns');
-};
function WalkontableScrollbars(instance) {
- if(instance.getSetting('scrollbarModelV') === 'native') {
- instance.update('scrollbarModelH', 'none');
- }
-
- switch (instance.getSetting('scrollbarModelV')) {
- case 'dragdealer':
- this.vertical = new WalkontableVerticalScrollbar(instance);
- break;
-
- case 'native':
- this.vertical = new WalkontableVerticalScrollbarNative(instance);
- break;
- }
-
- switch (instance.getSetting('scrollbarModelH')) {
- case 'dragdealer':
- this.horizontal = new WalkontableHorizontalScrollbar(instance);
- break;
-
- case 'native':
- this.horizontal = new WalkontableHorizontalScrollbarNative(instance);
- break;
- }
+ this.vertical = new WalkontableVerticalScrollbar(instance);
+ this.horizontal = new WalkontableHorizontalScrollbar(instance);
}
WalkontableScrollbars.prototype.destroy = function () {
@@ -8677,8 +8533,6 @@ function WalkontableSettings(instance, settings) {
//presentation mode
scrollH: 'auto', //values: scroll (always show scrollbar), auto (show scrollbar if table does not fit in the container), none (never show scrollbar)
scrollV: 'auto', //values: see above
- scrollbarModelH: 'dragdealer', //values: dragdealer, native
- scrollbarModelV: 'dragdealer', //values: dragdealer, native
stretchH: 'hybrid', //values: hybrid, all, last, none
currentRowClassName: null,
currentColumnClassName: null,
@@ -8882,10 +8736,6 @@ function WalkontableTable(instance) {
this.columnFilter = new WalkontableColumnFilter();
this.verticalRenderReverse = false;
-
- if (this.instance.getSetting('scrollbarModelV') === 'native' || this.instance.getSetting('scrollbarModelH') === 'native') {
- this.instance.isNativeScroll = true;
- }
}
WalkontableTable.prototype.refreshHiderDimensions = function () {
@@ -8894,7 +8744,7 @@ WalkontableTable.prototype.refreshHiderDimensions = function () {
var spreaderStyle = this.spreader.style;
- if ((height !== Infinity || width !== Infinity) && !this.instance.isNativeScroll) {
+ if (height !== Infinity || width !== Infinity) {
if (height === Infinity) {
height = this.instance.wtViewport.getWorkspaceActualHeight();
}
@@ -8908,13 +8758,9 @@ WalkontableTable.prototype.refreshHiderDimensions = function () {
spreaderStyle.top = '0';
spreaderStyle.left = '0';
- if (this.instance.getSetting('scrollbarModelV') === 'dragdealer') {
- spreaderStyle.height = '4000px';
- }
-
- if (this.instance.getSetting('scrollbarModelH') === 'dragdealer') {
- spreaderStyle.width = '4000px';
- }
+ // For dragdealer
+ spreaderStyle.height = '4000px';
+ spreaderStyle.width = '4000px';
if (height < 0) { //this happens with WalkontableScrollbarNative and causes "Invalid argument" error in IE8
height = 0;
@@ -8923,11 +8769,6 @@ WalkontableTable.prototype.refreshHiderDimensions = function () {
this.hiderStyle.height = height + 'px';
this.hiderStyle.width = width + 'px';
}
- else {
- spreaderStyle.position = 'relative';
- spreaderStyle.width = 'auto';
- spreaderStyle.height = 'auto';
- }
};
WalkontableTable.prototype.refreshStretching = function () {
@@ -8960,9 +8801,6 @@ WalkontableTable.prototype.refreshStretching = function () {
}
var containerHeightFn = function (cacheHeight) {
- if (that.instance.isNativeScroll) {
- return 2 * that.instance.wtViewport.getViewportHeight(cacheHeight);
- }
return that.instance.wtViewport.getViewportHeight(cacheHeight);
};
@@ -9075,10 +8913,6 @@ WalkontableTable.prototype.adjustColumns = function (TR, desiredCount) {
};
WalkontableTable.prototype.draw = function (selectionsOnly) {
- if (this.instance.isNativeScroll) {
- this.verticalRenderReverse = false; //this is only supported in dragdealer mode, not in native
- }
-
this.rowFilter.readSettings(this.instance);
this.columnFilter.readSettings(this.instance);
@@ -9180,7 +9014,6 @@ WalkontableTable.prototype._doDraw = function () {
}
if (first) {
-// if (r === 0) {
first = false;
this.adjustAvailableNodes();
@@ -9248,9 +9081,7 @@ WalkontableTable.prototype._doDraw = function () {
res = this.rowStrategy.add(r, TD, this.verticalRenderReverse);
if (res === false) {
- if (!this.instance.isNativeScroll) {
- this.rowStrategy.removeOutstanding();
- }
+ this.rowStrategy.removeOutstanding();
}
if (this.rowStrategy.isLastIncomplete()) {
@@ -9368,10 +9199,6 @@ WalkontableTable.prototype.refreshSelections = function (selectionsOnly) {
*
*/
WalkontableTable.prototype.getCell = function (coords) {
- if (this.instance.isNativeScroll) {
- return this.instance.wtTable.TBODY.querySelectorAll('[data-row="' + coords[0] + '"][data-column="' + coords[1] + '"]')[0];
- }
-
if (this.isRowBeforeViewport(coords[0])) {
return -1; //row before viewport
}
@@ -9425,21 +9252,11 @@ WalkontableTable.prototype.isColumnAfterViewport = function (c) {
};
WalkontableTable.prototype.isRowInViewport = function (r) {
- if (this.instance.isNativeScroll) {
- return !!this.instance.wtTable.TBODY.querySelectorAll('[data-row="' + r + '"]')[0];
- }
- else {
- return (!this.isRowBeforeViewport(r) && !this.isRowAfterViewport(r));
- }
+ return (!this.isRowBeforeViewport(r) && !this.isRowAfterViewport(r));
};
WalkontableTable.prototype.isColumnInViewport = function (c) {
- if (this.instance.isNativeScroll) {
- return !!this.instance.wtTable.TBODY.querySelectorAll('[data-column="' + c + '"]')[0];
- }
- else {
- return (!this.isColumnBeforeViewport(c) && !this.isColumnAfterViewport(c));
- }
+ return (!this.isColumnBeforeViewport(c) && !this.isColumnAfterViewport(c));
};
WalkontableTable.prototype.isLastRowFullyVisible = function () {
@@ -9453,22 +9270,10 @@ WalkontableTable.prototype.isLastColumnFullyVisible = function () {
function WalkontableViewport(instance) {
this.instance = instance;
this.resetSettings();
-
- if (this.instance.isNativeScroll) {
- var that = this;
- that.clientHeight = document.documentElement.clientHeight; //browser viewport height
- $(window).on('resize', function () {
- that.clientHeight = document.documentElement.clientHeight;
- });
- }
}
// Used by scrollbar
WalkontableViewport.prototype.getWorkspaceHeight = function (proposedHeight) {
- if (this.instance.isNativeScroll) {
- return this.clientHeight;
- }
-
var height = this.instance.getSetting('height');
if (height === Infinity || height === void 0 || height === null || height < 1) {
@@ -9584,10 +9389,6 @@ WalkontableViewport.prototype.resetSettings = function () {
this.columnHeaderHeight = NaN;
};
function WalkontableWheel(instance) {
- if (instance.isNativeScroll) {
- return;
- }
-
//spreader === instance.wtTable.TABLE.parentNode
$(instance.wtTable.spreader).on('mousewheel', function (event, delta, deltaX, deltaY) {
if (!deltaX && !deltaY && delta) { //we are in IE8, see https://github.com/brandonaaron/jquery-mousewheel/issues/53
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/jquery.handsontable.less b/modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.less
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/jquery.handsontable.less
rename to modules/backend/widgets/grid/assets/vendor/handsontable/jquery.handsontable.less
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/removebutton.js b/modules/backend/widgets/grid/assets/vendor/handsontable/removebutton.js
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/removebutton.js
rename to modules/backend/widgets/grid/assets/vendor/handsontable/removebutton.js
diff --git a/modules/backend/formwidgets/datagrid/assets/vendor/handsontable/rowmove.js b/modules/backend/widgets/grid/assets/vendor/handsontable/rowmove.js
similarity index 100%
rename from modules/backend/formwidgets/datagrid/assets/vendor/handsontable/rowmove.js
rename to modules/backend/widgets/grid/assets/vendor/handsontable/rowmove.js
diff --git a/modules/backend/widgets/grid/partials/_grid.htm b/modules/backend/widgets/grid/partials/_grid.htm
new file mode 100644
index 000000000..149c01f65
--- /dev/null
+++ b/modules/backend/widgets/grid/partials/_grid.htm
@@ -0,0 +1,23 @@
+
+
diff --git a/modules/backend/widgets/grid/partials/_toolbar.htm b/modules/backend/widgets/grid/partials/_toolbar.htm
new file mode 100644
index 000000000..88a64a12e
--- /dev/null
+++ b/modules/backend/widgets/grid/partials/_toolbar.htm
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/modules/cms/ServiceProvider.php b/modules/cms/ServiceProvider.php
index 28f57e5c2..2a528b8b4 100644
--- a/modules/cms/ServiceProvider.php
+++ b/modules/cms/ServiceProvider.php
@@ -4,7 +4,6 @@ use Lang;
use Backend;
use BackendMenu;
use BackendAuth;
-use System\Classes\MarkupManager;
use Backend\Classes\WidgetManager;
use October\Rain\Support\ModuleServiceProvider;
@@ -93,48 +92,6 @@ class ServiceProvider extends ModuleServiceProvider
]);
});
- /*
- * Register markup tags
- */
- MarkupManager::instance()->registerCallback(function($manager){
- $manager->registerFunctions([
- // Global helpers
- 'post' => 'post',
-
- // Form helpers
- 'form_ajax' => ['Form', 'ajax'],
- 'form_open' => ['Form', 'open'],
- 'form_close' => ['Form', 'close'],
- 'form_token' => ['Form', 'token'],
- 'form_session_key' => ['Form', 'sessionKey'],
- 'form_token' => ['Form', 'token'],
- 'form_model' => ['Form', 'model'],
- 'form_label' => ['Form', 'label'],
- 'form_text' => ['Form', 'text'],
- 'form_password' => ['Form', 'password'],
- 'form_checkbox' => ['Form', 'checkbox'],
- 'form_radio' => ['Form', 'radio'],
- 'form_file' => ['Form', 'file'],
- 'form_select' => ['Form', 'select'],
- 'form_select_range' => ['Form', 'selectRange'],
- 'form_select_month' => ['Form', 'selectMonth'],
- 'form_submit' => ['Form', 'submit'],
- 'form_macro' => ['Form', '__call'],
- 'form_value' => ['Form', 'value'],
- ]);
-
- $manager->registerFilters([
- // String helpers
- 'slug' => ['Str', 'slug'],
- 'plural' => ['Str', 'plural'],
- 'singular' => ['Str', 'singular'],
- 'finish' => ['Str', 'finish'],
- 'snake' => ['Str', 'snake'],
- 'camel' => ['Str', 'camel'],
- 'studly' => ['Str', 'studly'],
- ]);
- });
-
/*
* Register widgets
*/
diff --git a/modules/cms/classes/CombineAssets.php b/modules/cms/classes/CombineAssets.php
index f1e41418d..be749c15d 100644
--- a/modules/cms/classes/CombineAssets.php
+++ b/modules/cms/classes/CombineAssets.php
@@ -4,6 +4,7 @@ use URL;
use File;
use Lang;
use Cache;
+use Route;
use Config;
use Request;
use Response;
@@ -193,7 +194,13 @@ class CombineAssets
*/
protected function getCombinedUrl($outputFilename = 'undefined.css')
{
- return URL::action('Cms\Classes\Controller@combine', [$outputFilename], false);
+ $combineAction = 'Cms\Classes\Controller@combine';
+ $actionExists = Route::getRoutes()->getByAction($combineAction) !== null;
+
+ if ($actionExists)
+ return URL::action($combineAction, [$outputFilename], false);
+ else
+ return Request::getBasePath().'/combine/'.$outputFilename;
}
/**
diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php
index dba82c288..430934ab3 100644
--- a/modules/system/ServiceProvider.php
+++ b/modules/system/ServiceProvider.php
@@ -10,6 +10,7 @@ use BackendAuth;
use Twig_Environment;
use Twig_Loader_String;
use System\Classes\ErrorHandler;
+use System\Classes\MarkupManager;
use System\Classes\PluginManager;
use System\Classes\SettingsManager;
use System\Twig\Engine as TwigEngine;
@@ -179,6 +180,44 @@ class ServiceProvider extends ModuleServiceProvider
]);
});
+ /*
+ * Register markup tags
+ */
+ MarkupManager::instance()->registerCallback(function($manager){
+ $manager->registerFunctions([
+ // Functions
+ 'post' => 'post',
+ 'link_to' => 'link_to',
+ 'link_to_asset' => 'link_to_asset',
+ 'link_to_route' => 'link_to_route',
+ 'link_to_action' => 'link_to_action',
+ 'asset' => 'asset',
+ 'action' => 'action',
+ 'url' => 'url',
+ 'route' => 'route',
+ 'secure_url' => 'secure_url',
+ 'secure_asset' => 'secure_asset',
+
+ // Classes
+ 'str_*' => ['Str', '*'],
+ 'url_*' => ['URL', '*'],
+ 'html_*' => ['HTML', '*'],
+ 'form_*' => ['Form', '*'],
+ 'form_macro' => ['Form', '__call'],
+ ]);
+
+ $manager->registerFilters([
+ // Classes
+ 'slug' => ['Str', 'slug'],
+ 'plural' => ['Str', 'plural'],
+ 'singular' => ['Str', 'singular'],
+ 'finish' => ['Str', 'finish'],
+ 'snake' => ['Str', 'snake'],
+ 'camel' => ['Str', 'camel'],
+ 'studly' => ['Str', 'studly'],
+ ]);
+ });
+
/*
* Register settings
*/
diff --git a/modules/system/classes/MarkupManager.php b/modules/system/classes/MarkupManager.php
index 969e7886f..6a6453900 100644
--- a/modules/system/classes/MarkupManager.php
+++ b/modules/system/classes/MarkupManager.php
@@ -1,5 +1,10 @@
listExtensions(self::EXTENSION_TOKEN_PARSER);
}
+ /**
+ * Makes a set of Twig functions for use in a twig extension.
+ * @param array $functions Current collection
+ * @return array
+ */
+ public function makeTwigFunctions($functions = [])
+ {
+ if (!is_array($functions))
+ $functions = [];
+
+ foreach ($this->listFunctions() as $name => $callable) {
+
+ /*
+ * Handle a wildcard function
+ */
+ if (strpos($name, '*') !== false && $this->isWildCallable($callable)) {
+ $callable = function($name) use ($callable) {
+ $arguments = array_slice(func_get_args(), 1);
+ $method = $this->isWildCallable($callable, Str::camel($name));
+ return call_user_func_array($method, $arguments);
+ };
+ }
+
+ if (!is_callable($callable))
+ throw new ApplicationException(sprintf('The markup function for %s is not callable.', $name));
+
+ $functions[] = new Twig_SimpleFunction($name, $callable, ['is_safe' => ['html']]);
+ }
+
+ return $functions;
+ }
+
+ /**
+ * Makes a set of Twig filters for use in a twig extension.
+ * @param array $filters Current collection
+ * @return array
+ */
+ public function makeTwigFilters($filters = [])
+ {
+ if (!is_array($filters))
+ $filters = [];
+
+ foreach ($this->listFilters() as $name => $callable) {
+
+ /*
+ * Handle a wildcard function
+ */
+ if (strpos($name, '*') !== false && $this->isWildCallable($callable)) {
+ $callable = function($name) use ($callable) {
+ $arguments = array_slice(func_get_args(), 1);
+ $method = $this->isWildCallable($callable, Str::camel($name));
+ return call_user_func_array($method, $arguments);
+ };
+ }
+
+ if (!is_callable($callable))
+ throw new ApplicationException(sprintf('The markup filter for %s is not callable.', $name));
+
+ $filters[] = new Twig_SimpleFilter($name, $callable, ['is_safe' => ['html']]);
+ }
+
+ return $filters;
+ }
+
+ /**
+ * Makes a set of Twig token parsers for use in a twig extension.
+ * @param array $parsers Current collection
+ * @return array
+ */
+ public function makeTwigTokenParsers($parsers = [])
+ {
+ if (!is_array($parsers))
+ $parsers = [];
+
+ $extraParsers = $this->listTokenParsers();
+ foreach ($extraParsers as $obj) {
+ if (!$obj instanceof Twig_TokenParser)
+ continue;
+
+ $parsers[] = $obj;
+ }
+
+ return $parsers;
+ }
+
+ /**
+ * Tests if a callable type contains a wildcard, also acts as a
+ * utility to replace the wildcard with a string.
+ * @param callable $callable
+ * @param string $replaceWith
+ * @return mixed
+ */
+ protected function isWildCallable($callable, $replaceWith = false)
+ {
+ $isWild = false;
+
+ if (is_string($callable) && strpos($callable, '*') !== false)
+ $isWild = $replaceWith ? str_replace('*', $replaceWith, $callable) : true;
+
+ if (is_array($callable)) {
+ if (is_string($callable[0]) && strpos($callable[0], '*') !== false) {
+ if ($replaceWith) {
+ $isWild = $callable;
+ $isWild[0] = str_replace('*', $replaceWith, $callable[0]);
+ }
+ else
+ $isWild = true;
+ }
+
+ if (!empty($callable[1]) && strpos($callable[1], '*') !== false) {
+ if ($replaceWith) {
+ $isWild = $isWild ?: $callable;
+ $isWild[1] = str_replace('*', $replaceWith, $callable[1]);
+ }
+ else
+ $isWild = true;
+ }
+ }
+
+ return $isWild;
+ }
+
}
\ No newline at end of file
diff --git a/modules/system/twig/Extension.php b/modules/system/twig/Extension.php
index c51a8450b..71483a294 100644
--- a/modules/system/twig/Extension.php
+++ b/modules/system/twig/Extension.php
@@ -52,12 +52,7 @@ class Extension extends Twig_Extension
/*
* Include extensions provided by plugins
*/
- foreach ($this->markupManager->listFunctions() as $name => $callable) {
- if (!is_callable($callable))
- throw new ApplicationException(sprintf('The markup function for %s is not callable.', $name));
-
- $functions[] = new Twig_SimpleFunction($name, $callable, ['is_safe' => ['html']]);
- }
+ $functions = $this->markupManager->makeTwigFunctions($functions);
return $functions;
}
@@ -76,12 +71,7 @@ class Extension extends Twig_Extension
/*
* Include extensions provided by plugins
*/
- foreach ($this->markupManager->listFilters() as $name => $callable) {
- if (!is_callable($callable))
- throw new ApplicationException(sprintf('The markup filter for %s is not callable.', $name));
-
- $filters[] = new Twig_SimpleFilter($name, $callable, ['is_safe' => ['html']]);
- }
+ $filters = $this->markupManager->makeTwigFilters($filters);
return $filters;
}
@@ -95,13 +85,10 @@ class Extension extends Twig_Extension
{
$parsers = [];
- $extraParsers = $this->markupManager->listTokenParsers();
- foreach ($extraParsers as $obj) {
- if (!$obj instanceof Twig_TokenParser)
- continue;
-
- $parsers[] = $obj;
- }
+ /*
+ * Include extensions provided by plugins
+ */
+ $parsers = $this->markupManager->makeTwigTokenParsers($parsers);
return $parsers;
}
diff --git a/tests/unit/cms/classes/ControllerTest.php b/tests/unit/cms/classes/ControllerTest.php
index 4c04e7f2f..cae7f33d3 100644
--- a/tests/unit/cms/classes/ControllerTest.php
+++ b/tests/unit/cms/classes/ControllerTest.php
@@ -5,7 +5,8 @@ use Cms\Classes\Theme;
class ControllerTest extends TestCase
{
- public function tearDown() {
+ public function tearDown()
+ {
Mockery::close();
}
diff --git a/tests/unit/system/classes/MarkupManagerTest.php b/tests/unit/system/classes/MarkupManagerTest.php
new file mode 100644
index 000000000..f22c4c799
--- /dev/null
+++ b/tests/unit/system/classes/MarkupManagerTest.php
@@ -0,0 +1,111 @@
+getMethod($name);
+ $method->setAccessible(true);
+ return $method->invokeArgs($object, $params);
+ }
+
+ public static function getProtectedProperty($object, $name)
+ {
+ $className = get_class($object);
+ $class = new ReflectionClass($className);
+ $property = $class->getProperty($name);
+ $property->setAccessible(true);
+ return $property->getValue($object);
+ }
+
+ public static function setProtectedProperty($object, $name, $value)
+ {
+ $className = get_class($object);
+ $class = new ReflectionClass($className);
+ $property = $class->getProperty($name);
+ $property->setAccessible(true);
+ return $property->setValue($object, $value);
+ }
+
+ //
+ // Tests
+ //
+
+ public function testIsWildCallable()
+ {
+ $manager = MarkupManager::instance();
+
+ /*
+ * Negatives
+ */
+ $callable = 'something';
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
+ $this->assertFalse($result);
+
+ $callable = ['Form', 'open'];
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
+ $this->assertFalse($result);
+
+ $callable = function() { return 'O, Hai!'; };
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
+ $this->assertFalse($result);
+
+ /*
+ * String
+ */
+ $callable = 'something_*';
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
+ $this->assertTrue($result);
+
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable, 'delicious']);
+ $this->assertEquals('something_delicious', $result);
+
+ /*
+ * Array
+ */
+ $callable = ['Class', 'foo_*'];
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
+ $this->assertTrue($result);
+
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable, 'bar']);
+ $this->assertTrue(isset($result[0]));
+ $this->assertTrue(isset($result[1]));
+ $this->assertEquals('Class', $result[0]);
+ $this->assertEquals('foo_bar', $result[1]);
+
+ $callable = ['My*', 'method'];
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
+ $this->assertTrue($result);
+
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable, 'Class']);
+ $this->assertTrue(isset($result[0]));
+ $this->assertTrue(isset($result[1]));
+ $this->assertEquals('MyClass', $result[0]);
+ $this->assertEquals('method', $result[1]);
+
+ $callable = ['My*', 'my*'];
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable]);
+ $this->assertTrue($result);
+
+ $result = self::callProtectedMethod($manager, 'isWildCallable', [$callable, 'Food']);
+ $this->assertTrue(isset($result[0]));
+ $this->assertTrue(isset($result[1]));
+ $this->assertEquals('MyFood', $result[0]);
+ $this->assertEquals('myFood', $result[1]);
+ }
+
+}
\ No newline at end of file