%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
<?php
/**
* @package yii2-builder
* @author Kartik Visweswaran <kartikv2@gmail.com>
* @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2022
* @version 1.6.9
*/
namespace kartik\builder;
use Closure;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\helpers\ArrayHelper;
use kartik\helpers\Html;
use kartik\form\ActiveForm;
/**
* Form is a builder widget for rendering the form attributes using [[ActiveForm]]. The widget uses Bootstrap
* 5.x, 4.x, or 3.x styling (depending on [[bsVersion]]) for generating form styles and multiple field columns.
*
* For example,
*
* ```php
* use kartik\form\ActiveForm;
* use kartik\builder\Form;
*
* $options = []; // $options is your ActiveForm configuration
* $form = ActiveForm::begin($options);
* echo Form::widget([
* 'model' => $model, // your model
* 'form' => $form,
* 'columns' => 2,
* 'attributes' => [
* 'username' => ['type' => Form::INPUT_TEXT, 'options'=> ['placeholder'=>'Enter username...']],
* 'password' => ['type' => Form::INPUT_PASSWORD],
* 'rememberMe' => ['type' => Form::INPUT_CHECKBOX, 'enclosedByLabel' => true],
* ]
* ]);
* ActiveForm::end();
* ```
*
* @author Kartik Visweswaran <kartikv2@gmail.com>
*/
class Form extends BaseForm
{
/**
* @var int maximum bootstrap grid width (layout columns count).
*/
const GRID_WIDTH = 12;
/**
* @var string the [[ActiveFormEvent]] triggered before parsing input.
*/
const EVENT_BEFORE_PARSE_INPUT = 'eBeforeParseInput';
/**
* @var string the [[ActiveFormEvent]] triggered after parsing input.
*/
const EVENT_AFTER_PARSE_INPUT = 'eAfterParseInput';
/**
* @var string the [[ActiveFormEvent]] triggered before rendering sub attribute.
*/
const EVENT_BEFORE_RENDER_SUB_ATTR = 'eBeforeRenderSubAttr';
/**
* @var string the [[ActiveFormEvent]] triggered after rendering sub attribute.
*/
const EVENT_AFTER_RENDER_SUB_ATTR = 'eAfterRenderSubAttr';
/**
* @var Model the data model used for the form.
*/
public $model;
/**
* @var string content to display before the generated form fields. This is not HTML encoded.
*/
public $contentBefore = '';
/**
* @var string content to display after the generated form fields. This is not HTML encoded.
*/
public $contentAfter = '';
/**
* @var int the number of columns in which to split the fields horizontally. If not set, defaults to 1 column.
*/
public $columns = 1;
/**
* @var bool whether to use a compact row/column grid layout as supported by Bootstrap 4.x via `form-row` class.
* Supported only when [[bsVersion]] = '4.x' or [[bsVersion]] = '5.x'
*
* @see https://getbootstrap.com/docs/4.6/components/forms/#form-row
* @see https://getbootstrap.com/docs/5.1/layout/gutters/#horizontal--vertical-gutters
*/
public $compactGrid = false;
/**
* @var boolean calculate the number of columns automatically based on count of attributes configured in the
* [[Form]] widget. Columns will be created maximum upto the [[GRID_WIDTH]].
*/
public $autoGenerateColumns = false;
/**
* @var string the bootstrap device size for rendering each grid column. Defaults to [[SIZE_SMALL]].
*/
public $columnSize = self::SIZE_SMALL;
/**
* @var array the HTML attributes for the grid columns. Applicable only if [[columns]] is greater than `1`.
*/
public $columnOptions = [];
/**
* @var array the HTML attributes for the rows. Applicable only if [[columns]] is greater than `1`.
*/
public $rowOptions = [];
/**
* @var array the HTML attributes for the field / attributes container. The following options are additionally
* recognized:
*
* - `tag`: _string_, the HTML tag for the container. Defaults to `fieldset`.
*/
public $options = [];
/**
* @var bool whether to enclose and render the fieldset container
*/
public $encloseFieldSet = false;
/**
* @var string the tag for the fieldset.
*/
private $_fieldsetTag;
/**
* @var string the form orientation.
*/
private $_orientation = ActiveForm::TYPE_VERTICAL;
/**
* @var string the Bootstrap grid row CSS
*/
private $_rowCss;
/**
* @var string the Bootstrap Label CSS
*/
private $_labelCss;
/**
* @inheritdoc
* @throws InvalidConfigException
*/
public function init()
{
parent::init();
$this->initOptions();
$this->registerAssets();
if ($this->autoGenerateColumns) {
$cols = count($this->attributes);
$this->columns = $cols >= self::GRID_WIDTH ? self::GRID_WIDTH : $cols;
}
if ($this->encloseFieldSet) {
echo Html::beginTag($this->_fieldsetTag, $this->options) . "\n";
}
}
/**
* Initializes the [[Form]] widget options.
*/
protected function initOptions()
{
$this->checkFormConfig();
if (empty($this->columnSize)) {
$this->columnSize = empty($this->form->formConfig['deviceSize']) ?
self::SIZE_SMALL :
$this->form->formConfig['deviceSize'];
}
if (isset($this->form->type)) {
$this->_orientation = $this->form->type;
}
if (!isset($this->form->bsVersion) && isset($this->bsVersion)) {
$this->form->bsVersion = $this->bsVersion;
}
$this->_fieldsetTag = ArrayHelper::remove($this->options, 'tag', 'fieldset');
$notBs3 = !$this->isBs(3);
$this->_labelCss = $notBs3 ? 'col-form-label' : 'control-label';
$this->_rowCss = $notBs3 && $this->compactGrid ? ($this->isBs(4) ? 'form-row' : 'g-2') : 'row';
}
/**
* Registers the client assets for the [[Form]] widget.
*/
protected function registerAssets()
{
$view = $this->getView();
FormAsset::register($view);
$view->registerJs('jQuery("#' . $this->options['id'] . '").kvFormBuilder({});');
}
/**
* @inheritdoc
*/
public function run()
{
echo $this->contentBefore;
echo $this->renderFieldSet();
echo $this->contentAfter;
if ($this->encloseFieldSet) {
echo Html::endTag($this->_fieldsetTag);
}
parent::run();
}
/**
* Renders each field set within the form.
*
* @return string
* @throws InvalidConfigException
*/
protected function renderFieldSet()
{
$content = '';
$cols = (is_int($this->columns) && $this->columns >= 1) ? $this->columns : 1;
$index = 0;
$attrCount = count($this->attributes);
$rows = (float)($attrCount / $cols);
$rows = ceil($rows);
$names = array_keys($this->attributes);
$values = array_values($this->attributes);
$width = (int)(self::GRID_WIDTH / $cols);
Html::addCssClass($this->rowOptions, $this->_rowCss);
$skip = ($attrCount == 1);
for ($row = 1; $row <= $rows; $row++) {
$content .= $this->beginTag('div', $this->rowOptions, $skip);
for ($col = 1; $col <= $cols; $col++) {
if ($index > ($attrCount - 1)) {
break;
}
$attribute = $names[$index];
$settings = $values[$index];
$settings = array_replace_recursive($this->attributeDefaults, $settings);
$colOptions = ArrayHelper::getValue($settings, 'columnOptions', $this->columnOptions);
$colWidth = $width;
if (isset($colOptions['colspan'])) {
$colWidth = $colWidth * (int)($colOptions['colspan']);
unset($colOptions['colspan']);
}
$colWidth = (int)$colWidth;
Html::addCssClass($colOptions, 'col-' . $this->columnSize . '-' . $colWidth);
if (ArrayHelper::getValue($colOptions, 'visible', true) === false) {
Html::addCssStyle($colOptions, 'display:none');
}
$content .= "\t" . $this->beginTag('div', $colOptions, $skip) . "\n";
if (!empty($settings['attributes'])) {
$this->raise(self::EVENT_BEFORE_RENDER_SUB_ATTR, $attribute, $index, ['settings' => &$settings]);
$content .= $this->renderSubAttributes($settings, $index);
$this->raise(self::EVENT_AFTER_RENDER_SUB_ATTR, $attribute, $index, ['content' => &$content]);
} else {
$this->raise(self::EVENT_BEFORE_PARSE_INPUT, $attribute, $index, ['settings' => &$settings]);
$content .= "\t\t" . $this->parseInput($attribute, $settings, $index) . "\n";
$this->raise(self::EVENT_AFTER_PARSE_INPUT, $attribute, $index, ['content' => &$content]);
}
$content .= "\t" . $this->endTag('div', $skip) . "\n";
$index++;
}
$content .= $this->endTag('div', $skip) . "\n";
}
return $content;
}
/**
* Render sub attributes.
*
* @param array $settings the attribute settings
* @param string $index the zero-based index of the attribute
*
* @return string
* @throws InvalidConfigException
*/
protected function renderSubAttributes($settings, $index)
{
$content = $this->getSubAttributesContent($settings, $index);
$labelOptions = ArrayHelper::getValue($settings, 'labelOptions', []);
$label = ArrayHelper::getValue($settings, 'label', '');
if ($this->_orientation === ActiveForm::TYPE_INLINE) {
Html::addCssClass($labelOptions, ActiveForm::SCREEN_READER);
} elseif ($this->_orientation === ActiveForm::TYPE_VERTICAL) {
Html::addCssClass($labelOptions, $this->_labelCss);
}
if ($this->_orientation !== ActiveForm::TYPE_HORIZONTAL) {
return '<div class="kv-nested-attribute-block">' . "\n" .
Html::label($label, null, $labelOptions) . "\n" .
$content . "\n" .
'</div>';
}
$defaultLabelSpan = ArrayHelper::getValue($this->form->formConfig, 'labelSpan', 3);
$labelSpan = ArrayHelper::getValue($settings, 'labelSpan', $defaultLabelSpan);
Html::addCssClass($labelOptions, ["col-{$this->columnSize}-{$labelSpan}", $this->_labelCss]);
$inputSpan = self::GRID_WIDTH - $labelSpan;
$rowCss = !$this->isBs(3) ? ' ' . $this->_rowCss : '';
$rowOptions = ['class' => 'kv-nested-attribute-block form-sub-attributes form-group' . $rowCss];
$inputOptions = ['class' => "col-{$this->columnSize}-{$inputSpan}"];
return Html::beginTag('div', $rowOptions) . "\n" .
Html::beginTag('label', $labelOptions) . "\n" .
$label . "\n" .
Html::endTag('label') . "\n" .
Html::beginTag('div', $inputOptions) . "\n" .
$content . "\n" .
Html::endTag('div') . "\n" .
Html::endTag('div') . "\n";
}
/**
* Gets sub attribute markup content.
*
* @param array $settings the attribute settings
* @param string $index the zero-based index of the attribute
*
* @return string
* @throws InvalidConfigException
*/
protected function getSubAttributesContent($settings, $index)
{
$subIndex = 0;
$defaultSubColOptions = ArrayHelper::getValue($settings, 'subColumnOptions', $this->columnOptions);
$content = '';
$content .= "\t" . $this->beginTag('div', $this->rowOptions) . "\n";
$attrCount = count($settings['attributes']);
$cols = ArrayHelper::getValue($settings, 'columns', $attrCount);
foreach ($settings['attributes'] as $subAttr => $subSettings) {
$subColWidth = (int)(self::GRID_WIDTH / $cols);
$subSettings = array_replace_recursive($this->attributeDefaults, $subSettings);
if (!isset($subSettings['label'])) {
$subSettings['label'] = false;
}
$subColOptions = ArrayHelper::getValue($subSettings, 'columnOptions', $defaultSubColOptions);
if (isset($subColOptions['colspan'])) {
$subColWidth = (int)$subColWidth * (int)($subColOptions['colspan']);
unset($subColOptions['colspan']);
}
Html::addCssClass($subColOptions, 'col-' . $this->columnSize . '-' . $subColWidth);
$subSettings['columnOptions'] = $subColOptions;
$subSettings['fieldConfig']['skipFormLayout'] = true;
if ($this->isBs(5)) {
$opts = ArrayHelper::getValue($subSettings['fieldConfig'], 'options', []);
Html::addCssClass($opts, ['mb-3', 'g-0']);
$subSettings['fieldConfig']['options'] = $opts;
}
$content .= "\t\t" . $this->beginTag('div', $subColOptions) . "\n";
/** @var integer $index */
$content .= "\t\t\t" . $this->parseInput($subAttr, $subSettings, $index * 10 + $subIndex) . "\n";
$subIndex++;
$content .= "\t\t" . $this->endTag('div') . "\n";
}
$content .= "\t" . $this->endTag('div') . "\n";
return $content;
}
/**
* Parses the input markup based on type.
*
* @param string $attribute the model attribute.
* @param array $settings the column settings.
* @param integer $index the row index.
*
* @return string the generated input.
* @throws InvalidConfigException
*/
protected function parseInput($attribute, $settings, $index)
{
$type = ArrayHelper::getValue($settings, 'type', self::INPUT_TEXT);
if ($this->staticOnly === true) {
if (isset($this->form)) {
$this->form->staticOnly = true;
} else {
$settings['type'] = self::INPUT_STATIC;
}
if ($type !== self::INPUT_HIDDEN_STATIC) {
$type = self::INPUT_STATIC;
}
}
if (($type === self::INPUT_STATIC || $type === self::INPUT_HIDDEN_STATIC) && isset($settings['staticValue'])) {
$val = $settings['staticValue'];
if ($val instanceof Closure) {
$val = call_user_func($val, $this->hasModel() ? $this->model : $this->formName, $index, $this);
}
if ($this->hasModel()) {
$settings['fieldConfig']['staticValue'] = $val;
} else {
$settings['value'] = $val;
}
}
$val = ArrayHelper::getValue($settings, 'value', null);
if ($type === self::INPUT_RAW) {
$source = $this->hasModel() ? $this->model : $this->formName;
return $val instanceof Closure ? call_user_func($val, $source, $index, $this) : $val;
} else {
$hidden = '';
if ($type === self::INPUT_HIDDEN || $type === self::INPUT_HIDDEN_STATIC) {
$options = ArrayHelper::getValue($settings, 'options', []);
$hidden = $this->hasModel() ? Html::activeHiddenInput($this->model, $attribute, $options) :
Html::hiddenInput("{$this->formName}[{$attribute}]", $val, $options);
if ($type === self::INPUT_HIDDEN) {
return $hidden;
}
$settings['type'] = self::INPUT_STATIC;
$settings['options'] = ArrayHelper::getValue($settings, 'hiddenStaticOptions', []);
}
if ($this->hasModel()) {
$out = $this->renderActiveInput($this->form, $this->model, $attribute, $settings);
} else {
//Check whether atrtribute defined in array way ([])
if (substr($attribute, 0, 1) == '[' && substr($attribute, -1) == ']') {
$out = $this->renderInput("{$this->formName}{$attribute}", $settings);
} else {
$out = $this->renderInput("{$this->formName}[{$attribute}]", $settings);
}
}
return $out . $hidden;
}
}
/**
* Begins a tag markup based on orientation.
*
* @param string $tag the HTML tag
* @param array $options the HTML attributes for the tag
* @param boolean $skip whether to skip the tag generation
*
* @return string
*/
protected function beginTag($tag, $options = [], $skip = false)
{
if ($this->_orientation !== ActiveForm::TYPE_INLINE && !$skip) {
return Html::beginTag($tag, $options) . "\n";
}
return '';
}
/**
* Ends a tag markup based on orientation.
*
* @param string $tag the HTML tag
* @param boolean $skip whether to skip the tag generation
*
* @return string
*/
protected function endTag($tag, $skip = false)
{
if ($this->_orientation !== ActiveForm::TYPE_INLINE && !$skip) {
return Html::endTag($tag) . "\n";
}
return '';
}
/**
* Triggers and raises an [[ActiveFormEvent]].
*
* @param string $event the event name.
* @param string $attribute the attribute name.
* @param integer|string $index the row index.
* @param array $data the event data to pass to the event.
*/
protected function raise($event = '', $attribute = '', $index = '', $data = [])
{
$this->trigger(
$event,
new ActiveFormEvent(['attribute' => $attribute, 'index' => $index, 'eventData' => $data])
);
}
}