From 784bdb326ecfc5b6d6b5618ac5c31b94a78c1ef7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 26 Apr 2013 22:10:06 -0400 Subject: [PATCH] refactoring activeform. --- framework/helpers/base/Html.php | 491 +++++++++++++++++++++++++++++++++++++- framework/widgets/ActiveField.php | 321 +++++++++++++++++++++++++ framework/widgets/ActiveForm.php | 235 +++--------------- 3 files changed, 833 insertions(+), 214 deletions(-) create mode 100644 framework/widgets/ActiveField.php diff --git a/framework/helpers/base/Html.php b/framework/helpers/base/Html.php index 6f30716..110dad9 100644 --- a/framework/helpers/base/Html.php +++ b/framework/helpers/base/Html.php @@ -10,6 +10,7 @@ namespace yii\helpers\base; use Yii; use yii\base\InvalidParamException; use yii\web\Request; +use yii\base\Model; /** * Html provides a set of static methods for generating commonly used HTML tags. @@ -397,7 +398,7 @@ class Html /** * Generates a label tag. * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code - * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * such as an image tag. If this is is coming from end users, you should [[encode()]] * it to prevent XSS attacks. * @param string $for the ID of the HTML element that this label is associated with. * If this is null, the "for" attribute will not be generated. @@ -609,7 +610,7 @@ class Html * @param string $name the name attribute. * @param boolean $checked whether the radio button should be checked. * @param string $value the value attribute. If it is null, the value attribute will not be rendered. - * @param array $options the tag options in terms of name-value pairs. The following options are supported: + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: * * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, @@ -639,7 +640,7 @@ class Html * @param string $name the name attribute. * @param boolean $checked whether the checkbox should be checked. * @param string $value the value attribute. If it is null, the value attribute will not be rendered. - * @param array $options the tag options in terms of name-value pairs. The following options are supported: + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: * * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, @@ -676,7 +677,7 @@ class Html * * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are supported: + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: * * - prompt: string, a prompt text to be displayed as the first option; * - options: array, the attributes for the select option tags. The array keys must be valid option values, @@ -716,7 +717,7 @@ class Html * * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in * the labels will also be HTML-encoded. - * @param array $options the tag options in terms of name-value pairs. The following options are supported: + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: * * - prompt: string, a prompt text to be displayed as the first option; * - options: array, the attributes for the select option tags. The array keys must be valid option values, @@ -772,7 +773,7 @@ class Html * @param array $items the data item used to generate the checkboxes. * The array keys are the labels, while the array values are the corresponding checkbox values. * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the checkbox list. The following options are supported: + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: * * - unselect: string, the value that should be submitted when none of the checkboxes is selected. * By setting this option, a hidden input will be generated. @@ -830,7 +831,7 @@ class Html * @param array $items the data item used to generate the radio buttons. * The array keys are the labels, while the array values are the corresponding radio button values. * Note that the labels will NOT be HTML-encoded, while the values will. - * @param array $options options (name => config) for the radio button list. The following options are supported: + * @param array $options options (name => config) for the radio button list. The following options are specially handled: * * - unselect: string, the value that should be submitted when none of the radio buttons is selected. * By setting this option, a hidden input will be generated. @@ -876,6 +877,373 @@ class Html } /** + * Generates a label tag for the given model attribute. + * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * The following options are specially handled: + * + * - label: this specifies the label to be displayed. If this is not set, [[Model::getAttributeLabel()]] + * will be called to get the label for display. Note that this will NOT be [[encoded()]]. + * + * @return string the generated label tag + */ + public static function activeLabel($model, $attribute, $options = array()) + { + $attribute = static::getAttributeName($attribute); + $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); + $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); + return static::label($label, $for, $options); + } + + /** + * Generates an input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param string $type the input type (e.g. 'text', 'password') + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeInput($type, $model, $attribute, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::input($type, $name, $value, $options); + } + + /** + * Generates a text input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeTextInput($model, $attribute, $options = array()) + { + return static::activeInput('text', $model, $attribute, $options); + } + + /** + * Generates a hidden input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeHiddenInput($model, $attribute, $options = array()) + { + return static::activeInput('hidden', $model, $attribute, $options); + } + + /** + * Generates a password input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activePasswordInput($model, $attribute, $options = array()) + { + return static::activeInput('password', $model, $attribute, $options); + } + + /** + * Generates a file input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public static function activeFileInput($model, $attribute, $options = array()) + { + return static::activeInput('file', $model, $attribute, $options); + } + + /** + * Generates a textarea tag for the given model attribute. + * The model attribute value will be used as the content in the textarea. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated textarea tag + */ + public static function activeTextarea($model, $attribute, $options = array()) + { + $name = static::getInputName($model, $attribute); + $value = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::textarea($name, $value, $options); + } + + /** + * Generates a radio button tag for the given model attribute. + * This method will generate the "name" tag attribute automatically unless it is explicitly specified in `$options`. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param string $value the value tag attribute. If it is null, the value attribute will not be rendered. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public static function activeRadio($model, $attribute, $value = '1', $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['unchecked'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::radio($name, $checked, $value, $options); + } + + /** + * Generates a checkbox tag for the given model attribute. + * This method will generate the "name" tag attribute automatically unless it is explicitly specified in `$options`. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param string $value the value tag attribute. If it is null, the value attribute will not be rendered. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public static function activeCheckbox($model, $attribute, $value = '1', $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('uncheck', $options)) { + $options['unchecked'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::checkbox($name, $checked, $value, $options); + } + + /** + * Generates a drop-down list for the given model attribute. + * The selection of the drop-down list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public static function activeDropDownList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::dropDownList($name, $checked, $items, $options); + } + + /** + * Generates a list box. + * The selection of the list box is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public static function activeListBox($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::listBox($name, $checked, $items, $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * The selection of the checkbox list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return string the generated checkbox list + */ + public static function activeCheckboxList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + return static::checkboxList($name, $checked, $items, $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * The selection of the radio buttons is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return string the generated radio button list + */ + public static function activeRadioList($model, $attribute, $items, $options = array()) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $checked = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = '0'; + } + return static::radioList($name, $checked, $items, $options); + } + + /** * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. * @param string|array $selection the selected value(s). This can be either a string for single selection * or an array for multiple selections. @@ -995,4 +1363,113 @@ class Html return Yii::getAlias($url); } } + + /** + * Returns the real attribute name from the given attribute expression. + * + * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. + * It is mainly used in tabular data input and/or input of array type. Below are some examples: + * + * - `[0]content` is used in tabular data input to represent the "content" attribute + * for the first model in tabular input; + * - `dates[0]` represents the first array element of the "dates" attribute; + * - `[0]dates[0]` represents the first array element of the "dates" attribute + * for the first model in tabular input. + * + * If `$attribute` has neither prefix nor suffix, it will be returned back without change. + * @param string $attribute the attribute name or expression + * @return string the attribute name without prefix and suffix. + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeName($attribute) + { + if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + return $matches[2]; + } else { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + } + + /** + * Returns the value of the specified attribute name or expression. + * + * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. + * See [[getAttributeName()]] for more details about attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return mixed the corresponding attribute value + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeValue($model, $attribute) + { + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $attribute = $matches[2]; + $index = $matches[3]; + if ($index === '') { + return $model->$attribute; + } else { + $value = $model->$attribute; + foreach (explode('][', trim($index, '[]')) as $id) { + if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { + $value = $value[$id]; + } else { + return null; + } + } + return $value; + } + } + + /** + * Generates an appropriate input name for the specified attribute name or expression. + * + * This method generates a name that can be used as the input name to collect user input + * for the specified attribute. The name is generated according to the [[Model::formName|form name]] + * of the model and the given attribute name. For example, if the form name of the `Post` model + * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. + * + * See [[getAttributeName()]] for explanation of attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return string the generated input name + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputName($model, $attribute) + { + $formName = $model->formName(); + if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $prefix = $matches[1]; + $attribute = $matches[2]; + $suffix = $matches[3]; + if ($formName === '' && $prefix === '') { + return $attribute . $suffix; + } elseif ($formName !== '') { + return $formName . $prefix . "[$attribute]" . $suffix; + } else { + throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.'); + } + } + + /** + * Generates an appropriate input ID for the specified attribute name or expression. + * + * This method converts the result [[getInputName()]] into a valid input ID. + * For example, [[getInputName()]] returns `Post[content]`, this method will return `Post-method`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. + * @return string the generated input ID + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputId($model, $attribute) + { + $name = static::getInputName($model, $attribute); + return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); + } + } diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php new file mode 100644 index 0000000..50b9619 --- /dev/null +++ b/framework/widgets/ActiveField.php @@ -0,0 +1,321 @@ + + * @since 2.0 + */ +class ActiveField extends Component +{ + /** + * @var ActiveForm + */ + public $form; + /** + * @var \yii\base\Model + */ + public $model; + /** + * @var string + */ + public $attribute; + /** + * @var array + */ + public $options; + + public function begin() + { + return Html::beginTag('div', $this->options); + } + + public function end() + { + return Html::endTag('div'); + } + + public function error($options = array()) + { + if (empty($options)) { + $options = $this->form->errorOptions; + } + $attribute = Html::getAttributeName($this->attribute); + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag']); + $error = $this->model->getFirstError($attribute); + return Html::tag($tag, Html::encode($error), $options); + } + + public function label($options = array()) + { + if (empty($options)) { + $options = $this->form->labelOptions; + } + return Html::activeLabel($this->model, $this->attribute, $options); + } + + /** + * Generates an input tag for the given model attribute. + * @param string $type the input type (e.g. 'text', 'password') + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public function input($type, $options = array()) + { + return Html::activeInput($type, $this->model, $this->attribute, $options); + } + + /** + * Generates a text input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public function textInput($options = array()) + { + return Html::activeTextInput($this->model, $this->attribute, $options); + } + + /** + * Generates a hidden input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public function hiddenInput($options = array()) + { + return Html::activeHiddenInput($this->model, $this->attribute, $options); + } + + /** + * Generates a password input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public function passwordInput($options = array()) + { + return Html::activeHiddenInput($this->model, $this->attribute, $options); + } + + /** + * Generates a file input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated input tag + */ + public function fileInput($options = array()) + { + return Html::activeFileInput($this->model, $this->attribute, $options); + } + + /** + * Generates a textarea tag for the given model attribute. + * The model attribute value will be used as the content in the textarea. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * @return string the generated textarea tag + */ + public function textarea($options = array()) + { + return Html::activeTextarea($this->model, $this->attribute, $options); + } + + /** + * Generates a radio button tag for the given model attribute. + * This method will generate the "name" tag attribute automatically unless it is explicitly specified in `$options`. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param string $value the value tag attribute. If it is null, the value attribute will not be rendered. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated radio button tag + */ + public function radio($value = '1', $options = array()) + { + return Html::activeRadio($this->model, $this->attribute, $value, $options); + } + + /** + * Generates a checkbox tag for the given model attribute. + * This method will generate the "name" tag attribute automatically unless it is explicitly specified in `$options`. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param string $value the value tag attribute. If it is null, the value attribute will not be rendered. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated checkbox tag + */ + public function checkbox($value = '1', $options = array()) + { + return Html::activeCheckbox($this->model, $this->attribute, $value, $options); + } + + /** + * Generates a drop-down list for the given model attribute. + * The selection of the drop-down list is taken from the value of the model attribute. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated drop-down list tag + */ + public function DropDownList($items, $options = array()) + { + return Html::activeDropDownList($this->model, $this->attribute, $items, $options); + } + + /** + * Generates a list box. + * The selection of the list box is taken from the value of the model attribute. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return string the generated list box tag + */ + public function listBox($items, $options = array()) + { + return Html::activeListBox($this->model, $this->attribute, $items, $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * The selection of the checkbox list is taken from the value of the model attribute. + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return string the generated checkbox list + */ + public function checkboxList($items, $options = array()) + { + return Html::activeCheckboxList($this->model, $this->attribute, $items, $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * The selection of the radio buttons is taken from the value of the model attribute. + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return string the generated radio button list + */ + public function radioList($items, $options = array()) + { + return Html::activeRadioList($this->model, $this->attribute, $items, $options); + } +} \ No newline at end of file diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index 79331e5..7bb790f 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -8,6 +8,7 @@ namespace yii\widgets; use Yii; +use yii\base\InvalidCallException; use yii\base\InvalidParamException; use yii\base\Widget; use yii\base\Model; @@ -31,25 +32,26 @@ class ActiveForm extends Widget * Defaults to 'post'. */ public $method = 'post'; + public $options = array(); + public $errorOptions = array('tag' => 'div', 'class' => 'yii-error-message'); + public $labelOptions = array('class' => 'yii-input-label'); /** * @var string the default CSS class for the error summary container. * @see errorSummary() */ - public $errorSummaryClass = 'yii-error-summary'; - public $errorMessageClass = 'yii-error-message'; + public $errorSummaryCssClass = 'yii-error-summary'; /** * @var string the default CSS class that indicates an input has error. */ - public $errorClass = 'yii-error'; + public $errorCssClass = 'yii-error'; /** * @var string the default CSS class that indicates an input validated successfully. */ - public $successClass = 'yii-success'; - + public $successCssClass = 'yii-success'; /** * @var string the default CSS class that indicates an input is currently being validated. */ - public $validatingClass = 'yii-validating'; + public $validatingCssClass = 'yii-validating'; /** * @var boolean whether to enable client-side data validation. Defaults to false. * When this property is set true, client-side validation will be performed by validators @@ -57,9 +59,7 @@ class ActiveForm extends Widget */ public $enableClientValidation = false; - public $options = array(); - - + public $fieldClass = 'yii\widgets\ActiveField'; /** * Initializes the widget. * This renders the form open tag. @@ -108,9 +108,9 @@ class ActiveForm extends Widget unset($options['showAll'], $options['header'], $options['footer'], $options['container']); if (!isset($options['class'])) { - $options['class'] = $this->errorSummaryClass; + $options['class'] = $this->errorSummaryCssClass; } else { - $options['class'] .= ' ' . $this->errorSummaryClass; + $options['class'] .= ' ' . $this->errorSummaryCssClass; } if ($lines !== array()) { @@ -124,209 +124,30 @@ class ActiveForm extends Widget } /** - * @param Model $model - * @param string $attribute - * @param array $options - * @return string - */ - public function error($model, $attribute, $options = array()) - { - $attribute = $this->getAttributeName($attribute); - $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag']); - $error = $model->getFirstError($attribute); - return Html::tag($tag, Html::encode($error), $options); - } - - /** - * @param Model $model - * @param string $attribute - * @param array $options - * @return string + * @var ActiveField[] */ - public function label($model, $attribute, $options = array()) - { - $attribute = $this->getAttributeName($attribute); - $label = isset($options['label']) ? $options['label'] : Html::encode($model->getAttributeLabel($attribute)); - $for = array_key_exists('for', $options) ? $options['for'] : $this->getInputId($model, $attribute); - return Html::label($label, $for, $options); - } + private $_fieldStack = array(); - /** - * @param string $type - * @param Model $model - * @param string $attribute - * @param array $options - * - * @return string - */ - public function input($type, $model, $attribute, $options = array()) + public function beginField($model, $attribute, $options = array()) { - $value = $this->getAttributeValue($model, $attribute); - $name = $this->getInputName($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = $this->getInputId($model, $attribute); - } - return Html::input($type, $name, $value, $options); - } - - public function textInput($model, $attribute, $options = array()) - { - return $this->input('text', $model, $attribute, $options); - } - - public function hiddenInput($model, $attribute, $options = array()) - { - return $this->input('hidden', $model, $attribute, $options); - } - - public function passwordInput($model, $attribute, $options = array()) - { - return $this->input('password', $model, $attribute, $options); - } - - public function fileInput($model, $attribute, $options = array()) - { - return $this->input('file', $model, $attribute, $options); - } - - public function textarea($model, $attribute, $options = array()) - { - $value = $this->getAttributeValue($model, $attribute); - $name = $this->getInputName($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = $this->getInputId($model, $attribute); - } - return Html::textarea($name, $value, $options); - } - - public function radio($model, $attribute, $value = '1', $options = array()) - { - $checked = $this->getAttributeValue($model, $attribute); - $name = $this->getInputName($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['unchecked'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = $this->getInputId($model, $attribute); - } - return Html::radio($name, $checked, $value, $options); + $field = Yii::createObject(array( + 'class' => $this->fieldClass, + 'model' => $model, + 'attribute' => $attribute, + 'form' => $this, + 'options' => $options, + )); + echo $field->begin(); + return $this->_fieldStack[] = $field; } - public function checkbox($model, $attribute, $value = '1', $options = array()) + public function endField() { - $checked = $this->getAttributeValue($model, $attribute); - $name = $this->getInputName($model, $attribute); - if (!array_key_exists('uncheck', $options)) { - $options['unchecked'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = $this->getInputId($model, $attribute); - } - return Html::checkbox($name, $checked, $value, $options); - } - - public function dropDownList($model, $attribute, $items, $options = array()) - { - $checked = $this->getAttributeValue($model, $attribute); - $name = $this->getInputName($model, $attribute); - if (!array_key_exists('id', $options)) { - $options['id'] = $this->getInputId($model, $attribute); - } - return Html::dropDownList($name, $checked, $items, $options); - } - - public function listBox($model, $attribute, $items, $options = array()) - { - $checked = $this->getAttributeValue($model, $attribute); - $name = $this->getInputName($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - if (!array_key_exists('id', $options)) { - $options['id'] = $this->getInputId($model, $attribute); - } - return Html::listBox($name, $checked, $items, $options); - } - - public function checkboxList($model, $attribute, $items, $options = array()) - { - $checked = $this->getAttributeValue($model, $attribute); - $name = $this->getInputName($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - return Html::checkboxList($name, $checked, $items, $options); - } - - public function radioList($model, $attribute, $items, $options = array()) - { - $checked = $this->getAttributeValue($model, $attribute); - $name = $this->getInputName($model, $attribute); - if (!array_key_exists('unselect', $options)) { - $options['unselect'] = '0'; - } - return Html::radioList($name, $checked, $items, $options); - } - - public function getAttributeValue($model, $attribute) - { - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $attribute = $matches[2]; - $index = $matches[3]; - if ($index === '') { - return $model->$attribute; + if ($this->_fieldStack !== array()) { + $field = array_pop($this->_fieldStack); + echo $field->end(); } else { - $value = $model->$attribute; - foreach (explode('][', trim($index, '[]')) as $id) { - if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { - $value = $value[$id]; - } else { - return null; - } - } - return $value; + throw new InvalidCallException('The "beginField" and "endField" calls are not matching.'); } } - - public function getAttributeName($attribute) - { - if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - return $matches[2]; - } else { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - } - - /** - * @param Model $model - * @param string $attribute - * @return string - * @throws \yii\base\InvalidParamException - */ - public static function getInputName($model, $attribute) - { - $formName = $model->formName(); - if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) { - throw new InvalidParamException('Attribute name must contain word characters only.'); - } - $prefix = $matches[1]; - $attribute = $matches[2]; - $suffix = $matches[3]; - if ($formName === '' && $prefix === '') { - return $attribute . $suffix; - } elseif ($formName !== '') { - return $formName . $prefix . "[$attribute]" . $suffix; - } else { - throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.'); - } - } - - public static function getInputId($model, $attribute) - { - $name = static::getInputName($model, $attribute); - return str_replace(array('[]', '][', '[', ']', ' '), array('', '-', '-', '', '-'), $name); - } }