2014-05-14 23:24:20 +10:00
|
|
|
<?php namespace Backend\Widgets;
|
|
|
|
|
|
|
|
use DB as Db;
|
|
|
|
use HTML as Html;
|
|
|
|
use App;
|
|
|
|
use Lang;
|
|
|
|
use Input;
|
|
|
|
use Event;
|
|
|
|
use Backend;
|
2014-05-20 15:45:20 +10:00
|
|
|
use DbDongle;
|
2014-05-27 13:44:28 +10:00
|
|
|
use Carbon\Carbon;
|
2014-05-14 23:24:20 +10:00
|
|
|
use October\Rain\Router\Helper as RouterHelper;
|
|
|
|
use Backend\Classes\ListColumn;
|
|
|
|
use Backend\Classes\WidgetBase;
|
|
|
|
use System\Classes\ApplicationException;
|
|
|
|
use October\Rain\Database\Model;
|
2014-05-27 13:44:28 +10:00
|
|
|
use DateTime;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/**
|
|
|
|
* List Widget
|
|
|
|
* Used for building back end lists, renders a list of model objects
|
|
|
|
*
|
|
|
|
* @package october\backend
|
|
|
|
* @author Alexey Bobkov, Samuel Georges
|
|
|
|
*/
|
|
|
|
class Lists extends WidgetBase
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* {@inheritDoc}
|
|
|
|
*/
|
|
|
|
public $defaultAlias = 'list';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Model List model object.
|
|
|
|
*/
|
|
|
|
public $model;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Override default columns with supplied key names.
|
|
|
|
*/
|
|
|
|
public $columnOverride;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Columns to display and their order.
|
|
|
|
*/
|
|
|
|
protected $visibleColumns;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array All available columns.
|
|
|
|
*/
|
|
|
|
protected $columns;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Model data collection.
|
|
|
|
*/
|
|
|
|
protected $records;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string Link for each record row. Replace :id with the record id.
|
|
|
|
*/
|
|
|
|
public $recordUrl;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string Click event for each record row. Replace :id with the record id.
|
|
|
|
*/
|
|
|
|
public $recordOnClick;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var int Rows to display for each page.
|
|
|
|
*/
|
|
|
|
public $recordsPerPage;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string Message to display when there are no records in the list.
|
|
|
|
*/
|
|
|
|
public $noRecordsMessage = 'No records found';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string Filter the records by a search term.
|
|
|
|
*/
|
2014-08-13 21:23:19 +10:00
|
|
|
protected $searchTerm;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Collection of functions to apply to each list query.
|
|
|
|
*/
|
|
|
|
protected $filterCallbacks = [];
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool Shows the sorting options for each column.
|
|
|
|
*/
|
|
|
|
public $showSorting = true;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array All sortable columns.
|
|
|
|
*/
|
|
|
|
protected $sortableColumns;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var mixed A default sort column to look for.
|
|
|
|
*/
|
|
|
|
public $defaultSort;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string Sets the list sorting column.
|
|
|
|
*/
|
|
|
|
public $sortColumn;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string Sets the list sorting direction (asc, desc)
|
|
|
|
*/
|
|
|
|
public $sortDirection;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool Display a checkbox next to each record row.
|
|
|
|
*/
|
|
|
|
public $showCheckboxes = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool Display the list set up used for column visibility and ordering.
|
|
|
|
*/
|
|
|
|
public $showSetup = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool Display pagination when limiting records per page.
|
|
|
|
*/
|
|
|
|
public $showPagination = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool Display parent/child relationships in the list.
|
|
|
|
*/
|
|
|
|
public $showTree = false;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var bool Expand the tree nodes by default.
|
|
|
|
*/
|
|
|
|
public $treeExpanded = true;
|
|
|
|
|
2014-06-30 19:57:53 +10:00
|
|
|
/**
|
|
|
|
* @var array List of CSS classes to apply to the list container element
|
|
|
|
*/
|
|
|
|
public $cssClasses = [];
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
/**
|
|
|
|
* Initialize the widget, called by the constructor and free from its parameters.
|
|
|
|
*/
|
|
|
|
public function init()
|
|
|
|
{
|
|
|
|
$this->validateModel();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Configure the list widget
|
|
|
|
*/
|
|
|
|
$this->recordUrl = $this->getConfig('recordUrl', $this->recordUrl);
|
|
|
|
$this->recordOnClick = $this->getConfig('recordOnClick', $this->recordOnClick);
|
|
|
|
$this->recordsPerPage = $this->getSession('per_page', $this->getConfig('recordsPerPage', $this->recordsPerPage));
|
|
|
|
$this->noRecordsMessage = $this->getConfig('noRecordsMessage', $this->noRecordsMessage);
|
|
|
|
$this->defaultSort = $this->getConfig('defaultSort', $this->defaultSort);
|
|
|
|
$this->showSorting = $this->getConfig('showSorting', $this->showSorting);
|
|
|
|
$this->showSetup = $this->getConfig('showSetup', $this->showSetup);
|
|
|
|
$this->showCheckboxes = $this->getConfig('showCheckboxes', $this->showCheckboxes);
|
|
|
|
$this->showTree = $this->getConfig('showTree', $this->showTree);
|
|
|
|
$this->treeExpanded = $this->getConfig('treeExpanded', $this->treeExpanded);
|
|
|
|
$this->showPagination = $this->recordsPerPage && $this->recordsPerPage > 0;
|
|
|
|
$this->validateTree();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritDoc}
|
|
|
|
*/
|
|
|
|
public function loadAssets()
|
|
|
|
{
|
2014-05-24 16:57:38 +10:00
|
|
|
$this->addJs('js/october.list.js', 'core');
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Renders the widget.
|
|
|
|
*/
|
|
|
|
public function render()
|
|
|
|
{
|
|
|
|
$this->prepareVars();
|
2014-06-30 18:27:17 +10:00
|
|
|
return $this->makePartial('list-container');
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepares the list data
|
|
|
|
*/
|
|
|
|
public function prepareVars()
|
|
|
|
{
|
2014-06-30 19:57:53 +10:00
|
|
|
$this->vars['cssClasses'] = implode(' ', $this->cssClasses);
|
2014-05-14 23:24:20 +10:00
|
|
|
$this->vars['columns'] = $this->getVisibleListColumns();
|
|
|
|
$this->vars['columnTotal'] = $this->getTotalColumns();
|
|
|
|
$this->vars['records'] = $this->getRecords();
|
|
|
|
$this->vars['noRecordsMessage'] = trans($this->noRecordsMessage);
|
|
|
|
$this->vars['showCheckboxes'] = $this->showCheckboxes;
|
|
|
|
$this->vars['showSetup'] = $this->showSetup;
|
|
|
|
$this->vars['showPagination'] = $this->showPagination;
|
|
|
|
$this->vars['showSorting'] = $this->showSorting;
|
|
|
|
$this->vars['sortColumn'] = $this->getSortColumn();
|
|
|
|
$this->vars['sortDirection'] = $this->sortDirection;
|
|
|
|
$this->vars['showTree'] = $this->showTree;
|
|
|
|
$this->vars['treeLevel'] = 0;
|
|
|
|
|
|
|
|
if ($this->showPagination) {
|
|
|
|
$this->vars['recordTotal'] = $this->records->getTotal();
|
|
|
|
$this->vars['pageCurrent'] = $this->records->getCurrentPage();
|
|
|
|
$this->vars['pageLast'] = $this->records->getLastPage();
|
|
|
|
$this->vars['pageFrom'] = $this->records->getFrom();
|
|
|
|
$this->vars['pageTo'] = $this->records->getTo();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$this->vars['recordTotal'] = $this->records->count();
|
|
|
|
$this->vars['pageCurrent'] = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Event handler for refreshing the list.
|
|
|
|
*/
|
2014-07-03 18:35:35 +10:00
|
|
|
public function onRefresh()
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
$this->prepareVars();
|
|
|
|
return ['#'.$this->getId() => $this->makePartial('list')];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Event handler for switching the page number.
|
|
|
|
*/
|
|
|
|
public function onPaginate()
|
|
|
|
{
|
|
|
|
App::make('paginator')->setCurrentPage(post('page'));
|
2014-07-03 18:35:35 +10:00
|
|
|
return $this->onRefresh();
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validate the supplied form model.
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
protected function validateModel()
|
|
|
|
{
|
|
|
|
$this->model = $this->getConfig('model');
|
|
|
|
|
|
|
|
if (!$this->model)
|
|
|
|
throw new ApplicationException(Lang::get('backend::lang.list.missing_model', ['class'=>get_class($this->controller)]));
|
|
|
|
|
|
|
|
if (!$this->model instanceof Model)
|
|
|
|
throw new ApplicationException(Lang::get('backend::lang.model.invalid_class', ['model'=>get_class($this->model), 'class'=>get_class($this->controller)]));
|
|
|
|
|
|
|
|
return $this->model;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Replaces the @ symbol with a table name in a model
|
|
|
|
* @param string $sql
|
|
|
|
* @param string $table
|
|
|
|
* @return string
|
|
|
|
*/
|
2014-08-01 18:18:09 +10:00
|
|
|
protected function parseTableName($sql, $table)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
return str_replace('@', $table.'.', $sql);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies any filters to the model.
|
|
|
|
*/
|
|
|
|
protected function prepareModel()
|
|
|
|
{
|
|
|
|
$query = $this->model->newQuery();
|
|
|
|
$selects = [$this->model->getTable().'.*'];
|
|
|
|
$tables = ['base'=>$this->model->getTable()];
|
|
|
|
$joins = [];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extensibility
|
|
|
|
*/
|
|
|
|
Event::fire('backend.list.extendQueryBefore', [$this, $query]);
|
2014-07-04 18:20:26 +10:00
|
|
|
$this->fireEvent('list.extendQueryBefore', [$query]);
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Related custom selects, must come first
|
|
|
|
*/
|
|
|
|
foreach ($this->getVisibleListColumns() as $column) {
|
|
|
|
if (!isset($column->relation) || !isset($column->sqlSelect))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (!$this->model->hasRelation($column->relation))
|
|
|
|
throw new ApplicationException(Lang::get('backend::lang.model.missing_relation', ['class'=>get_class($this->model), 'relation'=>$column->relation]));
|
|
|
|
|
|
|
|
$alias = Db::getQueryGrammar()->wrap($column->columnName);
|
|
|
|
$table = $this->model->makeRelation($column->relation)->getTable();
|
2014-06-05 18:52:53 +10:00
|
|
|
$relationType = $this->model->getRelationType($column->relation);
|
2014-05-14 23:24:20 +10:00
|
|
|
$sqlSelect = $this->parseTableName($column->sqlSelect, $table);
|
2014-05-16 11:49:08 +10:00
|
|
|
|
2014-06-05 18:52:53 +10:00
|
|
|
if (in_array($relationType, ['hasMany', 'belongsToMany', 'morphToMany', 'morphedByMany', 'morphMany', 'attachMany', 'hasManyThrough']))
|
|
|
|
$selects[] = DbDongle::raw("group_concat(" . $sqlSelect . " separator ', ') as ". $alias);
|
|
|
|
else
|
|
|
|
$selects[] = DbDongle::raw($sqlSelect . ' as '. $alias);
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
$joins[] = $column->relation;
|
|
|
|
$tables[$column->relation] = $table;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($joins)
|
2014-06-05 18:52:53 +10:00
|
|
|
$query->joinWith(array_unique($joins), false);
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Custom select queries
|
|
|
|
*/
|
|
|
|
foreach ($this->getVisibleListColumns() as $column) {
|
|
|
|
if (!isset($column->sqlSelect) || isset($column->relation))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
$alias = Db::getQueryGrammar()->wrap($column->columnName);
|
|
|
|
$sqlSelect = $this->parseTableName($column->sqlSelect, $tables['base']);
|
2014-05-20 15:45:20 +10:00
|
|
|
$selects[] = DbDongle::raw($sqlSelect . ' as '. $alias);
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle a supplied search term
|
|
|
|
*/
|
|
|
|
if (!empty($this->searchTerm) && ($searchableColumns = $this->getSearchableColumns())) {
|
|
|
|
$query->orWhere(function($innerQuery) use ($searchableColumns, $tables) {
|
|
|
|
$columnsToSearch = [];
|
|
|
|
foreach ($searchableColumns as $column) {
|
|
|
|
|
|
|
|
if (isset($column->sqlSelect)) {
|
|
|
|
$table = (isset($column->relation)) ? $tables[$column->relation] : 'base';
|
2014-05-20 19:06:56 +10:00
|
|
|
$columnName = DbDongle::raw($this->parseTableName($column->sqlSelect, $table));
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
else
|
2014-05-29 19:40:02 +10:00
|
|
|
$columnName = $tables['base'] . '.' . $column->columnName;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
$columnsToSearch[] = $columnName;
|
|
|
|
}
|
|
|
|
|
|
|
|
$innerQuery->searchWhere($this->searchTerm, $columnsToSearch);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle sorting
|
|
|
|
*/
|
|
|
|
if ($sortColumn = $this->getSortColumn()) {
|
|
|
|
$query->orderBy($sortColumn, $this->sortDirection);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2014-08-13 21:23:19 +10:00
|
|
|
* Apply filters
|
2014-05-14 23:24:20 +10:00
|
|
|
*/
|
2014-08-13 21:23:19 +10:00
|
|
|
foreach ($this->filterCallbacks as $callback) {
|
|
|
|
$callback($query);
|
|
|
|
}
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Extensibility
|
|
|
|
*/
|
|
|
|
Event::fire('backend.list.extendQuery', [$this, $query]);
|
2014-07-04 18:20:26 +10:00
|
|
|
$this->fireEvent('list.extendQuery', [$query]);
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
// Grouping due to the joinWith() call
|
|
|
|
$query->select($selects);
|
|
|
|
$query->groupBy($this->model->getQualifiedKeyName());
|
|
|
|
return $query;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all the records from the supplied model, after filtering.
|
|
|
|
* @return Collection
|
|
|
|
*/
|
|
|
|
protected function getRecords()
|
|
|
|
{
|
|
|
|
if ($this->showTree) {
|
|
|
|
$records = $this->model->getAllRoot();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$model = $this->prepareModel();
|
|
|
|
$records = ($this->showPagination)
|
|
|
|
? $model->paginate($this->recordsPerPage)
|
|
|
|
: $model->get();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->records = $records;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the record URL address for a list row.
|
|
|
|
* @param Model $record
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getRecordUrl($record)
|
|
|
|
{
|
|
|
|
if (isset($this->recordOnClick))
|
|
|
|
return 'javascript:;';
|
|
|
|
|
|
|
|
if (!isset($this->recordUrl))
|
|
|
|
return null;
|
|
|
|
|
|
|
|
$columns = array_keys($record->getAttributes());
|
|
|
|
$url = RouterHelper::parseValues($record, $columns, $this->recordUrl);
|
|
|
|
return Backend::url($url);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the onclick event for a list row.
|
|
|
|
* @param Model $record
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getRecordOnClick($record)
|
|
|
|
{
|
|
|
|
if (!isset($this->recordOnClick))
|
|
|
|
return null;
|
|
|
|
|
|
|
|
$columns = array_keys($record->getAttributes());
|
|
|
|
$recordOnClick = RouterHelper::parseValues($record, $columns, $this->recordOnClick);
|
|
|
|
return Html::attributes(['onclick' => $recordOnClick]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the list columns that are visible by list settings or default
|
|
|
|
*/
|
|
|
|
protected function getVisibleListColumns()
|
|
|
|
{
|
|
|
|
$definitions = $this->getListColumns();
|
|
|
|
$columns = [];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Supplied column list
|
|
|
|
*/
|
|
|
|
if ($this->columnOverride === null)
|
|
|
|
$this->columnOverride = $this->getSession('visible', null);
|
|
|
|
|
|
|
|
if ($this->columnOverride && is_array($this->columnOverride)) {
|
|
|
|
|
|
|
|
$invalidColumns = array_diff($this->columnOverride, array_keys($definitions));
|
|
|
|
if (!count($definitions))
|
|
|
|
throw new ApplicationException(Lang::get('backend::lang.list.missing_column', ['columns'=>implode(',', $invalidColumns)]));
|
|
|
|
|
|
|
|
foreach ($this->columnOverride as $columnName) {
|
|
|
|
$definitions[$columnName]->invisible = false;
|
|
|
|
$columns[$columnName] = $definitions[$columnName];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Use default column list
|
|
|
|
*/
|
|
|
|
else {
|
|
|
|
foreach ($definitions as $columnName => $column) {
|
|
|
|
if ($column->invisible)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
$columns[$columnName] = $definitions[$columnName];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->visibleColumns = $columns;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds an array of list columns with keys as the column name and values as a ListColumn object.
|
|
|
|
*/
|
|
|
|
protected function getListColumns()
|
|
|
|
{
|
|
|
|
if (!isset($this->config->columns) || !is_array($this->config->columns) || !count($this->config->columns))
|
|
|
|
throw new ApplicationException(Lang::get('backend::lang.list.missing_columns', ['class'=>get_class($this->controller)]));
|
|
|
|
|
2014-06-08 10:52:46 +10:00
|
|
|
$this->addColumns($this->config->columns);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extensibility
|
|
|
|
*/
|
|
|
|
Event::fire('backend.list.extendColumns', [$this]);
|
2014-07-04 18:20:26 +10:00
|
|
|
$this->fireEvent('list.extendColumns');
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Use a supplied column order
|
|
|
|
*/
|
|
|
|
if ($columnOrder = $this->getSession('order', null)) {
|
|
|
|
$orderedDefinitions = [];
|
|
|
|
foreach ($columnOrder as $column) {
|
2014-06-08 10:52:46 +10:00
|
|
|
$orderedDefinitions[$column] = $this->columns[$column];
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
2014-06-08 10:52:46 +10:00
|
|
|
$this->columns = array_merge($orderedDefinitions, $this->columns);
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
2014-06-08 10:52:46 +10:00
|
|
|
return $this->columns;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Programatically add columns, used internally and for extensibility.
|
|
|
|
*/
|
|
|
|
public function addColumns(array $columns)
|
|
|
|
{
|
2014-05-14 23:24:20 +10:00
|
|
|
/*
|
|
|
|
* Build a final collection of list column objects
|
|
|
|
*/
|
2014-06-08 10:52:46 +10:00
|
|
|
foreach ($columns as $columnName => $config) {
|
2014-05-14 23:24:20 +10:00
|
|
|
$this->columns[$columnName] = $this->makeListColumn($columnName, $config);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a list column object from it's name and configuration.
|
|
|
|
*/
|
|
|
|
protected function makeListColumn($name, $config)
|
|
|
|
{
|
|
|
|
if (is_string($config))
|
|
|
|
$label = $config;
|
|
|
|
elseif (isset($config['label']))
|
|
|
|
$label = $config['label'];
|
|
|
|
else
|
|
|
|
$label = studly_case($name);
|
|
|
|
|
2014-07-31 19:34:38 +10:00
|
|
|
$columnType = isset($config['type']) ? $config['type'] : null;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2014-07-31 19:34:38 +10:00
|
|
|
$column = new ListColumn($name, $label);
|
|
|
|
$column->displayAs($columnType, $config);
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
return $column;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculates the total columns used in the list, including checkboxes
|
|
|
|
* and other additions.
|
|
|
|
*/
|
|
|
|
protected function getTotalColumns()
|
|
|
|
{
|
|
|
|
$columns = $this->visibleColumns ?: $this->getVisibleListColumns();
|
|
|
|
$total = count($columns);
|
|
|
|
if ($this->showCheckboxes) $total++;
|
|
|
|
if ($this->showSetup) $total++;
|
|
|
|
return $total;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Looks up the column header
|
|
|
|
*/
|
|
|
|
public function getHeaderValue($column)
|
|
|
|
{
|
|
|
|
$value = Lang::get($column->label);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extensibility
|
|
|
|
*/
|
2014-05-15 16:22:22 +10:00
|
|
|
if ($response = Event::fire('backend.list.overrideHeaderValue', [$this, $column, $value], true))
|
|
|
|
$value = $response;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2014-07-04 18:20:26 +10:00
|
|
|
if ($response = $this->fireEvent('list.overrideHeaderValue', [$column, $value], true))
|
2014-05-15 17:23:46 +10:00
|
|
|
$value = $response;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Looks up the column value
|
|
|
|
*/
|
|
|
|
public function getColumnValue($record, $column)
|
|
|
|
{
|
2014-06-29 09:12:11 +10:00
|
|
|
/*
|
|
|
|
* If the column is a relation, it will be a custom select,
|
|
|
|
* so prevent the Model from attempting to load the relation
|
|
|
|
* if the value is NULL.
|
|
|
|
*/
|
|
|
|
$columnName = $column->columnName;
|
|
|
|
if ($record->hasRelation($columnName) && array_key_exists($columnName, $record->attributes))
|
|
|
|
$value = $record->attributes[$columnName];
|
|
|
|
else
|
|
|
|
$value = $record->{$columnName};
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
if (method_exists($this, 'eval'. studly_case($column->type) .'TypeValue'))
|
2014-08-06 18:18:10 +10:00
|
|
|
$value = $this->{'eval'. studly_case($column->type) .'TypeValue'}($record, $column, $value);
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Extensibility
|
|
|
|
*/
|
2014-05-15 16:22:22 +10:00
|
|
|
if ($response = Event::fire('backend.list.overrideColumnValue', [$this, $record, $column, $value], true))
|
|
|
|
$value = $response;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2014-07-04 18:20:26 +10:00
|
|
|
if ($response = $this->fireEvent('list.overrideColumnValue', [$record, $column, $value], true))
|
2014-05-15 17:23:46 +10:00
|
|
|
$value = $response;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a custom CSS class string to a record row
|
|
|
|
* @param Model $record Populated model
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getRowClass($record)
|
|
|
|
{
|
|
|
|
$value = '';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extensibility
|
|
|
|
*/
|
2014-05-15 16:22:22 +10:00
|
|
|
if ($response = Event::fire('backend.list.injectRowClass', [$this, $record], true))
|
|
|
|
$value = $response;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
2014-07-04 18:20:26 +10:00
|
|
|
if ($response = $this->fireEvent('list.injectRowClass', [$record], true))
|
2014-05-15 17:23:46 +10:00
|
|
|
$value = $response;
|
2014-05-14 23:24:20 +10:00
|
|
|
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Value processing
|
|
|
|
//
|
|
|
|
|
2014-08-01 17:42:00 +10:00
|
|
|
/**
|
|
|
|
* Process as boolean switch
|
|
|
|
*/
|
2014-08-06 18:18:10 +10:00
|
|
|
protected function evalPartialTypeValue($record, $column, $value)
|
2014-08-01 17:42:00 +10:00
|
|
|
{
|
2014-08-06 18:18:10 +10:00
|
|
|
return $this->controller->makePartial($column->path ?: $column->columnName, [
|
|
|
|
'listColumn' => $column,
|
|
|
|
'record' => $record,
|
|
|
|
'value' => $value
|
|
|
|
]);
|
2014-08-01 17:42:00 +10:00
|
|
|
}
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
/**
|
|
|
|
* Process as boolean switch
|
|
|
|
*/
|
2014-08-06 18:18:10 +10:00
|
|
|
protected function evalSwitchTypeValue($record, $column, $value)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
// return ($value) ? '<i class="icon-check"></i>' : '<i class="icon-times"></i>';
|
|
|
|
return ($value) ? 'Yes' : 'No';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process as a datetime value
|
|
|
|
*/
|
2014-08-06 18:18:10 +10:00
|
|
|
protected function evalDatetimeTypeValue($record, $column, $value)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
if ($value === null)
|
|
|
|
return null;
|
|
|
|
|
2014-06-20 19:30:37 +10:00
|
|
|
$value = $this->validateDateTimeValue($value, $column);
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
if ($column->format !== null)
|
|
|
|
return $value->format($column->format);
|
|
|
|
|
|
|
|
return $value->toDayDateTimeString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process as a time value
|
|
|
|
*/
|
2014-08-06 18:18:10 +10:00
|
|
|
protected function evalTimeTypeValue($record, $column, $value)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
if ($value === null)
|
|
|
|
return null;
|
|
|
|
|
2014-06-20 19:30:37 +10:00
|
|
|
$value = $this->validateDateTimeValue($value, $column);
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
if ($column->format === null)
|
|
|
|
$column->format = 'g:i A';
|
|
|
|
|
|
|
|
return $value->format($column->format);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process as a date value
|
|
|
|
*/
|
2014-08-06 18:18:10 +10:00
|
|
|
protected function evalDateTypeValue($record, $column, $value)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
if ($value === null)
|
|
|
|
return null;
|
|
|
|
|
2014-06-20 19:30:37 +10:00
|
|
|
$value = $this->validateDateTimeValue($value, $column);
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
if ($column->format !== null)
|
|
|
|
return $value->format($column->format);
|
|
|
|
|
|
|
|
return $value->toFormattedDateString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Process as diff for humans (1 min ago)
|
|
|
|
*/
|
2014-08-06 18:18:10 +10:00
|
|
|
protected function evalTimesinceTypeValue($record, $column, $value)
|
2014-05-14 23:24:20 +10:00
|
|
|
{
|
|
|
|
if ($value === null)
|
|
|
|
return null;
|
2014-05-27 13:44:28 +10:00
|
|
|
|
2014-06-20 19:30:37 +10:00
|
|
|
$value = $this->validateDateTimeValue($value, $column);
|
|
|
|
|
|
|
|
return $value->diffForHumans();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validates a column type as a date
|
|
|
|
*/
|
2014-08-01 18:18:09 +10:00
|
|
|
protected function validateDateTimeValue($value, $column)
|
2014-06-20 19:30:37 +10:00
|
|
|
{
|
2014-05-27 13:44:28 +10:00
|
|
|
if ($value instanceof DateTime)
|
|
|
|
$value = Carbon::instance($value);
|
|
|
|
|
|
|
|
if (!$value instanceof Carbon)
|
2014-06-20 19:30:37 +10:00
|
|
|
throw new ApplicationException(Lang::get('backend::lang.list.invalid_column_datetime', ['column' => $column->columnName]));
|
2014-05-27 13:44:28 +10:00
|
|
|
|
2014-06-20 19:30:37 +10:00
|
|
|
return $value;
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
2014-08-13 21:23:19 +10:00
|
|
|
//
|
|
|
|
// Filtering
|
|
|
|
//
|
|
|
|
|
|
|
|
public function addFilter(callable $filter)
|
|
|
|
{
|
|
|
|
$this->filterCallbacks[] = $filter;
|
|
|
|
}
|
|
|
|
|
2014-05-14 23:24:20 +10:00
|
|
|
//
|
|
|
|
// Searching
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Applies a search term to the list results, searching will disable tree
|
|
|
|
* view if a value is supplied.
|
|
|
|
* @param string $term
|
|
|
|
*/
|
|
|
|
public function setSearchTerm($term)
|
|
|
|
{
|
|
|
|
if (empty($term)) {
|
|
|
|
$this->showTree = $this->getConfig('showTree', $this->showTree);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$this->showTree = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->searchTerm = $term;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a collection of columns which can be searched.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function getSearchableColumns()
|
|
|
|
{
|
|
|
|
$columns = $this->columns ?: $this->getListColumns();
|
|
|
|
$searchable = [];
|
|
|
|
|
|
|
|
foreach ($columns as $column) {
|
|
|
|
if (!$column->searchable)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
$searchable[] = $column;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $searchable;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Sorting
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Event handler for sorting the list.
|
|
|
|
*/
|
|
|
|
public function onSort()
|
|
|
|
{
|
|
|
|
if ($column = post('sortColumn')) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Toggle the sort direction and set the sorting column
|
|
|
|
*/
|
|
|
|
$sortOptions = ['column' => $this->getSortColumn(), 'direction' => $this->sortDirection];
|
|
|
|
|
|
|
|
if ($column != $sortOptions['column'] || $sortOptions['direction'] == 'asc')
|
|
|
|
$this->sortDirection = $sortOptions['direction'] = 'desc';
|
|
|
|
else
|
|
|
|
$this->sortDirection = $sortOptions['direction'] = 'asc';
|
|
|
|
|
|
|
|
$this->sortColumn = $sortOptions['column'] = $column;
|
|
|
|
|
|
|
|
$this->putSession('sort', $sortOptions);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Persist the page number
|
|
|
|
*/
|
|
|
|
App::make('paginator')->setCurrentPage(post('page'));
|
|
|
|
|
2014-07-03 18:35:35 +10:00
|
|
|
return $this->onRefresh();
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the current sorting column, saved in a session or cached.
|
|
|
|
*/
|
|
|
|
protected function getSortColumn()
|
|
|
|
{
|
|
|
|
if (!$this->isSortable())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if ($this->sortColumn !== null)
|
|
|
|
return $this->sortColumn;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* User preference
|
|
|
|
*/
|
|
|
|
if ($this->showSorting && ($sortOptions = $this->getSession('sort'))) {
|
|
|
|
$this->sortColumn = $sortOptions['column'];
|
|
|
|
$this->sortDirection = $sortOptions['direction'];
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Supplied default
|
|
|
|
*/
|
|
|
|
else {
|
|
|
|
if (is_string($this->defaultSort)) {
|
|
|
|
$this->sortColumn = $this->defaultSort;
|
|
|
|
$this->sortDirection = 'desc';
|
|
|
|
}
|
|
|
|
elseif (is_array($this->defaultSort) && isset($this->defaultSort['column'])) {
|
|
|
|
$this->sortColumn = $this->defaultSort['column'];
|
|
|
|
$this->sortDirection = (isset($this->defaultSort['direction'])) ? $this->defaultSort['direction'] : 'desc';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* First available column
|
|
|
|
*/
|
|
|
|
if ($this->sortColumn === null || !$this->isSortable($this->sortColumn)) {
|
|
|
|
$columns = $this->visibleColumns ?: $this->getVisibleListColumns();
|
|
|
|
$this->sortColumn = key($columns);
|
|
|
|
$this->sortDirection = 'desc';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->sortColumn;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns true if the column can be sorted.
|
|
|
|
*/
|
|
|
|
protected function isSortable($column = null)
|
|
|
|
{
|
|
|
|
if ($column === null)
|
|
|
|
return (count($this->getSortableColumns()) > 0);
|
|
|
|
else
|
|
|
|
return array_key_exists($column, $this->getSortableColumns());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a collection of columns which are sortable.
|
|
|
|
*/
|
|
|
|
protected function getSortableColumns()
|
|
|
|
{
|
|
|
|
if ($this->sortableColumns !== null)
|
|
|
|
return $this->sortableColumns;
|
|
|
|
|
|
|
|
$columns = $this->columns ?: $this->getListColumns();
|
|
|
|
$sortable = [];
|
|
|
|
|
|
|
|
foreach ($columns as $column) {
|
|
|
|
if (!$column->sortable)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
$sortable[$column->columnName] = $column;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->sortableColumns = $sortable;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// List Setup
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Event handler to display the list set up.
|
|
|
|
*/
|
|
|
|
public function onLoadSetup()
|
|
|
|
{
|
|
|
|
$this->vars['columns'] = $this->getSetupListColumns();
|
|
|
|
$this->vars['perPageOptions'] = $this->getSetupPerPageOptions();
|
|
|
|
$this->vars['recordsPerPage'] = $this->recordsPerPage;
|
|
|
|
return $this->makePartial('setup_form');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Event handler to apply the list set up.
|
|
|
|
*/
|
|
|
|
public function onApplySetup()
|
|
|
|
{
|
|
|
|
if (($visibleColumns = post('visible_columns')) && is_array($visibleColumns)) {
|
|
|
|
$this->columnOverride = array_keys($visibleColumns);
|
|
|
|
$this->putSession('visible', array_keys($visibleColumns));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->putSession('order', post('column_order'));
|
|
|
|
$this->putSession('per_page', post('records_per_page', $this->recordsPerPage));
|
2014-07-03 18:35:35 +10:00
|
|
|
return $this->onRefresh();
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of allowable records per page.
|
|
|
|
*/
|
|
|
|
protected function getSetupPerPageOptions()
|
|
|
|
{
|
|
|
|
$perPageOptions = [20, 40, 80, 100, 120];
|
|
|
|
if (!in_array($this->recordsPerPage, $perPageOptions))
|
|
|
|
$perPageOptions[] = $this->recordsPerPage;
|
|
|
|
|
|
|
|
sort($perPageOptions);
|
|
|
|
return $perPageOptions;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all the list columns used for list set up.
|
|
|
|
*/
|
|
|
|
protected function getSetupListColumns()
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Force all columns invisible
|
|
|
|
*/
|
|
|
|
$allColumns = $this->getListColumns();
|
|
|
|
foreach ($allColumns as $column) {
|
|
|
|
$column->invisible = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_merge($allColumns, $this->getVisibleListColumns());
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Tree
|
|
|
|
//
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Validates the model and settings if showTree is used
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function validateTree()
|
|
|
|
{
|
|
|
|
if (!$this->showTree) return;
|
|
|
|
|
|
|
|
$this->showSorting = $this->showPagination = false;
|
|
|
|
|
|
|
|
if (!$this->model->methodExists('getChildren'))
|
|
|
|
throw new ApplicationException('To display list as a tree, the specified model must have a method "getChildren"');
|
|
|
|
|
|
|
|
if (!$this->model->methodExists('getChildCount'))
|
|
|
|
throw new ApplicationException('To display list as a tree, the specified model must have a method "getChildCount"');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if a node (model) is expanded in the session.
|
|
|
|
* @param Model $node
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function isTreeNodeExpanded($node)
|
|
|
|
{
|
|
|
|
return $this->getSession('tree_node_status_' . $node->getKey(), $this->treeExpanded);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a node (model) to an expanded or collapsed state, stored in the
|
|
|
|
* session, then renders the list again.
|
|
|
|
* @return string List HTML contents.
|
|
|
|
*/
|
|
|
|
public function onToggleTreeNode()
|
|
|
|
{
|
|
|
|
$this->putSession('tree_node_status_' . post('node_id'), post('status') ? 0 : 1);
|
2014-07-03 18:35:35 +10:00
|
|
|
return $this->onRefresh();
|
2014-05-14 23:24:20 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|