PageRenderTime 63ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/webroot/lib/vendor/kartik-v/yii2-builder/Form.php

https://gitlab.com/tangsengjiu/Talk
PHP | 408 lines | 251 code | 27 blank | 130 comment | 37 complexity | bd6b6165753102d85e6c3f8605b9c872 MD5 | raw file
  1. <?php
  2. /**
  3. * @package yii2-builder
  4. * @author Kartik Visweswaran <kartikv2@gmail.com>
  5. * @copyright Copyright &copy; Kartik Visweswaran, Krajee.com, 2014 - 2015
  6. * @version 1.6.1
  7. */
  8. namespace kartik\builder;
  9. use Yii;
  10. use yii\base\InvalidConfigException;
  11. use yii\helpers\ArrayHelper;
  12. use yii\helpers\Html;
  13. use \Closure;
  14. use kartik\form\ActiveForm;
  15. /**
  16. * A form builder widget for rendering the form attributes using kartik\form\ActiveForm.
  17. * The widget uses Bootstrap 3.x styling for generating form styles and multiple field columns.
  18. *
  19. * Usage:
  20. * ```
  21. * use kartik\form\ActiveForm;
  22. * use kartik\builder\Form;
  23. * $form = ActiveForm::begin($options); // $options is array for your form config
  24. * echo Form::widget([
  25. * 'model' => $model, // your model
  26. * 'form' => $form,
  27. * 'columns' => 2,
  28. * 'attributes' => [
  29. * 'username' => ['type' => Form::INPUT_TEXT, 'options'=> ['placeholder'=>'Enter username...']],
  30. * 'password' => ['type' => Form::INPUT_PASSWORD],
  31. * 'rememberMe' => ['type' => Form::INPUT_CHECKBOX, 'enclosedByLabel' => true],
  32. * ]
  33. * ]);
  34. * ActiveForm::end();
  35. * ```
  36. *
  37. *
  38. * @author Kartik Visweswaran <kartikv2@gmail.com>
  39. * @since 1.0
  40. */
  41. class Form extends BaseForm
  42. {
  43. // bootstrap grid column sizes
  44. const SIZE_LARGE = 'lg';
  45. const SIZE_MEDIUM = 'md';
  46. const SIZE_SMALL = 'sm';
  47. const SIZE_TINY = 'xs';
  48. // bootstrap maximum grid width
  49. const GRID_WIDTH = 12;
  50. // Form events
  51. const EVENT_BEFORE_PARSE_INPUT = "eBeforeParseInput";
  52. const EVENT_AFTER_PARSE_INPUT = "eAfterParseInput";
  53. const EVENT_BEFORE_RENDER_SUB_ATTR = "eBeforeRenderSubAttr";
  54. const EVENT_AFTER_RENDER_SUB_ATTR = "eAfterRenderSubAttr";
  55. /**
  56. * @var yii\db\ActiveRecord | yii\base\Model the model used for the form
  57. */
  58. public $model;
  59. /**
  60. * @var string, content to display before the generated form fields.
  61. * This is not HTML encoded.
  62. */
  63. public $contentBefore = '';
  64. /**
  65. * @var string, content to display after the generated form fields.
  66. * This is not HTML encoded.
  67. */
  68. public $contentAfter = '';
  69. /**
  70. * @var integer, the number of columns in which to split the fields horizontally. If not set, defaults to 1 column.
  71. */
  72. public $columns = 1;
  73. /**
  74. * @var boolean, calculate the number of columns automatically based on count of attributes
  75. * configured in the Form widget. Columns will be created max upto the Form::GRID_WIDTH.
  76. */
  77. public $autoGenerateColumns = false;
  78. /**
  79. * @var string, the bootstrap device size for rendering each grid column. Defaults to `SIZE_SMALL`.
  80. */
  81. public $columnSize = self::SIZE_SMALL;
  82. /**
  83. * @var array the HTML attributes for the grid columns. Applicable only if `$columns` is greater than 1.
  84. */
  85. public $columnOptions = [];
  86. /**
  87. * @var array the HTML attributes for the rows. Applicable only if `$columns` is greater than 1.
  88. */
  89. public $rowOptions = [];
  90. /**
  91. * @var array the HTML attributes for the field/attributes container. The following options are additionally
  92. * recognized:
  93. * - `tag`: the HTML tag for the container. Defaults to `fieldset`.
  94. */
  95. public $options = [];
  96. /**
  97. * @var string the tag for the fieldset
  98. */
  99. private $_tag;
  100. /**
  101. * @var string the form orientation
  102. */
  103. private $_orientation = ActiveForm::TYPE_VERTICAL;
  104. /**
  105. * Initializes the widget
  106. *
  107. * @throws \yii\base\InvalidConfigException
  108. */
  109. public function init()
  110. {
  111. parent::init();
  112. $this->checkFormConfig();
  113. if (empty($this->columnSize)) {
  114. $this->columnSize = empty($this->form->formConfig['deviceSize']) ?
  115. self::SIZE_SMALL :
  116. $this->form->formConfig['deviceSize'];
  117. }
  118. if (isset($this->form->type)) {
  119. $this->_orientation = $this->form->type;
  120. }
  121. $this->initOptions();
  122. $this->registerAssets();
  123. if ($this->autoGenerateColumns) {
  124. $cols = count($this->attributes);
  125. $this->columns = $cols >= self::GRID_WIDTH ? self::GRID_WIDTH : $cols;
  126. }
  127. echo Html::beginTag($this->_tag, $this->options) . "\n";
  128. }
  129. /**
  130. * Initializes the widget options
  131. */
  132. protected function initOptions()
  133. {
  134. $this->_tag = ArrayHelper::remove($this->options, 'tag', 'fieldset');
  135. if (empty($this->options['id'])) {
  136. $this->options['id'] = $this->getId();
  137. }
  138. }
  139. /**
  140. * Registers widget assets
  141. */
  142. protected function registerAssets()
  143. {
  144. $view = $this->getView();
  145. FormAsset::register($view);
  146. $view->registerJs('jQuery("#' . $this->options['id'] . '").kvFormBuilder({});');
  147. }
  148. /**
  149. * @inheritdoc
  150. */
  151. public function run()
  152. {
  153. echo $this->contentBefore;
  154. echo $this->renderFieldSet();
  155. echo $this->contentAfter;
  156. echo Html::endTag($this->_tag);
  157. parent::run();
  158. }
  159. /**
  160. * Renders the field set
  161. *
  162. * @return string
  163. */
  164. protected function renderFieldSet()
  165. {
  166. $content = '';
  167. $cols = (is_int($this->columns) && $this->columns >= 1) ? $this->columns : 1;
  168. $index = 0;
  169. $attrCount = count($this->attributes);
  170. $rows = (float)($attrCount / $cols);
  171. $rows = ceil($rows);
  172. $names = array_keys($this->attributes);
  173. $values = array_values($this->attributes);
  174. $width = (int)(self::GRID_WIDTH / $cols);
  175. Html::addCssClass($this->rowOptions, 'row');
  176. $skip = ($attrCount == 1);
  177. for ($row = 1; $row <= $rows; $row++) {
  178. $content .= $this->beginTag('div', $this->rowOptions, $skip);
  179. for ($col = 1; $col <= $cols; $col++) {
  180. if ($index > ($attrCount - 1)) {
  181. break;
  182. }
  183. $attribute = $names[$index];
  184. $settings = $values[$index];
  185. $settings = array_replace_recursive($this->attributeDefaults, $settings);
  186. $colOptions = ArrayHelper::getValue($settings, 'columnOptions', $this->columnOptions);
  187. $colWidth = $width;
  188. if (isset($colOptions['colspan'])) {
  189. $colWidth = $colWidth * (int)($colOptions['colspan']);
  190. unset($colOptions['colspan']);
  191. }
  192. $colWidth = (int)$colWidth;
  193. Html::addCssClass($colOptions, 'col-' . $this->columnSize . '-' . $colWidth);
  194. $content .= "\t" . $this->beginTag('div', $colOptions, $skip) . "\n";
  195. if (!empty($settings['attributes'])) {
  196. $this->raise(self::EVENT_BEFORE_RENDER_SUB_ATTR, $attribute, $index, ['settings' => &$settings]);
  197. $content .= $this->renderSubAttributes($attribute, $settings, $index);
  198. $this->raise(self::EVENT_AFTER_RENDER_SUB_ATTR, $attribute, $index, ['content' => &$content]);
  199. } else {
  200. $this->raise(self::EVENT_BEFORE_PARSE_INPUT, $attribute, $index, ['settings' => &$settings]);
  201. $content .= "\t\t" . $this->parseInput($attribute, $settings, $index) . "\n";
  202. $this->raise(self::EVENT_AFTER_PARSE_INPUT, $attribute, $index, ['content' => &$content]);
  203. }
  204. $content .= "\t" . $this->endTag('div', $skip) . "\n";
  205. $index++;
  206. }
  207. $content .= $this->endTag('div', $skip) . "\n";
  208. }
  209. return $content;
  210. }
  211. /**
  212. * Render sub attributes
  213. *
  214. * @return string
  215. */
  216. protected function renderSubAttributes($attribute, $settings, $index)
  217. {
  218. $content = $this->getSubAttributesContent($attribute, $settings, $index);
  219. $labelOptions = ArrayHelper::getValue($settings, 'labelOptions', []);
  220. $label = ArrayHelper::getValue($settings, 'label', '');
  221. if ($this->_orientation === ActiveForm::TYPE_INLINE) {
  222. Html::addCssClass($labelOptions, ActiveForm::SCREEN_READER);
  223. } elseif ($this->_orientation === ActiveForm::TYPE_VERTICAL) {
  224. Html::addCssClass($labelOptions, "control-label");
  225. }
  226. if ($this->_orientation !== ActiveForm::TYPE_HORIZONTAL) {
  227. return '<div class="kv-nested-attribute-block">' . "\n" .
  228. Html::label($label, null, $labelOptions) . "\n" .
  229. $content . "\n" .
  230. '</div>';
  231. }
  232. if (isset($this->form->formConfig['labelSpan'])) {
  233. $defaultLabelSpan = $this->form->formConfig['labelSpan'];
  234. }
  235. $labelSpan = ArrayHelper::getValue($settings, 'labelSpan', 3);
  236. Html::addCssClass($labelOptions, "col-{$this->columnSize}-{$labelSpan} control-label");
  237. $inputSpan = self::GRID_WIDTH - $labelSpan;
  238. $rowOptions = ['class' => 'kv-nested-attribute-block form-sub-attributes form-group'];
  239. $inputOptions = ['class' => "col-{$this->columnSize}-{$inputSpan}"];
  240. return Html::beginTag('div', $rowOptions) . "\n" .
  241. Html::beginTag('label', $labelOptions) . "\n" .
  242. $label . "\n" .
  243. Html::endTag('label') . "\n" .
  244. Html::beginTag('div', $inputOptions) . "\n" .
  245. $content . "\n" .
  246. Html::endTag('div') . "\n" .
  247. Html::endTag('div') . "\n";
  248. }
  249. /**
  250. * Gets sub attribute markup content
  251. *
  252. * @return string
  253. */
  254. protected function getSubAttributesContent($attribute, $settings, $index)
  255. {
  256. $subIndex = 0;
  257. $defaultSubColOptions = ArrayHelper::getValue($settings, 'subColumnOptions', $this->columnOptions);
  258. $content = '';
  259. $content .= "\t" . $this->beginTag('div', $this->rowOptions) . "\n";
  260. $attrCount = count($settings['attributes']);
  261. $cols = ArrayHelper::getValue($settings, 'columns', $attrCount);
  262. foreach ($settings['attributes'] as $subAttr => $subSettings) {
  263. $subColWidth = (int)(self::GRID_WIDTH / $cols);
  264. $subSettings = array_replace_recursive($this->attributeDefaults, $subSettings);
  265. if (!isset($subSettings['label'])) {
  266. $subSettings['label'] = false;
  267. }
  268. $subColOptions = ArrayHelper::getValue($subSettings, 'columnOptions', $defaultSubColOptions);
  269. if (isset($subColOptions['colspan'])) {
  270. $subColWidth = (int)$subColWidth * (int)($subColOptions['colspan']);
  271. unset($subColOptions['colspan']);
  272. }
  273. Html::addCssClass($subColOptions, 'col-' . $this->columnSize . '-' . $subColWidth);
  274. $subSettings['columnOptions'] = $subColOptions;
  275. $subSettings['fieldConfig']['skipFormLayout'] = true;
  276. $content .= "\t\t" . $this->beginTag('div', $subColOptions) . "\n";
  277. $content .= "\t\t\t" . $this->parseInput($subAttr, $subSettings, $index * 10 + $subIndex) . "\n";
  278. $subIndex++;
  279. $content .= "\t\t" . $this->endTag('div') . "\n";
  280. }
  281. $content .= "\t" . $this->endTag('div') . "\n";
  282. return $content;
  283. }
  284. /**
  285. * Parses the input markup based on type
  286. *
  287. * @param string $attribute the model attribute
  288. * @param string $settings the column settings
  289. * @param int $index the row index
  290. *
  291. * @return \kartik\form\ActiveField|mixed
  292. * @throws InvalidConfigException
  293. */
  294. protected function parseInput($attribute, $settings, $index)
  295. {
  296. $type = ArrayHelper::getValue($settings, 'type', self::INPUT_TEXT);
  297. if ($this->staticOnly === true) {
  298. if (isset($this->form)) {
  299. $this->form->staticOnly = true;
  300. } else {
  301. $settings['type'] = self::INPUT_STATIC;
  302. }
  303. if ($type !== self::INPUT_HIDDEN_STATIC) {
  304. $type = self::INPUT_STATIC;
  305. }
  306. }
  307. if (($type === self::INPUT_STATIC || $type === self::INPUT_HIDDEN_STATIC) && isset($settings['staticValue'])) {
  308. $val = $settings['staticValue'];
  309. if ($val instanceof Closure) {
  310. $val = call_user_func($val, $this->hasModel() ? $this->model : $this->formName, $index, $this);
  311. }
  312. if ($this->hasModel()) {
  313. $settings['fieldConfig']['staticValue'] = $val;
  314. } else {
  315. $settings['value'] = $val;
  316. }
  317. } else {
  318. $val = ArrayHelper::getValue($settings, 'value', null);
  319. }
  320. $val = ArrayHelper::getValue($settings, 'value', null);
  321. if ($type === self::INPUT_RAW) {
  322. if ($this->hasModel()) {
  323. return $val instanceof Closure ? call_user_func($val, $this->model, $index, $this) : $val;
  324. } else {
  325. return $val instanceof Closure ? call_user_func($val, $this->formName, $index, $this) : $val;
  326. }
  327. } else {
  328. $hidden = '';
  329. if ($type === self::INPUT_HIDDEN_STATIC) {
  330. $settings['type'] = self::INPUT_STATIC;
  331. $options = ArrayHelper::getValue($settings, 'options', []);
  332. $hidden = $this->hasModel() ? Html::activeHiddenInput($this->model, $attribute, $options) :
  333. Html::hiddenInput("{$this->formName}[{$attribute}]", $val, $options);
  334. $settings['options'] = ArrayHelper::getValue($settings, 'hiddenStaticOptions', []);
  335. }
  336. $out = $this->hasModel() ?
  337. static::renderActiveInput($this->form, $this->model, $attribute, $settings) :
  338. static::renderInput("{$this->formName}[{$attribute}]", $settings);
  339. return $out . $hidden;
  340. }
  341. }
  342. /**
  343. * Begins a tag markup based on orientation
  344. *
  345. * @return string
  346. */
  347. protected function beginTag($tag, $options, $skip = false)
  348. {
  349. if ($this->_orientation !== ActiveForm::TYPE_INLINE && !$skip) {
  350. return Html::beginTag($tag, $options) . "\n";
  351. }
  352. return '';
  353. }
  354. /**
  355. * Ends a tag markup based on orientation
  356. *
  357. * @return string
  358. */
  359. protected function endTag($tag, $skip = false)
  360. {
  361. if ($this->_orientation !== ActiveForm::TYPE_INLINE && !$skip) {
  362. return Html::endTag($tag) . "\n";
  363. }
  364. return '';
  365. }
  366. /**
  367. * Triggers an ActiveForm event
  368. *
  369. * @param string $event
  370. * @param string $attribute
  371. * @param string $index
  372. * @param array $data
  373. */
  374. protected function raise($event = '', $attribute = '', $index = '', $data = [])
  375. {
  376. $this->trigger(
  377. $event,
  378. new ActiveFormEvent(['attribute' => $attribute, 'index' => $index, 'eventData' => $data])
  379. );
  380. }
  381. }