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_confirm' => 'Are you sure?'
|
||||
],
|
||||
'reorder' => [
|
||||
'default_title' => 'Reorder records',
|
||||
'no_records' => 'There are no records available to sort.',
|
||||
],
|
||||
'model' => [
|
||||
'name' => 'Model',
|
||||
'not_found' => "Model ':class' with an ID of :id could not be found",
|
||||
|
|
|
|||
Loading…
Reference in New Issue