Form fields can now define "depends" to refresh individual fields when they are changed
Form field options can now be deferred using a closure
This commit is contained in:
parent
dd3bb5d918
commit
4a9fe06d50
|
|
@ -8749,11 +8749,13 @@ html.cssanimations .loading-indicator.transparent > span {
|
|||
margin-top: -10px;
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
.loading-indicator-container.size-form-field .loading-indicator,
|
||||
.loading-indicator-container.size-input-text .loading-indicator {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.loading-indicator-container.size-form-field .loading-indicator > span,
|
||||
.loading-indicator-container.size-input-text .loading-indicator > span {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
|
@ -8764,6 +8766,13 @@ html.cssanimations .loading-indicator.transparent > span {
|
|||
height: 23px;
|
||||
background-size: 23px 23px;
|
||||
}
|
||||
.loading-indicator-container.size-form-field .loading-indicator > span {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
html.cssanimations .cursor-loading-indicator {
|
||||
background: transparent url(../images/loading-indicator-transparent.svg) no-repeat 50% 50%;
|
||||
-webkit-animation: spin 1s linear infinite;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
*
|
||||
* JavaScript API:
|
||||
*
|
||||
* $('#buttons').loadIndicator({text: 'Saving...', 'opaque': true}) - display the indicator
|
||||
* $('#buttons').loadIndicator({text: 'Saving...', opaque: true}) - display the indicator in a solid (opaque) state
|
||||
* $('#buttons').loadIndicator({text: 'Saving...'}) - display the indicator in a transparent state
|
||||
* $('#buttons').loadIndicator('hide') - display the indicator
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ html.cssanimations {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
.loading-indicator-container.size-form-field .loading-indicator,
|
||||
.loading-indicator-container.size-input-text .loading-indicator {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
|
|
@ -83,6 +85,14 @@ html.cssanimations {
|
|||
}
|
||||
}
|
||||
|
||||
.loading-indicator-container.size-form-field .loading-indicator > span {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-size: 20px 20px;
|
||||
}
|
||||
|
||||
//
|
||||
// Cursor loading indicator
|
||||
// --------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@
|
|||
padding-left: 20px;
|
||||
li {
|
||||
border-top: 1px solid transparent;
|
||||
.active {
|
||||
&.active {
|
||||
border-top: 1px solid @color-tab-content-border;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class FormField
|
|||
/**
|
||||
* @var string Field options.
|
||||
*/
|
||||
public $options = [];
|
||||
public $options;
|
||||
|
||||
/**
|
||||
* @var string Specifies a side. Possible values: auto, left, right, full
|
||||
|
|
@ -127,6 +127,11 @@ class FormField
|
|||
*/
|
||||
public $config;
|
||||
|
||||
/**
|
||||
* @var array Other field names this field depends on, when the other fields are modified, this field will update.
|
||||
*/
|
||||
public $depends;
|
||||
|
||||
public function __construct($columnName, $label)
|
||||
{
|
||||
$this->columnName = $columnName;
|
||||
|
|
@ -167,9 +172,23 @@ class FormField
|
|||
* @param array $value
|
||||
* @return self
|
||||
*/
|
||||
public function options($value = [])
|
||||
public function options($value = null)
|
||||
{
|
||||
if ($value === null) {
|
||||
if (is_array($this->options)) {
|
||||
return $this->options;
|
||||
}
|
||||
elseif (is_callable($this->options)) {
|
||||
$callable = $this->options;
|
||||
return $callable();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
else {
|
||||
$this->options = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -209,6 +228,7 @@ class FormField
|
|||
if (isset($config['default'])) $this->defaults = $config['default'];
|
||||
if (isset($config['cssClass'])) $this->cssClass = $config['cssClass'];
|
||||
if (isset($config['attributes'])) $this->attributes = $config['attributes'];
|
||||
if (isset($config['depends'])) $this->depends = $config['depends'];
|
||||
if (isset($config['path'])) $this->path = $config['path'];
|
||||
|
||||
if (array_key_exists('required', $config)) $this->required = $config['required'];
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ class Form extends WidgetBase
|
|||
*/
|
||||
public function loadAssets()
|
||||
{
|
||||
$this->addJs('js/form.js', 'core');
|
||||
$this->addJs('js/october.form.js', 'core');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -205,6 +205,57 @@ class Form extends WidgetBase
|
|||
$this->vars['secondaryTabs'] = $this->secondaryTabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets form field values.
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function setFormValues($data = null)
|
||||
{
|
||||
if ($data == null)
|
||||
$data = $this->getSaveData();
|
||||
|
||||
$this->model->fill($data);
|
||||
$this->data = (object) array_merge((array) $this->data, (array) $data);
|
||||
|
||||
foreach ($this->allFields as $field)
|
||||
$field->value = $this->getFieldValue($field);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler for refreshing the form.
|
||||
*/
|
||||
public function onRender()
|
||||
{
|
||||
$this->setFormValues();
|
||||
$this->prepareVars();
|
||||
$result = [];
|
||||
|
||||
/*
|
||||
* If an array of fields is supplied, update specified fields individually.
|
||||
*/
|
||||
if (($updateFields = post('fields')) && is_array($updateFields)) {
|
||||
|
||||
foreach ($updateFields as $field) {
|
||||
if (!isset($this->allFields[$field]))
|
||||
continue;
|
||||
|
||||
$fieldObject = $this->allFields[$field];
|
||||
$result['#' . $fieldObject->getId('container')] = $this->renderField($fieldObject);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the whole form
|
||||
*/
|
||||
if (empty($result))
|
||||
$result = ['#'.$this->getId() => $this->makePartial('form')];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a flat array of form fields from the configuration.
|
||||
* Also slots fields in to their respective tabs.
|
||||
|
|
@ -400,14 +451,25 @@ class Form extends WidgetBase
|
|||
*/
|
||||
$optionModelTypes = ['dropdown', 'radio', 'checkboxlist'];
|
||||
if (in_array($field->type, $optionModelTypes)) {
|
||||
|
||||
/*
|
||||
* Defer the execution of option data collection
|
||||
*/
|
||||
$field->options(function() use ($field, $config) {
|
||||
$fieldOptions = (isset($config['options'])) ? $config['options'] : null;
|
||||
$fieldOptions = $this->getOptionsFromModel($field, $fieldOptions);
|
||||
$field->options($fieldOptions);
|
||||
return $fieldOptions;
|
||||
});
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a field type is a widget or not
|
||||
* @param string $fieldType
|
||||
* @return boolean
|
||||
*/
|
||||
private function isFormWidget($fieldType)
|
||||
{
|
||||
if ($fieldType === null)
|
||||
|
|
@ -521,6 +583,22 @@ class Form extends WidgetBase
|
|||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a HTML encoded value containing the other fields this
|
||||
* field depends on
|
||||
* @param use Backend\Classes\FormField $field
|
||||
* @return string
|
||||
*/
|
||||
public function getFieldDepends($field)
|
||||
{
|
||||
if (!$field->depends)
|
||||
return;
|
||||
|
||||
$depends = is_array($field->depends) ? $field->depends : [$field->depends];
|
||||
$depends = htmlspecialchars(json_encode($depends), ENT_QUOTES, 'UTF-8');
|
||||
return $depends;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns postback data from a submitted form.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
/*
|
||||
* Form Behavior
|
||||
*/
|
||||
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Form Widget
|
||||
*
|
||||
* Dependences:
|
||||
* - Nil
|
||||
*/
|
||||
+function ($) { "use strict";
|
||||
|
||||
var FormWidget = function (element, options) {
|
||||
|
||||
var $el = this.$el = $(element);
|
||||
|
||||
this.options = options || {};
|
||||
|
||||
this.bindDependants()
|
||||
}
|
||||
|
||||
FormWidget.DEFAULTS = {
|
||||
refreshHandler: null
|
||||
}
|
||||
|
||||
/*
|
||||
* Bind dependant fields
|
||||
*/
|
||||
FormWidget.prototype.bindDependants = function() {
|
||||
var self = this,
|
||||
form = this.$el,
|
||||
formEl = form.closest('form'),
|
||||
fieldMap = {}
|
||||
|
||||
/*
|
||||
* Map master and slave field map
|
||||
*/
|
||||
form.find('[data-field-depends]').each(function(){
|
||||
var name = $(this).data('column-name'),
|
||||
depends = $(this).data('field-depends')
|
||||
|
||||
$.each(depends, function(index, depend){
|
||||
if (!fieldMap[depend])
|
||||
fieldMap[depend] = { fields: [] }
|
||||
|
||||
fieldMap[depend].fields.push(name)
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
* When a master is updated, refresh its slaves
|
||||
*/
|
||||
$.each(fieldMap, function(columnName, toRefresh){
|
||||
form.find('[data-column-name="'+columnName+'"]')
|
||||
.on('change', 'select, input', function(){
|
||||
formEl.request(self.options.refreshHandler, {
|
||||
data: toRefresh
|
||||
})
|
||||
|
||||
$.each(toRefresh.fields, function(index, field){
|
||||
form.find('[data-column-name="'+field+'"]')
|
||||
.addClass('loading-indicator-container size-form-field')
|
||||
.loadIndicator()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// dependants.on('change', 'select, input', function(){
|
||||
// var depends = $(this).closest('[data-field-depends]').data('field-depends'),
|
||||
// form = $(this).closest('form')
|
||||
|
||||
// if (!form.length || !self.options.refreshHandler)
|
||||
// return
|
||||
|
||||
// form.request(self.options.refreshHandler)
|
||||
// })
|
||||
|
||||
}
|
||||
|
||||
|
||||
// FORM WIDGET PLUGIN DEFINITION
|
||||
// ============================
|
||||
|
||||
var old = $.fn.formWidget
|
||||
|
||||
$.fn.formWidget = function (option) {
|
||||
var args = arguments,
|
||||
result
|
||||
|
||||
this.each(function () {
|
||||
var $this = $(this)
|
||||
var data = $this.data('oc.formwidget')
|
||||
var options = $.extend({}, FormWidget.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||
if (!data) $this.data('oc.formwidget', (data = new FormWidget(this, options)))
|
||||
if (typeof option == 'string') result = data[option].call($this)
|
||||
if (typeof result != 'undefined') return false
|
||||
})
|
||||
|
||||
return result ? result : this
|
||||
}
|
||||
|
||||
$.fn.formWidget.Constructor = FormWidget
|
||||
|
||||
// FORM WIDGET NO CONFLICT
|
||||
// =================
|
||||
|
||||
$.fn.formWidget.noConflict = function () {
|
||||
$.fn.formWidget = old
|
||||
return this
|
||||
}
|
||||
|
||||
// FORM WIDGET DATA-API
|
||||
// ==============
|
||||
|
||||
$(document).render(function(){
|
||||
$('[data-control="formwidget"]').formWidget();
|
||||
})
|
||||
|
||||
}(window.jQuery);
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
<div class="form-group <?= $this->previewMode ? 'form-group-preview' : '' ?> <?= $field->type ?>-field span-<?= $field->span ?> <?= $field->required?'is-required':'' ?> <?= $field->stretch?'layout-relative':'' ?> <?= $field->cssClass ?>">
|
||||
|
||||
<?php if (in_array($field->type, ['checkbox', 'switch'])): ?>
|
||||
|
||||
<?= $this->makePartial('field_'.$field->type, ['field' => $field]) ?>
|
||||
|
|
@ -21,4 +19,3 @@
|
|||
<?php endif ?>
|
||||
|
||||
<?php endif ?>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
<?php
|
||||
$fieldOptions = $field->options();
|
||||
?>
|
||||
<!-- Balloon selector -->
|
||||
<div data-control="balloon-selector" id="<?= $field->getId() ?>" <?= HTML::attributes($field->attributes) ?>>
|
||||
<ul>
|
||||
<?php foreach ($field->options as $value => $text): ?>
|
||||
<?php foreach ($fieldOptions as $value => $text): ?>
|
||||
<li data-value="<?= e($value) ?>" class="<?= $value == $field->value ? 'active' : '' ?>"><?= e($text) ?></li>
|
||||
<?php endforeach ?>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
<!-- Checkbox List -->
|
||||
<?php
|
||||
$fieldOptions = $field->options();
|
||||
$checkedValues = (is_array($field->value)) ? $field->value : [$field->value];
|
||||
?>
|
||||
<?php if (count($field->options)): ?>
|
||||
<!-- Checkbox List -->
|
||||
<?php if (count($fieldOptions)): ?>
|
||||
|
||||
<?php if (count($field->options) > 10): ?>
|
||||
<?php if (count($fieldOptions) > 10): ?>
|
||||
<!-- Quick selection -->
|
||||
<small>
|
||||
<?= e(trans('backend::lang.form.select')) ?>:
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
<div class="control-scrollbar" data-control="scrollbar">
|
||||
<?php endif ?>
|
||||
|
||||
<?php $index = 0; foreach ($field->options as $value => $option): ?>
|
||||
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
|
||||
<?php
|
||||
$index++;
|
||||
$checkboxId = 'checkbox_'.$field->columnName.'_'.$index;
|
||||
|
|
@ -42,7 +43,7 @@
|
|||
</div>
|
||||
<?php endforeach ?>
|
||||
|
||||
<?php if (count($field->options) > 10): ?>
|
||||
<?php if (count($fieldOptions) > 10): ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
$fieldOptions = $field->options();
|
||||
?>
|
||||
<!-- Dropdown -->
|
||||
<?php if ($this->previewMode): ?>
|
||||
<div class="form-control"><?= (isset($field->options[$field->value])) ? e($field->options[$field->value]) : '' ?></div>
|
||||
<div class="form-control"><?= (isset($fieldOptions[$field->value])) ? e($fieldOptions[$field->value]) : '' ?></div>
|
||||
<?php else: ?>
|
||||
<select
|
||||
id="<?= $field->getId() ?>"
|
||||
|
|
@ -10,7 +13,7 @@
|
|||
<?php if ($field->placeholder): ?>
|
||||
<option value=""><?= e(trans($field->placeholder)) ?></option>
|
||||
<?php endif ?>
|
||||
<?php foreach ($field->options as $value => $text): ?>
|
||||
<?php foreach ($fieldOptions as $value => $text): ?>
|
||||
<option
|
||||
<?= $value == $field->value ? 'selected="selected"' : '' ?>
|
||||
value="<?= $value ?>">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
<?php
|
||||
$fieldOptions = $field->options();
|
||||
?>
|
||||
<!-- Radio List -->
|
||||
<?php if (count($field->options)): ?>
|
||||
<?php if (count($fieldOptions)): ?>
|
||||
|
||||
<?php $index = 0; foreach ($field->options as $value => $option): ?>
|
||||
<?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
|
||||
<?php
|
||||
$index++;
|
||||
if (is_string($option))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
<div class="form-widget form-elements layout" role="form" id="<?= $this->getId() ?>">
|
||||
<div
|
||||
data-control="formwidget"
|
||||
data-refresh-handler="<?= $this->getEventHandler('onRender') ?>"
|
||||
class="form-widget form-elements layout"
|
||||
role="form"
|
||||
id="<?= $this->getId() ?>">
|
||||
|
||||
<?php if ($outsideFields): ?>
|
||||
<?= $this->makePartial('form_outside_fields') ?>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
<?php foreach ($fields as $field): ?>
|
||||
<?= $this->makePartial('field', ['field' => $field]) ?>
|
||||
<div
|
||||
class="form-group <?= $this->previewMode ? 'form-group-preview' : '' ?> <?= $field->type ?>-field span-<?= $field->span ?> <?= $field->required?'is-required':'' ?> <?= $field->stretch?'layout-relative':'' ?> <?= $field->cssClass ?>"
|
||||
<?php if ($depends = $this->getFieldDepends($field)): ?>data-field-depends="<?= $depends ?>"<?php endif ?>
|
||||
data-column-name="<?= $field->columnName ?>"
|
||||
id="<?= $field->getId('group') ?>">
|
||||
<?= $this->renderField($field) ?>
|
||||
</div>
|
||||
<?php endforeach ?>
|
||||
Loading…
Reference in New Issue