Collapse.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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: boolean, 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 boolean whether the labels for header items should be HTML-encoded.
  88. */
  89. public $encodeLabels = true;
  90. /**
  91. * @var boolean 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. * Initializes the widget.
  98. */
  99. public function init()
  100. {
  101. parent::init();
  102. Html::addCssClass($this->options, ['widget' => 'panel-group']);
  103. }
  104. /**
  105. * Renders the widget.
  106. */
  107. public function run()
  108. {
  109. $this->registerPlugin('collapse');
  110. return implode("\n", [
  111. Html::beginTag('div', $this->options),
  112. $this->renderItems(),
  113. Html::endTag('div')
  114. ]) . "\n";
  115. }
  116. /**
  117. * Renders collapsible items as specified on [[items]].
  118. * @throws InvalidConfigException if label isn't specified
  119. * @return string the rendering result
  120. */
  121. public function renderItems()
  122. {
  123. $items = [];
  124. $index = 0;
  125. foreach ($this->items as $key => $item) {
  126. if (!is_array($item)) {
  127. $item = ['content' => $item];
  128. }
  129. if (!array_key_exists('label', $item)) {
  130. if (is_int($key)) {
  131. throw new InvalidConfigException("The 'label' option is required.");
  132. } else {
  133. $item['label'] = $key;
  134. }
  135. }
  136. $header = $item['label'];
  137. $options = ArrayHelper::getValue($item, 'options', []);
  138. Html::addCssClass($options, ['panel' => 'panel', 'widget' => 'panel-default']);
  139. $items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options);
  140. }
  141. return implode("\n", $items);
  142. }
  143. /**
  144. * Renders a single collapsible item group
  145. * @param string $header a label of the item group [[items]]
  146. * @param array $item a single item from [[items]]
  147. * @param int $index the item index as each item group content must have an id
  148. * @return string the rendering result
  149. * @throws InvalidConfigException
  150. */
  151. public function renderItem($header, $item, $index)
  152. {
  153. if (array_key_exists('content', $item)) {
  154. $id = $this->options['id'] . '-collapse' . $index;
  155. $options = ArrayHelper::getValue($item, 'contentOptions', []);
  156. $options['id'] = $id;
  157. Html::addCssClass($options, ['widget' => 'panel-collapse', 'collapse' => 'collapse']);
  158. $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
  159. if ($encodeLabel) {
  160. $header = Html::encode($header);
  161. }
  162. $headerOptions = [
  163. 'class' => 'collapse-toggle',
  164. 'data-toggle' => 'collapse',
  165. ];
  166. if ($this->autoCloseItems) {
  167. $headerOptions['data-parent'] = '#' . $this->options['id'];
  168. }
  169. $headerToggle = Html::a($header, '#' . $id, $headerOptions) . "\n";
  170. $header = Html::tag('h4', $headerToggle, ['class' => 'panel-title']);
  171. if (is_string($item['content']) || is_numeric($item['content']) || is_object($item['content'])) {
  172. $content = Html::tag('div', $item['content'], ['class' => 'panel-body']) . "\n";
  173. } elseif (is_array($item['content'])) {
  174. $content = Html::ul($item['content'], [
  175. 'class' => 'list-group',
  176. 'itemOptions' => [
  177. 'class' => 'list-group-item'
  178. ],
  179. 'encode' => false,
  180. ]) . "\n";
  181. if (isset($item['footer'])) {
  182. $content .= Html::tag('div', $item['footer'], ['class' => 'panel-footer']) . "\n";
  183. }
  184. } else {
  185. throw new InvalidConfigException('The "content" option should be a string, array or object.');
  186. }
  187. } else {
  188. throw new InvalidConfigException('The "content" option is required.');
  189. }
  190. $group = [];
  191. $group[] = Html::tag('div', $header, ['class' => 'panel-heading']);
  192. $group[] = Html::tag('div', $content, $options);
  193. return implode("\n", $group);
  194. }
  195. }