269 linhas
10KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\bootstrap;
  8. use yii\base\InvalidConfigException;
  9. use yii\helpers\ArrayHelper;
  10. /**
  11. * Tabs renders a Tab bootstrap javascript component.
  12. *
  13. * For example:
  14. *
  15. * ```php
  16. * echo Tabs::widget([
  17. * 'items' => [
  18. * [
  19. * 'label' => 'One',
  20. * 'content' => 'Anim pariatur cliche...',
  21. * 'active' => true
  22. * ],
  23. * [
  24. * 'label' => 'Two',
  25. * 'content' => 'Anim pariatur cliche...',
  26. * 'headerOptions' => [...],
  27. * 'options' => ['id' => 'myveryownID'],
  28. * ],
  29. * [
  30. * 'label' => 'Example',
  31. * 'url' => 'http://www.example.com',
  32. * ],
  33. * [
  34. * 'label' => 'Dropdown',
  35. * 'items' => [
  36. * [
  37. * 'label' => 'DropdownA',
  38. * 'content' => 'DropdownA, Anim pariatur cliche...',
  39. * ],
  40. * [
  41. * 'label' => 'DropdownB',
  42. * 'content' => 'DropdownB, Anim pariatur cliche...',
  43. * ],
  44. * ],
  45. * ],
  46. * ],
  47. * ]);
  48. * ```
  49. *
  50. * @see http://getbootstrap.com/javascript/#tabs
  51. * @author Antonio Ramirez <amigo.cobos@gmail.com>
  52. * @since 2.0
  53. */
  54. class Tabs extends Widget
  55. {
  56. /**
  57. * @var array list of tabs in the tabs widget. Each array element represents a single
  58. * tab with the following structure:
  59. *
  60. * - label: string, required, the tab header label.
  61. * - encode: boolean, optional, whether this label should be HTML-encoded. This param will override
  62. * global `$this->encodeLabels` param.
  63. * - headerOptions: array, optional, the HTML attributes of the tab header.
  64. * - linkOptions: array, optional, the HTML attributes of the tab header link tags.
  65. * - content: string, optional, the content (HTML) of the tab pane.
  66. * - url: string, optional, an external URL. When this is specified, clicking on this tab will bring
  67. * the browser to this URL. This option is available since version 2.0.4.
  68. * - options: array, optional, the HTML attributes of the tab pane container.
  69. * - active: boolean, optional, whether this item tab header and pane should be active. If no item is marked as
  70. * 'active' explicitly - the first one will be activated.
  71. * - visible: boolean, optional, whether the item tab header and pane should be visible or not. Defaults to true.
  72. * - items: array, optional, can be used instead of `content` to specify a dropdown items
  73. * configuration array. Each item can hold three extra keys, besides the above ones:
  74. * * active: boolean, optional, whether the item tab header and pane should be visible or not.
  75. * * content: string, required if `items` is not set. The content (HTML) of the tab pane.
  76. * * contentOptions: optional, array, the HTML attributes of the tab content container.
  77. */
  78. public $items = [];
  79. /**
  80. * @var array list of HTML attributes for the item container tags. This will be overwritten
  81. * by the "options" set in individual [[items]]. The following special options are recognized:
  82. *
  83. * - tag: string, defaults to "div", the tag name of the item container tags.
  84. *
  85. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  86. */
  87. public $itemOptions = [];
  88. /**
  89. * @var array list of HTML attributes for the header container tags. This will be overwritten
  90. * by the "headerOptions" set in individual [[items]].
  91. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  92. */
  93. public $headerOptions = [];
  94. /**
  95. * @var array list of HTML attributes for the tab header link tags. This will be overwritten
  96. * by the "linkOptions" set in individual [[items]].
  97. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  98. */
  99. public $linkOptions = [];
  100. /**
  101. * @var boolean whether the labels for header items should be HTML-encoded.
  102. */
  103. public $encodeLabels = true;
  104. /**
  105. * @var string specifies the Bootstrap tab styling.
  106. */
  107. public $navType = 'nav-tabs';
  108. /**
  109. * @var boolean whether to render the `tab-content` container and its content. You may set this property
  110. * to be false so that you can manually render `tab-content` yourself in case your tab contents are complex.
  111. * @since 2.0.1
  112. */
  113. public $renderTabContent = true;
  114. /**
  115. * Initializes the widget.
  116. */
  117. public function init()
  118. {
  119. parent::init();
  120. Html::addCssClass($this->options, ['widget' => 'nav', $this->navType]);
  121. }
  122. /**
  123. * Renders the widget.
  124. */
  125. public function run()
  126. {
  127. $this->registerPlugin('tab');
  128. return $this->renderItems();
  129. }
  130. /**
  131. * Renders tab items as specified on [[items]].
  132. * @return string the rendering result.
  133. * @throws InvalidConfigException.
  134. */
  135. protected function renderItems()
  136. {
  137. $headers = [];
  138. $panes = [];
  139. if (!$this->hasActiveTab() && !empty($this->items)) {
  140. $this->items[0]['active'] = true;
  141. }
  142. foreach ($this->items as $n => $item) {
  143. if (!ArrayHelper::remove($item, 'visible', true)) {
  144. continue;
  145. }
  146. if (!array_key_exists('label', $item)) {
  147. throw new InvalidConfigException("The 'label' option is required.");
  148. }
  149. $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
  150. $label = $encodeLabel ? Html::encode($item['label']) : $item['label'];
  151. $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', []));
  152. $linkOptions = array_merge($this->linkOptions, ArrayHelper::getValue($item, 'linkOptions', []));
  153. if (isset($item['items'])) {
  154. $label .= ' <b class="caret"></b>';
  155. Html::addCssClass($headerOptions, ['widget' => 'dropdown']);
  156. if ($this->renderDropdown($n, $item['items'], $panes)) {
  157. Html::addCssClass($headerOptions, 'active');
  158. }
  159. Html::addCssClass($linkOptions, ['widget' => 'dropdown-toggle']);
  160. if (!isset($linkOptions['data-toggle'])) {
  161. $linkOptions['data-toggle'] = 'dropdown';
  162. }
  163. $header = Html::a($label, "#", $linkOptions) . "\n"
  164. . Dropdown::widget(['items' => $item['items'], 'clientOptions' => false, 'view' => $this->getView()]);
  165. } else {
  166. $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
  167. $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n);
  168. Html::addCssClass($options, ['widget' => 'tab-pane']);
  169. if (ArrayHelper::remove($item, 'active')) {
  170. Html::addCssClass($options, 'active');
  171. Html::addCssClass($headerOptions, 'active');
  172. }
  173. if (isset($item['url'])) {
  174. $header = Html::a($label, $item['url'], $linkOptions);
  175. } else {
  176. if (!isset($linkOptions['data-toggle'])) {
  177. $linkOptions['data-toggle'] = 'tab';
  178. }
  179. $header = Html::a($label, '#' . $options['id'], $linkOptions);
  180. }
  181. if ($this->renderTabContent) {
  182. $tag = ArrayHelper::remove($options, 'tag', 'div');
  183. $panes[] = Html::tag($tag, isset($item['content']) ? $item['content'] : '', $options);
  184. }
  185. }
  186. $headers[] = Html::tag('li', $header, $headerOptions);
  187. }
  188. return Html::tag('ul', implode("\n", $headers), $this->options)
  189. . ($this->renderTabContent ? "\n" . Html::tag('div', implode("\n", $panes), ['class' => 'tab-content']) : '');
  190. }
  191. /**
  192. * @return boolean if there's active tab defined
  193. */
  194. protected function hasActiveTab()
  195. {
  196. foreach ($this->items as $item) {
  197. if (isset($item['active']) && $item['active'] === true) {
  198. return true;
  199. }
  200. }
  201. return false;
  202. }
  203. /**
  204. * Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also
  205. * configure `panes` accordingly.
  206. * @param string $itemNumber number of the item
  207. * @param array $items the dropdown items configuration.
  208. * @param array $panes the panes reference array.
  209. * @return boolean whether any of the dropdown items is `active` or not.
  210. * @throws InvalidConfigException
  211. */
  212. protected function renderDropdown($itemNumber, &$items, &$panes)
  213. {
  214. $itemActive = false;
  215. foreach ($items as $n => &$item) {
  216. if (is_string($item)) {
  217. continue;
  218. }
  219. if (isset($item['visible']) && !$item['visible']) {
  220. continue;
  221. }
  222. if (!array_key_exists('content', $item)) {
  223. throw new InvalidConfigException("The 'content' option is required.");
  224. }
  225. $content = ArrayHelper::remove($item, 'content');
  226. $options = ArrayHelper::remove($item, 'contentOptions', []);
  227. Html::addCssClass($options, ['widget' => 'tab-pane']);
  228. if (ArrayHelper::remove($item, 'active')) {
  229. Html::addCssClass($options, 'active');
  230. Html::addCssClass($item['options'], 'active');
  231. $itemActive = true;
  232. }
  233. $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd' . $itemNumber . '-tab' . $n);
  234. $item['url'] = '#' . $options['id'];
  235. if (!isset($item['linkOptions']['data-toggle'])) {
  236. $item['linkOptions']['data-toggle'] = 'tab';
  237. }
  238. $panes[] = Html::tag('div', $content, $options);
  239. unset($item);
  240. }
  241. return $itemActive;
  242. }
  243. }