cerebrate/src/View/Helper/BootstrapElements/BootstrapTabs.php

308 lines
11 KiB
PHP

<?php
namespace App\View\Helper\BootstrapElements;
use Cake\Utility\Security;
use App\View\Helper\BootstrapGeneric;
/**
* Creates a bootstrap panel with navigation component.
*
* # Options:
* - fill-header: Should the navigation header takes up all the space available
* - justify-header: Allow to specify how the naviation component should be justified. Accepts: false (no justify), 'start', 'end', 'center';
* - pills: Should the navigation element be pills
* - card: Should the content and navigation elements be wrapped in a Bootstrap card component
* - header-variant, body-variant: The variant that the card's header and body should have. Ignore if $card is not set
* - body-class, nav-class, nav-class-item, content-class: Additional classes to be added to the nav, body, navigation items or content
* - vertical: Should the navigation component be placed vertically next to the content. Best used with the `pills` option enabled.
* - vertical-size: Controls the horizontal size of the vertical header. Must be between [1, 11]
* - vertical-position: Controls the position of the header. Accepts 'start and 'end'
* - horizontal-position: Controls the position of the header. Accepts 'top and 'bottom'
* - data: The data used to generate the tabs. Must have a `navs` and `content` key. See the "# Data" section
*
* # Data
* - navs: The data for the navigation items. Supported options:
* - id: The ID of the nav. Auto-generated if left empty
* - active: Should the tab be active
* - disabled: Should the tab be disabled
* - text: The text content of the tab
* - html: The HTML content of the tab
*
* - content: The HTML content for each tabs
*
* # Usage:
* ## Simple formatted tabs using the card option
* echo $this->Bootstrap->tabs([
* 'horizontal-position' => 'top',
* 'header-variant' => 'danger',
* 'card' => true,
* 'data' => [
* 'navs' => [
* ['text' => 'nav 1'],
* ['html' => '<b>nav 2</b>', 'active' => true],
* ],
* 'content' => [
* '<i>content 1</i>',
* 'content 2',
* ]
* ]
* ]);
*
* ## Simple formatted tabs using the card option and vertical options
* echo $this->Bootstrap->tabs([
* 'pills' => true,
* 'vertical' => true,
* 'vertical-position' => 'start',
* 'card' => true,
* 'data' => [
* 'navs' => [
* ['text' => 'nav 1'],
* ['html' => '<b>nav 2</b>', 'disabled' => true],
* ],
* 'content' => [
* '<i>content 1</i>',
* 'content 2',
* ]
* ]
* ]);
*/
class BootstrapTabs extends BootstrapGeneric
{
private $defaultOptions = [
'fill-header' => false,
'justify-header' => false,
'pills' => false,
'vertical' => false,
'vertical-size' => 3,
'vertical-position' => 'start',
'horizontal-position' => 'top',
'card' => false,
'header-variant' => '',
'body-variant' => '',
'body-class' => [],
'nav-class' => [],
'nav-item-class' => [],
'content-class' => [],
'data' => [
'navs' => [],
'content' => [],
],
];
private $bsClasses = null;
function __construct($options)
{
$this->allowedOptionValues = [
'justify-header' => [false, 'center', 'end', 'start'],
'vertical-position' => ['start', 'end'],
'horizontal-position' => ['top', 'bottom'],
'body-variant' => array_merge(BootstrapGeneric::$variants, ['']),
'header-variant' => array_merge(BootstrapGeneric::$variants, ['']),
];
$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();
if (empty($this->data['navs'])) {
throw new InvalidArgumentException(__('No navigation data provided'));
}
$this->bsClasses = [
'nav' => [],
'nav-item' => $this->options['nav-item-class'],
];
if (!empty($this->options['justify-header'])) {
$this->bsClasses['nav'][] = 'justify-content-' . $this->options['justify-header'];
}
if ($this->options['vertical'] && !isset($options['pills']) && !isset($options['card'])) {
$this->options['pills'] = true;
$this->options['card'] = true;
}
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['fill-header']) {
$this->bsClasses['nav'][] = 'nav-fill';
}
if ($this->options['justify-header']) {
$this->bsClasses['nav'][] = 'nav-justify';
}
$activeTab = array_key_first($this->data['navs']);
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;
if (!empty($this->options['vertical-size']) && $this->options['vertical-size'] != 'auto') {
$this->options['vertical-size'] = ($this->options['vertical-size'] < 0 || $this->options['vertical-size'] > 11) ? 3 : $this->options['vertical-size'];
}
if (!is_array($this->options['nav-class'])) {
$this->options['nav-class'] = [$this->options['nav-class']];
}
if (!is_array($this->options['content-class'])) {
$this->options['content-class'] = [$this->options['content-class']];
}
}
private function genTabs()
{
return $this->options['vertical'] ? $this->genVerticalTabs() : $this->genHorizontalTabs();
}
private function genHorizontalTabs()
{
if ($this->options['card']) {
$cardOptions = [
'bodyHTML' => $this->genContent(),
'bodyVariant' => $this->options['body-variant'],
];
if ($this->options['horizontal-position'] === 'bottom') {
$cardOptions['footerHTML'] = $this->genNav();
$cardOptions['footerVariant'] = $this->options['header-variant'];
$cardOptions['headerVariant'] = $this->options['header-variant'];
} else {
$cardOptions['headerHTML'] = $this->genNav();
$cardOptions['headerVariant'] = $this->options['header-variant'];
}
$bsCard = new BootstrapCard($cardOptions);
return $bsCard->card();
} else {
return $this->genNav() . $this->genContent();
}
}
private function genVerticalTabs()
{
$header = $this->node('div', ['class' => array_merge(
[
($this->options['vertical-size'] != 'auto' ? 'col-' . $this->options['vertical-size'] : ''),
($this->options['card'] ? 'card-header border-end' : '')
],
[
"bg-{$this->options['header-variant']}",
"text-{$this->options['header-text-variant']}",
"border-{$this->options['header-border-variant']}"
]
)], $this->genNav());
$content = $this->node('div', ['class' => array_merge(
[
($this->options['vertical-size'] != 'auto' ? 'col-' . (12 - $this->options['vertical-size']) : ''),
($this->options['card'] ? 'card-body2' : '')
],
[
"bg-{$this->options['body-variant']}",
"text-{$this->options['body-text-variant']}"
]
)], $this->genContent());
$containerContent = $this->options['vertical-position'] === 'start' ? [$header, $content] : [$content, $header];
$container = $this->node('div', ['class' => array_merge(
[
'row',
($this->options['card'] ? 'card flex-row' : ''),
($this->options['vertical-size'] == 'auto' ? 'flex-nowrap' : '')
],
[
"border-{$this->options['header-border-variant']}"
]
)], $containerContent);
return $container;
}
private function genNav()
{
$html = $this->nodeOpen('ul', [
'class' => array_merge(['nav'], $this->bsClasses['nav'], $this->options['nav-class']),
'role' => 'tablist',
]);
foreach ($this->data['navs'] as $navItem) {
$html .= $this->genNavItem($navItem);
}
$html .= $this->nodeClose('ul');
return $html;
}
private function genNavItem($navItem)
{
$html = $this->nodeOpen('li', [
'class' => array_merge(['nav-item'], $this->bsClasses['nav-item'], $this->options['nav-item-class']),
'role' => 'presentation',
]);
$html .= $this->nodeOpen('a', [
'class' => array_merge(
['nav-link'],
[!empty($navItem['active']) ? 'active' : ''],
[!empty($navItem['disabled']) ? 'disabled' : '']
),
'data-bs-toggle' => $this->options['pills'] ? 'pill' : 'tab',
'id' => $navItem['id'] . '-tab',
'href' => '#' . $navItem['id'],
'aria-controls' => $navItem['id'],
'aria-selected' => !empty($navItem['active']),
'role' => 'tab',
]);
$html .= $navItem['html'] ?? h($navItem['text']);
$html .= $this->nodeClose('a');
$html .= $this->nodeClose('li');
return $html;
}
private function genContent()
{
$html = $this->nodeOpen('div', [
'class' => array_merge(['tab-content'], $this->options['content-class']),
]);
foreach ($this->data['content'] as $i => $content) {
$navItem = $this->data['navs'][$i];
$html .= $this->genContentItem($navItem, $content);
}
$html .= $this->nodeClose('div');
return $html;
}
private function genContentItem($navItem, $content)
{
return $this->node('div', [
'class' => array_merge(['tab-pane', 'fade'], [!empty($navItem['active']) ? 'show active' : '']),
'role' => 'tabpanel',
'id' => $navItem['id'],
'aria-labelledby' => $navItem['id'] . '-tab'
], $content);
}
}