├── ActiveField.php ├── ActiveForm.php ├── BaseHtml.php ├── BootstrapWidgetTrait.php ├── Html.php ├── InputWidget.php ├── LICENSE ├── README.md ├── Widget.php ├── assets ├── BootstrapAsset.php ├── BootstrapGridAsset.php └── BootstrapPluginAsset.php ├── composer.json └── widgets ├── Alert.php ├── Breadcrumbs.php ├── Button.php ├── ButtonDropdown.php ├── ButtonGroup.php ├── Card.php ├── Carousel.php ├── Collapse.php ├── Dropdown.php ├── Modal.php ├── Nav.php ├── NavBar.php ├── Progress.php ├── Tabs.php └── ToggleButtonGroup.php /ActiveField.php: -------------------------------------------------------------------------------- 1 | 'horizontal']); 54 | * 55 | * // Form field without label 56 | * echo $form->field($model, 'demo', [ 57 | * 'inputOptions' => [ 58 | * 'placeholder' => $model->getAttributeLabel('demo'), 59 | * ], 60 | * ])->label(false); 61 | * 62 | * // Inline radio list 63 | * echo $form->field($model, 'demo')->inline()->radioList($items); 64 | * 65 | * // Control sizing in horizontal mode 66 | * echo $form->field($model, 'demo', [ 67 | * 'horizontalCssClasses' => [ 68 | * 'wrapper' => 'col-sm-2', 69 | * ] 70 | * ]); 71 | * 72 | * // With 'default' layout you would use 'template' to size a specific field: 73 | * echo $form->field($model, 'demo', [ 74 | * 'template' => '{label}
{input}{error}{hint}
' 75 | * ]); 76 | * 77 | * // Input group 78 | * echo $form->field($model, 'demo', [ 79 | * 'inputTemplate' => '
@{input}
', 80 | * ]); 81 | * 82 | * ActiveForm::end(); 83 | * ``` 84 | * 85 | * @see \yii\bootstrap\ActiveForm 86 | * @see http://getbootstrap.com/css/#forms 87 | * 88 | * @author Michael Härtl 89 | * @since 2.0 90 | * 91 | * @property ActiveForm $form 92 | */ 93 | class ActiveField extends \yii\widgets\ActiveField 94 | { 95 | /** 96 | * @var boolean whether to render [[checkboxList()]] and [[radioList()]] inline. 97 | */ 98 | public $inline = false; 99 | /** 100 | * @var string|null optional template to render the `{input}` placeholder content 101 | */ 102 | public $inputTemplate; 103 | /** 104 | * @var array options for the wrapper tag, used in the `{beginWrapper}` placeholder 105 | */ 106 | public $wrapperOptions = []; 107 | /** 108 | * @var null|array CSS grid classes for horizontal layout. This must be an array with these keys: 109 | * - 'offset' the offset grid class to append to the wrapper if no label is rendered 110 | * - 'label' the label grid class 111 | * - 'wrapper' the wrapper grid class 112 | * - 'error' the error grid class 113 | * - 'hint' the hint grid class 114 | */ 115 | public $horizontalCssClasses; 116 | /** 117 | * @var string the template for checkboxes in default layout 118 | */ 119 | public $checkboxTemplate = "
\n{input}\n{beginLabel}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n
"; 120 | /** 121 | * @var string the template for radios in default layout 122 | */ 123 | public $radioTemplate = "
\n{input}\n{beginLabel}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n
"; 124 | /** 125 | * @var string the template for checkboxes in horizontal layout 126 | */ 127 | public $horizontalCheckboxTemplate = "{beginWrapper}\n
\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n
\n{error}\n{endWrapper}\n{hint}"; 128 | /** 129 | * @var string the template for radio buttons in horizontal layout 130 | */ 131 | public $horizontalRadioTemplate = "{beginWrapper}\n
\n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n
\n{error}\n{endWrapper}\n{hint}"; 132 | /** 133 | * @var string the template for inline checkboxLists 134 | */ 135 | public $inlineCheckboxListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; 136 | /** 137 | * @var string the template for inline radioLists 138 | */ 139 | public $inlineRadioListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; 140 | /** 141 | * @var boolean whether to render the error. Default is `true` except for layout `inline`. 142 | */ 143 | public $enableError = true; 144 | /** 145 | * @var boolean whether to render the label. Default is `true`. 146 | */ 147 | public $enableLabel = true; 148 | 149 | 150 | /** 151 | * @inheritdoc 152 | */ 153 | public function __construct($config = []) 154 | { 155 | $layoutConfig = $this->createLayoutConfig($config); 156 | $config = ArrayHelper::merge($layoutConfig, $config); 157 | parent::__construct($config); 158 | } 159 | 160 | /** 161 | * @inheritdoc 162 | */ 163 | public function render($content = null) 164 | { 165 | if ($content === null) { 166 | if (!isset($this->parts['{beginWrapper}'])) { 167 | $options = $this->wrapperOptions; 168 | $tag = ArrayHelper::remove($options, 'tag', 'div'); 169 | $this->parts['{beginWrapper}'] = Html::beginTag($tag, $options); 170 | $this->parts['{endWrapper}'] = Html::endTag($tag); 171 | } 172 | if ($this->enableLabel === false) { 173 | $this->parts['{label}'] = ''; 174 | $this->parts['{beginLabel}'] = ''; 175 | $this->parts['{labelTitle}'] = ''; 176 | $this->parts['{endLabel}'] = ''; 177 | } elseif (!isset($this->parts['{beginLabel}'])) { 178 | $this->renderLabelParts(); 179 | } 180 | if ($this->enableError === false) { 181 | $this->parts['{error}'] = ''; 182 | } 183 | if ($this->inputTemplate) { 184 | $input = isset($this->parts['{input}']) ? 185 | $this->parts['{input}'] : Html::activeTextInput($this->model, $this->attribute, $this->inputOptions); 186 | $this->parts['{input}'] = strtr($this->inputTemplate, ['{input}' => $input]); 187 | } 188 | } 189 | return parent::render($content); 190 | } 191 | 192 | /** 193 | * @inheritdoc 194 | */ 195 | public function checkbox($options = [], $enclosedByLabel = true) 196 | { 197 | if ($enclosedByLabel) { 198 | if (!isset($options['template'])) { 199 | $this->template = $this->form->layout === 'horizontal' ? 200 | $this->horizontalCheckboxTemplate : $this->checkboxTemplate; 201 | } else { 202 | $this->template = $options['template']; 203 | unset($options['template']); 204 | } 205 | if (isset($options['label'])) { 206 | $this->parts['{labelTitle}'] = $options['label']; 207 | } 208 | if ($this->form->layout === 'horizontal') { 209 | Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); 210 | } 211 | $this->labelOptions['class'] = 'form-check-label'; 212 | Html::addCssClass($options, 'form-check-input'); 213 | } 214 | 215 | return parent::checkbox($options, false); 216 | } 217 | 218 | /** 219 | * @inheritdoc 220 | */ 221 | public function radio($options = [], $enclosedByLabel = true) 222 | { 223 | if ($enclosedByLabel) { 224 | if (!isset($options['template'])) { 225 | $this->template = $this->form->layout === 'horizontal' ? 226 | $this->horizontalRadioTemplate : $this->radioTemplate; 227 | } else { 228 | $this->template = $options['template']; 229 | unset($options['template']); 230 | } 231 | if (isset($options['label'])) { 232 | $this->parts['{labelTitle}'] = $options['label']; 233 | } 234 | if ($this->form->layout === 'horizontal') { 235 | Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); 236 | } 237 | $this->labelOptions['class'] = 'form-check-label'; 238 | Html::addCssClass($options, 'form-check-input'); 239 | } 240 | 241 | return parent::radio($options, false); 242 | } 243 | 244 | /** 245 | * @inheritdoc 246 | */ 247 | public function checkboxList($items, $options = []) 248 | { 249 | if ($this->inline) { 250 | if (!isset($options['template'])) { 251 | $this->template = $this->inlineCheckboxListTemplate; 252 | } else { 253 | $this->template = $options['template']; 254 | unset($options['template']); 255 | } 256 | if (!isset($options['itemOptions'])) { 257 | $options['itemOptions'] = [ 258 | 'class' => null, 259 | 'labelOptions' => [], 260 | ]; 261 | } 262 | Html::addCssClass($options['itemOptions'], 'form-check-input'); 263 | Html::addCssClass($options['itemOptions']['labelOptions'], 'form-check-label'); 264 | } 265 | if (!isset($options['item'])) { 266 | $isInline = $this->inline; 267 | $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; 268 | $options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions, $isInline) { 269 | $options = array_merge(['label' => $label, 'value' => $value], $itemOptions); 270 | return $isInline 271 | ? '
' . Html::checkbox($name, $checked, $options) . '
' 272 | : '
' . Html::checkbox($name, $checked, $options) . '
'; 273 | }; 274 | } 275 | parent::checkboxList($items, $options); 276 | return $this; 277 | } 278 | 279 | /** 280 | * @inheritdoc 281 | */ 282 | public function radioList($items, $options = []) 283 | { 284 | if ($this->inline) { 285 | if (!isset($options['template'])) { 286 | $this->template = $this->inlineRadioListTemplate; 287 | } else { 288 | $this->template = $options['template']; 289 | unset($options['template']); 290 | } 291 | if (!isset($options['itemOptions'])) { 292 | $options['itemOptions'] = [ 293 | 'class' => null, 294 | 'labelOptions' => [], 295 | ]; 296 | } 297 | } 298 | if (!isset($options['item'])) { 299 | $isInline = $this->inline; 300 | $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; 301 | $options['item'] = function ($index, $label, $name, $checked, $value) use ($itemOptions, $isInline) { 302 | $options = array_merge(['label' => $label, 'value' => $value], $itemOptions); 303 | return $isInline ? '
' . Html::radio($name, $checked, $options) . '
' 304 | : '
' . Html::radio($name, $checked, $options) . '
'; 305 | }; 306 | } 307 | parent::radioList($items, $options); 308 | return $this; 309 | } 310 | 311 | /** 312 | * Renders Bootstrap static form control. 313 | * @param array $options the tag options in terms of name-value pairs. These will be rendered as 314 | * the attributes of the resulting tag. There are also a special options: 315 | * 316 | * - encode: boolean, whether value should be HTML-encoded or not. 317 | * 318 | * @return $this the field object itself 319 | * @since 2.0.5 320 | * @see http://getbootstrap.com/css/#forms-controls-static 321 | */ 322 | public function staticControl($options = []) 323 | { 324 | $this->adjustLabelFor($options); 325 | $this->parts['{input}'] = Html::activeStaticControl($this->model, $this->attribute, $options); 326 | return $this; 327 | } 328 | 329 | /** 330 | * @inheritdoc 331 | */ 332 | public function label($label = null, $options = []) 333 | { 334 | if (is_bool($label)) { 335 | $this->enableLabel = $label; 336 | if ($label === false && $this->form->layout === 'horizontal') { 337 | Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); 338 | } 339 | } else { 340 | $this->enableLabel = true; 341 | $this->renderLabelParts($label, $options); 342 | parent::label($label, $options); 343 | } 344 | return $this; 345 | } 346 | 347 | /** 348 | * @param bool $value whether to render a inline list 349 | * @return $this the field object itself 350 | * Make sure you call this method before [[checkboxList()]] or [[radioList()]] to have any effect. 351 | */ 352 | public function inline($value = true) 353 | { 354 | $this->inline = (bool) $value; 355 | return $this; 356 | } 357 | 358 | /** 359 | * @param array $instanceConfig the configuration passed to this instance's constructor 360 | * @return array the layout specific default configuration for this instance 361 | */ 362 | protected function createLayoutConfig($instanceConfig) 363 | { 364 | $config = [ 365 | 'hintOptions' => [ 366 | 'tag' => 'small', 367 | 'class' => 'form-text text-muted', 368 | ], 369 | 'errorOptions' => [ 370 | 'tag' => 'small', 371 | 'class' => 'form-text text-danger', 372 | ], 373 | 'inputOptions' => [ 374 | 'class' => 'form-control', 375 | ], 376 | ]; 377 | 378 | $layout = $instanceConfig['form']->layout; 379 | 380 | if ($layout === 'horizontal') { 381 | $config['template'] = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; 382 | $cssClasses = [ 383 | 'offset' => 'col-sm-offset-3', 384 | 'label' => 'col-sm-3', 385 | 'wrapper' => 'col-sm-6', 386 | 'error' => '', 387 | 'hint' => 'col-sm-3', 388 | ]; 389 | if (isset($instanceConfig['horizontalCssClasses'])) { 390 | $cssClasses = ArrayHelper::merge($cssClasses, $instanceConfig['horizontalCssClasses']); 391 | } 392 | $config['horizontalCssClasses'] = $cssClasses; 393 | $config['wrapperOptions'] = ['class' => $cssClasses['wrapper']]; 394 | $config['labelOptions'] = ['class' => 'control-label ' . $cssClasses['label']]; 395 | $config['errorOptions'] = ['class' => 'form-text text-danger ' . $cssClasses['error'], 'tag' => 'small']; 396 | $config['hintOptions'] = ['class' => 'form-text ' . $cssClasses['hint'], 'tag' => 'small']; 397 | Html::addCssClass($config['options'], 'row'); 398 | } elseif ($layout === 'inline') { 399 | $config['labelOptions'] = ['class' => 'sr-only']; 400 | $config['enableError'] = false; 401 | } 402 | 403 | return $config; 404 | } 405 | 406 | /** 407 | * @param string|null $label the label or null to use model label 408 | * @param array $options the tag options 409 | */ 410 | protected function renderLabelParts($label = null, $options = []) 411 | { 412 | $options = array_merge($this->labelOptions, $options); 413 | if ($label === null) { 414 | if (isset($options['label'])) { 415 | $label = $options['label']; 416 | unset($options['label']); 417 | } else { 418 | $attribute = Html::getAttributeName($this->attribute); 419 | $label = Html::encode($this->model->getAttributeLabel($attribute)); 420 | } 421 | } 422 | if (!isset($options['for'])) { 423 | $options['for'] = Html::getInputId($this->model, $this->attribute); 424 | } 425 | $this->parts['{beginLabel}'] = Html::beginTag('label', $options); 426 | $this->parts['{endLabel}'] = Html::endTag('label'); 427 | if (!isset($this->parts['{labelTitle}'])) { 428 | $this->parts['{labelTitle}'] = $label; 429 | } 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /ActiveForm.php: -------------------------------------------------------------------------------- 1 | 'horizontal']) 23 | * ``` 24 | * 25 | * This will set default values for the [[ActiveField]] 26 | * to render horizontal form fields. In particular the [[ActiveField::template|template]] 27 | * is set to `{label} {beginWrapper} {input} {error} {endWrapper} {hint}` and the 28 | * [[ActiveField::horizontalCssClasses|horizontalCssClasses]] are set to: 29 | * 30 | * ```php 31 | * [ 32 | * 'offset' => 'col-sm-offset-3', 33 | * 'label' => 'col-sm-3', 34 | * 'wrapper' => 'col-sm-6', 35 | * 'error' => '', 36 | * 'hint' => 'col-sm-3', 37 | * ] 38 | * ``` 39 | * 40 | * To get a different column layout in horizontal mode you can modify those options 41 | * through [[fieldConfig]]: 42 | * 43 | * ```php 44 | * $form = ActiveForm::begin([ 45 | * 'layout' => 'horizontal', 46 | * 'fieldConfig' => [ 47 | * 'template' => "{label}\n{beginWrapper}\n{input}\n{hint}\n{error}\n{endWrapper}", 48 | * 'horizontalCssClasses' => [ 49 | * 'label' => 'col-sm-4', 50 | * 'offset' => 'col-sm-offset-4', 51 | * 'wrapper' => 'col-sm-8', 52 | * 'error' => '', 53 | * 'hint' => '', 54 | * ], 55 | * ], 56 | * ]); 57 | * ``` 58 | * 59 | * @see ActiveField for details on the [[fieldConfig]] options 60 | * @see http://getbootstrap.com/css/#forms 61 | * 62 | * @author Michael Härtl 63 | * @since 2.0 64 | */ 65 | class ActiveForm extends \yii\widgets\ActiveForm 66 | { 67 | /** 68 | * @var string the default field class name when calling [[field()]] to create a new field. 69 | * @see fieldConfig 70 | */ 71 | public $fieldClass = 'digitv\bootstrap\ActiveField'; 72 | /** 73 | * @var array HTML attributes for the form tag. Default is `[]`. 74 | */ 75 | public $options = []; 76 | /** 77 | * @var string the form layout. Either 'default', 'horizontal' or 'inline'. 78 | * By choosing a layout, an appropriate default field configuration is applied. This will 79 | * render the form fields with slightly different markup for each layout. You can 80 | * override these defaults through [[fieldConfig]]. 81 | * @see \yii\bootstrap\ActiveField for details on Bootstrap 3 field configuration 82 | */ 83 | public $layout = 'default'; 84 | 85 | 86 | /** 87 | * @inheritdoc 88 | */ 89 | public function init() 90 | { 91 | if (!in_array($this->layout, ['default', 'horizontal', 'inline'])) { 92 | throw new InvalidConfigException('Invalid layout type: ' . $this->layout); 93 | } 94 | 95 | if ($this->layout !== 'default') { 96 | Html::addCssClass($this->options, 'form-' . $this->layout); 97 | } 98 | parent::init(); 99 | } 100 | 101 | /** 102 | * @inheritdoc 103 | * @return ActiveField the created ActiveField object 104 | */ 105 | public function field($model, $attribute, $options = []) 106 | { 107 | return parent::field($model, $attribute, $options); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /BaseHtml.php: -------------------------------------------------------------------------------- 1 | 'form-check-label']); 149 | } 150 | Html::addCssClass($options, ['widget' => 'form-check-input']); 151 | if(!isset($options['id'])) { 152 | $idMain = strtolower(str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $name)); 153 | $options['id'] = $idMain . '-opt-' . $options['value']; 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /BootstrapWidgetTrait.php: -------------------------------------------------------------------------------- 1 | options['id'])) { 35 | $this->options['id'] = $this->getId(); 36 | } 37 | } 38 | 39 | /** 40 | * Registers a specific Bootstrap plugin and the related events 41 | * @param string $name the name of the Bootstrap plugin 42 | */ 43 | protected function registerPlugin($name) 44 | { 45 | $view = $this->getView(); 46 | 47 | BootstrapPluginAsset::register($view); 48 | 49 | $id = $this->options['id']; 50 | 51 | if ($this->clientOptions !== false) { 52 | $options = empty($this->clientOptions) ? '' : Json::htmlEncode($this->clientOptions); 53 | $js = "jQuery('#$id').$name($options);"; 54 | $view->registerJs($js); 55 | } 56 | 57 | $this->registerClientEvents(); 58 | } 59 | 60 | /** 61 | * Registers JS event handlers that are listed in [[clientEvents]]. 62 | * @since 2.0.2 63 | */ 64 | protected function registerClientEvents() 65 | { 66 | if (!empty($this->clientEvents)) { 67 | $id = $this->options['id']; 68 | $js = []; 69 | foreach ($this->clientEvents as $event => $handler) { 70 | $js[] = "jQuery('#$id').on('$event', $handler);"; 71 | } 72 | $this->getView()->registerJs(implode("\n", $js)); 73 | } 74 | } 75 | 76 | /** 77 | * @return \yii\web\View the view object that can be used to render views or view files. 78 | * @see \yii\base\Widget::getView() 79 | */ 80 | abstract function getView(); 81 | } -------------------------------------------------------------------------------- /Html.php: -------------------------------------------------------------------------------- 1 | 18 | * @since 2.0.5 19 | */ 20 | class Html extends BaseHtml 21 | {} -------------------------------------------------------------------------------- /InputWidget.php: -------------------------------------------------------------------------------- 1 | 14 | * @since 2.0.6 15 | */ 16 | class InputWidget extends \yii\widgets\InputWidget 17 | { 18 | use BootstrapWidgetTrait; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public static function widget($config = []) 24 | { 25 | return parent::widget($config); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

Yii2 bootstrap widgets on Bootstrap 4

6 |

7 | 8 | 9 | Those are ported and partially changed `yiisoft/yii2-bootstrap` widgets to use with Bootstrap v4. 10 | 11 | It is using `twbs/bootstrap` package with Bootstrap v4 CSS/JS. 12 | 13 | Use it similarly to `yiisoft/yii2-bootstrap` package. 14 | 15 | > __Please feel free to create a issue / pull request if I forgot something or if you find some bugs.__ 16 | 17 | |yiisoft/yii2-bootstrap |digitv/yii2bootstrap4 | 18 | |-------------------------------|-----------------------------------| 19 | |`yii\bootstrap`\Html |`digitv\bootstrap`\Html | 20 | |`...`\ActiveForm |`...`\ActiveForm | 21 | |`...`\ActiveField |`...`\ActiveField | 22 | |yii\widgets\Breadcrumbs |`...`\widgets\Breadcrumbs | 23 | |[* new card widget](http://getbootstrap.com/docs/4.0/components/card/)|`...`\widgets\Card| 24 | |`...`\Alert |`...`\widgets\Alert | 25 | |`...`\Button |`...`\widgets\Button | 26 | |`...`\ButtonDropdown |`...`\widgets\ButtonDropdown | 27 | |`...`\Carousel |`...`\widgets\Carousel | 28 | |`...`\Collapse |`...`\widgets\Collapse | 29 | |`...`\Dropdown |`...`\widgets\Dropdown | 30 | |`...`\Modal |`...`\widgets\Modal | 31 | |`...`\Nav |`...`\widgets\Nav | 32 | |`...`\Navbar |`...`\widgets\Navbar | 33 | |`...`\Progress |`...`\widgets\Progress | 34 | |`...`\Tabs |`...`\widgets\Tabs | 35 | |`...`\ToggleButtonGroup |`...`\widgets\ToggleButtonGroup | 36 | 37 | Examples: 38 | 39 | ```php 40 | 60, 'label' => 'Test label']) ?> 41 | ``` 42 | 43 | ```php 44 | //Breadcrumbs in layout view 45 | isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 47 | ]) ?> 48 | ``` 49 | 50 | ```php 51 | Yii::$app->name, 55 | 'brandUrl' => Yii::$app->homeUrl, 56 | 'options' => [ 57 | 'class' => 'navbar-dark bg-dark navbar-expand-lg fixed-top', 58 | ], 59 | ]); 60 | $menuItems = [ 61 | ['label' => 'Home', 'url' => ['/site/index']], 62 | ['label' => 'Dropdown', 'url' => ['/site/index'], 'items' => [ 63 | ['label' => 'First', 'url' => ['/site/index']], 64 | ['label' => 'Second', 'url' => '/'], 65 | ]], 66 | ['label' => 'About', 'url' => ['/site/about']], 67 | ['label' => 'Contact', 'url' => ['/site/contact']], 68 | ]; 69 | if (Yii::$app->user->isGuest) { 70 | $menuItems[] = ['label' => 'Signup', 'url' => ['/site/signup']]; 71 | $menuItems[] = ['label' => 'Login', 'url' => ['/site/login']]; 72 | } else { 73 | $menuItems[] = '
  • ' 74 | . digitv\bootstrap\Html::beginForm(['/site/logout'], 'post') 75 | . digitv\bootstrap\Html::submitButton( 76 | 'Logout (' . Yii::$app->user->identity->username . ')', 77 | ['class' => 'btn btn-link logout'] 78 | ) 79 | . digitv\bootstrap\Html::endForm() 80 | . '
  • '; 81 | } 82 | echo digitv\bootstrap\widgets\Nav::widget([ 83 | 'options' => ['class' => 'navbar-nav ml-auto'], 84 | 'items' => $menuItems, 85 | ]); 86 | digitv\bootstrap\widgets\NavBar::end(); 87 | ?> 88 | ``` 89 | -------------------------------------------------------------------------------- /Widget.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class BootstrapAsset extends AssetBundle 14 | { 15 | public $sourcePath = '@vendor/twbs/bootstrap/dist/css'; 16 | public $css = []; 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | public function init() 22 | { 23 | //Add css depending on user environment 24 | $this->css[] = YII_ENV_DEV ? 'bootstrap.css' : 'bootstrap.min.css'; 25 | parent::init(); 26 | } 27 | } -------------------------------------------------------------------------------- /assets/BootstrapGridAsset.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class BootstrapGridAsset extends AssetBundle 14 | { 15 | public $sourcePath = '@vendor/twbs/bootstrap/dist/css'; 16 | public $css = []; 17 | public $depends = []; 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | public function init() 23 | { 24 | //Add css depending on user environment 25 | $this->css[] = YII_ENV_DEV ? 'bootstrap-grid.css' : 'bootstrap-grid.min.css'; 26 | parent::init(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /assets/BootstrapPluginAsset.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class BootstrapPluginAsset extends AssetBundle 14 | { 15 | public $sourcePath = '@vendor/twbs/bootstrap/dist/js'; 16 | public $js = []; 17 | 18 | public $depends = [ 19 | 'yii\web\JqueryAsset', 20 | ]; 21 | 22 | /** 23 | * @inheritdoc 24 | */ 25 | public function init() 26 | { 27 | //Add js depending on user environment 28 | $this->js[] = YII_ENV_DEV ? 'bootstrap.bundle.js' : 'bootstrap.bundle.min.js'; 29 | parent::init(); 30 | } 31 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "digitv/yii2bootstrap4", 3 | "version": "1.1.2", 4 | "description": "Yii2 Bootstrap 4 widgets", 5 | "keywords": ["yii2", "bootstrap", "bootstrap 4", "yii2 bootstrap4", "yii2 bootstrap 4"], 6 | "type": "yii2-extension", 7 | "license": "Apache-2.0", 8 | "support": { 9 | "issues": "https://github.com/digitv/yii2bootstrap4/issues", 10 | "source": "https://github.com/digitv/yii2bootstrap4" 11 | }, 12 | "authors": [ 13 | { 14 | "name": "Digit", 15 | "email": "digit.vova@gmail.com" 16 | } 17 | ], 18 | "minimum-stability": "dev", 19 | "require": { 20 | "yiisoft/yii2": "^2.0.13", 21 | "twbs/bootstrap": "^4.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "digitv\\bootstrap\\": "" 26 | } 27 | }, 28 | "extra": { 29 | "branch-alias": { 30 | "dev-master": "1.x-dev" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /widgets/Alert.php: -------------------------------------------------------------------------------- 1 | [ 22 | * 'class' => 'alert-info', 23 | * ], 24 | * 'body' => 'Say hello...', 25 | * ]); 26 | * ``` 27 | * 28 | * The following example will show the content enclosed between the [[begin()]] 29 | * and [[end()]] calls within the alert box: 30 | * 31 | * ```php 32 | * Alert::begin([ 33 | * 'options' => [ 34 | * 'class' => 'alert-warning', 35 | * ], 36 | * ]); 37 | * 38 | * echo 'Say hello...'; 39 | * 40 | * Alert::end(); 41 | * ``` 42 | * 43 | * @see http://getbootstrap.com/components/#alerts 44 | * @author Antonio Ramirez 45 | * @since 2.0 46 | */ 47 | class Alert extends Widget 48 | { 49 | /** 50 | * @var string the body content in the alert component. Note that anything between 51 | * the [[begin()]] and [[end()]] calls of the Alert widget will also be treated 52 | * as the body content, and will be rendered before this. 53 | */ 54 | public $body; 55 | /** 56 | * @var array|false the options for rendering the close button tag. 57 | * The close button is displayed in the header of the modal window. Clicking 58 | * on the button will hide the modal window. If this is false, no close button will be rendered. 59 | * 60 | * The following special options are supported: 61 | * 62 | * - tag: string, the tag name of the button. Defaults to 'button'. 63 | * - label: string, the label of the button. Defaults to '×'. 64 | * 65 | * The rest of the options will be rendered as the HTML attributes of the button tag. 66 | * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts) 67 | * for the supported HTML attributes. 68 | */ 69 | public $closeButton = []; 70 | 71 | 72 | /** 73 | * Initializes the widget. 74 | */ 75 | public function init() 76 | { 77 | parent::init(); 78 | 79 | $this->initOptions(); 80 | 81 | echo Html::beginTag('div', $this->options) . "\n"; 82 | echo $this->renderBodyBegin() . "\n"; 83 | } 84 | 85 | /** 86 | * Renders the widget. 87 | */ 88 | public function run() 89 | { 90 | echo "\n" . $this->renderBodyEnd(); 91 | echo "\n" . Html::endTag('div'); 92 | 93 | $this->registerPlugin('alert'); 94 | } 95 | 96 | /** 97 | * Renders the close button if any before rendering the content. 98 | * @return string the rendering result 99 | */ 100 | protected function renderBodyBegin() 101 | { 102 | return $this->renderCloseButton(); 103 | } 104 | 105 | /** 106 | * Renders the alert body (if any). 107 | * @return string the rendering result 108 | */ 109 | protected function renderBodyEnd() 110 | { 111 | return $this->body . "\n"; 112 | } 113 | 114 | /** 115 | * Renders the close button. 116 | * @return string the rendering result 117 | */ 118 | protected function renderCloseButton() 119 | { 120 | if (($closeButton = $this->closeButton) !== false) { 121 | $tag = ArrayHelper::remove($closeButton, 'tag', 'button'); 122 | $label = ArrayHelper::remove($closeButton, 'label', '×'); 123 | if ($tag === 'button' && !isset($closeButton['type'])) { 124 | $closeButton['type'] = 'button'; 125 | } 126 | 127 | return Html::tag($tag, $label, $closeButton); 128 | } else { 129 | return null; 130 | } 131 | } 132 | 133 | /** 134 | * Initializes the widget options. 135 | * This method sets the default values for various options. 136 | */ 137 | protected function initOptions() 138 | { 139 | Html::addCssClass($this->options, ['alert']); 140 | 141 | if ($this->closeButton !== false) { 142 | Html::addCssClass($this->options, ['alert-dismissible', 'fade', 'show']); 143 | $this->closeButton = array_merge([ 144 | 'data-dismiss' => 'alert', 145 | 'aria-hidden' => 'true', 146 | 'aria-label' => 'Close', 147 | 'class' => 'close', 148 | ], $this->closeButton); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /widgets/Breadcrumbs.php: -------------------------------------------------------------------------------- 1 | "
  • {link}
  • \n", // template for all links 29 | * 'links' => [ 30 | * [ 31 | * 'label' => 'Post Category', 32 | * 'url' => ['post-category/view', 'id' => 10], 33 | * 'template' => "
  • {link}
  • \n", // template for this link only 34 | * ], 35 | * ['label' => 'Sample Post', 'url' => ['post/edit', 'id' => 1]], 36 | * 'Edit', 37 | * ], 38 | * ]); 39 | * ``` 40 | * 41 | * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view. 42 | * You can use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different 43 | * views. In the layout view, you assign this view parameter to the [[links]] property like the following: 44 | * 45 | * ```php 46 | * // $this is the view object currently being used 47 | * echo Breadcrumbs::widget([ 48 | * 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], 49 | * ]); 50 | * ``` 51 | * 52 | * @author Qiang Xue 53 | * @since 2.0 54 | */ 55 | class Breadcrumbs extends Widget 56 | { 57 | /** 58 | * @var string the name of the breadcrumb container tag. 59 | */ 60 | public $tag = 'ol'; 61 | /** 62 | * @var array the HTML attributes for the breadcrumb container tag. 63 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 64 | */ 65 | public $options = ['class' => 'breadcrumb']; 66 | /** 67 | * @var bool whether to HTML-encode the link labels. 68 | */ 69 | public $encodeLabels = true; 70 | /** 71 | * @var array the first hyperlink in the breadcrumbs (called home link). 72 | * Please refer to [[links]] on the format of the link. 73 | * If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]] 74 | * with the label 'Home'. If this property is false, the home link will not be rendered. 75 | */ 76 | public $homeLink; 77 | /** 78 | * @var array list of links to appear in the breadcrumbs. If this property is empty, 79 | * the widget will not render anything. Each array element represents a single link in the breadcrumbs 80 | * with the following structure: 81 | * 82 | * ```php 83 | * [ 84 | * 'label' => 'label of the link', // required 85 | * 'url' => 'url of the link', // optional, will be processed by Url::to() 86 | * 'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used 87 | * ] 88 | * ``` 89 | * 90 | * If a link is active, you only need to specify its "label", and instead of writing `['label' => $label]`, 91 | * you may simply use `$label`. 92 | * 93 | * Since version 2.0.1, any additional array elements for each link will be treated as the HTML attributes 94 | * for the hyperlink tag. For example, the following link specification will generate a hyperlink 95 | * with CSS class `external`: 96 | * 97 | * ```php 98 | * [ 99 | * 'label' => 'demo', 100 | * 'url' => 'http://example.com', 101 | * 'class' => 'external', 102 | * ] 103 | * ``` 104 | * 105 | * Since version 2.0.3 each individual link can override global [[encodeLabels]] param like the following: 106 | * 107 | * ```php 108 | * [ 109 | * 'label' => 'Hello!', 110 | * 'encode' => false, 111 | * ] 112 | * ``` 113 | */ 114 | public $links = []; 115 | /** 116 | * @var string the template used to render each inactive item in the breadcrumbs. The token `{link}` 117 | * will be replaced with the actual HTML link for each inactive item. 118 | */ 119 | public $itemTemplate = "
  • {link}
  • \n"; 120 | /** 121 | * @var string the template used to render each active item in the breadcrumbs. The token `{link}` 122 | * will be replaced with the actual HTML link for each active item. 123 | */ 124 | public $activeItemTemplate = "
  • {link}
  • \n"; 125 | 126 | 127 | /** 128 | * Renders the widget. 129 | */ 130 | public function run() 131 | { 132 | if (empty($this->links)) { 133 | return; 134 | } 135 | $links = []; 136 | if ($this->homeLink === null) { 137 | $links[] = $this->renderItem([ 138 | 'label' => Yii::t('yii', 'Home'), 139 | 'url' => Yii::$app->homeUrl, 140 | ], $this->itemTemplate); 141 | } elseif ($this->homeLink !== false) { 142 | $links[] = $this->renderItem($this->homeLink, $this->itemTemplate); 143 | } 144 | foreach ($this->links as $link) { 145 | if (!is_array($link)) { 146 | $link = ['label' => $link]; 147 | } 148 | $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate); 149 | } 150 | $list = Html::tag($this->tag, implode('', $links), $this->options); 151 | echo Html::tag('nav', $list, ['aria-label' => 'breadcrumb']); 152 | } 153 | 154 | /** 155 | * Renders a single breadcrumb item. 156 | * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional. 157 | * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the link. 158 | * @return string the rendering result 159 | * @throws InvalidConfigException if `$link` does not have "label" element. 160 | */ 161 | protected function renderItem($link, $template) 162 | { 163 | $encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels); 164 | if (array_key_exists('label', $link)) { 165 | $label = $encodeLabel ? Html::encode($link['label']) : $link['label']; 166 | } else { 167 | throw new InvalidConfigException('The "label" element is required for each link.'); 168 | } 169 | if (isset($link['template'])) { 170 | $template = $link['template']; 171 | } 172 | if (isset($link['url'])) { 173 | $options = $link; 174 | unset($options['template'], $options['label'], $options['url']); 175 | $link = Html::a($label, $link['url'], $options); 176 | } else { 177 | $link = $label; 178 | } 179 | 180 | return strtr($template, ['{link}' => $link]); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /widgets/Button.php: -------------------------------------------------------------------------------- 1 | 'Action', 21 | * 'options' => ['class' => 'btn-lg'], 22 | * ]); 23 | * ``` 24 | * @see http://getbootstrap.com/javascript/#buttons 25 | * @author Antonio Ramirez 26 | * @since 2.0 27 | */ 28 | class Button extends Widget 29 | { 30 | /** 31 | * @var string the tag to use to render the button 32 | */ 33 | public $tagName = 'button'; 34 | /** 35 | * @var string the button label 36 | */ 37 | public $label = 'Button'; 38 | /** 39 | * @var boolean whether the label should be HTML-encoded. 40 | */ 41 | public $encodeLabel = true; 42 | 43 | 44 | /** 45 | * Initializes the widget. 46 | * If you override this method, make sure you call the parent implementation first. 47 | */ 48 | public function init() 49 | { 50 | parent::init(); 51 | $this->clientOptions = false; 52 | Html::addCssClass($this->options, ['widget' => 'btn']); 53 | } 54 | 55 | /** 56 | * Renders the widget. 57 | */ 58 | public function run() 59 | { 60 | $this->registerPlugin('button'); 61 | return Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /widgets/ButtonDropdown.php: -------------------------------------------------------------------------------- 1 | 'Action', 23 | * 'dropdown' => [ 24 | * 'items' => [ 25 | * ['label' => 'DropdownA', 'url' => '/'], 26 | * ['label' => 'DropdownB', 'url' => '#'], 27 | * ], 28 | * ], 29 | * ]); 30 | * ``` 31 | * @see http://getbootstrap.com/javascript/#buttons 32 | * @see http://getbootstrap.com/components/#btn-dropdowns 33 | * @author Antonio Ramirez 34 | * @since 2.0 35 | */ 36 | class ButtonDropdown extends Widget 37 | { 38 | /** 39 | * @var string the button label 40 | */ 41 | public $label = 'Button'; 42 | /** 43 | * @var array the HTML attributes of the button group container. 44 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 45 | */ 46 | public $options = []; 47 | /** 48 | * @var array the HTML attributes of the button. 49 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 50 | */ 51 | public $buttonOptions = []; 52 | /** 53 | * @var array the configuration array for [[Dropdown]]. 54 | */ 55 | public $dropdown = []; 56 | /** 57 | * @var boolean whether to display a group of split-styled button group. 58 | */ 59 | public $split = false; 60 | /** 61 | * @var boolean whether to render dropup. 62 | */ 63 | public $dropUp = false; 64 | /** 65 | * @var string the tag to use to render the button 66 | */ 67 | public $tagName = 'button'; 68 | /** 69 | * @var boolean whether the label should be HTML-encoded. 70 | */ 71 | public $encodeLabel = true; 72 | /** 73 | * @var string name of a class to use for rendering dropdowns withing this widget. Defaults to [[Dropdown]]. 74 | * @since 2.0.7 75 | */ 76 | public $dropdownClass = 'digitv\bootstrap\widgets\Dropdown'; 77 | 78 | 79 | /** 80 | * Renders the widget. 81 | */ 82 | public function run() 83 | { 84 | unset($this->options['id']); 85 | Html::addCssClass($this->options, ['widget' => 'btn-group']); 86 | if($this->dropUp) { 87 | Html::addCssClass($this->options, 'dropup'); 88 | } 89 | $options = $this->options; 90 | $tag = ArrayHelper::remove($options, 'tag', 'div'); 91 | 92 | $this->registerPlugin('dropdown'); 93 | return implode("\n", [ 94 | Html::beginTag($tag, $options), 95 | $this->renderButton(), 96 | $this->renderDropdown(), 97 | Html::endTag($tag) 98 | ]); 99 | } 100 | 101 | /** 102 | * Generates the button dropdown. 103 | * @return string the rendering result. 104 | */ 105 | protected function renderButton() 106 | { 107 | Html::addCssClass($this->buttonOptions, ['widget' => 'btn']); 108 | $label = $this->label; 109 | if ($this->encodeLabel) { 110 | $label = Html::encode($label); 111 | } 112 | if ($this->split) { 113 | $options = $this->buttonOptions; 114 | $this->buttonOptions['data-toggle'] = 'dropdown'; 115 | Html::addCssClass($this->buttonOptions, ['toggle' => 'dropdown-toggle']); 116 | $splitButton = Button::widget([ 117 | 'label' => '', 118 | 'encodeLabel' => false, 119 | 'options' => $this->buttonOptions, 120 | 'view' => $this->getView(), 121 | ]); 122 | } else { 123 | $options = $this->buttonOptions; 124 | if (!isset($options['href']) && $this->tagName === 'a') { 125 | $options['href'] = '#'; 126 | } 127 | Html::addCssClass($options, ['toggle' => 'dropdown-toggle']); 128 | $options['data-toggle'] = 'dropdown'; 129 | $splitButton = ''; 130 | } 131 | 132 | return Button::widget([ 133 | 'tagName' => $this->tagName, 134 | 'label' => $label, 135 | 'options' => $options, 136 | 'encodeLabel' => false, 137 | 'view' => $this->getView(), 138 | ]) . "\n" . $splitButton; 139 | } 140 | 141 | /** 142 | * Generates the dropdown menu. 143 | * @return string the rendering result. 144 | */ 145 | protected function renderDropdown() 146 | { 147 | $config = $this->dropdown; 148 | $config['clientOptions'] = false; 149 | $config['view'] = $this->getView(); 150 | /** @var Widget $dropdownClass */ 151 | $dropdownClass = $this->dropdownClass; 152 | return $dropdownClass::widget($config); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /widgets/ButtonGroup.php: -------------------------------------------------------------------------------- 1 | [ 24 | * ['label' => 'A'], 25 | * ['label' => 'B'], 26 | * ['label' => 'C', 'visible' => false], 27 | * ] 28 | * ]); 29 | * 30 | * // button group with an item as a string 31 | * echo ButtonGroup::widget([ 32 | * 'buttons' => [ 33 | * Button::widget(['label' => 'A']), 34 | * ['label' => 'B'], 35 | * ] 36 | * ]); 37 | * ``` 38 | * 39 | * Pressing on the button should be handled via JavaScript. See the following for details: 40 | * 41 | * @see http://getbootstrap.com/javascript/#buttons 42 | * @see http://getbootstrap.com/components/#btn-groups 43 | * 44 | * @author Antonio Ramirez 45 | * @since 2.0 46 | */ 47 | class ButtonGroup extends Widget 48 | { 49 | /** 50 | * @var array list of buttons. Each array element represents a single button 51 | * which can be specified as a string or an array of the following structure: 52 | * 53 | * - label: string, required, the button label. 54 | * - options: array, optional, the HTML attributes of the button. 55 | * - visible: boolean, optional, whether this button is visible. Defaults to true. 56 | */ 57 | public $buttons = []; 58 | /** 59 | * @var boolean whether to HTML-encode the button labels. 60 | */ 61 | public $encodeLabels = true; 62 | 63 | 64 | /** 65 | * Initializes the widget. 66 | * If you override this method, make sure you call the parent implementation first. 67 | */ 68 | public function init() 69 | { 70 | parent::init(); 71 | Html::addCssClass($this->options, ['widget' => 'btn-group']); 72 | } 73 | 74 | /** 75 | * Renders the widget. 76 | */ 77 | public function run() 78 | { 79 | BootstrapAsset::register($this->getView()); 80 | return Html::tag('div', $this->renderButtons(), $this->options); 81 | } 82 | 83 | /** 84 | * Generates the buttons that compound the group as specified on [[buttons]]. 85 | * @return string the rendering result. 86 | */ 87 | protected function renderButtons() 88 | { 89 | $buttons = []; 90 | foreach ($this->buttons as $button) { 91 | if (is_array($button)) { 92 | $visible = ArrayHelper::remove($button, 'visible', true); 93 | if ($visible === false) { 94 | continue; 95 | } 96 | 97 | $button['view'] = $this->getView(); 98 | if (!isset($button['encodeLabel'])) { 99 | $button['encodeLabel'] = $this->encodeLabels; 100 | } 101 | $buttons[] = Button::widget($button); 102 | } else { 103 | $buttons[] = $button; 104 | } 105 | } 106 | 107 | return implode("\n", $buttons); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /widgets/Card.php: -------------------------------------------------------------------------------- 1 | options, ['widget' => 'card']); 42 | ob_start(); 43 | ob_implicit_flush(true); 44 | } 45 | 46 | /** 47 | * @inheritdoc 48 | */ 49 | public function run() 50 | { 51 | $content = []; 52 | $content[] = Html::beginTag($this->tag, $this->options); 53 | $content[] = $this->renderHeader(); 54 | $content[] = $this->renderHeaderImage(); 55 | $content[] = $this->renderOverlayImage(); 56 | $content[] = $this->renderBody(null, true); 57 | $content[] = $this->renderFooter(); 58 | $content[] = Html::endTag($this->tag); 59 | return implode("\n", $content); 60 | } 61 | 62 | /** 63 | * Render card header 64 | * @return string 65 | */ 66 | protected function renderHeader() { 67 | if(!isset($this->header)) return ''; 68 | if(isset($this->headerOptions)) { 69 | Html::addCssClass($this->headerOptions, ['widget' => 'card-header']); 70 | $tag = ArrayHelper::remove($this->headerOptions, 'tag', 'div'); 71 | $header = Html::tag($tag, $this->header, $this->headerOptions); 72 | } else { 73 | $header = $this->header; 74 | } 75 | return $header; 76 | } 77 | 78 | /** 79 | * Render card top image 80 | * @return string 81 | */ 82 | protected function renderHeaderImage() { 83 | if(!isset($this->headerImage)) return ''; 84 | Html::addCssClass($this->headerImageOptions, ['widget' => 'card-img-top']); 85 | return Html::img($this->headerImage, $this->headerImageOptions); 86 | } 87 | 88 | /** 89 | * Render card bottom image 90 | * @return string 91 | */ 92 | protected function renderFooterImage() { 93 | if(!isset($this->footerImage)) return ''; 94 | Html::addCssClass($this->footerImageOptions, ['widget' => 'card-img-bottom']); 95 | return Html::img($this->footerImage, $this->footerImageOptions); 96 | } 97 | 98 | /** 99 | * Render card bottom image 100 | * @return string 101 | */ 102 | protected function renderOverlayImage() { 103 | if(!isset($this->overlayImage)) return ''; 104 | Html::addCssClass($this->footerImageOptions, ['widget' => 'card-img-bottom']); 105 | return Html::img($this->overlayImage, ['class' => 'card-img']); 106 | } 107 | 108 | /** 109 | * Render card body 110 | * @param string $body 111 | * @param bool $includeOb 112 | * @return string 113 | */ 114 | protected function renderBody($body = null, $includeOb = false) { 115 | $body = isset($body) ? $body : $this->body; 116 | $bodyContent = ''; 117 | $bodyTag = 'div'; 118 | if(is_array($body)) { 119 | $bodyRows = []; 120 | foreach ($body as $bodyRow) { 121 | $bodyRowContent = $this->renderBody($bodyRow); 122 | if(empty($bodyRowContent)) continue; 123 | $bodyRows[] = $bodyRowContent; 124 | } 125 | $bodyContent = implode("\n", $bodyRows); 126 | } else { 127 | if(isset($this->bodyOptions)) { 128 | if(isset($this->overlayImage)) { 129 | Html::addCssClass($this->bodyOptions, ['widget' => 'card-img-overlay']); 130 | } else { 131 | Html::addCssClass($this->bodyOptions, ['widget' => 'card-body']); 132 | } 133 | $bodyTag = ArrayHelper::remove($this->bodyOptions, 'tag', 'div'); 134 | $bodyOptions = $this->bodyOptions; 135 | } 136 | if(isset($body)) { 137 | $bodyContent .= $body; 138 | } 139 | } 140 | if($includeOb) { 141 | $bodyContent .= ob_get_clean(); 142 | } 143 | return isset($bodyOptions) ? Html::tag($bodyTag, $bodyContent, $bodyOptions) : $bodyContent; 144 | } 145 | 146 | /** 147 | * Render card footer 148 | * @return string 149 | */ 150 | protected function renderFooter() { 151 | if(!isset($this->footer)) return ''; 152 | if(isset($this->footerOptions)) { 153 | Html::addCssClass($this->footerOptions, ['widget' => 'card-footer']); 154 | $tag = ArrayHelper::remove($this->footerOptions, 'tag', 'div'); 155 | $footer = Html::tag($tag, $this->footer, $this->footerOptions); 156 | } else { 157 | $footer = $this->footer; 158 | } 159 | return $footer; 160 | } 161 | } -------------------------------------------------------------------------------- /widgets/Carousel.php: -------------------------------------------------------------------------------- 1 | [ 23 | * // the item contains only the image 24 | * '', 25 | * // equivalent to the above 26 | * ['content' => ''], 27 | * // the item contains both the image and the caption 28 | * [ 29 | * 'content' => 'http://via.placeholder.com/1200x350/0A0"/>', 30 | * 'caption' => '

    This is title

    This is the caption text

    ', 31 | * 'captionOptions' => ['class' => 'my-own-class'], 32 | * 'options' => [...], 33 | * ], 34 | * ] 35 | * ]); 36 | * ``` 37 | * 38 | * @see http://getbootstrap.com/javascript/#carousel 39 | * @author Antonio Ramirez 40 | * @since 2.0 41 | */ 42 | class Carousel extends Widget 43 | { 44 | /** 45 | * @var array|boolean the labels for the previous and the next control buttons. 46 | * If false, it means the previous and the next control buttons should not be displayed. 47 | */ 48 | public $controls = []; 49 | /** 50 | * @var array the text labels for the previous and the next control buttons. 51 | */ 52 | public $controlsLabels = ['Previous', 'Next']; 53 | /** 54 | * @var boolean 55 | * If false carousel indicators (
      tag with anchors to items) should not be displayed. 56 | */ 57 | public $showIndicators = true; 58 | /** 59 | * @var array list of slides in the carousel. Each array element represents a single 60 | * slide with the following structure: 61 | * 62 | * ```php 63 | * [ 64 | * // required, slide content (HTML), such as an image tag 65 | * 'content' => '', 66 | * // optional, the caption (HTML) of the slide 67 | * 'caption' => '

      This is title

      This is the caption text

      ', 68 | * // optional the HTML attributes of the slide container 69 | * 'options' => [], 70 | * ] 71 | * ``` 72 | */ 73 | public $items = []; 74 | 75 | 76 | /** 77 | * Initializes the widget. 78 | */ 79 | public function init() 80 | { 81 | parent::init(); 82 | Html::addCssClass($this->options, ['widget' => 'carousel slide']); 83 | if(isset($this->controls) && empty($this->controls)) { 84 | $this->controls[] = Html::tag('span', '', ['class' => 'carousel-control-prev-icon']) 85 | . Html::tag('span', $this->controlsLabels[0], ['class' => 'sr-only']); 86 | $this->controls[] = Html::tag('span', '', ['class' => 'carousel-control-next-icon']) 87 | . Html::tag('span', $this->controlsLabels[1], ['class' => 'sr-only']); 88 | } 89 | } 90 | 91 | /** 92 | * Renders the widget. 93 | */ 94 | public function run() 95 | { 96 | $this->registerPlugin('carousel'); 97 | return implode("\n", [ 98 | Html::beginTag('div', $this->options), 99 | $this->renderIndicators(), 100 | $this->renderItems(), 101 | $this->renderControls(), 102 | Html::endTag('div') 103 | ]) . "\n"; 104 | } 105 | 106 | /** 107 | * Renders carousel indicators. 108 | * @return string the rendering result 109 | */ 110 | public function renderIndicators() 111 | { 112 | if ($this->showIndicators === false) { 113 | return ''; 114 | } 115 | $indicators = []; 116 | for ($i = 0, $count = count($this->items); $i < $count; $i++) { 117 | $options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i]; 118 | if ($i === 0) { 119 | Html::addCssClass($options, 'active'); 120 | } 121 | $indicators[] = Html::tag('li', '', $options); 122 | } 123 | 124 | return Html::tag('ol', implode("\n", $indicators), ['class' => 'carousel-indicators']); 125 | } 126 | 127 | /** 128 | * Renders carousel items as specified on [[items]]. 129 | * @return string the rendering result 130 | */ 131 | public function renderItems() 132 | { 133 | $items = []; 134 | for ($i = 0, $count = count($this->items); $i < $count; $i++) { 135 | $items[] = $this->renderItem($this->items[$i], $i); 136 | } 137 | 138 | return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']); 139 | } 140 | 141 | /** 142 | * Renders a single carousel item 143 | * @param string|array $item a single item from [[items]] 144 | * @param int $index the item index as the first item should be set to `active` 145 | * @return string the rendering result 146 | * @throws InvalidConfigException if the item is invalid 147 | */ 148 | public function renderItem($item, $index) 149 | { 150 | if (is_string($item)) { 151 | $content = $item; 152 | $caption = null; 153 | $options = []; 154 | } elseif (isset($item['content'])) { 155 | $content = $item['content']; 156 | $caption = ArrayHelper::getValue($item, 'caption'); 157 | if ($caption !== null) { 158 | $captionOptions = ArrayHelper::getValue($item, 'captionOptions', []); 159 | Html::addCssClass($captionOptions, ['widget' => 'carousel-caption']); 160 | $caption = Html::tag('div', $caption, $captionOptions); 161 | } 162 | $options = ArrayHelper::getValue($item, 'options', []); 163 | } else { 164 | throw new InvalidConfigException('The "content" option is required.'); 165 | } 166 | 167 | Html::addCssClass($options, ['widget' => 'carousel-item']); 168 | if ($index === 0) { 169 | Html::addCssClass($options, 'active'); 170 | } 171 | 172 | return Html::tag('div', $content . "\n" . $caption, $options); 173 | } 174 | 175 | /** 176 | * Renders previous and next control buttons. 177 | * @throws InvalidConfigException if [[controls]] is invalid. 178 | */ 179 | public function renderControls() 180 | { 181 | if (isset($this->controls[0], $this->controls[1])) { 182 | return Html::a($this->controls[0], '#' . $this->options['id'], [ 183 | 'class' => 'carousel-control-prev', 184 | 'data-slide' => 'prev', 185 | ]) . "\n" 186 | . Html::a($this->controls[1], '#' . $this->options['id'], [ 187 | 'class' => 'carousel-control-next', 188 | 'data-slide' => 'next', 189 | ]); 190 | } elseif ($this->controls === false) { 191 | return ''; 192 | } else { 193 | throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.'); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /widgets/Collapse.php: -------------------------------------------------------------------------------- 1 | [ 25 | * // equivalent to the above 26 | * [ 27 | * 'label' => 'Collapsible Group Item #1', 28 | * 'content' => 'Anim pariatur cliche...', 29 | * // open its content by default 30 | * 'contentOptions' => ['class' => 'in'] 31 | * ], 32 | * // another group item 33 | * [ 34 | * 'label' => 'Collapsible Group Item #1', 35 | * 'content' => 'Anim pariatur cliche...', 36 | * 'contentOptions' => [...], 37 | * 'options' => [...], 38 | * ], 39 | * // if you want to swap out .panel-body with .list-group, you may use the following 40 | * [ 41 | * 'label' => 'Collapsible Group Item #1', 42 | * 'content' => [ 43 | * 'Anim pariatur cliche...', 44 | * 'Anim pariatur cliche...' 45 | * ], 46 | * 'contentOptions' => [...], 47 | * 'options' => [...], 48 | * 'footer' => 'Footer' // the footer label in list-group 49 | * ], 50 | * ] 51 | * ]); 52 | * ``` 53 | * 54 | * @see http://getbootstrap.com/javascript/#collapse 55 | * @author Antonio Ramirez 56 | * @since 2.0 57 | */ 58 | class Collapse extends Widget 59 | { 60 | /** 61 | * @var array list of groups in the collapse widget. Each array element represents a single 62 | * group with the following structure: 63 | * 64 | * - label: string, required, the group header label. 65 | * - encode: boolean, optional, whether this label should be HTML-encoded. This param will override 66 | * global `$this->encodeLabels` param. 67 | * - content: array|string|object, required, the content (HTML) of the group 68 | * - options: array, optional, the HTML attributes of the group 69 | * - contentOptions: optional, the HTML attributes of the group's content 70 | * 71 | * Since version 2.0.7 you may also specify this property as key-value pairs, where the key refers to the 72 | * `label` and the value refers to `content`. If value is a string it is interpreted as label. If it is 73 | * an array, it is interpreted as explained above. 74 | * 75 | * For example: 76 | * 77 | * ```php 78 | * echo Collapse::widget([ 79 | * 'items' => [ 80 | * 'Introduction' => 'This is the first collapsable menu', 81 | * 'Second panel' => [ 82 | * 'content' => 'This is the second collapsable menu', 83 | * ], 84 | * [ 85 | * 'label' => 'Third panel', 86 | * 'content' => 'This is the third collapsable menu', 87 | * ], 88 | * ] 89 | * ]) 90 | * ``` 91 | */ 92 | public $items = []; 93 | /** 94 | * @var boolean whether the labels for header items should be HTML-encoded. 95 | */ 96 | public $encodeLabels = true; 97 | /** 98 | * @var boolean whether to close other items if an item is opened. Defaults to `true` which causes an 99 | * accordion effect. Set this to `false` to allow keeping multiple items open at once. 100 | * @since 2.0.7 101 | */ 102 | public $autoCloseItems = true; 103 | 104 | 105 | /** 106 | * Initializes the widget. 107 | */ 108 | public function init() 109 | { 110 | parent::init(); 111 | } 112 | 113 | /** 114 | * Renders the widget. 115 | */ 116 | public function run() 117 | { 118 | $this->registerPlugin('collapse'); 119 | return implode("\n", [ 120 | Html::beginTag('div', $this->options), 121 | $this->renderItems(), 122 | Html::endTag('div') 123 | ]) . "\n"; 124 | } 125 | 126 | /** 127 | * Renders collapsible items as specified on [[items]]. 128 | * @throws InvalidConfigException if label isn't specified 129 | * @return string the rendering result 130 | */ 131 | public function renderItems() 132 | { 133 | $items = []; 134 | $index = 0; 135 | foreach ($this->items as $key => $item) { 136 | if (!is_array($item)) { 137 | $item = ['content' => $item]; 138 | } 139 | if (!array_key_exists('label', $item)) { 140 | if (is_int($key)) { 141 | throw new InvalidConfigException("The 'label' option is required."); 142 | } else { 143 | $item['label'] = $key; 144 | } 145 | } 146 | $items[] = $this->renderItem($item, ++$index); 147 | } 148 | 149 | return implode("\n", $items); 150 | } 151 | 152 | /** 153 | * Renders a single collapsible item group 154 | * @param string $header a label of the item group [[items]] 155 | * @param array $item a single item from [[items]] 156 | * @param int $index the item index as each item group content must have an id 157 | * @return string the rendering result 158 | * @throws InvalidConfigException 159 | */ 160 | public function renderItem($item, $index) 161 | { 162 | $header = $item['label']; 163 | if (array_key_exists('content', $item)) { 164 | $id = $this->options['id'] . '-collapse-' . $index; 165 | $options = ArrayHelper::getValue($item, 'contentOptions', []); 166 | $options['id'] = $id; 167 | Html::addCssClass($options, ['widget' => 'collapse']); 168 | 169 | $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; 170 | if ($encodeLabel) { 171 | $header = Html::encode($header); 172 | } 173 | 174 | $headerOptions = [ 175 | 'class' => 'collapse-toggle', 176 | 'data-toggle' => 'collapse', 177 | ]; 178 | 179 | $headerToggle = Html::a($header, '#' . $id, $headerOptions) . "\n"; 180 | 181 | if (is_string($item['content']) || is_numeric($item['content']) || is_object($item['content'])) { 182 | $content = Html::tag('div', $item['content'], ['class' => 'card-body']) . "\n"; 183 | } elseif (is_array($item['content'])) { 184 | $content = Html::ul($item['content'], [ 185 | 'class' => 'list-group list-group-flush', 186 | 'itemOptions' => [ 187 | 'class' => 'list-group-item' 188 | ], 189 | 'encode' => false, 190 | ]) . "\n"; 191 | } else { 192 | throw new InvalidConfigException('The "content" option should be a string, array or object.'); 193 | } 194 | } else { 195 | throw new InvalidConfigException('The "content" option is required.'); 196 | } 197 | 198 | if ($this->autoCloseItems) { 199 | $options['data-parent'] = '#' . $this->options['id']; 200 | } 201 | $contentCollapse = Html::tag('div', $content, $options); 202 | 203 | $cardConfig = [ 204 | 'options' => [ 205 | 'id' => $this->id . '-collapse-card-' . $index, 206 | ], 207 | 'body' => $contentCollapse, 208 | 'bodyOptions' => null, 209 | 'header' => $headerToggle, 210 | 'footer' => isset($item['footer']) ? $item['footer'] : null, 211 | ]; 212 | 213 | return Card::widget($cardConfig); 214 | } 215 | 216 | /** 217 | * Registers a specific Bootstrap plugin and the related events 218 | * @param string $name the name of the Bootstrap plugin 219 | */ 220 | protected function registerPlugin($name = 'collapse') 221 | { 222 | $view = $this->getView(); 223 | 224 | BootstrapPluginAsset::register($view); 225 | 226 | $this->registerClientEvents(); 227 | } 228 | 229 | /** 230 | * Registers JS event handlers that are listed in [[clientEvents]]. 231 | * @since 2.0.2 232 | */ 233 | protected function registerClientEvents() 234 | { 235 | if (!empty($this->clientEvents)) { 236 | $idWidget = $this->options['id']; 237 | $itemsCount = count($this->items); 238 | for ($i = 1; $i <= $itemsCount; $i++) { 239 | $id = $idWidget . '-collapse-' . $i; 240 | $js = []; 241 | foreach ($this->clientEvents as $event => $handler) { 242 | $js[] = "jQuery('#$id').on('$event', $handler);"; 243 | } 244 | $this->getView()->registerJs(implode("\n", $js)); 245 | } 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /widgets/Dropdown.php: -------------------------------------------------------------------------------- 1 | 23 | * Label 24 | * [ 27 | * ['label' => 'DropdownA', 'url' => '/'], 28 | * ['label' => 'DropdownB', 'url' => '#'], 29 | * ], 30 | * ]); 31 | * ?> 32 | * 33 | * ``` 34 | * @see http://getbootstrap.com/javascript/#dropdowns 35 | * @author Antonio Ramirez 36 | * @since 2.0 37 | */ 38 | class Dropdown extends Widget 39 | { 40 | const DIVIDER = 'divider'; 41 | 42 | /** 43 | * @var array list of menu items in the dropdown. Each array element can be either an HTML string, 44 | * or an array representing a single menu with the following structure: 45 | * 46 | * - label: string, required, the label of the item link. 47 | * - encode: boolean, optional, whether to HTML-encode item label. 48 | * - url: string|array, optional, the URL of the item link. This will be processed by [[\yii\helpers\Url::to()]]. 49 | * If not set, the item will be treated as a menu header when the item has no sub-menu. 50 | * - visible: boolean, optional, whether this menu item is visible. Defaults to true. 51 | * - linkOptions: array, optional, the HTML attributes of the item link. 52 | * - options: array, optional, the HTML attributes of the item. 53 | * - items: array, optional, the submenu items. The structure is the same as this property. 54 | * Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it. 55 | * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be 56 | * merged with [[submenuOptions]]. 57 | * 58 | * To insert divider use Dropdown::DIVIDER. 59 | */ 60 | public $items = []; 61 | /** 62 | * @var boolean whether the labels for header items should be HTML-encoded. 63 | */ 64 | public $encodeLabels = true; 65 | 66 | /** 67 | * Initializes the widget. 68 | * If you override this method, make sure you call the parent implementation first. 69 | */ 70 | public function init() 71 | { 72 | parent::init(); 73 | Html::addCssClass($this->options, ['widget' => 'dropdown-menu']); 74 | } 75 | 76 | /** 77 | * Renders the widget. 78 | * @return string 79 | * @throws InvalidConfigException 80 | */ 81 | public function run() 82 | { 83 | BootstrapPluginAsset::register($this->getView()); 84 | $this->registerClientEvents(); 85 | return $this->renderItems($this->items, $this->options); 86 | } 87 | 88 | /** 89 | * Renders menu items. 90 | * @param array $items the menu items to be rendered 91 | * @param array $options the container HTML attributes 92 | * @return string the rendering result. 93 | * @throws InvalidConfigException if the label option is not specified in one of the items. 94 | */ 95 | protected function renderItems($items, $options = []) 96 | { 97 | $lines = []; 98 | foreach ($items as $item) { 99 | if (is_string($item)) { 100 | if($item === static::DIVIDER) $lines[] = $this->renderDivider(); 101 | else $lines[] = $item; 102 | continue; 103 | } 104 | if (isset($item['visible']) && !$item['visible']) { 105 | continue; 106 | } 107 | if (!array_key_exists('label', $item)) { 108 | throw new InvalidConfigException("The 'label' option is required."); 109 | } 110 | $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; 111 | $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; 112 | $itemOptions = ArrayHelper::getValue($item, 'options', []); 113 | $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); 114 | $itemOptions = ArrayHelper::merge($itemOptions, $linkOptions); 115 | Html::addCssClass($itemOptions, ['widget' => 'dropdown-item']); 116 | $url = array_key_exists('url', $item) ? $item['url'] : null; 117 | if ($url === null) { 118 | Html::addCssClass($itemOptions, ['widget' => 'dropdown-header']); 119 | $content = $this->renderHeader($label); 120 | } else { 121 | $content = Html::a($label, $url, $itemOptions); 122 | } 123 | 124 | $lines[] = $content; 125 | } 126 | 127 | return Html::tag('div', implode("\n", $lines), $options); 128 | } 129 | 130 | /** 131 | * Renders divider 132 | * @param string $tag 133 | * @return string 134 | */ 135 | protected function renderDivider($tag = 'div') { 136 | return Html::tag($tag, '', ['class' => 'dropdown-divider']); 137 | } 138 | 139 | /** 140 | * Renders header 141 | * @param string $content 142 | * @param string $tag 143 | * @return string 144 | */ 145 | protected function renderHeader($content = '', $tag = 'h6') { 146 | return Html::tag($tag, $content, ['class' => 'dropdown-header']); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /widgets/Modal.php: -------------------------------------------------------------------------------- 1 | '

      Hello world

      ', 24 | * 'toggleButton' => ['label' => 'click me'], 25 | * ]); 26 | * 27 | * echo 'Say hello...'; 28 | * 29 | * Modal::end(); 30 | * ~~~ 31 | * 32 | * @see http://getbootstrap.com/javascript/#modals 33 | * @author Antonio Ramirez 34 | * @author Qiang Xue 35 | * @since 2.0 36 | */ 37 | class Modal extends Widget 38 | { 39 | const SIZE_LARGE = "modal-lg"; 40 | const SIZE_SMALL = "modal-sm"; 41 | const SIZE_DEFAULT = ""; 42 | 43 | /** 44 | * @var string the header content in the modal window. 45 | */ 46 | public $header; 47 | /** 48 | * @var array additional header options 49 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 50 | * @since 2.0.1 51 | */ 52 | public $headerOptions = []; 53 | /** 54 | * @var array body options 55 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 56 | * @since 2.0.7 57 | */ 58 | public $bodyOptions = ['class' => 'modal-body']; 59 | /** 60 | * @var string the footer content in the modal window. 61 | */ 62 | public $footer; 63 | /** 64 | * @var string additional footer options 65 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 66 | * @since 2.0.1 67 | */ 68 | public $footerOptions; 69 | /** 70 | * @var string the modal size. Can be [[SIZE_LARGE]] or [[SIZE_SMALL]], or empty for default. 71 | */ 72 | public $size; 73 | /** 74 | * @var bool vertically centered modal 75 | */ 76 | public $centered = false; 77 | /** 78 | * @var array|false the options for rendering the close button tag. 79 | * The close button is displayed in the header of the modal window. Clicking 80 | * on the button will hide the modal window. If this is false, no close button will be rendered. 81 | * 82 | * The following special options are supported: 83 | * 84 | * - tag: string, the tag name of the button. Defaults to 'button'. 85 | * - label: string, the label of the button. Defaults to '×'. 86 | * 87 | * The rest of the options will be rendered as the HTML attributes of the button tag. 88 | * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals) 89 | * for the supported HTML attributes. 90 | */ 91 | public $closeButton = []; 92 | /** 93 | * @var array the options for rendering the toggle button tag. 94 | * The toggle button is used to toggle the visibility of the modal window. 95 | * If this property is false, no toggle button will be rendered. 96 | * 97 | * The following special options are supported: 98 | * 99 | * - tag: string, the tag name of the button. Defaults to 'button'. 100 | * - label: string, the label of the button. Defaults to 'Show'. 101 | * 102 | * The rest of the options will be rendered as the HTML attributes of the button tag. 103 | * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals) 104 | * for the supported HTML attributes. 105 | */ 106 | public $toggleButton = false; 107 | 108 | 109 | /** 110 | * Initializes the widget. 111 | */ 112 | public function init() 113 | { 114 | parent::init(); 115 | 116 | $this->initOptions(); 117 | $modalDialogOptions = ['class' => 'modal-dialog ' . $this->size]; 118 | $modalDialogOptions['class'] .= ($this->centered ? ' modal-dialog-centered' : ''); 119 | 120 | echo $this->renderToggleButton() . "\n"; 121 | echo Html::beginTag('div', $this->options) . "\n"; 122 | echo Html::beginTag('div', $modalDialogOptions) . "\n"; 123 | echo Html::beginTag('div', ['class' => 'modal-content']) . "\n"; 124 | echo $this->renderHeader() . "\n"; 125 | echo $this->renderBodyBegin() . "\n"; 126 | } 127 | 128 | /** 129 | * Renders the widget. 130 | */ 131 | public function run() 132 | { 133 | echo "\n" . $this->renderBodyEnd(); 134 | echo "\n" . $this->renderFooter(); 135 | echo "\n" . Html::endTag('div'); // modal-content 136 | echo "\n" . Html::endTag('div'); // modal-dialog 137 | echo "\n" . Html::endTag('div'); 138 | 139 | $this->registerPlugin('modal'); 140 | } 141 | 142 | /** 143 | * Renders the header HTML markup of the modal 144 | * @return string the rendering result 145 | */ 146 | protected function renderHeader() 147 | { 148 | $button = $this->renderCloseButton(); 149 | if ($button !== null) { 150 | $this->header = $this->header . "\n" . $button; 151 | } 152 | if ($this->header !== null) { 153 | Html::addCssClass($this->headerOptions, ['widget' => 'modal-header']); 154 | return Html::tag('div', "\n" . $this->header . "\n", $this->headerOptions); 155 | } else { 156 | return null; 157 | } 158 | } 159 | 160 | /** 161 | * Renders the opening tag of the modal body. 162 | * @return string the rendering result 163 | */ 164 | protected function renderBodyBegin() 165 | { 166 | return Html::beginTag('div', $this->bodyOptions); 167 | } 168 | 169 | /** 170 | * Renders the closing tag of the modal body. 171 | * @return string the rendering result 172 | */ 173 | protected function renderBodyEnd() 174 | { 175 | return Html::endTag('div'); 176 | } 177 | 178 | /** 179 | * Renders the HTML markup for the footer of the modal 180 | * @return string the rendering result 181 | */ 182 | protected function renderFooter() 183 | { 184 | if ($this->footer !== null) { 185 | Html::addCssClass($this->footerOptions, ['widget' => 'modal-footer']); 186 | return Html::tag('div', "\n" . $this->footer . "\n", $this->footerOptions); 187 | } else { 188 | return null; 189 | } 190 | } 191 | 192 | /** 193 | * Renders the toggle button. 194 | * @return string the rendering result 195 | */ 196 | protected function renderToggleButton() 197 | { 198 | if (($toggleButton = $this->toggleButton) !== false) { 199 | $tag = ArrayHelper::remove($toggleButton, 'tag', 'button'); 200 | $label = ArrayHelper::remove($toggleButton, 'label', 'Show'); 201 | if ($tag === 'button' && !isset($toggleButton['type'])) { 202 | $toggleButton['type'] = 'button'; 203 | } 204 | 205 | return Html::tag($tag, $label, $toggleButton); 206 | } else { 207 | return null; 208 | } 209 | } 210 | 211 | /** 212 | * Renders the close button. 213 | * @return string the rendering result 214 | */ 215 | protected function renderCloseButton() 216 | { 217 | if (($closeButton = $this->closeButton) !== false) { 218 | $tag = ArrayHelper::remove($closeButton, 'tag', 'button'); 219 | $label = ArrayHelper::remove($closeButton, 'label', '×'); 220 | $label = Html::tag('span', $label, ['aria-hidden' => 'true']); 221 | if ($tag === 'button' && !isset($closeButton['type'])) { 222 | $closeButton['type'] = 'button'; 223 | } 224 | 225 | return Html::tag($tag, $label, $closeButton); 226 | } else { 227 | return null; 228 | } 229 | } 230 | 231 | /** 232 | * Initializes the widget options. 233 | * This method sets the default values for various options. 234 | */ 235 | protected function initOptions() 236 | { 237 | $this->options = array_merge([ 238 | 'class' => 'fade', 239 | 'role' => 'dialog', 240 | 'tabindex' => -1, 241 | ], $this->options); 242 | Html::addCssClass($this->options, ['widget' => 'modal']); 243 | 244 | if ($this->clientOptions !== false) { 245 | $this->clientOptions = array_merge(['show' => false], $this->clientOptions); 246 | } 247 | 248 | if ($this->closeButton !== false) { 249 | $this->closeButton = array_merge([ 250 | 'data-dismiss' => 'modal', 251 | 'aria-hidden' => 'true', 252 | 'class' => 'close', 253 | ], $this->closeButton); 254 | } 255 | 256 | if ($this->toggleButton !== false) { 257 | $this->toggleButton = array_merge([ 258 | 'data-toggle' => 'modal', 259 | ], $this->toggleButton); 260 | if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) { 261 | $this->toggleButton['data-target'] = '#' . $this->options['id']; 262 | } 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /widgets/Nav.php: -------------------------------------------------------------------------------- 1 | ` will be used. To disable the caret, set this property to be an empty string. 64 | */ 65 | public $dropDownCaret; 66 | /** 67 | * @var string name of a class to use for rendering dropdowns within this widget. Defaults to [[Dropdown]]. 68 | * @since 2.0.7 69 | */ 70 | public $dropdownClass = 'digitv\bootstrap\widgets\Dropdown'; 71 | 72 | /** 73 | * Initializes the widget. 74 | */ 75 | public function init() 76 | { 77 | parent::init(); 78 | if ($this->route === null && Yii::$app->controller !== null) { 79 | $this->route = Yii::$app->controller->getRoute(); 80 | } 81 | if ($this->params === null) { 82 | $this->params = Yii::$app->request->getQueryParams(); 83 | } 84 | Html::addCssClass($this->options, ['widget' => 'nav']); 85 | } 86 | 87 | /** 88 | * Renders the widget. 89 | * @return string 90 | * @throws InvalidConfigException 91 | * @throws \Exception 92 | */ 93 | public function run() 94 | { 95 | BootstrapAsset::register($this->getView()); 96 | return $this->renderItems(); 97 | } 98 | 99 | /** 100 | * Renders widget items. 101 | * @return string 102 | * @throws InvalidConfigException 103 | * @throws \Exception 104 | */ 105 | public function renderItems() 106 | { 107 | $items = []; 108 | foreach ($this->items as $i => $item) { 109 | if (isset($item['visible']) && !$item['visible']) { 110 | continue; 111 | } 112 | $items[] = $this->renderItem($item); 113 | } 114 | 115 | return Html::tag('ul', implode("\n", $items), $this->options); 116 | } 117 | 118 | /** 119 | * Renders a widget's item. 120 | * @param string|array $item the item to render. 121 | * @return string the rendering result. 122 | * @throws InvalidConfigException 123 | * @throws \Exception 124 | */ 125 | public function renderItem($item) 126 | { 127 | if (is_string($item)) { 128 | return $item; 129 | } 130 | if (!isset($item['label'])) { 131 | throw new InvalidConfigException("The 'label' option is required."); 132 | } 133 | $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; 134 | $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; 135 | $options = ArrayHelper::getValue($item, 'options', []); 136 | $items = ArrayHelper::getValue($item, 'items'); 137 | $url = ArrayHelper::getValue($item, 'url', '#'); 138 | $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); 139 | 140 | if (isset($item['active'])) { 141 | $active = ArrayHelper::remove($item, 'active', false); 142 | } else { 143 | $active = $this->isItemActive($item); 144 | } 145 | 146 | if (empty($items)) { 147 | $items = ''; 148 | } else { 149 | $linkOptions['data-toggle'] = 'dropdown'; 150 | Html::addCssClass($options, ['widget' => 'dropdown']); 151 | Html::addCssClass($linkOptions, ['widget' => 'dropdown-toggle']); 152 | if (isset($this->dropDownCaret) && $this->dropDownCaret !== '') { 153 | $label .= ' ' . $this->dropDownCaret; 154 | } 155 | if (is_array($items)) { 156 | $items = $this->isChildActive($items, $active); 157 | $items = $this->renderDropdown($items, $item); 158 | } 159 | } 160 | 161 | if ($active) { 162 | Html::addCssClass($linkOptions, 'active'); 163 | } 164 | Html::addCssClass($options, 'nav-item'); 165 | Html::addCssClass($linkOptions, 'nav-link'); 166 | 167 | return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options); 168 | } 169 | 170 | /** 171 | * Renders the given items as a dropdown. 172 | * This method is called to create sub-menus. 173 | * @param array $items the given items. Please refer to [[Dropdown::items]] for the array structure. 174 | * @param array $parentItem the parent item information. Please refer to [[items]] for the structure of this array. 175 | * @return string the rendering result. 176 | * @since 2.0.1 177 | * @throws \Exception 178 | */ 179 | protected function renderDropdown($items, $parentItem) 180 | { 181 | /** @var Widget $dropdownClass */ 182 | $dropdownClass = $this->dropdownClass; 183 | return $dropdownClass::widget([ 184 | 'options' => ArrayHelper::getValue($parentItem, 'dropDownOptions', []), 185 | 'items' => $items, 186 | 'encodeLabels' => $this->encodeLabels, 187 | 'clientOptions' => false, 188 | 'view' => $this->getView(), 189 | ]); 190 | } 191 | 192 | /** 193 | * Check to see if a child item is active optionally activating the parent. 194 | * @param array $items @see items 195 | * @param bool $active should the parent be active too 196 | * @return array @see items 197 | */ 198 | protected function isChildActive($items, &$active) 199 | { 200 | foreach ($items as $i => $child) { 201 | if (is_array($child) && !ArrayHelper::getValue($child, 'visible', true)) { 202 | continue; 203 | } 204 | if (ArrayHelper::remove($items[$i], 'active', false) || $this->isItemActive($child)) { 205 | Html::addCssClass($items[$i]['options'], 'active'); 206 | if ($this->activateParents) { 207 | $active = true; 208 | } 209 | } 210 | $childItems = ArrayHelper::getValue($child, 'items'); 211 | if (is_array($childItems)) { 212 | $activeParent = false; 213 | $items[$i]['items'] = $this->isChildActive($childItems, $activeParent); 214 | if ($activeParent) { 215 | Html::addCssClass($items[$i]['options'], 'active'); 216 | $active = true; 217 | } 218 | } 219 | } 220 | return $items; 221 | } 222 | 223 | /** 224 | * Checks whether a menu item is active. 225 | * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item. 226 | * When the `url` option of a menu item is specified in terms of an array, its first element is treated 227 | * as the route for the item and the rest of the elements are the associated parameters. 228 | * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item 229 | * be considered active. 230 | * @param array $item the menu item to be checked 231 | * @return bool whether the menu item is active 232 | */ 233 | protected function isItemActive($item) 234 | { 235 | if (!$this->activateItems) { 236 | return false; 237 | } 238 | if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) { 239 | $route = $item['url'][0]; 240 | if ($route[0] !== '/' && Yii::$app->controller) { 241 | $route = Yii::$app->controller->module->getUniqueId() . '/' . $route; 242 | } 243 | if (ltrim($route, '/') !== $this->route) { 244 | return false; 245 | } 246 | unset($item['url']['#']); 247 | if (count($item['url']) > 1) { 248 | $params = $item['url']; 249 | unset($params[0]); 250 | foreach ($params as $name => $value) { 251 | if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) { 252 | return false; 253 | } 254 | } 255 | } 256 | 257 | return true; 258 | } 259 | 260 | return false; 261 | } 262 | } -------------------------------------------------------------------------------- /widgets/NavBar.php: -------------------------------------------------------------------------------- 1 | clientOptions = false; 69 | if (empty($this->options['class'])) { 70 | Html::addCssClass($this->options, ['navbar', 'navbar-light bg-light']); 71 | } else { 72 | Html::addCssClass($this->options, ['widget' => 'navbar']); 73 | } 74 | $options = $this->options; 75 | $tag = ArrayHelper::remove($options, 'tag', 'nav'); 76 | echo Html::beginTag($tag, $options); 77 | if ($this->renderInnerContainer) { 78 | if (!isset($this->innerContainerOptions['class'])) { 79 | Html::addCssClass($this->innerContainerOptions, 'container'); 80 | } 81 | echo Html::beginTag('div', $this->innerContainerOptions); 82 | } 83 | if (!isset($this->containerOptions['id'])) { 84 | $this->containerOptions['id'] = "{$this->options['id']}-collapse"; 85 | } 86 | if ($this->brandLabel !== false) { 87 | Html::addCssClass($this->brandOptions, ['widget' => 'navbar-brand']); 88 | echo Html::a($this->brandLabel, $this->brandUrl === false ? Yii::$app->homeUrl : $this->brandUrl, $this->brandOptions); 89 | } 90 | echo $this->renderToggleButton(); 91 | Html::addCssClass($this->containerOptions, ['collapse' => 'collapse', 'widget' => 'navbar-collapse']); 92 | $options = $this->containerOptions; 93 | $tag = ArrayHelper::remove($options, 'tag', 'div'); 94 | echo Html::beginTag($tag, $options); 95 | } 96 | 97 | /** 98 | * Renders the widget. 99 | */ 100 | public function run() 101 | { 102 | $tag = ArrayHelper::remove($this->containerOptions, 'tag', 'div'); 103 | echo Html::endTag($tag); 104 | if ($this->renderInnerContainer) { 105 | echo Html::endTag('div'); 106 | } 107 | $tag = ArrayHelper::remove($this->options, 'tag', 'nav'); 108 | echo Html::endTag($tag); 109 | BootstrapPluginAsset::register($this->getView()); 110 | } 111 | 112 | /** 113 | * Renders collapsible toggle button. 114 | * @return string the rendering toggle button. 115 | */ 116 | protected function renderToggleButton() 117 | { 118 | $icon = Html::tag('span', '', ['class' => 'navbar-toggler-icon']); 119 | $screenReader = isset($this->screenReaderToggleText) 120 | ? "{$this->screenReaderToggleText}" 121 | : ''; 122 | 123 | return Html::button("{$screenReader}\n{$icon}", [ 124 | 'class' => 'navbar-toggler', 125 | 'data-toggle' => 'collapse', 126 | 'data-target' => "#{$this->containerOptions['id']}", 127 | ]); 128 | } 129 | } -------------------------------------------------------------------------------- /widgets/Progress.php: -------------------------------------------------------------------------------- 1 | 60, 25 | * 'label' => 'test', 26 | * ]); 27 | * 28 | * // styled 29 | * echo Progress::widget([ 30 | * 'percent' => 65, 31 | * 'barOptions' => ['class' => 'bg-danger'] 32 | * ]); 33 | * 34 | * // styled and animated 35 | * echo Progress::widget([ 36 | * 'percent' => 65, 37 | * 'animated' => true, 38 | * 'barOptions' => ['class' => 'bg-danger'] 39 | * ]); 40 | * 41 | * // striped 42 | * echo Progress::widget([ 43 | * 'percent' => 70, 44 | * 'striped' => true, 45 | * 'barOptions' => ['class' => 'bg-warning'], 46 | * ]); 47 | * 48 | * // striped animated 49 | * echo Progress::widget([ 50 | * 'percent' => 70, 51 | * 'animated' => true, 52 | * 'barOptions' => ['class' => 'bg-success'], 53 | * ]); 54 | * 55 | * // stacked bars 56 | * echo Progress::widget([ 57 | * 'bars' => [ 58 | * ['percent' => 30, 'options' => ['class' => 'bg-danger']], 59 | * ['percent' => 30, 'label' => 'test', 'animated' => true, 'options' => ['class' => 'bg-success']], 60 | * ['percent' => 35, 'options' => ['class' => 'bg-warning']], 61 | * ] 62 | * ]); 63 | * ``` 64 | * @see http://getbootstrap.com/components/#progress 65 | * @author Antonio Ramirez 66 | * @author Alexander Makarov 67 | * @since 2.0 68 | */ 69 | class Progress extends Widget 70 | { 71 | /** 72 | * @var string the button label. 73 | */ 74 | public $label; 75 | /** 76 | * @var integer the amount of progress as a percentage. 77 | */ 78 | public $percent = 0; 79 | /** 80 | * @var array the HTML attributes of the bar. 81 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 82 | */ 83 | public $barOptions = []; 84 | /** 85 | * @var array a set of bars that are stacked together to form a single progress bar. 86 | * Each bar is an array of the following structure: 87 | * 88 | * ```php 89 | * [ 90 | * // required, the amount of progress as a percentage. 91 | * 'percent' => 30, 92 | * // optional, the label to be displayed on the bar 93 | * 'label' => '30%', 94 | * // optional, array, additional HTML attributes for the bar tag 95 | * 'options' => [], 96 | * ] 97 | * ``` 98 | */ 99 | public $bars; 100 | /** 101 | * @var bool animated or not 102 | */ 103 | public $animated = false; 104 | /** 105 | * @var bool striped or not 106 | */ 107 | public $striped = false; 108 | 109 | 110 | /** 111 | * Initializes the widget. 112 | * If you override this method, make sure you call the parent implementation first. 113 | */ 114 | public function init() 115 | { 116 | parent::init(); 117 | Html::addCssClass($this->options, ['widget' => 'progress']); 118 | } 119 | 120 | /** 121 | * Renders the widget. 122 | */ 123 | public function run() 124 | { 125 | BootstrapAsset::register($this->getView()); 126 | return implode("\n", [ 127 | Html::beginTag('div', $this->options), 128 | $this->renderProgress(), 129 | Html::endTag('div') 130 | ]) . "\n"; 131 | } 132 | 133 | /** 134 | * Renders the progress. 135 | * @return string the rendering result. 136 | * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar. 137 | */ 138 | protected function renderProgress() 139 | { 140 | if (empty($this->bars)) { 141 | $config = ['percent' => $this->percent, 'animated' => $this->animated, 'striped' => $this->striped]; 142 | return $this->renderBar($config, $this->label, $this->barOptions); 143 | } 144 | $bars = []; 145 | foreach ($this->bars as $bar) { 146 | $label = ArrayHelper::getValue($bar, 'label', ''); 147 | if (!isset($bar['percent'])) { 148 | throw new InvalidConfigException("The 'percent' option is required."); 149 | } 150 | $options = ArrayHelper::getValue($bar, 'options', []); 151 | $bars[] = $this->renderBar($bar, $label, $options); 152 | } 153 | 154 | return implode("\n", $bars); 155 | } 156 | /** 157 | * Generates a bar 158 | * @param array $config the bar config (percentage, striped, animated) 159 | * @param string $label, optional, the label to display at the bar 160 | * @param array $options the HTML attributes of the bar 161 | * @return string the rendering result. 162 | */ 163 | protected function renderBar($config = [], $label, $options = []) { 164 | $percent = ArrayHelper::getValue($config, 'percent', 0); 165 | $animated = ArrayHelper::getValue($config, 'animated', false); 166 | $striped = ArrayHelper::getValue($config, 'striped', false) || $animated; 167 | $percentFixed = number_format($percent, 2, '.', ''); 168 | $defaultOptions = [ 169 | 'role' => 'progressbar', 170 | 'aria-valuenow' => $percent, 171 | 'aria-valuemin' => 0, 172 | 'aria-valuemax' => 100, 173 | 'style' => "width:{$percentFixed}%", 174 | ]; 175 | $options = array_merge($defaultOptions, $options); 176 | Html::addCssClass($options, ['widget' => 'progress-bar']); 177 | if($animated) 178 | Html::addCssClass($options, ['animated' => 'progress-bar-animated']); 179 | if($striped) 180 | Html::addCssClass($options, ['striped' => 'progress-bar-striped']); 181 | 182 | $out = Html::beginTag('div', $options); 183 | $out .= $label; 184 | $out .= Html::tag('span', \Yii::t('yii', '{percent}% Complete', ['percent' => $percent]), [ 185 | 'class' => 'sr-only' 186 | ]); 187 | $out .= Html::endTag('div'); 188 | 189 | return $out; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /widgets/Tabs.php: -------------------------------------------------------------------------------- 1 | [ 25 | * [ 26 | * 'label' => 'One', 27 | * 'content' => 'Anim pariatur cliche...', 28 | * 'active' => true 29 | * ], 30 | * [ 31 | * 'label' => 'Two', 32 | * 'content' => 'Anim pariatur cliche...', 33 | * 'headerOptions' => [...], 34 | * 'options' => ['id' => 'myveryownID'], 35 | * ], 36 | * [ 37 | * 'label' => 'Example', 38 | * 'url' => 'http://www.example.com', 39 | * ], 40 | * [ 41 | * 'label' => 'Dropdown', 42 | * 'items' => [ 43 | * [ 44 | * 'label' => 'DropdownA', 45 | * 'content' => 'DropdownA, Anim pariatur cliche...', 46 | * ], 47 | * [ 48 | * 'label' => 'DropdownB', 49 | * 'content' => 'DropdownB, Anim pariatur cliche...', 50 | * ], 51 | * [ 52 | * 'label' => 'External Link', 53 | * 'url' => 'http://www.example.com', 54 | * ], 55 | * ], 56 | * ], 57 | * ], 58 | * ]); 59 | * ``` 60 | * 61 | * @see http://getbootstrap.com/javascript/#tabs 62 | * @author Antonio Ramirez 63 | * @since 2.0 64 | */ 65 | class Tabs extends Widget 66 | { 67 | /** 68 | * @var array list of tabs in the tabs widget. Each array element represents a single 69 | * tab with the following structure: 70 | * 71 | * - label: string, required, the tab header label. 72 | * - encode: boolean, optional, whether this label should be HTML-encoded. This param will override 73 | * global `$this->encodeLabels` param. 74 | * - headerOptions: array, optional, the HTML attributes of the tab header. 75 | * - linkOptions: array, optional, the HTML attributes of the tab header link tags. 76 | * - content: string, optional, the content (HTML) of the tab pane. 77 | * - url: string, optional, an external URL. When this is specified, clicking on this tab will bring 78 | * the browser to this URL. This option is available since version 2.0.4. 79 | * - options: array, optional, the HTML attributes of the tab pane container. 80 | * - active: boolean, optional, whether this item tab header and pane should be active. If no item is marked as 81 | * 'active' explicitly - the first one will be activated. 82 | * - visible: boolean, optional, whether the item tab header and pane should be visible or not. Defaults to true. 83 | * - items: array, optional, can be used instead of `content` to specify a dropdown items 84 | * configuration array. Each item can hold three extra keys, besides the above ones: 85 | * * active: boolean, optional, whether the item tab header and pane should be visible or not. 86 | * * content: string, required if `items` is not set. The content (HTML) of the tab pane. 87 | * * contentOptions: optional, array, the HTML attributes of the tab content container. 88 | */ 89 | public $items = []; 90 | /** 91 | * @var array list of HTML attributes for the item container tags. This will be overwritten 92 | * by the "options" set in individual [[items]]. The following special options are recognized: 93 | * 94 | * - tag: string, defaults to "div", the tag name of the item container tags. 95 | * 96 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 97 | */ 98 | public $itemOptions = []; 99 | /** 100 | * @var array list of HTML attributes for the header container tags. This will be overwritten 101 | * by the "headerOptions" set in individual [[items]]. 102 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 103 | */ 104 | public $headerOptions = []; 105 | /** 106 | * @var array list of HTML attributes for the tab header link tags. This will be overwritten 107 | * by the "linkOptions" set in individual [[items]]. 108 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 109 | */ 110 | public $linkOptions = []; 111 | /** 112 | * @var boolean whether the labels for header items should be HTML-encoded. 113 | */ 114 | public $encodeLabels = true; 115 | /** 116 | * @var string specifies the Bootstrap tab styling. 117 | */ 118 | public $navType = 'nav-tabs'; 119 | /** 120 | * @var boolean whether to render the `tab-content` container and its content. You may set this property 121 | * to be false so that you can manually render `tab-content` yourself in case your tab contents are complex. 122 | * @since 2.0.1 123 | */ 124 | public $renderTabContent = true; 125 | /** 126 | * @var array list of HTML attributes for the `tab-content` container. This will always contain the CSS class `tab-content`. 127 | * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. 128 | * @since 2.0.7 129 | */ 130 | public $tabContentOptions = []; 131 | /** 132 | * @var string name of a class to use for rendering dropdowns withing this widget. Defaults to [[Dropdown]]. 133 | * @since 2.0.7 134 | */ 135 | public $dropdownClass = 'digitv\bootstrap\widgets\Dropdown'; 136 | 137 | private $hasDropDown = false; 138 | 139 | 140 | /** 141 | * Initializes the widget. 142 | */ 143 | public function init() 144 | { 145 | parent::init(); 146 | $this->options['role'] = 'tablist'; 147 | Html::addCssClass($this->options, ['widget' => 'nav', $this->navType]); 148 | Html::addCssClass($this->tabContentOptions, 'tab-content'); 149 | Html::addCssClass($this->headerOptions, 'nav-item'); 150 | Html::addCssClass($this->linkOptions, 'nav-link'); 151 | } 152 | 153 | /** 154 | * Renders the widget. 155 | */ 156 | public function run() 157 | { 158 | $items = $this->renderItems(); 159 | $this->registerPlugin('tab'); 160 | return $items; 161 | } 162 | 163 | /** 164 | * Renders tab items as specified on [[items]]. 165 | * @return string the rendering result. 166 | * @throws InvalidConfigException. 167 | */ 168 | protected function renderItems() 169 | { 170 | $headers = []; 171 | $panes = []; 172 | 173 | if (!$this->hasActiveTab()) { 174 | $this->activateFirstVisibleTab(); 175 | } 176 | 177 | foreach ($this->items as $n => $item) { 178 | if (!ArrayHelper::remove($item, 'visible', true)) { 179 | continue; 180 | } 181 | if (!array_key_exists('label', $item)) { 182 | throw new InvalidConfigException("The 'label' option is required."); 183 | } 184 | $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; 185 | $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; 186 | $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); 187 | $linkOptions = array_merge($this->linkOptions, ArrayHelper::getValue($item, 'linkOptions', [])); 188 | 189 | if (isset($item['items'])) { 190 | Html::addCssClass($linkOptions, ['widget' => 'dropdown-toggle']); 191 | 192 | if ($this->renderDropdown($n, $item['items'], $panes)) { 193 | Html::addCssClass($linkOptions, 'active'); 194 | } 195 | 196 | Html::addCssClass($linkOptions, ['widget' => 'dropdown-toggle']); 197 | if (!isset($linkOptions['data-toggle'])) { 198 | $linkOptions['data-toggle'] = 'dropdown'; 199 | } 200 | 201 | /** @var Widget $dropdownClass */ 202 | $dropdownClass = $this->dropdownClass; 203 | $header = Html::a($label, "#", $linkOptions) . "\n" 204 | . $dropdownClass::widget(['items' => $item['items'], 'clientOptions' => false, 'view' => $this->getView()]); 205 | $this->hasDropDown = true; 206 | } else { 207 | $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); 208 | $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); 209 | 210 | Html::addCssClass($options, ['widget' => 'tab-pane']); 211 | if (ArrayHelper::remove($item, 'active')) { 212 | Html::addCssClass($options, 'active show'); 213 | Html::addCssClass($linkOptions, 'active'); 214 | } 215 | 216 | if (isset($item['url'])) { 217 | $header = Html::a($label, $item['url'], $linkOptions); 218 | } else { 219 | if (!isset($linkOptions['data-toggle'])) { 220 | $linkOptions['data-toggle'] = 'tab'; 221 | } 222 | $header = Html::a($label, '#' . $options['id'], $linkOptions); 223 | } 224 | 225 | if ($this->renderTabContent) { 226 | $tag = ArrayHelper::remove($options, 'tag', 'div'); 227 | $panes[] = Html::tag($tag, isset($item['content']) ? $item['content'] : '', $options); 228 | } 229 | } 230 | 231 | $headers[] = Html::tag('li', $header, $headerOptions); 232 | } 233 | 234 | return $this->renderNavTabs($headers, $this->options) 235 | . $this->renderPanes($panes); 236 | } 237 | 238 | /** 239 | * @return bool if there's active tab defined 240 | */ 241 | protected function hasActiveTab() 242 | { 243 | foreach ($this->items as $item) { 244 | if (isset($item['active']) && $item['active'] === true) { 245 | return true; 246 | } 247 | } 248 | 249 | return false; 250 | } 251 | 252 | /** 253 | * Sets the first visible tab as active. 254 | * 255 | * This method activates the first tab that is visible and 256 | * not explicitly set to inactive (`'active' => false`). 257 | * @since 2.0.7 258 | */ 259 | protected function activateFirstVisibleTab() 260 | { 261 | foreach ($this->items as $i => $item) { 262 | $active = ArrayHelper::getValue($item, 'active', null); 263 | $visible = ArrayHelper::getValue($item, 'visible', true); 264 | if ($visible && $active !== false) { 265 | $this->items[$i]['active'] = true; 266 | return; 267 | } 268 | } 269 | } 270 | 271 | /** 272 | * Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also 273 | * configure `panes` accordingly. 274 | * @param string $itemNumber number of the item 275 | * @param array $items the dropdown items configuration. 276 | * @param array $panes the panes reference array. 277 | * @return bool whether any of the dropdown items is `active` or not. 278 | * @throws InvalidConfigException 279 | */ 280 | protected function renderDropdown($itemNumber, &$items, &$panes) 281 | { 282 | $itemActive = false; 283 | 284 | foreach ($items as $n => &$item) { 285 | if (is_string($item)) { 286 | continue; 287 | } 288 | if (isset($item['visible']) && !$item['visible']) { 289 | continue; 290 | } 291 | if (!(array_key_exists('content', $item) xor array_key_exists('url', $item))) { 292 | throw new InvalidConfigException("Either the 'content' or the 'url' option is required, but only one can be set."); 293 | } 294 | if (array_key_exists('url', $item)) { 295 | continue; 296 | } 297 | 298 | $content = ArrayHelper::remove($item, 'content'); 299 | $options = ArrayHelper::remove($item, 'contentOptions', []); 300 | Html::addCssClass($options, ['widget' => 'tab-pane']); 301 | if (ArrayHelper::remove($item, 'active')) { 302 | Html::addCssClass($options, 'active'); 303 | Html::addCssClass($item['options'], 'active'); 304 | $itemActive = true; 305 | } 306 | 307 | $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd' . $itemNumber . '-tab' . $n); 308 | $item['url'] = '#' . $options['id']; 309 | if (!isset($item['linkOptions']['data-toggle'])) { 310 | $item['linkOptions']['data-toggle'] = 'tab'; 311 | } 312 | $panes[] = Html::tag('div', $content, $options); 313 | 314 | unset($item); 315 | } 316 | 317 | return $itemActive; 318 | } 319 | 320 | /** 321 | * Renders tab panes. 322 | * 323 | * @param array $panes 324 | * @return string the rendering result. 325 | * @since 2.0.7 326 | */ 327 | public function renderPanes($panes) 328 | { 329 | return $this->renderTabContent ? "\n" . Html::tag('div', implode("\n", $panes), $this->tabContentOptions) : ''; 330 | } 331 | 332 | protected function renderNavTabs($items = [], $options = []) { 333 | return Nav::widget([ 334 | 'items' => $items, 335 | 'options' => $options, 336 | ]); 337 | } 338 | 339 | /** 340 | * Registers a specific Bootstrap plugin and the related events 341 | * @param string $name the name of the Bootstrap plugin 342 | */ 343 | protected function registerPlugin($name) 344 | { 345 | $view = $this->getView(); 346 | 347 | BootstrapPluginAsset::register($view); 348 | 349 | if($this->hasDropDown && !isset($this->clientEvents['shown.bs.tab'])) { 350 | //FIX dropdown links 351 | $this->clientEvents['shown.bs.tab'] = ['a', new JsExpression("function(e) { 352 | var btn = $(this), nav = btn.parents('.nav:first'), dropDown = btn.parents('.dropdown-menu:first'); 353 | btn.parents('.nav:first').find('[data-toggle=\"tab\"]').not(btn).removeClass('active'); 354 | if(dropDown.length) { 355 | dropDown.siblings('.dropdown-toggle').addClass('active'); 356 | } 357 | }")]; 358 | } 359 | 360 | $this->registerClientEvents(); 361 | } 362 | 363 | /** 364 | * Registers JS event handlers that are listed in [[clientEvents]]. 365 | * @since 2.0.2 366 | */ 367 | protected function registerClientEvents() 368 | { 369 | if (!empty($this->clientEvents)) { 370 | $id = $this->options['id']; 371 | $js = []; 372 | foreach ($this->clientEvents as $event => $handler) { 373 | if(is_array($handler) && count($handler) === 2) { 374 | $subSelector = $handler[0]; 375 | $handlerCallback = $handler[1]; 376 | $js[] = "jQuery('#$id').on('$event', '$subSelector', $handlerCallback);"; 377 | } else { 378 | $js[] = "jQuery('#$id').on('$event', $handler);"; 379 | } 380 | } 381 | $this->getView()->registerJs(implode("\n", $js)); 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /widgets/ToggleButtonGroup.php: -------------------------------------------------------------------------------- 1 | field($model, 'item_id')->widget(\yii\bootstrap\ToggleButtonGroup::classname(), [ 22 | * // configure additional widget properties here 23 | * ]) ?> 24 | * ``` 25 | * 26 | * @see http://getbootstrap.com/javascript/#buttons-checkbox-radio 27 | * 28 | * @author Paul Klimov 29 | * @since 2.0.6 30 | */ 31 | class ToggleButtonGroup extends InputWidget 32 | { 33 | /** 34 | * @var string input type, can be: 35 | * - 'checkbox' 36 | * - 'radio' 37 | */ 38 | public $type; 39 | /** 40 | * @var array the data item used to generate the checkboxes. 41 | * The array values are the labels, while the array keys are the corresponding checkbox or radio values. 42 | */ 43 | public $items = []; 44 | /** 45 | * @var array, the HTML attributes for the label (button) tag. 46 | * @see Html::checkbox() 47 | * @see Html::radio() 48 | */ 49 | public $labelOptions = []; 50 | /** 51 | * @var boolean whether the items labels should be HTML-encoded. 52 | */ 53 | public $encodeLabels = true; 54 | 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | public function init() 60 | { 61 | parent::init(); 62 | $this->registerPlugin('button'); 63 | Html::addCssClass($this->options, 'btn-group btn-group-toggle'); 64 | $this->options['data-toggle'] = 'buttons'; 65 | } 66 | 67 | /** 68 | * @inheritdoc 69 | */ 70 | public function run() 71 | { 72 | if (!isset($this->options['item'])) { 73 | $this->options['item'] = [$this, 'renderItem']; 74 | } 75 | switch ($this->type) { 76 | case 'checkbox': 77 | if ($this->hasModel()) { 78 | return Html::activeCheckboxList($this->model, $this->attribute, $this->items, $this->options); 79 | } else { 80 | return Html::checkboxList($this->name, $this->value, $this->items, $this->options); 81 | } 82 | case 'radio': 83 | if ($this->hasModel()) { 84 | return Html::activeRadioList($this->model, $this->attribute, $this->items, $this->options); 85 | } else { 86 | return Html::radioList($this->name, $this->value, $this->items, $this->options); 87 | } 88 | default: 89 | throw new InvalidConfigException("Unsupported type '{$this->type}'"); 90 | } 91 | } 92 | 93 | /** 94 | * Default callback for checkbox/radio list item rendering. 95 | * @param int $index item index. 96 | * @param string $label item label. 97 | * @param string $name input name. 98 | * @param bool $checked whether value is checked or not. 99 | * @param string $value input value. 100 | * @return string generated HTML. 101 | * @see Html::checkbox() 102 | * @see Html::radio() 103 | */ 104 | public function renderItem($index, $label, $name, $checked, $value) 105 | { 106 | $labelOptions = $this->labelOptions; 107 | Html::addCssClass($labelOptions, 'btn'); 108 | if ($checked) { 109 | Html::addCssClass($labelOptions, 'active'); 110 | } 111 | $type = $this->type; 112 | if ($this->encodeLabels) { 113 | $label = Html::encode($label); 114 | } 115 | return Html::$type($name, $checked, ['label' => $label, 'labelOptions' => $labelOptions, 'value' => $value]); 116 | } 117 | } 118 | --------------------------------------------------------------------------------