Commit ae13b059 by Qiang Xue

Fixes #3410: yii.activeForm.js now supports adding/removing fields dynamically

parent 08d4d899
......@@ -133,6 +133,7 @@ Yii Framework 2 Change Log
- Enh #3380: Allow `value` in `defaultValueValidator` to be a closure (Alex-Code)
- Enh #3384: Added callback-style transactions (leandrogehlen, Ragazzo, samdark)
- Enh #3399, #3241: Added support for MS SQL Server older than 2012 (fourteenmeister, samdark)
- Enh #3410: yii.activeForm.js now supports adding/removing fields dynamically (qiangxue)
- Enh #3459: Added logging of errors, which may occur at `yii\caching\FileCache::gc()` (klimov-paul)
- Enh #3472: Added configurable option to encode spaces in dropDownLists and listBoxes (kartik-v)
- Enh #3518: `yii\helpers\Html::encode()` now replaces invalid code sequences with "�" (DaSourcerer)
......
......@@ -22,19 +22,24 @@
}
};
// NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveForm::getClientOptions() as well
var defaults = {
// whether to encode the error summary
encodeErrorSummary: true,
// the jQuery selector for the error summary
errorSummary: undefined,
errorSummary: '.error-summary',
// whether to perform validation before submitting the form.
validateOnSubmit: true,
// the container CSS class representing the corresponding attribute has validation error
errorCssClass: 'error',
errorCssClass: 'has-error',
// the container CSS class representing the corresponding attribute passes validation
successCssClass: 'success',
successCssClass: 'has-success',
// the container CSS class representing the corresponding attribute is being validated
validatingCssClass: 'validating',
// the GET parameter name indicating an AJAX-based validation
ajaxParam: 'ajax',
// the type of data that you're expecting back from the server
ajaxDataType: 'json',
// the URL for performing AJAX-based validation. If not set, it will use the the form's action
validationUrl: undefined,
// a callback that is called before submitting the form. The signature of the callback should be:
......@@ -57,13 +62,10 @@
ajaxBeforeSend: undefined,
// a function to be called when the request finishes on AJAX-based validation. The signature of the callback should be:
// function ($form, jqXHR, textStatus)
ajaxComplete: undefined,
// the GET parameter name indicating an AJAX-based validation
ajaxParam: 'ajax',
// the type of data that you're expecting back from the server
ajaxDataType: 'json'
ajaxComplete: undefined
};
// NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well
var attributeDefaults = {
// a unique ID identifying an attribute (e.g. "loginform-username") in a form
id: undefined,
......@@ -71,16 +73,16 @@
name: undefined,
// the jQuery selector of the container of the input field
container: undefined,
// the jQuery selector of the input field
// the jQuery selector of the input field under the context of the container
input: undefined,
// the jQuery selector of the error tag
error: undefined,
// the jQuery selector of the error tag under the context of the container
error: '.help-block',
// whether to encode the error
encodeError: true,
// whether to perform validation when a change is detected on the input
validateOnChange: false,
validateOnChange: true,
// whether to perform validation when the input loses focus
validateOnBlur: false,
validateOnBlur: true,
// whether to perform validation when the user is typing.
validateOnType: false,
// number of milliseconds that the validation should be delayed when a user is typing in the input field.
......@@ -107,9 +109,12 @@
if (settings.validationUrl === undefined) {
settings.validationUrl = $form.prop('action');
}
$.each(attributes, function (i) {
attributes[i] = $.extend({value: getValue($form, this)}, attributeDefaults, this);
watchAttribute($form, attributes[i]);
});
$form.data('yiiActiveForm', {
settings: settings,
attributes: attributes,
......@@ -117,8 +122,6 @@
validated: false
});
watchAttributes($form, attributes);
/**
* Clean up error status when the form is reset.
* Note that $form.on('reset', ...) does work because the "reset" event does not bubble on IE.
......@@ -134,6 +137,47 @@
});
},
// add a new attribute to the form dynamically.
// please refer to attributeDefaults for the structure of attribute
add: function (attribute) {
var $form = $(this);
attribute = $.extend({value: getValue($form, attribute)}, attributeDefaults, attribute);
$form.data('yiiActiveForm').attributes.push(attribute);
watchAttribute($form, attribute);
},
// remove the attribute with the specified ID from the form
remove: function (id) {
var $form = $(this),
attributes = $form.data('yiiActiveForm').attributes,
index = -1,
attribute;
$.each(attributes, function (i) {
if (attributes[i]['id'] == id) {
index = i;
attribute = attributes[i];
return false;
}
});
if (index >= 0) {
attributes.splice(index, 1);
unwatchAttribute($form, attribute);
}
return attribute;
},
// find an attribute config based on the specified attribute ID
find: function (id) {
var attributes = $(this).data('yiiActiveForm').attributes, result;
$.each(attributes, function (i) {
if (attributes[i]['id'] == id) {
result = attributes[i];
return false;
}
});
return result;
},
destroy: function () {
return this.each(function () {
$(this).unbind('.yiiActiveForm');
......@@ -256,6 +300,33 @@
});
};
var watchAttribute = function ($form, attribute) {
var $input = findInput($form, attribute);
if (attribute.validateOnChange) {
$input.on('change.yiiActiveForm',function () {
validateAttribute($form, attribute, false);
});
}
if (attribute.validateOnBlur) {
$input.on('blur.yiiActiveForm', function () {
if (attribute.status == 0 || attribute.status == 1) {
validateAttribute($form, attribute, !attribute.status);
}
});
}
if (attribute.validateOnType) {
$input.on('keyup.yiiActiveForm', function () {
if (attribute.value !== getValue($form, attribute)) {
validateAttribute($form, attribute, false);
}
});
}
};
var unwatchAttribute = function ($form, attribute) {
findInput($form, attribute).off('.yiiActiveForm');
};
var validateAttribute = function ($form, attribute, forceValidate) {
var data = $form.data('yiiActiveForm');
......
......@@ -693,9 +693,9 @@ class ActiveField extends Component
return [];
}
$options = [];
$enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
$enableAjaxValidation = $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
if ($enableClientValidation) {
$validators = [];
foreach ($this->model->getActiveValidators($attribute) as $validator) {
......@@ -708,39 +708,48 @@ class ActiveField extends Component
$validators[] = $js;
}
}
if (!empty($validators)) {
$options['validate'] = new JsExpression("function (attribute, value, messages, deferred) {" . implode('', $validators) . '}');
}
}
$enableAjaxValidation = $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
if ($enableAjaxValidation) {
$options['enableAjaxValidation'] = 1;
if (!$enableAjaxValidation && (!$enableClientValidation || empty($validators))) {
return [];
}
if ($enableClientValidation && !empty($options['validate']) || $enableAjaxValidation) {
$inputID = Html::getInputId($this->model, $this->attribute);
$options['id'] = $inputID;
$options['name'] = $this->attribute;
foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) {
$options[$name] = $this->$name === null ? $this->form->$name : $this->$name;
}
$options = [];
$options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID";
$options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID";
if (isset($this->selectors['error'])) {
$options['error'] = $this->selectors['error'];
} elseif (isset($this->errorOptions['class'])) {
$options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY));
} else {
$options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span';
}
$inputID = Html::getInputId($this->model, $this->attribute);
$options['id'] = $inputID;
$options['name'] = $this->attribute;
$options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID";
$options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID";
if (isset($this->selectors['error'])) {
$options['error'] = $this->selectors['error'];
} elseif (isset($this->errorOptions['class'])) {
$options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY));
} else {
$options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span';
}
$options['encodeError'] = !isset($this->errorOptions['encode']) || $this->errorOptions['encode'] !== false;
$options['encodeError'] = !isset($this->errorOptions['encode']) || !$this->errorOptions['encode'];
if ($enableAjaxValidation) {
$options['enableAjaxValidation'] = true;
}
foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) {
$options[$name] = $this->$name === null ? $this->form->$name : $this->$name;
}
return $options;
} else {
return [];
if (!empty($validators)) {
$options['validate'] = new JsExpression("function (attribute, value, messages, deferred) {" . implode('', $validators) . '}');
}
// only get the options that are different from the default ones (set in yii.activeForm.js)
return array_diff_assoc($options, [
'validateOnChange' => true,
'validateOnBlur' => true,
'validateOnType' => false,
'validationDelay' => 200,
'encodeError' => true,
'error' => '.help-block',
]);
}
}
......@@ -288,7 +288,17 @@ class ActiveForm extends Widget
}
}
return $options;
// only get the options that are different from the default ones (set in yii.activeForm.js)
return array_diff_assoc($options, [
'encodeErrorSummary' => true,
'errorSummary' => '.error-summary',
'validateOnSubmit' => true,
'errorCssClass' => 'has-error',
'successCssClass' => 'has-success',
'validatingCssClass' => 'validating',
'ajaxParam' => 'ajax',
'ajaxDataType' => 'json',
]);
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment