yiisoft /
yii-bootstrap4
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | namespace Yiisoft\Yii\Bootstrap4; |
||
| 6 | |||
| 7 | use JsonException; |
||
| 8 | use RuntimeException; |
||
| 9 | use Yiisoft\Arrays\ArrayHelper; |
||
| 10 | use Yiisoft\Html\Html; |
||
| 11 | |||
| 12 | use function array_key_exists; |
||
| 13 | use function array_merge; |
||
| 14 | use function array_merge_recursive; |
||
| 15 | use function is_string; |
||
| 16 | |||
| 17 | /** |
||
| 18 | * Dropdown renders a Bootstrap dropdown menu component. |
||
| 19 | * |
||
| 20 | * For example, |
||
| 21 | * |
||
| 22 | * ```php |
||
| 23 | * <div class="dropdown"> |
||
| 24 | * <?php |
||
| 25 | * echo Dropdown::widget() |
||
| 26 | * ->items([ |
||
| 27 | * ['label' => 'DropdownA', 'url' => '/'], |
||
| 28 | * ['label' => 'DropdownB', 'url' => '#'], |
||
| 29 | * ]); |
||
| 30 | * ?> |
||
| 31 | * </div> |
||
| 32 | * ``` |
||
| 33 | */ |
||
| 34 | class Dropdown extends Widget |
||
| 35 | { |
||
| 36 | private array $items = []; |
||
| 37 | private bool $encodeLabels = true; |
||
| 38 | private array $submenuOptions = []; |
||
| 39 | private array $options = []; |
||
| 40 | |||
| 41 | 16 | protected function run(): string |
|
| 42 | { |
||
| 43 | 16 | if (!isset($this->options['id'])) { |
|
| 44 | 15 | $this->options['id'] = "{$this->getId()}-dropdown"; |
|
| 45 | } |
||
| 46 | |||
| 47 | 16 | Html::addCssClass($this->options, ['widget' => 'dropdown-menu']); |
|
| 48 | |||
| 49 | 16 | $this->registerClientEvents($this->options['id']); |
|
| 50 | |||
| 51 | 16 | return $this->renderItems($this->items, $this->options); |
|
| 52 | } |
||
| 53 | |||
| 54 | /** |
||
| 55 | * Renders menu items. |
||
| 56 | * |
||
| 57 | * @param array $items the menu items to be rendered |
||
| 58 | * @param array $options the container HTML attributes |
||
| 59 | * |
||
| 60 | * @throws JsonException|RuntimeException if the label option is not specified in one of the items. |
||
| 61 | * |
||
| 62 | * @return string the rendering result. |
||
| 63 | */ |
||
| 64 | 16 | protected function renderItems(array $items, array $options = []): string |
|
| 65 | { |
||
| 66 | 16 | $lines = []; |
|
| 67 | |||
| 68 | 16 | foreach ($items as $item) { |
|
| 69 | 16 | if (is_string($item)) { |
|
| 70 | 3 | $item = ['label' => $item, 'encode' => false, 'enclose' => false]; |
|
| 71 | } |
||
| 72 | |||
| 73 | 16 | if (isset($item['visible']) && !$item['visible']) { |
|
| 74 | 3 | continue; |
|
| 75 | } |
||
| 76 | |||
| 77 | 16 | if (!array_key_exists('label', $item)) { |
|
| 78 | throw new RuntimeException('The "label" option is required.'); |
||
| 79 | } |
||
| 80 | |||
| 81 | 16 | $encodeLabel = $item['encode'] ?? $this->encodeLabels; |
|
| 82 | 16 | $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; |
|
| 83 | 16 | $itemOptions = ArrayHelper::getValue($item, 'options', []); |
|
| 84 | 16 | $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); |
|
| 85 | 16 | $active = ArrayHelper::getValue($item, 'active', false); |
|
| 86 | 16 | $disabled = ArrayHelper::getValue($item, 'disabled', false); |
|
| 87 | 16 | $enclose = ArrayHelper::getValue($item, 'enclose', true); |
|
| 88 | |||
| 89 | 16 | Html::addCssClass($linkOptions, 'dropdown-item'); |
|
| 90 | |||
| 91 | 16 | if ($disabled) { |
|
| 92 | 1 | ArrayHelper::setValue($linkOptions, 'tabindex', '-1'); |
|
| 93 | 1 | ArrayHelper::setValue($linkOptions, 'aria-disabled', 'true'); |
|
| 94 | 1 | Html::addCssClass($linkOptions, 'disabled'); |
|
| 95 | 16 | } elseif ($active) { |
|
| 96 | 3 | Html::addCssClass($linkOptions, 'active'); |
|
| 97 | } |
||
| 98 | |||
| 99 | 16 | $url = $item['url'] ?? null; |
|
| 100 | |||
| 101 | 16 | if (empty($item['items'])) { |
|
| 102 | 16 | if ($label === '-') { |
|
| 103 | 3 | $content = Html::div('', ['class' => 'dropdown-divider']); |
|
| 104 | 16 | } elseif ($enclose === false) { |
|
| 105 | 1 | $content = $label; |
|
| 106 | 16 | } elseif ($url === null) { |
|
| 107 | 8 | $content = Html::tag('h6', $label, ['class' => 'dropdown-header']); |
|
| 108 | } else { |
||
| 109 | 10 | $content = Html::a($label, $url, $linkOptions); |
|
| 110 | } |
||
| 111 | |||
| 112 | 16 | $lines[] = $content; |
|
| 113 | } else { |
||
| 114 | 3 | $submenuOptions = $this->submenuOptions; |
|
| 115 | |||
| 116 | 3 | if (isset($item['submenuOptions'])) { |
|
| 117 | 1 | $submenuOptions = array_merge($submenuOptions, $item['submenuOptions']); |
|
| 118 | } |
||
| 119 | |||
| 120 | 3 | Html::addCssClass($submenuOptions, ['dropdown-submenu']); |
|
| 121 | 3 | Html::addCssClass($linkOptions, ['dropdown-toggle']); |
|
| 122 | |||
| 123 | 3 | $lines[] = Html::beginTag( |
|
| 124 | 3 | 'div', |
|
| 125 | 3 | array_merge_recursive(['class' => ['dropdown'], 'aria-expanded' => 'false'], $itemOptions) |
|
| 126 | ); |
||
| 127 | |||
| 128 | 3 | $lines[] = Html::a($label, $url, array_merge([ |
|
| 129 | 3 | 'data-toggle' => 'dropdown', |
|
| 130 | 'aria-haspopup' => 'true', |
||
| 131 | 'aria-expanded' => 'false', |
||
| 132 | 'role' => 'button', |
||
| 133 | 3 | ], $linkOptions)); |
|
| 134 | |||
| 135 | 3 | $lines[] = self::widget() |
|
| 136 | 3 | ->items($item['items']) |
|
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 137 | 3 | ->options($submenuOptions) |
|
| 138 | 3 | ->submenuOptions($submenuOptions) |
|
| 139 | 3 | ->encodeLabels($this->encodeLabels) |
|
| 140 | 3 | ->run(); |
|
| 141 | 3 | $lines[] = Html::endTag('div'); |
|
| 142 | } |
||
| 143 | } |
||
| 144 | |||
| 145 | 16 | return Html::tag('div', implode("\n", $lines), $options); |
|
| 146 | } |
||
| 147 | |||
| 148 | /** |
||
| 149 | * List of menu items in the dropdown. Each array element can be either an HTML string, or an array representing a |
||
| 150 | * single menu with the following structure: |
||
| 151 | * |
||
| 152 | * - label: string, required, the label of the item link. |
||
| 153 | * - encode: bool, optional, whether to HTML-encode item label. |
||
| 154 | * - url: string|array, optional, the URL of the item link. This will be processed by {@see currentPath}. |
||
| 155 | * If not set, the item will be treated as a menu header when the item has no sub-menu. |
||
| 156 | * - visible: bool, optional, whether this menu item is visible. Defaults to true. |
||
| 157 | * - linkOptions: array, optional, the HTML attributes of the item link. |
||
| 158 | * - options: array, optional, the HTML attributes of the item. |
||
| 159 | * - items: array, optional, the submenu items. The structure is the same as this property. |
||
| 160 | * Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it. |
||
| 161 | * - submenuOptions: array, optional, the HTML attributes for sub-menu container tag. If specified it will be |
||
| 162 | * merged with {@see submenuOptions}. |
||
| 163 | * |
||
| 164 | * To insert divider use `-`. |
||
| 165 | * |
||
| 166 | * @param array $value |
||
| 167 | * |
||
| 168 | * @return $this |
||
| 169 | */ |
||
| 170 | 16 | public function items(array $value): self |
|
| 171 | { |
||
| 172 | 16 | $this->items = $value; |
|
| 173 | |||
| 174 | 16 | return $this; |
|
| 175 | } |
||
| 176 | |||
| 177 | /** |
||
| 178 | * Whether the labels for header items should be HTML-encoded. |
||
| 179 | * |
||
| 180 | * @param bool $value |
||
| 181 | * |
||
| 182 | * @return $this |
||
| 183 | */ |
||
| 184 | 12 | public function encodeLabels(bool $value): self |
|
| 185 | { |
||
| 186 | 12 | $this->encodeLabels = $value; |
|
| 187 | |||
| 188 | 12 | return $this; |
|
| 189 | } |
||
| 190 | |||
| 191 | /** |
||
| 192 | * The HTML attributes for sub-menu container tags. |
||
| 193 | * |
||
| 194 | * @param array $value |
||
| 195 | * |
||
| 196 | * @return $this |
||
| 197 | */ |
||
| 198 | 3 | public function submenuOptions(array $value): self |
|
| 199 | { |
||
| 200 | 3 | $this->submenuOptions = $value; |
|
| 201 | |||
| 202 | 3 | return $this; |
|
| 203 | } |
||
| 204 | |||
| 205 | /** |
||
| 206 | * @param array $value the HTML attributes for the widget container tag. The following special options are |
||
| 207 | * recognized. |
||
| 208 | * |
||
| 209 | * @return $this |
||
| 210 | * |
||
| 211 | * {@see \Yiisoft\Html\Html::renderTagAttributes()} for details on how attributes are being rendered. |
||
| 212 | */ |
||
| 213 | 12 | public function options(array $value): self |
|
| 214 | { |
||
| 215 | 12 | $this->options = $value; |
|
| 216 | |||
| 217 | 12 | return $this; |
|
| 218 | } |
||
| 219 | } |
||
| 220 |