From 398177b00633b5d5b80a48e1aca97699a6159846 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Thu, 19 Mar 2015 20:00:31 +1100 Subject: [PATCH] Full pivot support added to RelationController --- CHANGELOG.md | 6 + modules/backend/behaviors/FormController.php | 47 +--- .../backend/behaviors/RelationController.php | 249 +++++++++++------- .../partials/_manage_form.htm | 1 + modules/backend/formwidgets/FileUpload.php | 16 +- modules/backend/formwidgets/RecordFinder.php | 54 ++-- modules/backend/traits/FormModelSaver.php | 69 +++++ modules/backend/widgets/Lists.php | 6 + 8 files changed, 281 insertions(+), 167 deletions(-) create mode 100644 modules/backend/traits/FormModelSaver.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 24356341b..01c193538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +* **Build 22x** (2015-03-xx) + - Belongs-to-many model relations now support defining a custom pivot model with the `pivotModel` option (see Database > Model docs). + - The config definitions for behavior `RelationController` have been refactored. When using `pivot` mode all columns and fields should now reside in a `pivot[]` array (see Backend > Relations docs). + - Record Finder form widget now supports nested attributes for relations. + - List columns now support using array names (eg: `relation[attribute]`) which acts as an alias for the `valueFrom` option with `searchable` and `sortable` disabled. + * **Build 226** (2015-03-16) - Form Tabs now support specifying a default tab using the `defaultTab` option (see Backend > Forms docs). - Improved the Theme management features: Edit properties, import, export, duplicate and delete. diff --git a/modules/backend/behaviors/FormController.php b/modules/backend/behaviors/FormController.php index d50f12d86..11e050cc0 100644 --- a/modules/backend/behaviors/FormController.php +++ b/modules/backend/behaviors/FormController.php @@ -7,7 +7,6 @@ use Event; use Input; use Redirect; use Backend; -use Backend\Classes\FormField; use Backend\Classes\ControllerBehavior; use October\Rain\Router\Helper as RouterHelper; use ApplicationException; @@ -22,6 +21,8 @@ use Exception; */ class FormController extends ControllerBehavior { + use \Backend\Traits\FormModelSaver; + /** * @var string Default context for "create" pages. */ @@ -59,11 +60,6 @@ class FormController extends ControllerBehavior */ protected $context; - /** - * @var array List of prepared models that require saving. - */ - protected $modelsToSave = []; - /** * @var Model The initialized model used by the form. */ @@ -701,43 +697,4 @@ class FormController extends ControllerBehavior }); } - // - // Internals - // - - protected function prepareModelsToSave($model, $saveData) - { - $this->modelsToSave = []; - $this->setModelAttributes($model, $saveData); - return $this->modelsToSave; - } - - /** - * Sets a data collection to a model attributes, relations will also be set. - * @param array $saveData Data to save. - * @param Model $model Model to save to - * @return array The collection of models to save. - */ - protected function setModelAttributes($model, $saveData) - { - $this->modelsToSave[] = $model; - - if (!is_array($saveData)) { - return; - } - - $singularTypes = ['belongsTo', 'hasOne', 'morphOne']; - foreach ($saveData as $attribute => $value) { - if ( - is_array($value) && - $model->hasRelation($attribute) && - in_array($model->getRelationType($attribute), $singularTypes) - ) { - $this->setModelAttributes($model->{$attribute}, $value); - } - elseif ($value !== FormField::NO_SAVE_DATA) { - $model->{$attribute} = $value; - } - } - } } diff --git a/modules/backend/behaviors/RelationController.php b/modules/backend/behaviors/RelationController.php index 3c50db8c6..2022691df 100644 --- a/modules/backend/behaviors/RelationController.php +++ b/modules/backend/behaviors/RelationController.php @@ -17,6 +17,8 @@ use October\Rain\Database\Model; */ class RelationController extends ControllerBehavior { + use \Backend\Traits\FormModelSaver; + /** * @var const Postback parameter for the active relationship field. */ @@ -272,89 +274,6 @@ class RelationController extends ControllerBehavior } } - /** - * Determine the default buttons based on the model relationship type. - * @return string - */ - protected function evalToolbarButtons() - { - if ($buttons = $this->getConfig('view[toolbarButtons]')) { - return is_array($buttons) - ? $buttons - : array_map('trim', explode('|', $buttons)); - } - - switch ($this->relationType) { - case 'hasMany': - case 'belongsToMany': - return ['create', 'add', 'delete', 'remove']; - - case 'hasOne': - case 'belongsTo': - return ['create', 'update', 'link', 'delete', 'unlink']; - } - } - - /** - * Determine the view mode based on the model relationship type. - * @return string - */ - protected function evalViewMode() - { - if ($this->forceViewMode) { - return $this->forceViewMode; - } - - switch ($this->relationType) { - case 'hasMany': - case 'belongsToMany': - return 'multi'; - - case 'hasOne': - case 'belongsTo': - return 'single'; - } - } - - /** - * Determine the management mode based on the relation type and settings. - * @return string - */ - protected function evalManageMode() - { - if ($mode = post(self::PARAM_MODE)) { - return $mode; - } - - if ($this->forceManageMode) { - return $this->forceManageMode; - } - - switch ($this->eventTarget) { - case 'button-create': - case 'button-update': - return 'form'; - - case 'button-link': - return 'list'; - } - - switch ($this->relationType) { - case 'belongsTo': - return 'list'; - - case 'belongsToMany': - if (isset($this->config->pivot)) return 'pivot'; - elseif ($this->eventTarget == 'list') return 'form'; - else return 'list'; - - case 'hasOne': - case 'hasMany': - if ($this->eventTarget == 'button-add') return 'list'; - else return 'form'; - } - } - /** * Renders the relationship manager. * @param string $field The relationship field. @@ -889,8 +808,14 @@ class RelationController extends ControllerBehavior { $this->beforeAjax(); + $foreignKeyName = $this->relationModel->getQualifiedKeyName(); + $hydratedModel = $this->relationObject->where($foreignKeyName, $this->manageId)->first(); $saveData = $this->pivotWidget->getSaveData(); - $this->relationObject->updateExistingPivot($this->manageId, $saveData, true); + + $modelsToSave = $this->prepareModelsToSave($hydratedModel, $saveData); + foreach ($modelsToSave as $modelToSave) { + $modelToSave->save(); + } return ['#'.$this->relationGetId('view') => $this->relationRenderView()]; } @@ -960,7 +885,7 @@ class RelationController extends ControllerBehavior * Multiple (has many, belongs to many) */ if ($this->viewMode == 'multi') { - $config = $this->makeConfig($this->config->list); + $config = $this->makeConfigForMode('view', 'list'); $config->model = $this->relationModel; $config->alias = $this->alias . 'ViewList'; $config->showSorting = $this->getConfig('view[showSorting]', true); @@ -1027,7 +952,7 @@ class RelationController extends ControllerBehavior $this->controller->relationExtendQuery($query, $this->field); $this->viewModel = $query->getResults() ?: $this->relationModel; - $config = $this->makeConfig($this->config->form); + $config = $this->makeConfigForMode('view', 'form'); $config->model = $this->viewModel; $config->arrayName = class_basename($this->relationModel); $config->context = 'relation'; @@ -1046,13 +971,14 @@ class RelationController extends ControllerBehavior /* * Pivot */ - if ($this->manageMode == 'pivot' && isset($this->config->list)) { - $config = $this->makeConfig($this->config->list); + if ($this->manageMode == 'pivot' && isset($this->config->pivot)) { + $config = $this->makeConfigForMode('manage', 'list'); $config->model = $this->relationModel; $config->alias = $this->alias . 'ManagePivotList'; $config->showSetup = false; - $config->defaultSort = $this->getConfig('pivot[defaultSort]'); - $config->recordsPerPage = $this->getConfig('pivot[recordsPerPage]'); + $config->showSorting = $this->getConfig('manage[showSorting]', false); + $config->defaultSort = $this->getConfig('manage[defaultSort]'); + $config->recordsPerPage = $this->getConfig('manage[recordsPerPage]'); $config->recordOnClick = sprintf( "$.oc.relationBehavior.clickManagePivotListRecord(:id, '%s', '%s')", $this->field, @@ -1079,7 +1005,7 @@ class RelationController extends ControllerBehavior * List */ elseif ($this->manageMode == 'list' && isset($this->config->list)) { - $config = $this->makeConfig($this->config->list); + $config = $this->makeConfigForMode('manage', 'list'); $config->model = $this->relationModel; $config->alias = $this->alias . 'ManageList'; $config->showSetup = false; @@ -1131,7 +1057,7 @@ class RelationController extends ControllerBehavior } } - $config = $this->makeConfig($this->config->form); + $config = $this->makeConfigForMode('manage', 'form'); $config->model = $this->relationModel; $config->arrayName = class_basename($this->relationModel); $config->context = $context ?: 'relation'; @@ -1179,7 +1105,7 @@ class RelationController extends ControllerBehavior protected function makePivotWidget() { - $config = $this->makeConfig($this->config->pivot); + $config = $this->makeConfigForMode('pivot', 'form'); $config->model = $this->relationModel; $config->arrayName = class_basename($this->relationModel); $config->context = 'relation'; @@ -1189,10 +1115,11 @@ class RelationController extends ControllerBehavior * Existing record */ if ($this->manageId) { - $config->model = $this->relationModel->find($this->manageId); - $config->data = $this->relationObject->newPivotStatementForId($this->manageId)->first(); + $foreignKeyName = $this->relationModel->getQualifiedKeyName(); + $hydratedModel = $this->relationObject->where($foreignKeyName, $this->manageId)->first(); - if (!$config->model || !$config->data) { + $config->model = $hydratedModel; + if (!$config->model) { throw new ApplicationException(Lang::get('backend::lang.model.not_found', [ 'class' => get_class($config->model), 'id' => $this->manageId ])); @@ -1222,4 +1149,134 @@ class RelationController extends ControllerBehavior return $this->sessionKey = FormHelper::getSessionKey(); } + + // + // Helpers + // + + + /** + * Determine the default buttons based on the model relationship type. + * @return string + */ + protected function evalToolbarButtons() + { + if ($buttons = $this->getConfig('view[toolbarButtons]')) { + return is_array($buttons) + ? $buttons + : array_map('trim', explode('|', $buttons)); + } + + switch ($this->relationType) { + case 'hasMany': + case 'belongsToMany': + return ['create', 'add', 'delete', 'remove']; + + case 'hasOne': + case 'belongsTo': + return ['create', 'update', 'link', 'delete', 'unlink']; + } + } + + /** + * Determine the view mode based on the model relationship type. + * @return string + */ + protected function evalViewMode() + { + if ($this->forceViewMode) { + return $this->forceViewMode; + } + + switch ($this->relationType) { + case 'hasMany': + case 'belongsToMany': + return 'multi'; + + case 'hasOne': + case 'belongsTo': + return 'single'; + } + } + + /** + * Determine the management mode based on the relation type and settings. + * @return string + */ + protected function evalManageMode() + { + if ($mode = post(self::PARAM_MODE)) { + return $mode; + } + + if ($this->forceManageMode) { + return $this->forceManageMode; + } + + switch ($this->eventTarget) { + case 'button-create': + case 'button-update': + return 'form'; + + case 'button-link': + return 'list'; + } + + switch ($this->relationType) { + case 'belongsTo': + return 'list'; + + case 'belongsToMany': + if (isset($this->config->pivot)) return 'pivot'; + elseif ($this->eventTarget == 'list') return 'form'; + else return 'list'; + + case 'hasOne': + case 'hasMany': + if ($this->eventTarget == 'button-add') return 'list'; + else return 'form'; + } + } + + /** + * Returns the configuration for a mode (view, manage, pivot) for an + * expected type (list, form). Uses fallback configuration. + */ + protected function makeConfigForMode($mode = 'view', $type = 'list') + { + $config = null; + + /* + * Look for $this->config->view['list'] + */ + if ( + isset($this->config->{$mode}) && + array_key_exists($type, $this->config->{$mode}) + ) { + $config = $this->config->{$mode}[$type]; + } + /* + * Look for $this->config->list + */ + elseif (isset($this->config->{$type})) { + $config = $this->config->{$type}; + } + + /* + * Apply substitutes: + * + * - view.list => manage.list + */ + if (!$config) { + + if ($mode == 'manage' && $type == 'list') { + return $this->makeConfigForMode('view', $type); + } + + throw new ApplicationException('Missing configuration for '.$mode.'.'.$type.' in RelationController definition '.$this->field); + } + + return $this->makeConfig($config); + } + } diff --git a/modules/backend/behaviors/relationcontroller/partials/_manage_form.htm b/modules/backend/behaviors/relationcontroller/partials/_manage_form.htm index a58ca69c4..1ec08321d 100644 --- a/modules/backend/behaviors/relationcontroller/partials/_manage_form.htm +++ b/modules/backend/behaviors/relationcontroller/partials/_manage_form.htm @@ -5,6 +5,7 @@ +