Collapse.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. * Collapse renders an accordion bootstrap javascript component.
  12. *
  13. * For example:
  14. *
  15. * ```php
  16. * echo Collapse::widget([
  17. * 'items' => [
  18. * // equivalent to the above
  19. * [
  20. * 'label' => 'Collapsible Group Item #1',
  21. * 'content' => 'Anim pariatur cliche...',
  22. * // open its content by default
  23. * 'contentOptions' => ['class' => 'in']
  24. * ],
  25. * // another group item
  26. * [
  27. * 'label' => 'Collapsible Group Item #1',
  28. * 'content' => 'Anim pariatur cliche...',
  29. * 'contentOptions' => [...],
  30. * 'options' => [...],
  31. * ],
  32. * // if you want to swap out .panel-body with .list-group, you may use the following
  33. * [
  34. * 'label' => 'Collapsible Group Item #1',
  35. * 'content' => [
  36. * 'Anim pariatur cliche...',
  37. * 'Anim pariatur cliche...'
  38. * ],
  39. * 'contentOptions' => [...],
  40. * 'options' => [...],
  41. * 'footer' => 'Footer' // the footer label in list-group
  42. * ],
  43. * ]
  44. * ]);
  45. * ```
  46. *
  47. * @see http://getbootstrap.com/javascript/#collapse
  48. * @author Antonio Ramirez <amigo.cobos@gmail.com>
  49. * @since 2.0
  50. */
  51. class Collapse extends Widget
  52. {
  53. /**
  54. * @var array list of groups in the collapse widget. Each array element represents a single
  55. * group with the following structure:
  56. *
  57. * - label: string, required, the group header label.
  58. * - encode: bool, optional, whether this label should be HTML-encoded. This param will override
  59. * global `$this->encodeLabels` param.
  60. * - content: array|string|object, required, the content (HTML) of the group
  61. * - options: array, optional, the HTML attributes of the group
  62. * - contentOptions: optional, the HTML attributes of the group's content
  63. *
  64. * Since version 2.0.7 you may also specify this property as key-value pairs, where the key refers to the
  65. * `label` and the value refers to `content`. If value is a string it is interpreted as label. If it is
  66. * an array, it is interpreted as explained above.
  67. *
  68. * For example:
  69. *
  70. * ```php
  71. * echo Collapse::widget([
  72. * 'items' => [
  73. * 'Introduction' => 'This is the first collapsable menu',
  74. * 'Second panel' => [
  75. * 'content' => 'This is the second collapsable menu',
  76. * ],
  77. * [
  78. * 'label' => 'Third panel',
  79. * 'content' => 'This is the third collapsable menu',
  80. * ],
  81. * ]
  82. * ])
  83. * ```
  84. */
  85. public $items = [];
  86. /**
  87. * @var bool whether the labels for header items should be HTML-encoded.
  88. */
  89. public $encodeLabels = true;
  90. /**
  91. * @var bool whether to close other items if an item is opened. Defaults to `true` which causes an
  92. * accordion effect. Set this to `false` to allow keeping multiple items open at once.
  93. * @since 2.0.7
  94. */
  95. public $autoCloseItems = true;
  96. /**
  97. * @var string the HTML options for the item toggle tag. Key 'tag' might be used here for the tag name specification.
  98. * For example:
  99. *
  100. * ```php
  101. * [
  102. * 'tag' => 'div',
  103. * 'class' => 'custom-toggle',
  104. * ]
  105. * ```
  106. *
  107. * @since 2.0.8
  108. */
  109. public $itemToggleOptions = [];
  110. /**
  111. * Initializes the widget.
  112. */
  113. public function init()
  114. {
  115. parent::init();
  116. Html::addCssClass($this->options, ['widget' => 'panel-group']);
  117. }
  118. /**
  119. * Renders the widget.
  120. */
  121. public function run()
  122. {
  123. $this->registerPlugin('collapse');
  124. return implode("\n", [
  125. Html::beginTag('div', $this->options),
  126. $this->renderItems(),
  127. Html::endTag('div')
  128. ]) . "\n";
  129. }
  130. /**
  131. * Renders collapsible items as specified on [[items]].
  132. * @throws InvalidConfigException if label isn't specified
  133. * @return string the rendering result
  134. */
  135. public function renderItems()
  136. {
  137. $items = [];
  138. $index = 0;
  139. foreach ($this->items as $key => $item) {
  140. if (!is_array($item)) {
  141. $item = ['content' => $item];
  142. }
  143. if (!array_key_exists('label', $item)) {
  144. if (is_int($key)) {
  145. throw new InvalidConfigException("The 'label' option is required.");
  146. } else {
  147. $item['label'] = $key;
  148. }
  149. }
  150. $header = $item['label'];
  151. $options = ArrayHelper::getValue($item, 'options', []);
  152. Html::addCssClass($options, ['panel' => 'panel', 'widget' => 'panel-default']);
  153. $items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options);
  154. }
  155. return implode("\n", $items);
  156. }
  157. /**
  158. * Renders a single collapsible item group
  159. * @param string $header a label of the item group [[items]]
  160. * @param array $item a single item from [[items]]
  161. * @param int $index the item index as each item group content must have an id
  162. * @return string the rendering result
  163. * @throws InvalidConfigException
  164. */
  165. public function renderItem($header, $item, $index)
  166. {
  167. if (array_key_exists('content', $item)) {
  168. $id = $this->options['id'] . '-collapse' . $index;
  169. $options = ArrayHelper::getValue($item, 'contentOptions', []);
  170. $options['id'] = $id;
  171. Html::addCssClass($options, ['widget' => 'panel-collapse', 'collapse' => 'collapse']);
  172. $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
  173. if ($encodeLabel) {
  174. $header = Html::encode($header);
  175. }
  176. $active = false;
  177. if (isset($options['class'])) {
  178. $classes = is_string($options['class']) ? preg_split('/\s+/', $options['class'], -1, PREG_SPLIT_NO_EMPTY) : $options['class'];
  179. $active = in_array('in', $classes, true);
  180. }
  181. $itemToggleOptions = array_merge([
  182. 'tag' => 'a',
  183. 'data-toggle' => 'collapse',
  184. ], $this->itemToggleOptions);
  185. Html::addCssClass($itemToggleOptions, ['widget' => 'collapse-toggle']);
  186. if (!$active) {
  187. Html::addCssClass($itemToggleOptions, ['collapsed' => 'collapsed']);
  188. }
  189. if ($this->autoCloseItems) {
  190. $itemToggleOptions['data-parent'] = '#' . $this->options['id'];
  191. }
  192. $itemToggleTag = ArrayHelper::remove($itemToggleOptions, 'tag', 'a');
  193. if ($itemToggleTag === 'a') {
  194. $headerToggle = Html::a($header, '#' . $id, $itemToggleOptions) . "\n";
  195. } else {
  196. $itemToggleOptions['data-target'] = '#' . $id;
  197. $headerToggle = Html::tag($itemToggleTag, $header, $itemToggleOptions) . "\n";
  198. }
  199. $header = Html::tag('h4', $headerToggle, ['class' => 'panel-title']);
  200. if (is_string($item['content']) || is_numeric($item['content']) || is_object($item['content'])) {
  201. $content = Html::tag('div', $item['content'], ['class' => 'panel-body']) . "\n";
  202. } elseif (is_array($item['content'])) {
  203. $content = Html::ul($item['content'], [
  204. 'class' => 'list-group',
  205. 'itemOptions' => [
  206. 'class' => 'list-group-item'
  207. ],
  208. 'encode' => false,
  209. ]) . "\n";
  210. if (isset($item['footer'])) {
  211. $content .= Html::tag('div', $item['footer'], ['class' => 'panel-footer']) . "\n";
  212. }
  213. } else {
  214. throw new InvalidConfigException('The "content" option should be a string, array or object.');
  215. }
  216. } else {
  217. throw new InvalidConfigException('The "content" option is required.');
  218. }
  219. $group = [];
  220. $group[] = Html::tag('div', $header, ['class' => 'panel-heading']);
  221. $group[] = Html::tag('div', $content, $options);
  222. return implode("\n", $group);
  223. }
  224. }