Improved naming onReload -> onRefresh

Improved Record finder
This commit is contained in:
Sam Georges 2014-07-03 18:35:35 +10:00
parent bd8137ea97
commit b09b6d6863
15 changed files with 335 additions and 63 deletions

View File

@ -8249,7 +8249,6 @@ label {
background: transparent; background: transparent;
border-color: transparent; border-color: transparent;
height: auto; height: auto;
/*min-height: 38px;*/
} }
.field-recordfinder .btn { .field-recordfinder .btn {
position: absolute; position: absolute;
@ -8257,8 +8256,7 @@ label {
top: 50%; top: 50%;
margin-top: -44px; margin-top: -44px;
height: 88px; height: 88px;
width: 46px; width: 36px;
text-align: center;
} }
.field-recordfinder .btn:before { .field-recordfinder .btn:before {
content: ''; content: '';
@ -8267,9 +8265,9 @@ label {
height: 0; height: 0;
border-top: 44px solid transparent; border-top: 44px solid transparent;
border-bottom: 44px solid transparent; border-bottom: 44px solid transparent;
border-right: 36px solid #e3e3e3; border-right: 26px solid #e3e3e3;
position: absolute; position: absolute;
left: -26px; left: -16px;
top: 50%; top: 50%;
margin-top: -44px; margin-top: -44px;
} }
@ -8283,7 +8281,7 @@ label {
height: 0; height: 0;
border-top: 44px solid transparent; border-top: 44px solid transparent;
border-bottom: 44px solid transparent; border-bottom: 44px solid transparent;
border-right: 36px solid #cfcfcf; border-right: 26px solid #cfcfcf;
} }
.form-buttons { .form-buttons {
padding-bottom: 20px; padding-bottom: 20px;

View File

@ -200,7 +200,6 @@ label {
background: transparent; background: transparent;
border-color: transparent; border-color: transparent;
height: auto; height: auto;
/*min-height: 38px;*/
} }
.btn { .btn {
position: absolute; position: absolute;
@ -208,12 +207,11 @@ label {
top: 50%; top: 50%;
margin-top: -44px; margin-top: -44px;
height: 88px; height: 88px;
width: 46px; width: 36px;
text-align: center;
&:before { &:before {
.triangle(left, 36px, 88px, @btn-default-bg); .triangle(left, 26px, 88px, @btn-default-bg);
position: absolute; position: absolute;
left: -26px; left: -16px;
top: 50%; top: 50%;
margin-top: -44px; margin-top: -44px;
} }
@ -222,7 +220,7 @@ label {
&:active, &:active,
&.active { &.active {
&:before { &:before {
.triangle(left, 36px, 88px, darken(@btn-default-bg, 8%)); .triangle(left, 26px, 88px, darken(@btn-default-bg, 8%));
} }
} }
} }

View File

@ -87,6 +87,14 @@ class FormController extends ControllerBehavior
$this->controller->formExtendFields($host); $this->controller->formExtendFields($host);
}); });
$this->formWidget->bindEvent('form.refreshBefore', function($host, $saveData) {
return $this->controller->formExtendRefreshData($host, $saveData);
});
$this->formWidget->bindEvent('form.refresh', function($host, $result) {
return $this->controller->formExtendRefreshResults($host, $result);
});
$this->formWidget->bindToController(); $this->formWidget->bindToController();
/* /*
@ -509,6 +517,22 @@ class FormController extends ControllerBehavior
*/ */
public function formExtendFields($host) {} public function formExtendFields($host) {}
/**
* Called before the form is refreshed, should return an array of additional save data.
* @param Backend\Widgets\Form $host The hosting form widget
* @param array $saveData Current save data
* @return array
*/
public function formExtendRefreshData($host, $saveData) {}
/**
* Called after the form is refreshed, should return an array of additional result parameters.
* @param Backend\Widgets\Form $host The hosting form widget
* @param array $result Current result parameters.
* @return array
*/
public function formExtendRefreshResults($host, $result) {}
/** /**
* Extend supplied model used by create and update actions, the model can * Extend supplied model used by create and update actions, the model can
* be altered by overriding it in the controller. * be altered by overriding it in the controller.

View File

@ -164,7 +164,7 @@ class ListController extends ControllerBehavior
if ($searchWidget = $toolbarWidget->getSearchWidget()) { if ($searchWidget = $toolbarWidget->getSearchWidget()) {
$searchWidget->bindEvent('search.submit', function() use ($widget, $searchWidget) { $searchWidget->bindEvent('search.submit', function() use ($widget, $searchWidget) {
$widget->setSearchTerm($searchWidget->getActiveTerm()); $widget->setSearchTerm($searchWidget->getActiveTerm());
return $widget->onRender(); return $widget->onRefresh();
}); });
// Find predefined search term // Find predefined search term
@ -224,7 +224,7 @@ class ListController extends ControllerBehavior
if (!$definition || !isset($this->listDefinitions[$definition])) if (!$definition || !isset($this->listDefinitions[$definition]))
$definition = $this->primaryDefinition; $definition = $this->primaryDefinition;
return $this->listWidgets[$definition]->onRender(); return $this->listWidgets[$definition]->onRefresh();
} }
// //

View File

@ -401,6 +401,9 @@ class Controller extends Extendable
*/ */
$this->pageAction(); $this->pageAction();
if ($this->fatalError)
throw new SystemException($this->fatalError);
if (!isset($this->widget->{$widgetName})) if (!isset($this->widget->{$widgetName}))
throw new SystemException(Lang::get('backend::lang.widget.not_bound', ['name'=>$widgetName])); throw new SystemException(Lang::get('backend::lang.widget.not_bound', ['name'=>$widgetName]));

View File

@ -11,8 +11,8 @@ use Backend\Classes\FormWidgetBase;
* type: recordfinder * type: recordfinder
* list: @/plugins/rainlab/user/models/user/columns.yaml * list: @/plugins/rainlab/user/models/user/columns.yaml
* prompt: Click the Find button to find a user * prompt: Click the Find button to find a user
* displayName: name * nameColumn: name
* displayDescription: email * descriptionColumn: email
* *
* @package october\backend * @package october\backend
* @author Alexey Bobkov, Samuel Georges * @author Alexey Bobkov, Samuel Georges
@ -24,23 +24,65 @@ class RecordFinder extends FormWidgetBase
*/ */
public $defaultAlias = 'recordfinder'; public $defaultAlias = 'recordfinder';
/**
* @var string Relationship type
*/
public $relationType;
/**
* @var string Relationship name
*/
public $relationName;
/**
* @var Model Relationship model
*/
public $relationModel;
/**
* @var string Field name to use for key.
*/
public $keyField = 'id';
/** /**
* @var string Relation column to display for the name * @var string Relation column to display for the name
*/ */
public $displayName; public $nameColumn;
/** /**
* @var string Relation column to display for the description * @var string Relation column to display for the description
*/ */
public $displayDescription; public $descriptionColumn;
/**
* @var string Prompt to display if no record is selected.
*/
public $prompt;
/**
* @var Backend\Classes\WidgetBase Reference to the widget used for viewing (list or form).
*/
protected $listWidget;
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
public function init() public function init()
{ {
$this->displayName = $this->getConfig('displayName', $this->displayName); $this->relationName = $this->formField->columnName;
$this->displayDescription = $this->getConfig('displayDescription', $this->displayDescription); $this->relationType = $this->model->getRelationType($this->relationName);
$this->prompt = $this->getConfig('prompt', 'Click the %s button to find a record');
$this->keyField = $this->getConfig('keyField', $this->keyField);
$this->nameColumn = $this->getConfig('nameColumn', $this->nameColumn);
$this->descriptionColumn = $this->getConfig('descriptionColumn', $this->descriptionColumn);
if (!$this->model->hasRelation($this->relationName))
throw new SystemException(Lang::get('backend::lang.model.missing_relation', ['class'=>get_class($this->controller), 'relation'=>$this->relationName]));
if (post('recordfinder_flag')) {
$this->listWidget = $this->makeListWidget();
}
} }
/** /**
@ -52,18 +94,25 @@ class RecordFinder extends FormWidgetBase
return $this->makePartial('container'); return $this->makePartial('container');
} }
public function onRefresh()
{
$this->model->{$this->columnName} = post($this->formField->getName());
$this->prepareVars();
return ['#'.$this->getId('container') => $this->makePartial('recordfinder')];
}
/** /**
* Prepares the list data * Prepares the list data
*/ */
public function prepareVars() public function prepareVars()
{ {
$this->vars['name'] = $this->formField->getName(); $this->relationModel = $this->model->{$this->columnName};
$this->vars['value'] = $this->getKeyValue();
$value = $this->model->{$this->columnName}; $this->vars['field'] = $this->formField;
$this->vars['nameValue'] = $this->getNameValue();
$this->vars['value'] = $value ?: ''; $this->vars['descriptionValue'] = $this->getDescriptionValue();
$this->vars['displayName'] = $this->displayName; $this->vars['listWidget'] = $this->listWidget;
$this->vars['displayDescription'] = $this->displayDescription; $this->vars['prompt'] = str_replace('%s', '<i class="icon-search"></i>', $this->prompt);
} }
/** /**
@ -71,7 +120,7 @@ class RecordFinder extends FormWidgetBase
*/ */
public function loadAssets() public function loadAssets()
{ {
$this->addJs('js/recordfinder.js', 'core');
} }
/** /**
@ -79,6 +128,61 @@ class RecordFinder extends FormWidgetBase
*/ */
public function getSaveData($value) public function getSaveData($value)
{ {
return $value; return strlen($value) ? $value : null;
}
public function getKeyValue()
{
if (!$this->relationModel)
return null;
return $this->relationModel->{$this->keyField};
}
public function getNameValue()
{
if (!$this->relationModel || !$this->nameColumn)
return null;
return $this->relationModel->{$this->nameColumn};
}
public function getDescriptionValue()
{
if (!$this->relationModel || !$this->descriptionColumn)
return null;
return $this->relationModel->{$this->descriptionColumn};
}
public function onFindRecord()
{
$this->prepareVars();
return $this->makePartial('recordfinder_form');
}
protected function makeListWidget()
{
$config = $this->makeConfig($this->getConfig('list'));
$config->model = $this->model->makeRelation($this->relationName);
$config->alias = $this->alias . 'List';
$config->showSetup = false;
$config->showCheckboxes = false;
$config->recordOnClick = sprintf("$('#%s').recordFinder('updateRecord', this, ':id')", $this->getId());
$widget = $this->makeWidget('Backend\Widgets\Lists', $config);
// $widget->bindEvent('list.extendQueryBefore', function($host, $query) {
// /*
// * Where not in the current list of related records
// */
// $existingIds = $this->findExistingRelationIds();
// if (count($existingIds)) {
// $query->whereNotIn('id', $existingIds);
// }
// });
return $widget;
} }
} }

View File

@ -86,34 +86,34 @@ class Relation extends FormWidgetBase
*/ */
protected function makeRenderFormField() protected function makeRenderFormField()
{ {
$field = clone $this->formField; $field = clone $this->formField;
$relationObj = $this->model->{$this->relationName}(); $relationObj = $this->model->{$this->relationName}();
$relatedObj = $this->model->makeRelation($this->relationName); $relatedObj = $this->model->makeRelation($this->relationName);
$query = $relatedObj->newQuery(); $query = $relatedObj->newQuery();
if (in_array($this->relationType, ['belongsToMany', 'morphToMany', 'morphedByMany'])) { if (in_array($this->relationType, ['belongsToMany', 'morphToMany', 'morphedByMany'])) {
$field->type = 'checkboxlist'; $field->type = 'checkboxlist';
$field->value = $relationObj->getRelatedIds(); $field->value = $relationObj->getRelatedIds();
} }
else if ($this->relationType == 'belongsTo') { else if ($this->relationType == 'belongsTo') {
$field->type = 'dropdown'; $field->type = 'dropdown';
$field->placeholder = $this->emptyOption; $field->placeholder = $this->emptyOption;
$foreignKey = $relationObj->getForeignKey(); $foreignKey = $relationObj->getForeignKey();
$field->value = $this->model->$foreignKey; $field->value = $this->model->$foreignKey;
} }
// It is safe to assume that if the model and related model are of
// the exact same class, then it cannot be related to itself
if ($this->model->exists && (get_class($this->model) == get_class($relatedObj))) {
$query->where($relatedObj->getKeyName(), '<>', $this->model->id);
}
if (in_array('October\Rain\Database\Traits\NestedTree', class_uses($relatedObj))) // It is safe to assume that if the model and related model are of
// the exact same class, then it cannot be related to itself
if ($this->model->exists && (get_class($this->model) == get_class($relatedObj))) {
$query->where($relatedObj->getKeyName(), '<>', $this->model->id);
}
if (in_array('October\Rain\Database\Traits\NestedTree', class_uses($relatedObj)))
$field->options = $query->listsNested($this->nameColumn, $relatedObj->getKeyName()); $field->options = $query->listsNested($this->nameColumn, $relatedObj->getKeyName());
else else
$field->options = $query->lists($this->nameColumn, $relatedObj->getKeyName()); $field->options = $query->lists($this->nameColumn, $relatedObj->getKeyName());
return $this->renderFormField = $field; return $this->renderFormField = $field;
} }
/** /**

View File

@ -9,13 +9,13 @@
style="width:100%" style="width:100%"
class="control-datagrid" class="control-datagrid"
data-control="datagrid" data-control="datagrid"
data-data-locker="#<?= $this->getId('datalocker') ?>" data-data-locker="#<?= $this->getId('dataLocker') ?>"
data-autocomplete-handler="<?= $this->getEventHandler('onAutocomplete') ?>" data-autocomplete-handler="<?= $this->getEventHandler('onAutocomplete') ?>"
></div> ></div>
<input <input
type="hidden" type="hidden"
id="<?= $this->getId('datalocker') ?>" id="<?= $this->getId('dataLocker') ?>"
name="<?= $name ?>" name="<?= $name ?>"
value="<?= e($value) ?>" value="<?= e($value) ?>"
/> />

View File

@ -0,0 +1,81 @@
/*
* RecordFinder plugin
*
* Data attributes:
* - data-control="recordfinder" - enables the plugin on an element
* - data-option="value" - an option with a value
*
* JavaScript API:
* $('a#someElement').recordFinder({ option: 'value' })
*
* Dependences:
* - Some other plugin (filename.js)
*/
+function ($) { "use strict";
// RECORDFINDER CLASS DEFINITION
// ============================
var RecordFinder = function(element, options) {
var self = this
this.options = options
this.$el = $(element)
}
RecordFinder.DEFAULTS = {
refreshHandler: null,
dataLocker: null
}
RecordFinder.prototype.updateRecord = function(linkEl, recordId) {
if (!this.options.dataLocker) return
var self = this
$(this.options.dataLocker).val(recordId)
this.$el.request(this.options.refreshHandler, {
success: function(data) {
this.success(data)
$(self.options.dataLocker).trigger('change')
}
})
$(linkEl).closest('.recordfinder-popup').popup('hide')
}
// RECORDFINDER PLUGIN DEFINITION
// ============================
var old = $.fn.recordFinder
$.fn.recordFinder = function (option) {
var args = Array.prototype.slice.call(arguments, 1), result
this.each(function () {
var $this = $(this)
var data = $this.data('oc.recordfinder')
var options = $.extend({}, RecordFinder.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.recordfinder', (data = new RecordFinder(this, options)))
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : this
}
$.fn.recordFinder.Constructor = RecordFinder
// RECORDFINDER NO CONFLICT
// =================
$.fn.recordFinder.noConflict = function () {
$.fn.recordFinder = old
return this
}
// RECORDFINDER DATA-API
// ===============
$(document).render(function () {
$('[data-control="recordfinder"]').recordFinder()
})
}(window.jQuery);

View File

@ -1,7 +1,32 @@
<div class="field-recordfinder"> <div
class="field-recordfinder"
data-control="recordfinder"
data-refresh-handler="<?= $this->getEventHandler('onRefresh') ?>"
data-data-locker="#<?= $field->getId() ?>"
id="<?= $this->getId() ?>">
<span class="form-control"> <span class="form-control">
<!-- Hello<br /><small>sam@daftspunk.com</small> --> <?php if ($value): ?>
<span class="text-muted">Click the <i class="icon-search"></i> button to find a user</span> <?= $nameValue ?: 'Undefined' ?>
<?php if ($descriptionValue): ?>
<br /><small><?= $descriptionValue ?></small>
<?php endif ?>
<?php else: ?>
<span class="text-muted"><?= $prompt ?></span>
<?php endif ?>
</span> </span>
<button class="btn btn-default" type="button"><i class="icon-search"></i></button> <button
class="btn btn-default"
data-control="popup"
data-handler="<?= $this->getEventHandler('onFindRecord') ?>"
data-request-data="recordfinder_flag: 1"
type="button">
<i class="icon-search"></i>
</button>
<input
type="hidden"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($value) ?>"
/>
</div> </div>

View File

@ -0,0 +1,19 @@
<div id="<?= $this->getId('popup') ?>" class="recordfinder-popup">
<?= Form::open() ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title">Find Record</h4>
</div>
<?= $listWidget->render() ?>
<div class="modal-footer">
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
Cancel
</button>
</div>
<?= Form::close() ?>
</div>

View File

@ -248,11 +248,24 @@ class Form extends WidgetBase
/** /**
* Event handler for refreshing the form. * Event handler for refreshing the form.
*/ */
public function onRender() public function onRefresh()
{ {
$this->setFormValues();
$this->prepareVars();
$result = []; $result = [];
$saveData = $this->getSaveData();
/*
* Extensibility
*/
$eventResults = $this->fireEvent('form.refreshBefore', [$this, $saveData]) + Event::fire('backend.form.refreshBefore', [$this, $saveData]);
foreach ($eventResults as $eventResult)
$saveData = $eventResult + $saveData;
/*
* Set the form variables and prepare the widget
*/
$this->setFormValues($saveData);
$this->prepareVars();
/* /*
* If an array of fields is supplied, update specified fields individually. * If an array of fields is supplied, update specified fields individually.
@ -264,7 +277,7 @@ class Form extends WidgetBase
continue; continue;
$fieldObject = $this->allFields[$field]; $fieldObject = $this->allFields[$field];
$result['#' . $fieldObject->getId('group')] = $this->renderField($fieldObject); $result['#' . $fieldObject->getId('group')] = $this->makePartial('field', ['field' => $fieldObject]);
} }
} }
@ -274,6 +287,13 @@ class Form extends WidgetBase
if (empty($result)) if (empty($result))
$result = ['#'.$this->getId() => $this->makePartial('form')]; $result = ['#'.$this->getId() => $this->makePartial('form')];
/*
* Extensibility
*/
$eventResults = $this->fireEvent('form.refresh', [$this, $result]) + Event::fire('backend.form.refresh', [$this, $result]);
foreach ($eventResults as $eventResult)
$result = $eventResult + $result;
return $result; return $result;
} }

View File

@ -211,7 +211,7 @@ class Lists extends WidgetBase
/** /**
* Event handler for refreshing the list. * Event handler for refreshing the list.
*/ */
public function onRender() public function onRefresh()
{ {
$this->prepareVars(); $this->prepareVars();
return ['#'.$this->getId() => $this->makePartial('list')]; return ['#'.$this->getId() => $this->makePartial('list')];
@ -223,7 +223,7 @@ class Lists extends WidgetBase
public function onPaginate() public function onPaginate()
{ {
App::make('paginator')->setCurrentPage(post('page')); App::make('paginator')->setCurrentPage(post('page'));
return $this->onRender(); return $this->onRefresh();
} }
/** /**
@ -760,7 +760,7 @@ class Lists extends WidgetBase
*/ */
App::make('paginator')->setCurrentPage(post('page')); App::make('paginator')->setCurrentPage(post('page'));
return $this->onRender(); return $this->onRefresh();
} }
} }
@ -867,7 +867,7 @@ class Lists extends WidgetBase
$this->putSession('order', post('column_order')); $this->putSession('order', post('column_order'));
$this->putSession('per_page', post('records_per_page', $this->recordsPerPage)); $this->putSession('per_page', post('records_per_page', $this->recordsPerPage));
return $this->onRender(); return $this->onRefresh();
} }
/** /**
@ -938,7 +938,7 @@ class Lists extends WidgetBase
public function onToggleTreeNode() public function onToggleTreeNode()
{ {
$this->putSession('tree_node_status_' . post('node_id'), post('status') ? 0 : 1); $this->putSession('tree_node_status_' . post('node_id'), post('status') ? 0 : 1);
return $this->onRender(); return $this->onRefresh();
} }
} }

View File

@ -1,6 +1,6 @@
<div <div
data-control="formwidget" data-control="formwidget"
data-refresh-handler="<?= $this->getEventHandler('onRender') ?>" data-refresh-handler="<?= $this->getEventHandler('onRefresh') ?>"
class="form-widget form-elements layout" class="form-widget form-elements layout"
role="form" role="form"
id="<?= $this->getId() ?>"> id="<?= $this->getId() ?>">

View File

@ -1,6 +1,6 @@
<div <div
data-control="formwidget" data-control="formwidget"
data-refresh-handler="<?= $this->getEventHandler('onRender') ?>" data-refresh-handler="<?= $this->getEventHandler('onRefresh') ?>"
class="layout-row" class="layout-row"
role="form" role="form"
id="<?= $this->getId($renderSection.'Container') ?>"> id="<?= $this->getId($renderSection.'Container') ?>">