Introduce ReorderController for reordering of model records - Fixes #943
This commit is contained in:
parent
60c18d39df
commit
6d1673c4b3
|
|
@ -0,0 +1,247 @@
|
||||||
|
<?php namespace Backend\Behaviors;
|
||||||
|
|
||||||
|
use Lang;
|
||||||
|
use Backend;
|
||||||
|
use Backend\Classes\ControllerBehavior;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reorder Controller Behavior
|
||||||
|
* Used for reordering and sorting records.
|
||||||
|
*
|
||||||
|
* @package october\backend
|
||||||
|
* @author Alexey Bobkov, Samuel Georges
|
||||||
|
*/
|
||||||
|
class ReorderController extends ControllerBehavior
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
protected $requiredProperties = ['reorderConfig'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array Configuration values that must exist when applying the primary config file.
|
||||||
|
*/
|
||||||
|
protected $requiredConfig = ['modelClass'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Model Import model
|
||||||
|
*/
|
||||||
|
public $model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool Display parent/child relationships in the list.
|
||||||
|
*/
|
||||||
|
protected $showTree = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Reordering mode:
|
||||||
|
* - simple: October\Rain\Database\Traits\Sortable
|
||||||
|
* - nested: October\Rain\Database\Traits\NestedTree
|
||||||
|
*/
|
||||||
|
protected $sortMode = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Backend\Classes\WidgetBase Reference to the widget used for the toolbar.
|
||||||
|
*/
|
||||||
|
protected $toolbarWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Behavior constructor
|
||||||
|
* @param Backend\Classes\Controller $controller
|
||||||
|
*/
|
||||||
|
public function __construct($controller)
|
||||||
|
{
|
||||||
|
parent::__construct($controller);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build configuration
|
||||||
|
*/
|
||||||
|
$this->config = $this->makeConfig($controller->reorderConfig, $this->requiredConfig);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Form widgets
|
||||||
|
*/
|
||||||
|
if ($this->toolbarWidget = $this->makeToolbarWidget()) {
|
||||||
|
$this->toolbarWidget->bindToController();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Controller actions
|
||||||
|
//
|
||||||
|
|
||||||
|
public function reorder()
|
||||||
|
{
|
||||||
|
$this->addJs('js/october.reorder.js', 'core');
|
||||||
|
|
||||||
|
$this->controller->pageTitle = $this->controller->pageTitle
|
||||||
|
?: Lang::get($this->getConfig('title', 'backend::lang.reorder.default_title'));
|
||||||
|
|
||||||
|
$this->validateModel();
|
||||||
|
$this->prepareVars();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// AJAX
|
||||||
|
//
|
||||||
|
|
||||||
|
public function onReorder()
|
||||||
|
{
|
||||||
|
$model = $this->validateModel();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Simple
|
||||||
|
*/
|
||||||
|
if ($this->sortMode == 'simple') {
|
||||||
|
if (!$ids = post('record_ids')) return;
|
||||||
|
if (!$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reordering
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares common form data
|
||||||
|
*/
|
||||||
|
protected function prepareVars()
|
||||||
|
{
|
||||||
|
$this->vars['reorderRecords'] = $this->getRecords();
|
||||||
|
$this->vars['reorderModel'] = $this->model;
|
||||||
|
$this->vars['reorderSortMode'] = $this->sortMode;
|
||||||
|
$this->vars['reorderShowTree'] = $this->showTree;
|
||||||
|
$this->vars['reorderToolbarWidget'] = $this->toolbarWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reorderRender()
|
||||||
|
{
|
||||||
|
return $this->reorderMakePartial('container');
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the supplied form model.
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function validateModel()
|
||||||
|
{
|
||||||
|
$model = $this->controller->reorderGetModel();
|
||||||
|
$modelTraits = class_uses($model);
|
||||||
|
|
||||||
|
if (isset($modelTraits['October\Rain\Database\Traits\Sortable'])) {
|
||||||
|
$this->sortMode = 'simple';
|
||||||
|
}
|
||||||
|
elseif (isset($modelTraits['October\Rain\Database\Traits\NestedTree'])) {
|
||||||
|
$this->sortMode = 'nested';
|
||||||
|
$this->showTree = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new ApplicationException('The model must implement the NestedTree or Sortable traits.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all the records from the supplied model.
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
protected function getRecords()
|
||||||
|
{
|
||||||
|
$model = $this->controller->reorderGetModel();
|
||||||
|
$records = null;
|
||||||
|
|
||||||
|
if ($this->sortMode == 'simple') {
|
||||||
|
$records = $model
|
||||||
|
->orderBy($model->getSortOrderColumn())
|
||||||
|
->get()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
elseif ($this->sortMode == 'nested') {
|
||||||
|
$records = $model->getEagerRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $records;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Widgets
|
||||||
|
//
|
||||||
|
|
||||||
|
protected function makeToolbarWidget()
|
||||||
|
{
|
||||||
|
if ($toolbarConfig = $this->getConfig('toolbar')) {
|
||||||
|
$toolbarConfig = $this->makeConfig($toolbarConfig);
|
||||||
|
$toolbarWidget = $this->makeWidget('Backend\Widgets\Toolbar', $toolbarConfig);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$toolbarWidget = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $toolbarWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Helpers
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller accessor for making partials within this behavior.
|
||||||
|
* @param string $partial
|
||||||
|
* @param array $params
|
||||||
|
* @return string Partial contents
|
||||||
|
*/
|
||||||
|
public function reorderMakePartial($partial, $params = [])
|
||||||
|
{
|
||||||
|
$contents = $this->controller->makePartial('reorder_'.$partial, $params + $this->vars, false);
|
||||||
|
if (!$contents) {
|
||||||
|
$contents = $this->makePartial($partial, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Scripts for the Reorder controller behavior.
|
||||||
|
*
|
||||||
|
* The following functions are observed:
|
||||||
|
* - Simple sorting: Post back the original sort orders and the new ordered identifiers.
|
||||||
|
* - Nested sorting: Post back source and target nodes IDs and the move positioning.
|
||||||
|
*/
|
||||||
|
+function ($) { "use strict";
|
||||||
|
|
||||||
|
var ReorderBehavior = function() {
|
||||||
|
|
||||||
|
this.sortMode = null
|
||||||
|
|
||||||
|
this.simpleSortOrders = []
|
||||||
|
|
||||||
|
this.initSorting = function (mode) {
|
||||||
|
this.sortMode = mode
|
||||||
|
|
||||||
|
if (mode == 'simple') {
|
||||||
|
this.initSortingSimple()
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#reorderTreeList').on('move.oc.treelist', $.proxy(this.processReorder, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.processReorder = function(ev, sortData){
|
||||||
|
var postData
|
||||||
|
|
||||||
|
if (this.sortMode == 'simple') {
|
||||||
|
postData = { sort_orders: this.simpleSortOrders }
|
||||||
|
}
|
||||||
|
else if (this.sortMode == 'nested') {
|
||||||
|
postData = this.getNestedMoveData(sortData)
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#reorderTreeList').request('onReorder', {
|
||||||
|
data: postData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.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('li:first')) && $el.length) {
|
||||||
|
moveData.position = 'child'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($el.length) {
|
||||||
|
moveData.targetNode = $el.data('recordId')
|
||||||
|
}
|
||||||
|
|
||||||
|
return moveData
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initSortingSimple = function () {
|
||||||
|
var sortOrders = []
|
||||||
|
|
||||||
|
$('#reorderTreeList li').each(function(){
|
||||||
|
sortOrders.push($(this).data('recordSortOrder'))
|
||||||
|
})
|
||||||
|
|
||||||
|
this.simpleSortOrders = sortOrders
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$.oc.reorderBehavior = new ReorderBehavior;
|
||||||
|
}(window.jQuery);
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php if ($reorderToolbarWidget): ?>
|
||||||
|
<!-- Reorder Toolbar -->
|
||||||
|
<div id="<?= $this->getId('reorderToolbar') ?>" class="reorder-toolbar">
|
||||||
|
<?= $reorderToolbarWidget->render() ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<!-- Reorder List -->
|
||||||
|
<?= Form::open() ?>
|
||||||
|
<div
|
||||||
|
id="reorderTreeList"
|
||||||
|
class="control-treelist"
|
||||||
|
data-control="treelist"
|
||||||
|
<?= $reorderShowTree ? '' : 'data-nested="0"' ?>
|
||||||
|
data-handle="<?= $reorderShowTree ? 'a.move' : '> li > .record > a.move' ?>"
|
||||||
|
data-stripe-load-indicator>
|
||||||
|
<?php if ($reorderRecords): ?>
|
||||||
|
<ol id="reorderRecords">
|
||||||
|
<?= $this->reorderMakePartial('records', ['records' => $reorderRecords]) ?>
|
||||||
|
</ol>
|
||||||
|
<?php else: ?>
|
||||||
|
<p><?= Lang::get('backend::lang.reorder.no_records') ?></p>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
<?= Form::close() ?>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$.oc.reorderBehavior.initSorting('<?= $reorderSortMode ?>')
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php foreach ($records as $record): ?>
|
||||||
|
|
||||||
|
<li data-record-id="<?= $record->id ?>" data-record-sort-order="<?= $record->sort_order ?>">
|
||||||
|
<div class="record">
|
||||||
|
<a href="javascript:;" class="move"></a>
|
||||||
|
<span><?= $record->title ?></span>
|
||||||
|
<input name="record_ids[]" type="hidden" value="<?= $record->id ?>" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($reorderShowTree): ?>
|
||||||
|
<ol>
|
||||||
|
<?php if ($record->children): ?>
|
||||||
|
<?= $this->reorderMakePartial('records', ['records' => $record->children]) ?>
|
||||||
|
<?php endif ?>
|
||||||
|
</ol>
|
||||||
|
<?php endif ?>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<?php endforeach ?>
|
||||||
|
|
@ -236,6 +236,10 @@ return [
|
||||||
'unlink_name' => 'Unlink :name',
|
'unlink_name' => 'Unlink :name',
|
||||||
'unlink_confirm' => 'Are you sure?'
|
'unlink_confirm' => 'Are you sure?'
|
||||||
],
|
],
|
||||||
|
'reorder' => [
|
||||||
|
'default_title' => 'Reorder records',
|
||||||
|
'no_records' => 'There are no records available to sort.',
|
||||||
|
],
|
||||||
'model' => [
|
'model' => [
|
||||||
'name' => 'Model',
|
'name' => 'Model',
|
||||||
'not_found' => "Model ':class' with an ID of :id could not be found",
|
'not_found' => "Model ':class' with an ID of :id could not be found",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue