2020-12-04 16:08:11 +01:00
< ? php
/**
* Bootstrap Tabs helper
* Options :
* [ style ]
2021-01-13 14:16:52 +01:00
* - fill : Should the navigation items occupy all available space
2020-12-11 08:52:11 +01:00
* - justify : Should the navigation items be justified ( accept : [ 'center' , 'end' ])
* - pills : Should the navigation items be pills
* - vertical : Should the navigation bar be placed on the left side of the content
* - vertical - size : Specify how many boostrap ' s `cols` should be used for the navigation ( only used when `vertical` is true )
2020-12-04 16:08:11 +01:00
* - card : Should the navigation be placed in a bootstrap card
2021-01-13 14:16:52 +01:00
* - header - variant : The bootstrap variant to be used for the card header
* - body - variant : The bootstrap variant to be used for the card body
2020-12-04 16:08:11 +01:00
* - nav - class : additional class to add to the nav container
* - content - class : additional class to add to the content container
* [ data ]
* - data : contains the data for the tabs and content
* {
* 'navs' : [{ nav - item }, { nav - item }, ... ],
* 'content' : [{ nav - content }, { nav - content }, ... ]
* }
*
* # Usage:
* echo $this -> Bootstrap -> Tabs ([
* 'pills' => true ,
* 'card' => true ,
* 'data' => [
* 'navs' => [
* 'tab1' ,
* [ 'text' => 'tab2' , 'active' => true ],
* [ 'html' => '<b>tab3</b>' , 'disabled' => true ],
* ],
* 'content' => [
* 'body1' ,
* '<i>body2</i>' ,
* '~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 = [
2020-12-11 08:52:11 +01:00
'fill' => false ,
'justify' => false ,
2020-12-04 16:08:11 +01:00
'pills' => false ,
'vertical' => false ,
'vertical-size' => 3 ,
'card' => false ,
2021-01-13 14:16:52 +01:00
'header-variant' => 'light' ,
'body-variant' => '' ,
2020-12-04 16:08:11 +01:00
'nav-class' => [],
'nav-item-class' => [],
'content-class' => [],
'data' => [
'navs' => [],
'content' => [],
],
];
private $allowedOptionValues = [
2020-12-11 08:52:11 +01:00
'justify' => [ false , 'center' , 'end' ],
2021-01-13 14:16:52 +01:00
'body-variant' => [ 'primary' , 'success' , 'danger' , 'warning' , 'info' , 'light' , 'dark' , 'white' , 'transparent' , '' ],
'header-variant' => [ 'primary' , 'success' , 'danger' , 'warning' , 'info' , 'light' , 'dark' , 'white' , 'transparent' ],
2020-12-04 16:08:11 +01:00
];
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' ],
];
2020-12-11 08:52:11 +01:00
if ( ! empty ( $this -> options [ 'justify' ])) {
$this -> bsClasses [ 'nav' ][] = 'justify-content-' . $this -> options [ 'justify' ];
2020-12-04 16:08:11 +01:00
}
2021-01-13 14:16:52 +01:00
if ( $this -> options [ 'vertical' ] && ! isset ( $options [ 'pills' ]) && ! isset ( $options [ 'card' ])) {
$this -> options [ 'pills' ] = true ;
$this -> options [ 'card' ] = true ;
}
2020-12-04 16:08:11 +01:00
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' ;
}
}
2020-12-11 08:52:11 +01:00
if ( $this -> options [ 'fill' ]) {
2020-12-04 16:08:11 +01:00
$this -> bsClasses [ 'nav' ][] = 'nav-fill' ;
}
2020-12-11 08:52:11 +01:00
if ( $this -> options [ 'justify' ]) {
2020-12-04 16:08:11 +01:00
$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' ];
2020-12-07 09:52:35 +01:00
2021-01-13 14:16:52 +01:00
$this -> options [ 'header-text-variant' ] = $this -> options [ 'header-variant' ] == 'light' ? 'body' : 'white' ;
$this -> options [ 'header-border-variant' ] = $this -> options [ 'header-variant' ] == 'light' ? '' : $this -> options [ 'header-variant' ];
$this -> options [ 'body-text-variant' ] = $this -> options [ 'body-variant' ] == '' ? 'body' : 'white' ;
2020-12-07 09:52:35 +01:00
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' ]];
}
2020-12-04 16:08:11 +01:00
}
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' ));
}
}
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' ]) {
2021-01-13 14:16:52 +01:00
$html .= $this -> genNode ( 'div' , [ 'class' => array_merge ([ 'card' ], [ " border- { $this -> options [ 'header-border-variant' ] } " ])]);
$html .= $this -> genNode ( 'div' , [ 'class' => array_merge ([ 'card-header' ], [ " bg- { $this -> options [ 'header-variant' ] } " , " text- { $this -> options [ 'header-text-variant' ] } " ])]);
2020-12-04 16:08:11 +01:00
}
$html .= $this -> genNav ();
if ( $this -> options [ 'card' ]) {
$html .= '</div>' ;
2021-01-13 14:16:52 +01:00
$html .= $this -> genNode ( 'div' , [ 'class' => array_merge ([ 'card-body' ], [ " bg- { $this -> options [ 'body-variant' ] } " , " text- { $this -> options [ 'body-text-variant' ] } " ])]);
2020-12-04 16:08:11 +01:00
}
$html .= $this -> genContent ();
if ( $this -> options [ 'card' ]) {
$html .= '</div>' ;
2020-12-08 09:19:38 +01:00
$html .= '</div>' ;
2020-12-04 16:08:11 +01:00
}
return $html ;
}
private function genVerticalTabs ()
{
2021-01-13 14:16:52 +01:00
$html = $this -> genNode ( 'div' , [ 'class' => array_merge ([ 'row' , ( $this -> options [ 'card' ] ? 'card flex-row' : '' )], [ " border- { $this -> options [ 'header-border-variant' ] } " ])]);
$html .= $this -> genNode ( 'div' , [ 'class' => array_merge ([ 'col-' . $this -> options [ 'vertical-size' ], ( $this -> options [ 'card' ] ? 'card-header border-right' : '' )], [ " bg- { $this -> options [ 'header-variant' ] } " , " text- { $this -> options [ 'header-text-variant' ] } " , " border- { $this -> options [ 'header-border-variant' ] } " ])]);
2020-12-04 16:08:11 +01:00
$html .= $this -> genNav ();
$html .= '</div>' ;
2021-01-13 14:16:52 +01:00
$html .= $this -> genNode ( 'div' , [ 'class' => array_merge ([ 'col-' . ( 12 - $this -> options [ 'vertical-size' ]), ( $this -> options [ 'card' ] ? 'card-body2' : '' )], [ " bg- { $this -> options [ 'body-variant' ] } " , " text- { $this -> options [ 'body-text-variant' ] } " ])]);
2020-12-04 16:08:11 +01:00
$html .= $this -> genContent ();
$html .= '</div>' ;
$html .= '</div>' ;
return $html ;
}
private function genNav ()
{
2020-12-08 09:24:25 +01:00
$html = $this -> genNode ( 'ul' , [
2020-12-04 16:08:11 +01:00
'class' => array_merge ([ 'nav' ], $this -> bsClasses [ 'nav' ], $this -> options [ 'nav-class' ]),
'role' => 'tablist' ,
2020-12-08 09:24:25 +01:00
]);
2020-12-04 16:08:11 +01:00
foreach ( $this -> data [ 'navs' ] as $navItem ) {
$html .= $this -> genNavItem ( $navItem );
}
$html .= '</ul>' ;
return $html ;
}
private function genNavItem ( $navItem )
{
2020-12-08 09:24:25 +01:00
$html = $this -> genNode ( 'li' , [
2020-12-04 16:08:11 +01:00
'class' => array_merge ([ 'nav-item' ], $this -> bsClasses [ 'nav-item' ], $this -> options [ 'nav-item-class' ]),
'role' => 'presentation' ,
2020-12-08 09:24:25 +01:00
]);
$html .= $this -> genNode ( 'a' , [
2020-12-04 16:08:11 +01:00
'class' => array_merge (
[ 'nav-link' ],
[ ! empty ( $navItem [ 'active' ]) ? 'active' : '' ],
[ ! empty ( $navItem [ 'disabled' ]) ? 'disabled' : '' ]
),
'data-toggle' => $this -> options [ 'pills' ] ? 'pill' : 'tab' ,
'id' => $navItem [ 'id' ] . '-tab' ,
'href' => '#' . $navItem [ 'id' ],
'aria-controls' => $navItem [ 'id' ],
'aria-selected' => ! empty ( $navItem [ 'active' ]),
'role' => 'tab' ,
2020-12-08 09:24:25 +01:00
]);
2020-12-04 16:08:11 +01:00
if ( ! empty ( $navItem [ 'html' ])) {
$html .= $navItem [ 'html' ];
} else {
$html .= h ( $navItem [ 'text' ]);
}
$html .= '</a></li>' ;
return $html ;
}
private function genContent ()
{
2020-12-08 09:24:25 +01:00
$html = $this -> genNode ( 'div' , [
2020-12-04 16:08:11 +01:00
'class' => array_merge ([ 'tab-content' ], $this -> options [ 'content-class' ]),
2020-12-08 09:24:25 +01:00
]);
2020-12-04 16:08:11 +01:00
foreach ( $this -> data [ 'content' ] as $i => $content ) {
$navItem = $this -> data [ 'navs' ][ $i ];
$html .= $this -> genContentItem ( $navItem , $content );
}
2020-12-09 09:30:22 +01:00
$html .= '</div>' ;
2020-12-04 16:08:11 +01:00
return $html ;
}
private function genContentItem ( $navItem , $content )
{
2020-12-08 09:24:25 +01:00
$html = $this -> genNode ( 'div' , [
2020-12-04 16:08:11 +01:00
'class' => array_merge ([ 'tab-pane' , 'fade' ], [ ! empty ( $navItem [ 'active' ]) ? 'show active' : '' ]),
'role' => 'tabpanel' ,
'id' => $navItem [ 'id' ],
'aria-labelledby' => $navItem [ 'id' ] . '-tab'
2020-12-08 09:24:25 +01:00
]);
2020-12-04 16:08:11 +01:00
$html .= $content ;
$html .= '</div>' ;
return $html ;
}
private function genNode ( $node , $params )
{
return sprintf ( '<%s %s>' , $node , $this -> genHTMLParams ( $params ));
}
private function genHTMLParams ( $params )
{
$html = '' ;
foreach ( $params as $k => $v ) {
$html .= $this -> genHTMLParam ( $k , $v ) . ' ' ;
}
return $html ;
}
private function genHTMLParam ( $paramName , $values )
{
if ( ! is_array ( $values )) {
$values = [ $values ];
}
return sprintf ( '%s="%s"' , $paramName , implode ( ' ' , $values ));
}
}