You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
15 KiB
415 lines
15 KiB
<?php |
|
/** |
|
* @link http://www.yiiframework.com/ |
|
* @copyright Copyright (c) 2008 Yii Software LLC |
|
* @license http://www.yiiframework.com/license/ |
|
*/ |
|
|
|
namespace yii\bootstrap; |
|
|
|
use yii\helpers\ArrayHelper; |
|
|
|
/** |
|
* A Bootstrap 3 enhanced version of [[\yii\widgets\ActiveField]]. |
|
* |
|
* This class adds some useful features to [[\yii\widgets\ActiveField|ActiveField]] to render all |
|
* sorts of Bootstrap 3 form fields in different form layouts: |
|
* |
|
* - [[inputTemplate]] is an optional template to render complex inputs, for example input groups |
|
* - [[horizontalCssClasses]] defines the CSS grid classes to add to label, wrapper, error and hint |
|
* in horizontal forms |
|
* - [[inline]]/[[inline()]] is used to render inline [[checkboxList()]] and [[radioList()]] |
|
* - [[enableError]] can be set to `false` to disable to the error |
|
* - [[enableLabel]] can be set to `false` to disable to the label |
|
* - [[label()]] can be used with a `boolean` argument to enable/disable the label |
|
* |
|
* There are also some new placeholders that you can use in the [[template]] configuration: |
|
* |
|
* - `{beginLabel}`: the opening label tag |
|
* - `{labelTitle}`: the label title for use with `{beginLabel}`/`{endLabel}` |
|
* - `{endLabel}`: the closing label tag |
|
* - `{beginWrapper}`: the opening wrapper tag |
|
* - `{endWrapper}`: the closing wrapper tag |
|
* |
|
* The wrapper tag is only used for some layouts and form elements. |
|
* |
|
* Note that some elements use slightly different defaults for [[template]] and other options. |
|
* You may want to override those predefined templates for checkboxes, radio buttons, checkboxLists |
|
* and radioLists in the [[\yii\widgets\ActiveForm::fieldConfig|fieldConfig]] of the |
|
* [[\yii\widgets\ActiveForm]]: |
|
* |
|
* - [[checkboxTemplate]] the template for checkboxes in default layout |
|
* - [[radioTemplate]] the template for radio buttons in default layout |
|
* - [[horizontalCheckboxTemplate]] the template for checkboxes in horizontal layout |
|
* - [[horizontalRadioTemplate]] the template for radio buttons in horizontal layout |
|
* - [[inlineCheckboxListTemplate]] the template for inline checkboxLists |
|
* - [[inlineRadioListTemplate]] the template for inline radioLists |
|
* |
|
* Example: |
|
* |
|
* ```php |
|
* use yii\bootstrap\ActiveForm; |
|
* |
|
* $form = ActiveForm::begin(['layout' => 'horizontal']); |
|
* |
|
* // Form field without label |
|
* echo $form->field($model, 'demo', [ |
|
* 'inputOptions' => [ |
|
* 'placeholder' => $model->getAttributeLabel('demo'), |
|
* ], |
|
* ])->label(false); |
|
* |
|
* // Inline radio list |
|
* echo $form->field($model, 'demo')->inline()->radioList($items); |
|
* |
|
* // Control sizing in horizontal mode |
|
* echo $form->field($model, 'demo', [ |
|
* 'horizontalCssClasses' => [ |
|
* 'wrapper' => 'col-sm-2', |
|
* ] |
|
* ]); |
|
* |
|
* // With 'default' layout you would use 'template' to size a specific field: |
|
* echo $form->field($model, 'demo', [ |
|
* 'template' => '{label} <div class="row"><div class="col-sm-4">{input}{error}{hint}</div></div>' |
|
* ]); |
|
* |
|
* // Input group |
|
* echo $form->field($model, 'demo', [ |
|
* 'inputTemplate' => '<div class="input-group"><span class="input-group-addon">@</span>{input}</div>', |
|
* ]); |
|
* |
|
* ActiveForm::end(); |
|
* ``` |
|
* |
|
* @see \yii\bootstrap\ActiveForm |
|
* @see http://getbootstrap.com/css/#forms |
|
* |
|
* @author Michael Härtl <haertl.mike@gmail.com> |
|
* @since 2.0 |
|
*/ |
|
class ActiveField extends \yii\widgets\ActiveField |
|
{ |
|
/** |
|
* @var boolean whether to render [[checkboxList()]] and [[radioList()]] inline. |
|
*/ |
|
public $inline = false; |
|
/** |
|
* @var string|null optional template to render the `{input}` placeholder content |
|
*/ |
|
public $inputTemplate; |
|
/** |
|
* @var array options for the wrapper tag, used in the `{beginWrapper}` placeholder |
|
*/ |
|
public $wrapperOptions = []; |
|
/** |
|
* @var null|array CSS grid classes for horizontal layout. This must be an array with these keys: |
|
* - 'offset' the offset grid class to append to the wrapper if no label is rendered |
|
* - 'label' the label grid class |
|
* - 'wrapper' the wrapper grid class |
|
* - 'error' the error grid class |
|
* - 'hint' the hint grid class |
|
*/ |
|
public $horizontalCssClasses; |
|
/** |
|
* @var string the template for checkboxes in default layout |
|
*/ |
|
public $checkboxTemplate = "<div class=\"checkbox\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n</div>"; |
|
/** |
|
* @var string the template for radios in default layout |
|
*/ |
|
public $radioTemplate = "<div class=\"radio\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n</div>"; |
|
/** |
|
* @var string the template for checkboxes in horizontal layout |
|
*/ |
|
public $horizontalCheckboxTemplate = "{beginWrapper}\n<div class=\"checkbox\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n</div>\n{error}\n{endWrapper}\n{hint}"; |
|
/** |
|
* @var string the template for radio buttons in horizontal layout |
|
*/ |
|
public $horizontalRadioTemplate = "{beginWrapper}\n<div class=\"radio\">\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n</div>\n{error}\n{endWrapper}\n{hint}"; |
|
/** |
|
* @var string the template for inline checkboxLists |
|
*/ |
|
public $inlineCheckboxListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; |
|
/** |
|
* @var string the template for inline radioLists |
|
*/ |
|
public $inlineRadioListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; |
|
/** |
|
* @var boolean whether to render the error. Default is `true` except for layout `inline`. |
|
*/ |
|
public $enableError = true; |
|
/** |
|
* @var boolean whether to render the label. Default is `true`. |
|
*/ |
|
public $enableLabel = true; |
|
|
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function __construct($config = []) |
|
{ |
|
$layoutConfig = $this->createLayoutConfig($config); |
|
$config = ArrayHelper::merge($layoutConfig, $config); |
|
parent::__construct($config); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function render($content = null) |
|
{ |
|
if ($content === null) { |
|
if (!isset($this->parts['{beginWrapper}'])) { |
|
$options = $this->wrapperOptions; |
|
$tag = ArrayHelper::remove($options, 'tag', 'div'); |
|
$this->parts['{beginWrapper}'] = Html::beginTag($tag, $options); |
|
$this->parts['{endWrapper}'] = Html::endTag($tag); |
|
} |
|
if ($this->enableLabel === false) { |
|
$this->parts['{label}'] = ''; |
|
$this->parts['{beginLabel}'] = ''; |
|
$this->parts['{labelTitle}'] = ''; |
|
$this->parts['{endLabel}'] = ''; |
|
} elseif (!isset($this->parts['{beginLabel}'])) { |
|
$this->renderLabelParts(); |
|
} |
|
if ($this->enableError === false) { |
|
$this->parts['{error}'] = ''; |
|
} |
|
if ($this->inputTemplate) { |
|
$input = isset($this->parts['{input}']) ? |
|
$this->parts['{input}'] : Html::activeTextInput($this->model, $this->attribute, $this->inputOptions); |
|
$this->parts['{input}'] = strtr($this->inputTemplate, ['{input}' => $input]); |
|
} |
|
} |
|
return parent::render($content); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function checkbox($options = [], $enclosedByLabel = true) |
|
{ |
|
if ($enclosedByLabel) { |
|
if (!isset($options['template'])) { |
|
$this->template = $this->form->layout === 'horizontal' ? |
|
$this->horizontalCheckboxTemplate : $this->checkboxTemplate; |
|
} else { |
|
$this->template = $options['template']; |
|
unset($options['template']); |
|
} |
|
if (isset($options['label'])) { |
|
$this->parts['{labelTitle}'] = $options['label']; |
|
} |
|
if ($this->form->layout === 'horizontal') { |
|
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); |
|
} |
|
$this->labelOptions['class'] = null; |
|
} |
|
|
|
return parent::checkbox($options, false); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function radio($options = [], $enclosedByLabel = true) |
|
{ |
|
if ($enclosedByLabel) { |
|
if (!isset($options['template'])) { |
|
$this->template = $this->form->layout === 'horizontal' ? |
|
$this->horizontalRadioTemplate : $this->radioTemplate; |
|
} else { |
|
$this->template = $options['template']; |
|
unset($options['template']); |
|
} |
|
if (isset($options['label'])) { |
|
$this->parts['{labelTitle}'] = $options['label']; |
|
} |
|
if ($this->form->layout === 'horizontal') { |
|
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); |
|
} |
|
$this->labelOptions['class'] = null; |
|
} |
|
|
|
return parent::radio($options, false); |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function checkboxList($items, $options = []) |
|
{ |
|
if ($this->inline) { |
|
if (!isset($options['template'])) { |
|
$this->template = $this->inlineCheckboxListTemplate; |
|
} else { |
|
$this->template = $options['template']; |
|
unset($options['template']); |
|
} |
|
if (!isset($options['itemOptions'])) { |
|
$options['itemOptions'] = [ |
|
'labelOptions' => ['class' => 'checkbox-inline'], |
|
]; |
|
} |
|
} elseif (!isset($options['item'])) { |
|
$itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; |
|
$options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions) { |
|
$options = array_merge(['label' => $label, 'value' => $value], $itemOptions); |
|
return '<div class="checkbox">' . Html::checkbox($name, $checked, $options) . '</div>'; |
|
}; |
|
} |
|
parent::checkboxList($items, $options); |
|
return $this; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function radioList($items, $options = []) |
|
{ |
|
if ($this->inline) { |
|
if (!isset($options['template'])) { |
|
$this->template = $this->inlineRadioListTemplate; |
|
} else { |
|
$this->template = $options['template']; |
|
unset($options['template']); |
|
} |
|
if (!isset($options['itemOptions'])) { |
|
$options['itemOptions'] = [ |
|
'labelOptions' => ['class' => 'radio-inline'], |
|
]; |
|
} |
|
} elseif (!isset($options['item'])) { |
|
$itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; |
|
$options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions) { |
|
$options = array_merge(['label' => $label, 'value' => $value], $itemOptions); |
|
return '<div class="radio">' . Html::radio($name, $checked, $options) . '</div>'; |
|
}; |
|
} |
|
parent::radioList($items, $options); |
|
return $this; |
|
} |
|
|
|
/** |
|
* Renders Bootstrap static form control. |
|
* @param array $options the tag options in terms of name-value pairs. These will be rendered as |
|
* the attributes of the resulting tag. There are also a special options: |
|
* |
|
* - encode: boolean, whether value should be HTML-encoded or not. |
|
* |
|
* @return $this the field object itself |
|
* @since 2.0.5 |
|
* @see http://getbootstrap.com/css/#forms-controls-static |
|
*/ |
|
public function staticControl($options = []) |
|
{ |
|
$this->adjustLabelFor($options); |
|
$this->parts['{input}'] = Html::activeStaticControl($this->model, $this->attribute, $options); |
|
return $this; |
|
} |
|
|
|
/** |
|
* @inheritdoc |
|
*/ |
|
public function label($label = null, $options = []) |
|
{ |
|
if (is_bool($label)) { |
|
$this->enableLabel = $label; |
|
if ($label === false && $this->form->layout === 'horizontal') { |
|
Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); |
|
} |
|
} else { |
|
$this->enableLabel = true; |
|
$this->renderLabelParts($label, $options); |
|
parent::label($label, $options); |
|
} |
|
return $this; |
|
} |
|
|
|
/** |
|
* @param bool $value whether to render a inline list |
|
* @return $this the field object itself |
|
* Make sure you call this method before [[checkboxList()]] or [[radioList()]] to have any effect. |
|
*/ |
|
public function inline($value = true) |
|
{ |
|
$this->inline = (bool) $value; |
|
return $this; |
|
} |
|
|
|
/** |
|
* @param array $instanceConfig the configuration passed to this instance's constructor |
|
* @return array the layout specific default configuration for this instance |
|
*/ |
|
protected function createLayoutConfig($instanceConfig) |
|
{ |
|
$config = [ |
|
'hintOptions' => [ |
|
'tag' => 'p', |
|
'class' => 'help-block', |
|
], |
|
'errorOptions' => [ |
|
'tag' => 'p', |
|
'class' => 'help-block help-block-error', |
|
], |
|
'inputOptions' => [ |
|
'class' => 'form-control', |
|
], |
|
]; |
|
|
|
$layout = $instanceConfig['form']->layout; |
|
|
|
if ($layout === 'horizontal') { |
|
$config['template'] = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; |
|
$cssClasses = [ |
|
'offset' => 'col-sm-offset-3', |
|
'label' => 'col-sm-3', |
|
'wrapper' => 'col-sm-6', |
|
'error' => '', |
|
'hint' => 'col-sm-3', |
|
]; |
|
if (isset($instanceConfig['horizontalCssClasses'])) { |
|
$cssClasses = ArrayHelper::merge($cssClasses, $instanceConfig['horizontalCssClasses']); |
|
} |
|
$config['horizontalCssClasses'] = $cssClasses; |
|
$config['wrapperOptions'] = ['class' => $cssClasses['wrapper']]; |
|
$config['labelOptions'] = ['class' => 'control-label ' . $cssClasses['label']]; |
|
$config['errorOptions'] = ['class' => 'help-block help-block-error ' . $cssClasses['error']]; |
|
$config['hintOptions'] = ['class' => 'help-block ' . $cssClasses['hint']]; |
|
} elseif ($layout === 'inline') { |
|
$config['labelOptions'] = ['class' => 'sr-only']; |
|
$config['enableError'] = false; |
|
} |
|
|
|
return $config; |
|
} |
|
|
|
/** |
|
* @param string|null $label the label or null to use model label |
|
* @param array $options the tag options |
|
*/ |
|
protected function renderLabelParts($label = null, $options = []) |
|
{ |
|
$options = array_merge($this->labelOptions, $options); |
|
if ($label === null) { |
|
if (isset($options['label'])) { |
|
$label = $options['label']; |
|
unset($options['label']); |
|
} else { |
|
$attribute = Html::getAttributeName($this->attribute); |
|
$label = Html::encode($this->model->getAttributeLabel($attribute)); |
|
} |
|
} |
|
if (!isset($options['for'])) { |
|
$options['for'] = Html::getInputId($this->model, $this->attribute); |
|
} |
|
$this->parts['{beginLabel}'] = Html::beginTag('label', $options); |
|
$this->parts['{endLabel}'] = Html::endTag('label'); |
|
if (!isset($this->parts['{labelTitle}'])) { |
|
$this->parts['{labelTitle}'] = $label; |
|
} |
|
} |
|
}
|
|
|