Bootstrap->Tabs([ * 'pills' => true, * 'card' => true, * 'data' => [ * 'navs' => [ * 'tab1', * ['text' => 'tab2', 'active' => true], * ['html' => 'tab3', 'disabled' => true], * ], * 'content' => [ * 'body1', * 'body2', * '~body3~' * ] * ] * ]); */ namespace App\View\Helper; use Cake\View\Helper; use Cake\Utility\Security; use InvalidArgumentException; class BootstrapHelper extends Helper { public function tabs($options) { $bsTabs = new BootstrapTabs($options); return $bsTabs->tabs(); } } class BootstrapTabs extends Helper { private $defaultOptions = [ 'nav-fill' => false, 'nav-justify' => false, 'pills' => false, 'vertical' => false, 'vertical-size' => 3, 'card' => false, 'nav-class' => [], 'nav-item-class' => [], 'content-class' => [], 'data' => [ 'navs' => [], 'content' => [], ], ]; private $allowedOptionValues = [ 'nav-justify' => [false, 'center', 'end'], ]; private $options = null; private $bsClasses = null; function __construct($options) { $this->processOptions($options); } public function tabs() { return $this->genTabs(); } private function processOptions($options) { $this->options = array_merge($this->defaultOptions, $options); $this->data = $this->options['data']; $this->checkOptionValidity(); $this->bsClasses = [ 'nav' => [], 'nav-item' => $this->options['nav-item-class'], ]; if (!empty($this->options['nav-justify'])) { $this->bsClasses['nav'][] = 'justify-content-' . $this->options['nav-justify']; } if ($this->options['pills']) { $this->bsClasses['nav'][] = 'nav-pills'; if ($this->options['vertical']) { $this->bsClasses['nav'][] = 'flex-column'; } if ($this->options['card']) { $this->bsClasses['nav'][] = 'card-header-pills'; } } else { $this->bsClasses['nav'][] = 'nav-tabs'; if ($this->options['card']) { $this->bsClasses['nav'][] = 'card-header-tabs'; } } if ($this->options['nav-fill']) { $this->bsClasses['nav'][] = 'nav-fill'; } if ($this->options['nav-justify']) { $this->bsClasses['nav'][] = 'nav-justify'; } $activeTab = 0; foreach ($this->data['navs'] as $i => $nav) { if (!is_array($nav)) { $this->data['navs'][$i] = ['text' => $nav]; } if (!isset($this->data['navs'][$i]['id'])) { $this->data['navs'][$i]['id'] = 't-' . Security::randomString(8); } if (!empty($nav['active'])) { $activeTab = $i; } } $this->data['navs'][$activeTab]['active'] = true; $this->options['vertical-size'] = $this->options['vertical-size'] < 0 || $this->options['vertical-size'] > 11 ? 3 : $this->options['vertical-size']; } private function checkOptionValidity() { foreach ($this->allowedOptionValues as $option => $values) { if (!isset($this->options[$option])) { throw new InvalidArgumentException(__('Option `{0}` should have a value', $option)); } if (!in_array($this->options[$option], $values)) { throw new InvalidArgumentException(__('Option `{0}` is not a valid option for `{1}`. Accepted values: {2}', json_encode($this->options[$option]), $option, json_encode($values))); } } if (empty($this->data['navs'])) { throw new InvalidArgumentException(__('No navigation data provided')); } if ($this->options['card'] && $this->options['vertical']) { throw new InvalidArgumentException(__('`card` option can only be used on horizontal mode')); } } private function genTabs() { $html = ''; if ($this->options['vertical']) { $html .= $this->genVerticalTabs(); } else { $html .= $this->genHorizontalTabs(); } return $html; } private function genHorizontalTabs() { $html = ''; if ($this->options['card']) { $html .= $this->genNode('div', ['class' => ['card']]); $html .= $this->genNode('div', ['class' => ['card-header']]); } $html .= $this->genNav(); if ($this->options['card']) { $html .= ''; $html .= $this->genNode('div', ['class' => ['card-body']]); } $html .= $this->genContent(); if ($this->options['card']) { $html .= ''; } return $html; } private function genVerticalTabs() { $html = sprintf('