From 0c1d46ed2e89dcfb940115fe0625d44fda850113 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 11 May 2022 12:55:20 -0400 Subject: [PATCH 01/11] start to merge reordercontroller into listcontroller --- modules/backend/behaviors/ListController.php | 128 ++++++++++++++++++ .../backend/behaviors/RelationController.php | 1 + modules/backend/widgets/Lists.php | 34 +++++ .../widgets/lists/assets/js/winter.list.js | 28 +++- 4 files changed, 190 insertions(+), 1 deletion(-) diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index e69326e8ef..91d685c6f0 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -145,6 +145,7 @@ public function makeList($definition = null) 'showPageNumbers', 'noRecordsMessage', 'defaultSort', + 'reorder', 'showSorting', 'showSetup', 'showCheckboxes', @@ -599,4 +600,131 @@ public static function extendListFilterScopes($callback) call_user_func_array($callback, [$widget]); }); } + + /** + * Validate the supplied form model. + * @return void + */ + protected function validateModel() + { + $model = $this->reorderGetModel(); + $modelTraits = class_uses($model); + + if ( + isset($modelTraits[\Winter\Storm\Database\Traits\Sortable::class]) || + $model->isClassExtendedWith(\Winter\Storm\Database\Behaviors\Sortable::class) || + isset($modelTraits[\October\Rain\Database\Traits\Sortable::class]) || + $model->isClassExtendedWith(\October\Rain\Database\Behaviors\Sortable::class) + ) { + $this->sortMode = 'simple'; + } + elseif ( + isset($modelTraits[\Winter\Storm\Database\Traits\NestedTree::class]) || + isset($modelTraits[\October\Rain\Database\Traits\NestedTree::class]) + ) { + $this->sortMode = 'nested'; + $this->showTree = true; + } + else { + throw new ApplicationException('The model must implement the Sortable trait/behavior or the NestedTree trait.'); + } + + return $model; + } + + public function onReorder() + { + $model = $this->validateModel(); + + /* + * Simple + */ + if ($this->sortMode == 'simple') { + if ( + (!$ids = post('record_ids')) || + (!$orders = post('sort_orders')) + ) { + return; + } + + $model->setSortableOrder($ids, $orders); + } + /* + * Nested set + */ + elseif ($this->sortMode == 'nested') { + $sourceNode = $model->find(post('sourceNode')); + $targetNode = post('targetNode') ? $model->find(post('targetNode')) : null; + + if ($sourceNode == $targetNode) { + return; + } + + switch (post('position')) { + case 'before': + $sourceNode->moveBefore($targetNode); + break; + + case 'after': + $sourceNode->moveAfter($targetNode); + break; + + case 'child': + $sourceNode->makeChildOf($targetNode); + break; + + default: + $sourceNode->makeRoot(); + break; + } + } + } + + public function reorderGetModel() + { + if ($this->model !== null) { + return $this->model; + } + + $modelClass = $this->getConfig('modelClass'); + + if (!$modelClass) { + throw new ApplicationException('Please specify the modelClass property for reordering'); + } + + return $this->model = new $modelClass; + } + + /** + * Returns all the records from the supplied model. + * @return Collection + */ + protected function getRecords() + { + $records = null; + $model = $this->controller->reorderGetModel(); + $query = $model->newQuery(); + + $this->controller->reorderExtendQuery($query); + + if ($this->sortMode == 'simple') { + $records = $query + ->orderBy($model->getSortOrderColumn()) + ->get() + ; + } + elseif ($this->sortMode == 'nested') { + $records = $query->getNested(); + } + + return $records; + } + + /** + * Returns the sort order value for a specific record. + */ + public function getRecordSortOrder($record) + { + return $record->{$record->getSortOrderColumn()}; + } } diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php index 9e0f221ed8..6388af48a2 100644 --- a/modules/backend/behaviors/RelationController.php +++ b/modules/backend/behaviors/RelationController.php @@ -818,6 +818,7 @@ protected function makeManageWidget() $config->showCheckboxes = $this->getConfig('manage[showCheckboxes]', !$isPivot); $config->showSorting = $this->getConfig('manage[showSorting]', !$isPivot); $config->defaultSort = $this->getConfig('manage[defaultSort]'); + $config->sortable = $this->getConfig('view[sortable]', false); $config->recordsPerPage = $this->getConfig('manage[recordsPerPage]'); $config->noRecordsMessage = $this->getConfig('manage[noRecordsMessage]'); diff --git a/modules/backend/widgets/Lists.php b/modules/backend/widgets/Lists.php index 0e551f54dd..2a83fade6c 100644 --- a/modules/backend/widgets/Lists.php +++ b/modules/backend/widgets/Lists.php @@ -9,6 +9,7 @@ use Carbon\Carbon; use Winter\Storm\Html\Helper as HtmlHelper; use Winter\Storm\Router\Helper as RouterHelper; +use Winter\Storm\Database\Traits\Sortable; use System\Helpers\DateTime as DateTimeHelper; use System\Classes\PluginManager; use System\Classes\MediaLibrary; @@ -74,6 +75,11 @@ class Lists extends WidgetBase */ public $showSorting = true; + /** + * @var bool Allow this list to be reordered in-place. + */ + public $reorder = false; + /** * @var mixed A default sort column to look for. */ @@ -206,6 +212,7 @@ public function init() 'showPageNumbers', 'recordsPerPage', 'perPageOptions', + 'reorder', 'showSorting', 'defaultSort', 'showCheckboxes', @@ -233,6 +240,15 @@ public function init() $this->validateModel(); $this->validateTree(); + + if ($this->reorder) { + $this->addJs('/modules/system/assets/ui/js/list.sortable.js', 'core'); + $this->showSorting = false; + $this->showTree = false; + + $this->reorderColumn = $this->model->getSortOrderColumn(); + } + } /** @@ -267,6 +283,7 @@ public function prepareVars() $this->vars['showPagination'] = $this->showPagination; $this->vars['showPageNumbers'] = $this->showPageNumbers; $this->vars['showSorting'] = $this->showSorting; + $this->vars['reorder'] = $this->reorder; $this->vars['sortColumn'] = $this->getSortColumn(); $this->vars['sortDirection'] = $this->sortDirection; $this->vars['showTree'] = $this->showTree; @@ -543,6 +560,12 @@ public function prepareQuery() $sortColumn = Str::snake($column->relation) . '_count'; } + // Fix the sort order if this list has sortable set to true. + if ($this->reorder) { + $sortColumn = $this->reorderColumn; + $this->sortDirection = 'ASC'; + } + $query->orderBy($sortColumn, $this->sortDirection); } @@ -782,6 +805,17 @@ protected function defineListColumns() throw new ApplicationException(Lang::get('backend::lang.list.missing_columns', compact('class'))); } + if ($this->reorder) { + $this->allColumns['sort_handle'] = $this->makeListColumn('sort_handle', [ + #'label' => 'backend::lang.list.sort_handle', + 'path' => '~/modules/backend/widgets/lists/partials/_list_sort_handle.htm', + 'type' => 'partial', + 'width' => '20px', + 'sortable' => false, + 'clickable' => false, + ]); + } + $this->addColumns($this->columns); /** diff --git a/modules/backend/widgets/lists/assets/js/winter.list.js b/modules/backend/widgets/lists/assets/js/winter.list.js index c2bb07baa0..609feb815d 100644 --- a/modules/backend/widgets/lists/assets/js/winter.list.js +++ b/modules/backend/widgets/lists/assets/js/winter.list.js @@ -22,6 +22,17 @@ dragSelector: 'thead' }) + if (element.dataset.hasOwnProperty('sortable')) { + this.$el.find('.control-list-tbody').listSortable({ + handle: '.drag-handle' + }) + this.$el.on('dragged.list.sorted', $.proxy(this.processReorder, this)) + + this.$el.find('[data-record-sort-order]').each(function (index, el) { + this.sortOrders.push(el.dataset.recordSortOrder) + }.bind(this)) + } + this.update() } @@ -86,6 +97,21 @@ $checkbox.prop('checked', !$checkbox.is(':checked')).trigger('change') } + ListWidget.prototype.processReorder = function() { + var relation = this.$el.data('sortableRelation') + var handler = relation ? 'onReorderRelation' : 'onReorder' + + var recordIds = [] + this.$el.find('[data-record-id]').each(function (index, el) { + recordIds.push(el.dataset.recordId) + }.bind(this)) + + this.$el.request(handler, { + data: { sort_orders: this.sortOrders, record_ids: recordIds, _reorder_relation_name: relation }, + loading: $.wn.stripeLoadIndicator, + }) + } + // LIST WIDGET PLUGIN DEFINITION // ============================ @@ -143,4 +169,4 @@ $('[data-control="listwidget"]').listWidget(); }) -}(window.jQuery); \ No newline at end of file +}(window.jQuery); From ea52923f9bc93ad49072644b80218e88fcd3c931 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 11 May 2022 12:56:02 -0400 Subject: [PATCH 02/11] add sort_handle partial --- .../backend/widgets/lists/partials/_list_sort_handle.htm | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 modules/backend/widgets/lists/partials/_list_sort_handle.htm diff --git a/modules/backend/widgets/lists/partials/_list_sort_handle.htm b/modules/backend/widgets/lists/partials/_list_sort_handle.htm new file mode 100644 index 0000000000..1d6cb51f26 --- /dev/null +++ b/modules/backend/widgets/lists/partials/_list_sort_handle.htm @@ -0,0 +1,7 @@ +
+ ☰ +
From a7f4c805efe768fc939f83e20a504bd591127384 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 11 May 2022 20:16:38 -0400 Subject: [PATCH 03/11] second pass at merging ReorderController into ListController --- modules/backend/behaviors/ListController.php | 18 ++++++-- modules/backend/widgets/Lists.php | 13 +++--- .../widgets/lists/assets/js/winter.list.js | 8 ++-- .../backend/widgets/lists/partials/_list.php | 7 +++- .../widgets/lists/partials/_list_body_row.php | 2 +- modules/system/assets/ui/js/list.sortable.js | 41 ++++++++++++------- modules/system/assets/ui/less/list.base.less | 20 +++++++++ modules/system/assets/ui/less/list.less | 11 ++++- modules/system/assets/ui/storm-min.js | 35 +++++++++------- modules/system/assets/ui/storm.css | 3 ++ 10 files changed, 108 insertions(+), 50 deletions(-) diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index 91d685c6f0..a5345a84a8 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -678,13 +678,15 @@ public function onReorder() break; } } + + return $this->listRefresh(); } public function reorderGetModel() { - if ($this->model !== null) { - return $this->model; - } + #if ($this->model !== null) { + # return $this->model; + #} $modelClass = $this->getConfig('modelClass'); @@ -695,6 +697,16 @@ public function reorderGetModel() return $this->model = new $modelClass; } + /** + * Extend the query used for finding reorder records. Extra conditions + * can be applied to the query, for example, $query->withTrashed(); + * @param Winter\Storm\Database\Builder $query + * @return void + */ + public function reorderExtendQuery($query) + { + } + /** * Returns all the records from the supplied model. * @return Collection diff --git a/modules/backend/widgets/Lists.php b/modules/backend/widgets/Lists.php index 2a83fade6c..591e6cf649 100644 --- a/modules/backend/widgets/Lists.php +++ b/modules/backend/widgets/Lists.php @@ -560,12 +560,6 @@ public function prepareQuery() $sortColumn = Str::snake($column->relation) . '_count'; } - // Fix the sort order if this list has sortable set to true. - if ($this->reorder) { - $sortColumn = $this->reorderColumn; - $this->sortDirection = 'ASC'; - } - $query->orderBy($sortColumn, $this->sortDirection); } @@ -807,7 +801,7 @@ protected function defineListColumns() if ($this->reorder) { $this->allColumns['sort_handle'] = $this->makeListColumn('sort_handle', [ - #'label' => 'backend::lang.list.sort_handle', + 'label' => '', 'path' => '~/modules/backend/widgets/lists/partials/_list_sort_handle.htm', 'type' => 'partial', 'width' => '20px', @@ -1613,6 +1607,11 @@ public function onSort() */ public function getSortColumn() { + if ($this->reorder) { + $sortColumn = $this->reorderColumn; + $this->sortDirection = 'ASC'; + } + if (!$this->isSortable()) { return false; } diff --git a/modules/backend/widgets/lists/assets/js/winter.list.js b/modules/backend/widgets/lists/assets/js/winter.list.js index 609feb815d..dcee306c7d 100644 --- a/modules/backend/widgets/lists/assets/js/winter.list.js +++ b/modules/backend/widgets/lists/assets/js/winter.list.js @@ -11,6 +11,7 @@ var $el = this.$el = $(element); this.options = options || {}; + this.sortOrders = [] var scrollClassContainer = options.scrollClassContainer !== undefined ? options.scrollClassContainer @@ -98,16 +99,13 @@ } ListWidget.prototype.processReorder = function() { - var relation = this.$el.data('sortableRelation') - var handler = relation ? 'onReorderRelation' : 'onReorder' - var recordIds = [] this.$el.find('[data-record-id]').each(function (index, el) { recordIds.push(el.dataset.recordId) }.bind(this)) - this.$el.request(handler, { - data: { sort_orders: this.sortOrders, record_ids: recordIds, _reorder_relation_name: relation }, + this.$el.request('onReorder', { + data: { sort_orders: this.sortOrders, record_ids: recordIds }, loading: $.wn.stripeLoadIndicator, }) } diff --git a/modules/backend/widgets/lists/partials/_list.php b/modules/backend/widgets/lists/partials/_list.php index 5b3e50e656..2c463e7e02 100644 --- a/modules/backend/widgets/lists/partials/_list.php +++ b/modules/backend/widgets/lists/partials/_list.php @@ -1,9 +1,12 @@ -
+
+> makePartial('list_head_row') ?> - + makePartial('list_body_rows') ?> diff --git a/modules/backend/widgets/lists/partials/_list_body_row.php b/modules/backend/widgets/lists/partials/_list_body_row.php index 8341f1c4ff..4eadeeb120 100644 --- a/modules/backend/widgets/lists/partials/_list_body_row.php +++ b/modules/backend/widgets/lists/partials/_list_body_row.php @@ -3,7 +3,7 @@ $childRecords = $showTree ? $record->getChildren() : null; $treeLevelClass = $showTree ? 'list-tree-level-'.$treeLevel : ''; ?> - + > makePartial('list_body_checkbox', ['record' => $record]) ?> diff --git a/modules/system/assets/ui/js/list.sortable.js b/modules/system/assets/ui/js/list.sortable.js index 2c92b92b03..3a746fac65 100644 --- a/modules/system/assets/ui/js/list.sortable.js +++ b/modules/system/assets/ui/js/list.sortable.js @@ -38,6 +38,7 @@ * * Events: * - dragged.list.sortable - triggered on a list element after it was moved + * - dragged.list.sorted - triggered on a list after the drag action finished */ +function ($) { "use strict"; @@ -86,23 +87,23 @@ ListSortable.prototype.registerListHandlers = function(list) { var $list = $(list) - $list.on('dragstart', '> li', this.proxy(this.onDragStart)) - $list.on('dragover', '> li', this.proxy(this.onDragOver)) - $list.on('dragenter', '> li', this.proxy(this.onDragEnter)) - $list.on('dragleave', '> li', this.proxy(this.onDragLeave)) - $list.on('drop', '> li', this.proxy(this.onDragDrop)) - $list.on('dragend', '> li', this.proxy(this.onDragEnd)) + $list.on('dragstart', '> *', this.proxy(this.onDragStart)) + $list.on('dragover', '> *', this.proxy(this.onDragOver)) + $list.on('dragenter', '> *', this.proxy(this.onDragEnter)) + $list.on('dragleave', '> *', this.proxy(this.onDragLeave)) + $list.on('drop', '> *', this.proxy(this.onDragDrop)) + $list.on('dragend', '> *', this.proxy(this.onDragEnd)) } ListSortable.prototype.unregisterListHandlers = function(list) { var $list = $(list) - $list.off('dragstart', '> li', this.proxy(this.onDragStart)) - $list.off('dragover', '> li', this.proxy(this.onDragOver)) - $list.off('dragenter', '> li', this.proxy(this.onDragEnter)) - $list.off('dragleave', '> li', this.proxy(this.onDragLeave)) - $list.off('drop', '> li', this.proxy(this.onDragDrop)) - $list.off('dragend', '> li', this.proxy(this.onDragEnd)) + $list.off('dragstart', '> *', this.proxy(this.onDragStart)) + $list.off('dragover', '> *', this.proxy(this.onDragOver)) + $list.off('dragenter', '> *', this.proxy(this.onDragEnter)) + $list.off('dragleave', '> *', this.proxy(this.onDragLeave)) + $list.off('drop', '> *', this.proxy(this.onDragDrop)) + $list.off('dragend', '> *', this.proxy(this.onDragEnd)) } ListSortable.prototype.unregisterHandlers = function() { @@ -168,7 +169,6 @@ } elementsIdCounter++ - var elementId = elementsIdCounter element.setAttribute('data-list-sortable-element-id', elementsIdCounter) @@ -293,7 +293,7 @@ var current = element while (current) { - if (current.tagName === 'LI' && current.hasAttribute('draggable') ) { + if (current.hasAttribute('draggable')) { return current } @@ -315,6 +315,11 @@ ev.originalEvent.dataTransfer.setData('listsortable/elementid', this.getElementSortableId(ev.target)) ev.originalEvent.dataTransfer.setData(this.listSortableId, this.listSortableId) + // Make sure the sort placeholder is never cut off by any hidden overflow. + var container = $(ev.target).closest('[data-sortable]') + this.originalOverflow = container.css('overflow') + container.css({overflow: 'visible'}) + // The mousemove handler is used to remove the placeholder // when the drag is canceled with Escape button. We can't use // the dragend for removing the placeholders because dragend @@ -395,6 +400,12 @@ ListSortable.prototype.onDragEnd = function(ev) { $(document).off('dragover', this.proxy(this.onDocumentDragOver)) + + var container = $(ev.target).closest('[data-sortable]') + if (container) { + container.trigger('dragged.list.sorted') + container.css({overflow: this.originalOverflow}) + } } ListSortable.prototype.onDocumentDragOver = function(ev) { @@ -461,4 +472,4 @@ $('[data-control=list-sortable]').listSortable() }) -}(window.jQuery); \ No newline at end of file +}(window.jQuery); diff --git a/modules/system/assets/ui/less/list.base.less b/modules/system/assets/ui/less/list.base.less index 54b2f3ef48..813574b760 100644 --- a/modules/system/assets/ui/less/list.base.less +++ b/modules/system/assets/ui/less/list.base.less @@ -56,6 +56,26 @@ th { .table { background-color: @body-bg; } + + .list-sortable-placeholder { + display: block; + position: relative; + height: 0; + &:before { + display: block; + position: absolute; + .icon(@chevron-right); + font-size: 15px; + color: #d35714; + left: 0; + top: -9px; + z-index: 2000; + } + } + + .drag-handle { + cursor: move; + } } diff --git a/modules/system/assets/ui/less/list.less b/modules/system/assets/ui/less/list.less index fa64bcfa48..db63539a4c 100644 --- a/modules/system/assets/ui/less/list.less +++ b/modules/system/assets/ui/less/list.less @@ -3,6 +3,7 @@ // -------------------------------------------------- @import "global.less"; +@import "icon.less"; // // Lists @@ -88,7 +89,7 @@ table.table.data { } tbody { - tr:nth-child(even) { + tr:nth-of-type(even) { td, th { background-color: @color-list-accent; } } td, th { @@ -127,6 +128,12 @@ table.table.data { } } + // Prevent a border-top on the first row entry if the placeholder + // is placed right above it at the start of a table. + .list-sortable-placeholder:first-child + tr td { + border-top: 0; + } + tr:first-child { th, td { border-top-width: 0; @@ -559,7 +566,7 @@ table.table.data { } tbody { - tr:nth-child(even) { + tr:nth-of-type(even) { td, th { background-color: transparent; } } } diff --git a/modules/system/assets/ui/storm-min.js b/modules/system/assets/ui/storm-min.js index 6b6e09b1e1..a2549fbfb2 100644 --- a/modules/system/assets/ui/storm-min.js +++ b/modules/system/assets/ui/storm-min.js @@ -4684,19 +4684,19 @@ ListSortable.prototype.addList=function(list){this.lists.push(list) this.registerListHandlers(list) if(this.lists.length==1){$(list).one('dispose-control',this.proxy(this.dispose))}} ListSortable.prototype.registerListHandlers=function(list){var $list=$(list) -$list.on('dragstart','> li',this.proxy(this.onDragStart)) -$list.on('dragover','> li',this.proxy(this.onDragOver)) -$list.on('dragenter','> li',this.proxy(this.onDragEnter)) -$list.on('dragleave','> li',this.proxy(this.onDragLeave)) -$list.on('drop','> li',this.proxy(this.onDragDrop)) -$list.on('dragend','> li',this.proxy(this.onDragEnd))} +$list.on('dragstart','> *',this.proxy(this.onDragStart)) +$list.on('dragover','> *',this.proxy(this.onDragOver)) +$list.on('dragenter','> *',this.proxy(this.onDragEnter)) +$list.on('dragleave','> *',this.proxy(this.onDragLeave)) +$list.on('drop','> *',this.proxy(this.onDragDrop)) +$list.on('dragend','> *',this.proxy(this.onDragEnd))} ListSortable.prototype.unregisterListHandlers=function(list){var $list=$(list) -$list.off('dragstart','> li',this.proxy(this.onDragStart)) -$list.off('dragover','> li',this.proxy(this.onDragOver)) -$list.off('dragenter','> li',this.proxy(this.onDragEnter)) -$list.off('dragleave','> li',this.proxy(this.onDragLeave)) -$list.off('drop','> li',this.proxy(this.onDragDrop)) -$list.off('dragend','> li',this.proxy(this.onDragEnd))} +$list.off('dragstart','> *',this.proxy(this.onDragStart)) +$list.off('dragover','> *',this.proxy(this.onDragOver)) +$list.off('dragenter','> *',this.proxy(this.onDragEnter)) +$list.off('dragleave','> *',this.proxy(this.onDragLeave)) +$list.off('drop','> *',this.proxy(this.onDragDrop)) +$list.off('dragend','> *',this.proxy(this.onDragEnd))} ListSortable.prototype.unregisterHandlers=function(){$(document).off('dragover',this.proxy(this.onDocumentDragOver)) $(document).off('mousemove',this.proxy(this.onDocumentMouseMove)) $(this.lists[0]).off('dispose-control',this.proxy(this.dispose))} @@ -4712,7 +4712,6 @@ ListSortable.prototype.elementBelongsToManagedList=function(element){for(var i=t ListSortable.prototype.isDragStartAllowed=function(element){return true} ListSortable.prototype.elementIsPlaceholder=function(element){return element.getAttribute('class')==='list-sortable-placeholder'} ListSortable.prototype.getElementSortableId=function(element){if(element.hasAttribute('data-list-sortable-element-id')){return element.getAttribute('data-list-sortable-element-id')}elementsIdCounter++ -var elementId=elementsIdCounter element.setAttribute('data-list-sortable-element-id',elementsIdCounter) return elementsIdCounter} ListSortable.prototype.dataTransferContains=function(ev,element){if(ev.dataTransfer.types.indexOf!==undefined){return ev.dataTransfer.types.indexOf(element)>=0}return ev.dataTransfer.types.contains(element)} @@ -4741,10 +4740,13 @@ return true}return false} ListSortable.prototype.mouseOutsideLists=function(ev){var mousePosition=$.wn.foundation.event.pageCoordinates(ev) for(var i=this.lists.length-1;i>=0;i--){if($.wn.foundation.element.elementContainsPoint(this.lists[i],mousePosition)){return false}}return true} ListSortable.prototype.getClosestDraggableParent=function(element){var current=element -while(current){if(current.tagName==='LI'&¤t.hasAttribute('draggable')){return current}current=current.parentNode}return null} +while(current){if(current.hasAttribute('draggable')){return current}current=current.parentNode}return null} ListSortable.prototype.onDragStart=function(ev){if(!this.isDragStartAllowed(ev.target)){return}ev.originalEvent.dataTransfer.effectAllowed='move' ev.originalEvent.dataTransfer.setData('listsortable/elementid',this.getElementSortableId(ev.target)) ev.originalEvent.dataTransfer.setData(this.listSortableId,this.listSortableId) +var container=$(ev.target).closest('[data-sortable]') +this.originalOverflow=container.css('overflow') +container.css({overflow:'visible'}) $(document).on('mousemove',this.proxy(this.onDocumentMouseMove)) $(document).on('dragover',this.proxy(this.onDocumentDragOver))} ListSortable.prototype.onDragOver=function(ev){if(!this.isSourceManagedList(ev.originalEvent)){return}var draggable=this.getClosestDraggableParent(ev.target) @@ -4760,7 +4762,10 @@ ev.preventDefault()} ListSortable.prototype.onDragDrop=function(ev){if(!this.isSourceManagedList(ev.originalEvent)){return}var draggable=this.getClosestDraggableParent(ev.target) if(!draggable){return}this.moveElement(draggable,ev.originalEvent) this.removePlaceholders()} -ListSortable.prototype.onDragEnd=function(ev){$(document).off('dragover',this.proxy(this.onDocumentDragOver))} +ListSortable.prototype.onDragEnd=function(ev){$(document).off('dragover',this.proxy(this.onDocumentDragOver)) +var container=$(ev.target).closest('[data-sortable]') +if(container){container.trigger('dragged.list.sorted') +container.css({overflow:this.originalOverflow})}} ListSortable.prototype.onDocumentDragOver=function(ev){if(!this.isSourceManagedList(ev.originalEvent)){return}if(this.mouseOutsideLists(ev.originalEvent)){this.removePlaceholders() return}} ListSortable.prototype.onDocumentMouseMove=function(ev){$(document).off('mousemove',this.proxy(this.onDocumentMouseMove)) diff --git a/modules/system/assets/ui/storm.css b/modules/system/assets/ui/storm.css index b42de87255..86dffcf1f5 100644 --- a/modules/system/assets/ui/storm.css +++ b/modules/system/assets/ui/storm.css @@ -3011,6 +3011,9 @@ th{text-align:left} .table>thead:first-child>tr:first-child>td{border-top:0} .table>tbody + tbody{border-top:2px solid #ddd} .table .table{background-color:#f9f9f9} +.table .list-sortable-placeholder{display:block;position:relative;height:0} +.table .list-sortable-placeholder:before{display:block;position:absolute;font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f054";font-size:15px;color:#d35714;left:0;top:-9px;z-index:2000} +.table .drag-handle{cursor:move} .table-condensed>thead>tr>th, .table-condensed>tbody>tr>th, .table-condensed>tfoot>tr>th, From ac8e88cd16cfa7330afbf1c803415e62f8c50fc0 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 11 May 2022 21:01:32 -0400 Subject: [PATCH 04/11] recompile less asset files --- modules/system/assets/ui/storm.css | 90 ++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 4 deletions(-) diff --git a/modules/system/assets/ui/storm.css b/modules/system/assets/ui/storm.css index 86dffcf1f5..3c92b20fc0 100644 --- a/modules/system/assets/ui/storm.css +++ b/modules/system/assets/ui/storm.css @@ -2989,6 +2989,87 @@ body.slim-container .form-buttons{padding:0 20px 20px} [data-control=toolbar] .select2-container .select2-selection__rendered{line-height:17px} [data-control=toolbar] .select2-container .select2-selection--single{height:36px} [data-control=toolbar] select.form-control.custom-select{display:none} +@font-face{font-family:'Font Awesome 6 Free';font-style:normal;font-weight:400;font-display:block;src:url('font/fa-regular-400.woff2?v=2.0.0') format('woff2'),url('font/fa-regular-400.ttf?v=2.0.0') format('truetype')} +@font-face{font-family:'Font Awesome 6 Free';font-style:normal;font-weight:900;font-display:block;src:url('font/fa-solid-900.woff2?v=2.0.0') format('woff2'),url('font/fa-solid-900.ttf?v=2.0.0') format('truetype')} +@font-face{font-family:'Font Awesome 6 Brands';font-style:normal;font-weight:400;font-display:block;src:url('font/fa-brands-400.woff2?v=2.0.0') format('woff2'),url('font/fa-brands-400.ttf?v=2.0.0') format('truetype')} +[class^="icon-"], +[class*=" icon-"]{font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0% 0%;background-repeat:repeat;margin-top:0} +[class^="icon-"]::before, +[class*=" icon-"]::before{text-decoration:inherit;display:inline-block} +[class^="icon-"].icon-border, +[class*=" icon-"].icon-border{border-color:#eee;border-radius:0.1em;border-style:solid;border-width:0.08em;padding:0.2em 0.25em 0.15em} +[class^="icon-"].pull-left, +[class*=" icon-"].pull-left{margin-right:.3em} +[class^="icon-"].pull-right, +[class*=" icon-"].pull-right{margin-left:.3em} +.far, +.icon-regular, +.wn-icon-regular, +.oc-icon-regular{font-family:'Font Awesome 6 Free';font-weight:400} +.fad, +.icon-solid, +.wn-icon-solid, +.oc-icon-solid{font-family:'Font Awesome 6 Free';font-weight:900} +.fab, +.icon-brands, +.wn-icon-brands, +.oc-icon-brands{font-family:'Font Awesome 6 Brands';font-weight:400} +[class^="wn-icon-"]:before, +[class*=" wn-icon-"]:before, +[class^="oc-icon-"]:before, +[class*=" oc-icon-"]:before{display:inline-block;margin-right:8px;font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;vertical-align:baseline} +[class^="wn-icon-"].empty:before, +[class*=" wn-icon-"].empty:before, +[class^="oc-icon-"].empty:before, +[class*=" oc-icon-"].empty:before{margin-right:0} +.icon-1x{font-size:1em} +.icon-2x{font-size:2em} +.icon-3x{font-size:3em} +.icon-4x{font-size:4em} +.icon-5x{font-size:5em} +.icon-6x{font-size:6em} +.icon-7x{font-size:7em} +.icon-8x{font-size:8em} +.icon-9x{font-size:9em} +.icon-10x{font-size:10em} +.icon-2xs{font-size:0.625em;line-height:0.1em;vertical-align:0.225em} +.icon-xs{font-size:0.75em;line-height:0.08333333em;vertical-align:0.125em} +.icon-sm{font-size:0.875em;line-height:0.07142857em;vertical-align:0.05357143em} +.icon-lg{font-size:1.25em;line-height:0.05em;vertical-align:-0.075em} +.icon-xl{font-size:1.5em;line-height:0.04166667em;vertical-align:-0.125em} +.icon-2xl{font-size:2em;line-height:0.03125em;vertical-align:-0.1875em} +.icon-ul{list-style-type:none;margin-left:2.5em;padding-left:0} +.icon-ul>li{position:relative} +.icon-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit} +.icon-fw{text-align:center;width:1.25em} +.icon-beat{animation-name:icon-beat;animation-delay:var(--icon-animation-delay,0);animation-direction:var(--icon-animation-direction,normal);animation-duration:var(--icon-animation-duration,1s);animation-iteration-count:var(--icon-animation-iteration-count,infinite);animation-timing-function:var(--icon-animation-timing,ease-in-out)} +.icon-bounce{animation-name:icon-beat;animation-delay:var(--icon-animation-delay,0);animation-direction:var(--icon-animation-direction,normal);animation-duration:var(--icon-animation-duration,1s);animation-iteration-count:var(--icon-animation-iteration-count,infinite);animation-timing-function:var(--icon-animation-timing,cubic-bezier(0.280,0.840,0.420,1))} +.icon-fade{animation-name:icon-fade;animation-delay:var(--icon-animation-delay,0);animation-direction:var(--icon-animation-direction,normal);animation-duration:var(--icon-animation-duration,1s);animation-iteration-count:var(--icon-animation-iteration-count,infinite);animation-timing-function:var(--icon-animation-timing,cubic-bezier(.4,0,.6,1))} +.icon-beat-fade{animation-name:icon-beat-fade;animation-delay:var(--icon-animation-delay,0);animation-direction:var(--icon-animation-direction,normal);animation-duration:var(--icon-animation-duration,1s);animation-iteration-count:var(--icon-animation-iteration-count,infinite);animation-timing-function:var(--icon-animation-timing,cubic-bezier(.4,0,.6,1))} +.icon-flip{animation-name:icon-flip;animation-delay:var(--icon-animation-delay,0);animation-direction:var(--icon-animation-direction,normal);animation-duration:var(--icon-animation-duration,1s);animation-iteration-count:var(--icon-animation-iteration-count,infinite);animation-timing-function:var(--icon-animation-timing,ease-in-out)} +.icon-shake{animation-name:icon-shake;animation-delay:var(--icon-animation-delay,0);animation-direction:var(--icon-animation-direction,normal);animation-duration:var(--icon-animation-duration,1s);animation-iteration-count:var(--icon-animation-iteration-count,infinite);animation-timing-function:var(--icon-animation-timing,ease-in-out)} +.icon-spin{animation-name:icon-spin;animation-delay:var(--icon-animation-delay,0);animation-direction:var(--icon-animation-direction,normal);animation-duration:var(--icon-animation-duration,2s);animation-iteration-count:var(--icon-animation-iteration-count,infinite);animation-timing-function:var(--icon-animation-timing,linear)} +.icon-spin-reverse{--icon-animation-direction:reverse} +@media (prefers-reduced-motion:reduce){.icon-beat,.icon-bounce,.icon-fade,.icon-beat-fade,.icon-flip,.icon-pulse,.icon-shake,.icon-spin,.icon-spin-pulse{animation-delay:-1ms;animation-duration:1ms;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}} +@keyframes icon-beat{0%,90%{transform:scale(1)}45%{transform:scale(var(--icon-beat-scale,1.25))}} +@keyframes icon-bounce{0%{transform:scale(1,1) translateY(0)}10%{transform:scale(var(--#{$fa-css-prefix}-bounce-start-scale-x,1.1),var(--#{$fa-css-prefix}-bounce-start-scale-y,0.9)) translateY(0)}30%{transform:scale(var(--#{$fa-css-prefix}-bounce-jump-scale-x,0.9),var(--#{$fa-css-prefix}-bounce-jump-scale-y,1.1)) translateY(var(--#{$fa-css-prefix}-bounce-height,-0.5em))}50%{transform:scale(var(--#{$fa-css-prefix}-bounce-land-scale-x,1.05),var(--#{$fa-css-prefix}-bounce-land-scale-y,0.95)) translateY(0)}57%{transform:scale(1,1) translateY(var(--#{$fa-css-prefix}-bounce-rebound,-0.125em))}64%{transform:scale(1,1) translateY(0)}100%{transform:scale(1,1) translateY(0)}} +@keyframes icon-fade{50%{opacity:var(--icon-fade-opacity,0.4)}} +@keyframes icon-beat-fade{0%,100%{opacity:var(--icon-beat-fade-opacity,0.4);transform:scale(1)}50%{opacity:1;transform:scale(var(--icon-beat-fade-scale,1.125))}} +@keyframes icon-flip{50%{transform:rotate3d(var(--icon-flip-x,0),var(--icon-flip-y,1),var(--icon-flip-z,0),var(--icon-flip-angle,-180deg))}} +@keyframes icon-shake{0%{transform:rotate(-15deg)}4%{transform:rotate(15deg)}8%,24%{transform:rotate(-18deg)}12%,28%{transform:rotate(18deg)}16%{transform:rotate(-22deg)}20%{transform:rotate(22deg)}32%{transform:rotate(-12deg)}36%{transform:rotate(12deg)}40%,100%{transform:rotate(0deg)}} +@keyframes icon-spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}} +.icon-rotate-90{transform:rotate(90deg)} +.icon-rotate-180{transform:rotate(180deg)} +.icon-rotate-270{transform:rotate(270deg)} +.icon-flip-horizontal{transform:scale(-1,1)} +.icon-flip-vertical{transform:scale(1,-1)} +.icon-flip-both, +.icon-flip-horizontal.icon-flip-vertical{transform:scale(-1,-1)} +.icon-rotate-by{transform:rotate(var(--icon-rotate-angle,none))} +.sr-only, +.icon-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0} +.sr-only-focusable:not(:focus), +.icon-sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0} tr.rowlink:not(.nolink) td{cursor:pointer} tr.rowlink:not(.nolink) td.nolink{cursor:auto} .table tbody tr.rowlink:hover td{background-color:#f5f5f5} @@ -3155,8 +3236,8 @@ table.table.data thead th.active>a:after{color:#c63e26;opacity:1 !important;filt table.table.data thead tr th:first-child{padding-left:10px} table.table.data thead tr th:last-child a{padding-right:25px} table.table.data thead .list-checkbox .custom-checkbox{top:-16px} -table.table.data tbody tr:nth-child(even) td, -table.table.data tbody tr:nth-child(even) th{background-color:#ecf0f1} +table.table.data tbody tr:nth-of-type(even) td, +table.table.data tbody tr:nth-of-type(even) th{background-color:#ecf0f1} table.table.data tbody td, table.table.data tbody th{padding:12px 15px;color:#666;border-top:2px solid white} table.table.data tbody td a:not(.btn), @@ -3169,6 +3250,7 @@ table.table.data tbody td div.progress div.bar, table.table.data tbody th div.progress div.bar{position:absolute;left:-15px;top:-11px;bottom:-11px;background:#0181b9;opacity:0.3;filter:alpha(opacity=30)} table.table.data tbody td div.progress a, table.table.data tbody th div.progress a{position:relative} +table.table.data tbody .list-sortable-placeholder:first-child + tr td{border-top:0} table.table.data tbody tr:first-child th, table.table.data tbody tr:first-child td{border-top-width:0} table.table.data tbody tr:last-child th, @@ -3314,8 +3396,8 @@ table.table.data tr.list-tree-level-10 td.list-cell-index-1{padding-left:115px} .report-widget .table-container{margin:-15px} .report-widget .table-container table.table.data{margin-bottom:0} .report-widget .table-container table.table.data thead tr th{border-top:none !important} -.report-widget .table-container table.table.data tbody tr:nth-child(even) td, -.report-widget .table-container table.table.data tbody tr:nth-child(even) th{background-color:transparent} +.report-widget .table-container table.table.data tbody tr:nth-of-type(even) td, +.report-widget .table-container table.table.data tbody tr:nth-of-type(even) th{background-color:transparent} .list-scrollable-container{touch-action:auto;position:relative} .list-scrollable-container:after, .list-scrollable-container:before{display:none;position:absolute;top:50%;margin-top:-7px;height:9px;font-size:10px;color:#666} From 2a874e3ad3c91b7f461bdd816df79ce8db1e3b86 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 11 May 2022 21:22:22 -0400 Subject: [PATCH 05/11] force sort_order column ordering --- modules/backend/widgets/Lists.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/modules/backend/widgets/Lists.php b/modules/backend/widgets/Lists.php index 591e6cf649..bb4ebb4fc3 100644 --- a/modules/backend/widgets/Lists.php +++ b/modules/backend/widgets/Lists.php @@ -245,10 +245,7 @@ public function init() $this->addJs('/modules/system/assets/ui/js/list.sortable.js', 'core'); $this->showSorting = false; $this->showTree = false; - - $this->reorderColumn = $this->model->getSortOrderColumn(); } - } /** @@ -560,6 +557,11 @@ public function prepareQuery() $sortColumn = Str::snake($column->relation) . '_count'; } + if ($this->reorder) { + $sortColumn = $this->model->getSortOrderColumn(); + $this->sortDirection = 'ASC'; + } + $query->orderBy($sortColumn, $this->sortDirection); } @@ -1607,11 +1609,6 @@ public function onSort() */ public function getSortColumn() { - if ($this->reorder) { - $sortColumn = $this->reorderColumn; - $this->sortDirection = 'ASC'; - } - if (!$this->isSortable()) { return false; } From e115b7a2584bf890f5ffd0ed27eb95f8668ed91a Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 11 May 2022 21:28:39 -0400 Subject: [PATCH 06/11] fix space at EOL --- modules/backend/behaviors/ListController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index a5345a84a8..d805298f6d 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -732,11 +732,11 @@ protected function getRecords() return $records; } - /** + /** * Returns the sort order value for a specific record. - */ + */ public function getRecordSortOrder($record) - { + { return $record->{$record->getSortOrderColumn()}; - } + } } From 0a3d57dc9aaeca9292bf36b76f7c670cd215bdb5 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Thu, 12 May 2022 12:37:31 -0400 Subject: [PATCH 07/11] support multiple list configs; improve reorder handle --- modules/backend/behaviors/ListController.php | 32 +++++++++---------- modules/backend/widgets/Lists.php | 19 ++++------- .../widgets/lists/assets/js/winter.list.js | 3 +- .../lists/partials/_list-container.php | 2 +- .../backend/widgets/lists/partials/_list.php | 1 + .../lists/partials/_list_body_handle.php | 7 ++++ .../widgets/lists/partials/_list_body_row.php | 7 +++- .../widgets/lists/partials/_list_head_row.php | 6 ++++ .../lists/partials/_list_sort_handle.htm | 7 ---- 9 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 modules/backend/widgets/lists/partials/_list_body_handle.php delete mode 100644 modules/backend/widgets/lists/partials/_list_sort_handle.htm diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index d805298f6d..a137a315ce 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -160,6 +160,10 @@ public function makeList($definition = null) } } + if (isset($listConfig->reorder) && $listConfig->reorder) { + $columnConfig->definition = $definition; + } + /* * List Widget with extensibility */ @@ -605,9 +609,9 @@ public static function extendListFilterScopes($callback) * Validate the supplied form model. * @return void */ - protected function validateModel() + protected function validateModel($definition=null) { - $model = $this->reorderGetModel(); + $model = $this->controller->reorderGetModel($definition); $modelTraits = class_uses($model); if ( @@ -634,7 +638,8 @@ protected function validateModel() public function onReorder() { - $model = $this->validateModel(); + $definition = post('definition', $this->primaryDefinition); + $model = $this->validateModel($definition); /* * Simple @@ -679,22 +684,14 @@ public function onReorder() } } - return $this->listRefresh(); + return $this->listRefresh($definition); } - public function reorderGetModel() + public function reorderGetModel($definition=null) { - #if ($this->model !== null) { - # return $this->model; - #} - - $modelClass = $this->getConfig('modelClass'); - - if (!$modelClass) { - throw new ApplicationException('Please specify the modelClass property for reordering'); - } - - return $this->model = new $modelClass; + $listConfig = $this->controller->listGetConfig($definition); + $class = $listConfig->modelClass; + return new $class; } /** @@ -714,7 +711,8 @@ public function reorderExtendQuery($query) protected function getRecords() { $records = null; - $model = $this->controller->reorderGetModel(); + #$model = $this->controller->reorderGetModel(); + $model = $this->controller->validateModel(); $query = $model->newQuery(); $this->controller->reorderExtendQuery($query); diff --git a/modules/backend/widgets/Lists.php b/modules/backend/widgets/Lists.php index bb4ebb4fc3..4f40cdb222 100644 --- a/modules/backend/widgets/Lists.php +++ b/modules/backend/widgets/Lists.php @@ -45,6 +45,11 @@ class Lists extends WidgetBase */ public $model; + /** + * @var List config definition + */ + public $definition; + /** * @var string Link for each record row. Replace :id with the record id. */ @@ -213,6 +218,7 @@ public function init() 'recordsPerPage', 'perPageOptions', 'reorder', + 'definition', 'showSorting', 'defaultSort', 'showCheckboxes', @@ -244,7 +250,6 @@ public function init() if ($this->reorder) { $this->addJs('/modules/system/assets/ui/js/list.sortable.js', 'core'); $this->showSorting = false; - $this->showTree = false; } } @@ -281,6 +286,7 @@ public function prepareVars() $this->vars['showPageNumbers'] = $this->showPageNumbers; $this->vars['showSorting'] = $this->showSorting; $this->vars['reorder'] = $this->reorder; + $this->vars['definition'] = $this->definition; $this->vars['sortColumn'] = $this->getSortColumn(); $this->vars['sortDirection'] = $this->sortDirection; $this->vars['showTree'] = $this->showTree; @@ -801,17 +807,6 @@ protected function defineListColumns() throw new ApplicationException(Lang::get('backend::lang.list.missing_columns', compact('class'))); } - if ($this->reorder) { - $this->allColumns['sort_handle'] = $this->makeListColumn('sort_handle', [ - 'label' => '', - 'path' => '~/modules/backend/widgets/lists/partials/_list_sort_handle.htm', - 'type' => 'partial', - 'width' => '20px', - 'sortable' => false, - 'clickable' => false, - ]); - } - $this->addColumns($this->columns); /** diff --git a/modules/backend/widgets/lists/assets/js/winter.list.js b/modules/backend/widgets/lists/assets/js/winter.list.js index dcee306c7d..b8b637e047 100644 --- a/modules/backend/widgets/lists/assets/js/winter.list.js +++ b/modules/backend/widgets/lists/assets/js/winter.list.js @@ -12,6 +12,7 @@ this.options = options || {}; this.sortOrders = [] + this.definition = $el.data('definition') var scrollClassContainer = options.scrollClassContainer !== undefined ? options.scrollClassContainer @@ -105,7 +106,7 @@ }.bind(this)) this.$el.request('onReorder', { - data: { sort_orders: this.sortOrders, record_ids: recordIds }, + data: { sort_orders: this.sortOrders, record_ids: recordIds, definition: this.definition }, loading: $.wn.stripeLoadIndicator, }) } diff --git a/modules/backend/widgets/lists/partials/_list-container.php b/modules/backend/widgets/lists/partials/_list-container.php index 1f49426018..6f248b9226 100644 --- a/modules/backend/widgets/lists/partials/_list-container.php +++ b/modules/backend/widgets/lists/partials/_list-container.php @@ -1,3 +1,3 @@
makePartial('list') ?> -
\ No newline at end of file + diff --git a/modules/backend/widgets/lists/partials/_list.php b/modules/backend/widgets/lists/partials/_list.php index 2c463e7e02..efa1fe7032 100644 --- a/modules/backend/widgets/lists/partials/_list.php +++ b/modules/backend/widgets/lists/partials/_list.php @@ -1,5 +1,6 @@
>
diff --git a/modules/backend/widgets/lists/partials/_list_body_handle.php b/modules/backend/widgets/lists/partials/_list_body_handle.php new file mode 100644 index 0000000000..d82f25b65e --- /dev/null +++ b/modules/backend/widgets/lists/partials/_list_body_handle.php @@ -0,0 +1,7 @@ + + + + + diff --git a/modules/backend/widgets/lists/partials/_list_body_row.php b/modules/backend/widgets/lists/partials/_list_body_row.php index 4eadeeb120..646b420db4 100644 --- a/modules/backend/widgets/lists/partials/_list_body_row.php +++ b/modules/backend/widgets/lists/partials/_list_body_row.php @@ -2,8 +2,9 @@ $expanded = $showTree ? $this->isTreeNodeExpanded($record) : null; $childRecords = $showTree ? $record->getChildren() : null; $treeLevelClass = $showTree ? 'list-tree-level-'.$treeLevel : ''; +$draggable = ($reorder && $showTree) ? ($treeLevel === 0) : $reorder; ?> - > + > makePartial('list_body_checkbox', ['record' => $record]) ?> @@ -16,6 +17,10 @@ ]) ?> + + makePartial('list_body_handle', ['draggable' => $draggable]) ?> + + $column): ?> + + $column): ?> sortable): ?> - + From 6041d65c38d4d1c4f7b2ea5ff834aa6639be741b Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Thu, 12 May 2022 16:05:40 -0400 Subject: [PATCH 09/11] use an overlay as the drag handle --- modules/backend/behaviors/ListController.php | 1 - .../lists/partials/_list_body_handle.php | 7 ---- .../widgets/lists/partials/_list_body_row.php | 7 +--- .../widgets/lists/partials/_list_head_row.php | 6 --- .../system/assets/ui/less/list.rowlink.less | 38 +++++++++++++++++++ modules/system/assets/ui/storm.css | 6 +++ 6 files changed, 46 insertions(+), 19 deletions(-) delete mode 100644 modules/backend/widgets/lists/partials/_list_body_handle.php diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index 7b759acf04..80804bfcd8 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -711,7 +711,6 @@ public function reorderExtendQuery($query) protected function getRecords() { $records = null; - #$model = $this->controller->reorderGetModel(); $model = $this->controller->validateModel(); $query = $model->newQuery(); diff --git a/modules/backend/widgets/lists/partials/_list_body_handle.php b/modules/backend/widgets/lists/partials/_list_body_handle.php deleted file mode 100644 index 63b9ae5e10..0000000000 --- a/modules/backend/widgets/lists/partials/_list_body_handle.php +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/modules/backend/widgets/lists/partials/_list_body_row.php b/modules/backend/widgets/lists/partials/_list_body_row.php index 646b420db4..423f7acd0d 100644 --- a/modules/backend/widgets/lists/partials/_list_body_row.php +++ b/modules/backend/widgets/lists/partials/_list_body_row.php @@ -2,9 +2,10 @@ $expanded = $showTree ? $this->isTreeNodeExpanded($record) : null; $childRecords = $showTree ? $record->getChildren() : null; $treeLevelClass = $showTree ? 'list-tree-level-'.$treeLevel : ''; +$hasCheckboxesClass = $showCheckboxes ? 'has-list-checkbox' : ''; $draggable = ($reorder && $showTree) ? ($treeLevel === 0) : $reorder; ?> - > + > makePartial('list_body_checkbox', ['record' => $record]) ?> @@ -17,10 +18,6 @@ ]) ?> - - makePartial('list_body_handle', ['draggable' => $draggable]) ?> - - $column): ?> - - $column): ?> sortable): ?> > + + + > makePartial('list_body_checkbox', ['record' => $record]) ?> From fbaf22fc9e33ca61621765b7071d8dc755f72c98 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Wed, 15 Jun 2022 13:58:07 -0400 Subject: [PATCH 11/11] add remaining code --- .../widgets/lists/assets/js/winter.list.js | 69 ++++++++++++++----- .../backend/widgets/lists/partials/_list.php | 1 + .../widgets/lists/partials/_list_body_row.php | 2 +- modules/system/assets/ui/js/list.sortable.js | 4 +- modules/system/assets/ui/storm-min.js | 5 +- 5 files changed, 57 insertions(+), 24 deletions(-) diff --git a/modules/backend/widgets/lists/assets/js/winter.list.js b/modules/backend/widgets/lists/assets/js/winter.list.js index b8b637e047..4401236c4c 100644 --- a/modules/backend/widgets/lists/assets/js/winter.list.js +++ b/modules/backend/widgets/lists/assets/js/winter.list.js @@ -13,6 +13,7 @@ this.options = options || {}; this.sortOrders = [] this.definition = $el.data('definition') + this.sortMode = $el.data('sort-mode') var scrollClassContainer = options.scrollClassContainer !== undefined ? options.scrollClassContainer @@ -25,15 +26,13 @@ }) if (element.dataset.hasOwnProperty('sortable')) { - this.$el.find('.control-list-tbody').listSortable({ - handle: '.drag-handle' - }) - this.$el.on('dragged.list.sorted', $.proxy(this.processReorder, this)) + this.$el.find('.control-list-tbody').listSortable() + this.$el.on('dragged.list.sortable', $.proxy(this.processReorder, this)) this.$el.find('[data-record-sort-order]').each(function (index, el) { this.sortOrders.push(el.dataset.recordSortOrder) }.bind(this)) - } + } this.update() } @@ -41,6 +40,54 @@ ListWidget.DEFAULTS = { } + ListWidget.prototype.processReorder = function (ev, sortData) { + var recordIds = [] + var postData + + if (this.sortMode == 'simple') { + this.$el.find('[data-record-id]').each(function (index, el) { + recordIds.push(el.dataset.recordId) + }.bind(this)) + + postData = { definition: this.definition, sort_orders: this.sortOrders, record_ids: recordIds } + } + else if (this.sortMode == 'nested') { + postData = this.getNestedMoveData(sortData) + } + + this.$el.request('onReorder', { + data: postData, + loading: $.wn.stripeLoadIndicator, + }) + } + + ListWidget.prototype.getNestedMoveData = function (sortData) { + var $el, + $item = $(sortData.item), + moveData = { + targetNode: 0, + sourceNode: $item.data('recordId'), + position: 'root' + } + + if (($el = $item.next()) && $el.length) { + moveData.position = 'before' + } + else if (($el = $item.prev()) && $el.length) { + moveData.position = 'after' + } + else if (($el = $item.parents('tr:first')) && $el.length) { + moveData.position = 'child' + } + + if ($el.length) { + moveData.targetNode = $el.data('recordId') + } + + console.log(moveData) + return moveData + } + ListWidget.prototype.update = function() { var list = this.$el, @@ -99,18 +146,6 @@ $checkbox.prop('checked', !$checkbox.is(':checked')).trigger('change') } - ListWidget.prototype.processReorder = function() { - var recordIds = [] - this.$el.find('[data-record-id]').each(function (index, el) { - recordIds.push(el.dataset.recordId) - }.bind(this)) - - this.$el.request('onReorder', { - data: { sort_orders: this.sortOrders, record_ids: recordIds, definition: this.definition }, - loading: $.wn.stripeLoadIndicator, - }) - } - // LIST WIDGET PLUGIN DEFINITION // ============================ diff --git a/modules/backend/widgets/lists/partials/_list.php b/modules/backend/widgets/lists/partials/_list.php index efa1fe7032..32477c99f6 100644 --- a/modules/backend/widgets/lists/partials/_list.php +++ b/modules/backend/widgets/lists/partials/_list.php @@ -1,6 +1,7 @@
>
diff --git a/modules/backend/widgets/lists/partials/_list_head_row.php b/modules/backend/widgets/lists/partials/_list_head_row.php index 02f735f984..153310b806 100644 --- a/modules/backend/widgets/lists/partials/_list_head_row.php +++ b/modules/backend/widgets/lists/partials/_list_head_row.php @@ -14,6 +14,12 @@ + + + + - ☰ - From 3a6941884cf4f257b2ab1f864d3169e949764034 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Thu, 12 May 2022 12:48:10 -0400 Subject: [PATCH 08/11] fix spacing issues --- modules/backend/behaviors/ListController.php | 4 ++-- modules/backend/widgets/lists/partials/_list_body_handle.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index a137a315ce..7b759acf04 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -609,7 +609,7 @@ public static function extendListFilterScopes($callback) * Validate the supplied form model. * @return void */ - protected function validateModel($definition=null) + protected function validateModel($definition = null) { $model = $this->controller->reorderGetModel($definition); $modelTraits = class_uses($model); @@ -687,7 +687,7 @@ public function onReorder() return $this->listRefresh($definition); } - public function reorderGetModel($definition=null) + public function reorderGetModel($definition = null) { $listConfig = $this->controller->listGetConfig($definition); $class = $listConfig->modelClass; diff --git a/modules/backend/widgets/lists/partials/_list_body_handle.php b/modules/backend/widgets/lists/partials/_list_body_handle.php index d82f25b65e..63b9ae5e10 100644 --- a/modules/backend/widgets/lists/partials/_list_body_handle.php +++ b/modules/backend/widgets/lists/partials/_list_body_handle.php @@ -1,7 +1,7 @@ - +
diff --git a/modules/backend/widgets/lists/partials/_list_head_row.php b/modules/backend/widgets/lists/partials/_list_head_row.php index 153310b806..02f735f984 100644 --- a/modules/backend/widgets/lists/partials/_list_head_row.php +++ b/modules/backend/widgets/lists/partials/_list_head_row.php @@ -14,12 +14,6 @@ - - - - td:first-child { + position: relative; + } + &:hover > td:first-child { + &::before { + position: absolute; + top: 0; + margin: 0.7rem 0; + padding: 0.5rem 0.5rem; + border-radius: 15%; + background-color: @gray-lighter; + color: @text-color; + font-family: "Font Awesome 6 Free"; + font-weight: 600; + font-style: normal; + font-variant: normal; + content: "\f58e"; + cursor: grab; + } + } + &.has-list-checkbox:hover > td:first-child::before { + right: -10px; + } + + &:not(.has-list-checkbox) { + & > td:first-child { + min-width: 8rem; + } + &:hover > td:first-child { + padding-left: 4rem; + &::before { + left: 10px; + } + } + } +} diff --git a/modules/system/assets/ui/storm.css b/modules/system/assets/ui/storm.css index 3c92b20fc0..63745e73ca 100644 --- a/modules/system/assets/ui/storm.css +++ b/modules/system/assets/ui/storm.css @@ -3074,6 +3074,12 @@ tr.rowlink:not(.nolink) td{cursor:pointer} tr.rowlink:not(.nolink) td.nolink{cursor:auto} .table tbody tr.rowlink:hover td{background-color:#f5f5f5} a.rowlink{color:inherit;font:inherit;text-decoration:inherit} +tr.rowlink[draggable=true]>td:first-child{position:relative} +tr.rowlink[draggable=true]:hover>td:first-child::before{position:absolute;top:0;margin:0.7rem 0;padding:0.5rem 0.5rem;border-radius:15%;background-color:#eee;color:#333;font-family:"Font Awesome 6 Free";font-weight:600;font-style:normal;font-variant:normal;content:"\f58e";cursor:grab} +tr.rowlink[draggable=true].has-list-checkbox:hover>td:first-child::before{right:-10px} +tr.rowlink[draggable=true]:not(.has-list-checkbox)>td:first-child{min-width:8rem} +tr.rowlink[draggable=true]:not(.has-list-checkbox):hover>td:first-child{padding-left:4rem} +tr.rowlink[draggable=true]:not(.has-list-checkbox):hover>td:first-child::before{left:10px} table{max-width:100%;background-color:transparent} th{text-align:left} .table{width:100%;margin-bottom:20px;border-collapse:separate} From f006212ae9318bfa6b2b264bf25ea12822017b19 Mon Sep 17 00:00:00 2001 From: Marc Jauvin Date: Sat, 14 May 2022 12:48:12 -0400 Subject: [PATCH 10/11] set sortMode; properly set record-id and record-sort-order data attributes --- modules/backend/behaviors/ListController.php | 34 +++++++------------ modules/backend/widgets/Lists.php | 6 ++++ .../widgets/lists/partials/_list_body_row.php | 9 +++-- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/modules/backend/behaviors/ListController.php b/modules/backend/behaviors/ListController.php index 80804bfcd8..24be6981cc 100644 --- a/modules/backend/behaviors/ListController.php +++ b/modules/backend/behaviors/ListController.php @@ -160,15 +160,14 @@ public function makeList($definition = null) } } - if (isset($listConfig->reorder) && $listConfig->reorder) { - $columnConfig->definition = $definition; - } - /* * List Widget with extensibility */ $widget = $this->makeWidget(\Backend\Widgets\Lists::class, $columnConfig); + $widget->definition = $definition; + $widget->reorderSortMode = $this->reorderGetSortMode($model); + $widget->bindEvent('list.extendColumns', function () use ($widget) { $this->controller->listExtendColumns($widget); }); @@ -605,13 +604,8 @@ public static function extendListFilterScopes($callback) }); } - /** - * Validate the supplied form model. - * @return void - */ - protected function validateModel($definition = null) + protected function reorderGetSortMode($model) { - $model = $this->controller->reorderGetModel($definition); $modelTraits = class_uses($model); if ( @@ -620,31 +614,29 @@ protected function validateModel($definition = null) isset($modelTraits[\October\Rain\Database\Traits\Sortable::class]) || $model->isClassExtendedWith(\October\Rain\Database\Behaviors\Sortable::class) ) { - $this->sortMode = 'simple'; + return 'simple'; } elseif ( isset($modelTraits[\Winter\Storm\Database\Traits\NestedTree::class]) || isset($modelTraits[\October\Rain\Database\Traits\NestedTree::class]) ) { - $this->sortMode = 'nested'; - $this->showTree = true; + return 'nested'; } else { - throw new ApplicationException('The model must implement the Sortable trait/behavior or the NestedTree trait.'); + return null; } - - return $model; } public function onReorder() { $definition = post('definition', $this->primaryDefinition); - $model = $this->validateModel($definition); + $model = $this->listGetModel($definition); + $sortMode = $this->reorderGetSortMode($model); /* * Simple */ - if ($this->sortMode == 'simple') { + if ($sortMode == 'simple') { if ( (!$ids = post('record_ids')) || (!$orders = post('sort_orders')) @@ -657,7 +649,7 @@ public function onReorder() /* * Nested set */ - elseif ($this->sortMode == 'nested') { + elseif ($sortMode == 'nested') { $sourceNode = $model->find(post('sourceNode')); $targetNode = post('targetNode') ? $model->find(post('targetNode')) : null; @@ -687,7 +679,7 @@ public function onReorder() return $this->listRefresh($definition); } - public function reorderGetModel($definition = null) + public function listGetModel($definition = null) { $listConfig = $this->controller->listGetConfig($definition); $class = $listConfig->modelClass; @@ -711,7 +703,7 @@ public function reorderExtendQuery($query) protected function getRecords() { $records = null; - $model = $this->controller->validateModel(); + $model = $this->controller->listGetModel(); $query = $model->newQuery(); $this->controller->reorderExtendQuery($query); diff --git a/modules/backend/widgets/Lists.php b/modules/backend/widgets/Lists.php index 4f40cdb222..2bb82e4e48 100644 --- a/modules/backend/widgets/Lists.php +++ b/modules/backend/widgets/Lists.php @@ -85,6 +85,11 @@ class Lists extends WidgetBase */ public $reorder = false; + /** + * @var string reorder sort mode (simple|nested) + */ + public $reorderSortMode; + /** * @var mixed A default sort column to look for. */ @@ -286,6 +291,7 @@ public function prepareVars() $this->vars['showPageNumbers'] = $this->showPageNumbers; $this->vars['showSorting'] = $this->showSorting; $this->vars['reorder'] = $this->reorder; + $this->vars['reorderSortMode'] = $this->reorderSortMode; $this->vars['definition'] = $this->definition; $this->vars['sortColumn'] = $this->getSortColumn(); $this->vars['sortDirection'] = $this->sortDirection; diff --git a/modules/backend/widgets/lists/partials/_list_body_row.php b/modules/backend/widgets/lists/partials/_list_body_row.php index 423f7acd0d..d5aba4bae1 100644 --- a/modules/backend/widgets/lists/partials/_list_body_row.php +++ b/modules/backend/widgets/lists/partials/_list_body_row.php @@ -3,9 +3,14 @@ $childRecords = $showTree ? $record->getChildren() : null; $treeLevelClass = $showTree ? 'list-tree-level-'.$treeLevel : ''; $hasCheckboxesClass = $showCheckboxes ? 'has-list-checkbox' : ''; -$draggable = ($reorder && $showTree) ? ($treeLevel === 0) : $reorder; +$draggable = ($reorder && $showTree) ? ($reorderSortMode === 'nested' || $treeLevel === 0) : $reorder; ?> -
diff --git a/modules/backend/widgets/lists/partials/_list_body_row.php b/modules/backend/widgets/lists/partials/_list_body_row.php index d5aba4bae1..d6751ddabf 100644 --- a/modules/backend/widgets/lists/partials/_list_body_row.php +++ b/modules/backend/widgets/lists/partials/_list_body_row.php @@ -3,7 +3,7 @@ $childRecords = $showTree ? $record->getChildren() : null; $treeLevelClass = $showTree ? 'list-tree-level-'.$treeLevel : ''; $hasCheckboxesClass = $showCheckboxes ? 'has-list-checkbox' : ''; -$draggable = ($reorder && $showTree) ? ($reorderSortMode === 'nested' || $treeLevel === 0) : $reorder; +$draggable = ($reorder && $showTree) ? ($reorderSortMode !== 'nested' && $treeLevel === 0) : $reorder; ?> =0;i--){var list=this.lists[i],item=list.querySelector('[data-list-sortable-element-id="'+elementId+'"]') if(item){return item}}return null} ListSortable.prototype.getPlaceholderPlacement=function(hoverElement,ev){var mousePosition=$.wn.foundation.event.pageCoordinates(ev),elementPosition=$.wn.foundation.element.absolutePosition(hoverElement) @@ -4764,8 +4764,7 @@ if(!draggable){return}this.moveElement(draggable,ev.originalEvent) this.removePlaceholders()} ListSortable.prototype.onDragEnd=function(ev){$(document).off('dragover',this.proxy(this.onDocumentDragOver)) var container=$(ev.target).closest('[data-sortable]') -if(container){container.trigger('dragged.list.sorted') -container.css({overflow:this.originalOverflow})}} +if(container){container.css({overflow:this.originalOverflow})}} ListSortable.prototype.onDocumentDragOver=function(ev){if(!this.isSourceManagedList(ev.originalEvent)){return}if(this.mouseOutsideLists(ev.originalEvent)){this.removePlaceholders() return}} ListSortable.prototype.onDocumentMouseMove=function(ev){$(document).off('mousemove',this.proxy(this.onDocumentMouseMove))