Файл: cobisja/BootHelp/src/Carousel.php
Строк: 541
<?php
/**
 * The MIT License
 *
 * Copyright 2015 cobisja.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
namespace cobisjaBootHelp;
use cobisjaBootHelpBase;
use cobisjaBootHelpHelpersContentTag;
use cobisjaBootHelpIcon;
/**
 * Generates an HTML block tag that follows the Bootstrap documentation
 * on how to display <strong>Carousel</strong> component.
 *
 * See {@link http://getbootstrap.com/javascript/#carousel} for more information.
 */
class Carousel extends Base
{
    /**
     * Initializes the Carousel instance.
     *
     * @param mixed    $content_or_options_with_block the display options for the carousel.
     * @param Callable $block Block to generate customized carousel content.
     */
    public function __construct($content_or_options_with_block, $block = null)
    {
        $num_args = $this->getFunctionNumArgs(func_get_args());
        $content_block = is_callable(func_get_arg($num_args-1)) ? func_get_arg($num_args-1) : null;
        if (1 === $num_args) {
            $options = [];
            $content = is_null($content_block) ? $content_or_options_with_block : call_user_func($content_block);
        } else {
            if (is_array($content_or_options_with_block) && is_array($block)) {
                $content = $content_or_options_with_block;
                $options = $block;
            } else {
                $options = $content_or_options_with_block;
                $content = call_user_func($content_block);
            }
        }
        Base::setCarouselId(
            isset($options['id']) ? Base::getAndUnset('id', $options) : ('carousel-' . (string)(mt_rand(1, pow(10, 7))))
        );        
        $carousel = $this->buildCarousel($content, $options);
        Base::setCarouselId('');
        
        $this->setHtmlObject($carousel->getHtmlObject());
    }
    
    /**
     * Builds the Carousel component.
     * 
     * @param mixed $content Carousel's content
     * @param array $options Carousel's options to customize the component generation.
     * 
     * @return ContentTag a ContentTag instance that represents the Carousel.
     */
    private function buildCarousel($content, array $options)
    {
        $indicators = null;
        $controls = [];
        $active_item = isset($options['active_item']) ? Base::getAndUnset('active_item', $options) : 0;
        
        $indicators_options = Base::getAndUnset('indicators_options', $options);
        $content_options = Base::getAndUnset('content_options', $options);
        $controls_options = Base::getAndUnset('controls_options', $options);
        $show_indicators = Base::getAndUnset('show_indicators', $options);
        $show_controls = Base::getAndUnset('show_controls', $options);
        $items_number = count($content);
        
        Base::appendClass($content_options, 'carousel-inner');
        $content_options['role'] = 'listbox';
        
        (is_null($show_indicators) || !is_null($show_indicators) && $show_indicators)
            && ($indicators = $this->buildCarouselIndicators($items_number, $indicators_options, $active_item));
        (is_null($show_controls) || !is_null($show_controls) && $show_controls) 
            && ($controls = $this->buildCarouselControls($controls_options));
        $content = $this->buildCarouselItems($content, $content_options, $active_item);
        
        Base::appendClass($options, 'carousel slide');
        $options['id'] = Base::getCarouselId();
        $options['data-ride'] = 'carousel';
        
        $carousel = new ContentTag('div', $options, function () use ($indicators, $content, $controls) {
            return array_filter(array_merge([$indicators, $content], $controls), 'strlen');
        });
        
        return $carousel;
    }
    
    /**
     * Builds the Carousel's controls.
     * 
     * @param int   $quantity number of controls that must be generated.
     * @param array $options options to be passed to the control's html tags.
     * @param int   $active_control control that will be initially active.
     * 
     * @return ContentTag ContenTag instance that represents the Carousel's controls.
     */
    private function buildCarouselIndicators($quantity, $options, $active_control)
    {
        Base::appendClass($options, 'carousel-indicators');
        
        $indicators_object = new ContentTag('ol', $options, function () use ($quantity, $active_control) {
            $indicators = [];
            
            for ($idx=0; $idx < $quantity; $idx++) {
                $indicator = new ContentTag(
                    'li',
                    '',
                    [
                        'data-target'=>'#' . Base::getCarouselId(),
                        'data-slide-to'=>(string)$idx,
                        'class'=> $idx === $active_control ? 'active' : null
                    ]
                );
                
                array_push($indicators, $indicator);
            }
            
            return $indicators;
        });
        
        return $indicators_object;
    }
    
    /**
     * Builds the Carousel's content
     * 
     * @param array $content array of items associates to the Carousel's content.
     * @param array $options options to be passed to the html tags associated with Carousel's content.
     * @param int   $active_item_id active item that wiil be initially active.
     * 
     * @return ContentTag ContentTag instance that represents the Carousel's content.
     */
    private function buildCarouselItems(array $content, array $options, $active_item_id)
    {
        $carousel_items = [];
        for ($item_id = 0; $item_id < count($content); $item_id++) {
            $element = $this->getItemAndCaption($content[$item_id]);
            $item = $element['item'];
            $caption = $element['caption'];
            $item_class = 'item' . ($item_id === $active_item_id ? ' active' : '');
            !is_null($caption) ? $caption = new ContentTag('div', $caption, ['class'=>'carousel-caption']) : null;
            
            $carousel_items[] = new ContentTag(
                'div',
                (!is_null($caption) ? [$item, $caption] : $item),
                ['class'=>$item_class]
            );
        }
        
        $carousel_content = new ContentTag('div', $carousel_items, $options);
        
        return $carousel_content;
    }
    
    /**
     * Builds the Carouse's nav controls.
     * 
     * @param array $options options to be passed to the html tags associated with Carousel's nav controls.
     * 
     * @return array Left and Right navigation controls.
     */
    private function buildCarouselControls($options)
    {
        $href = '#' . Base::getCarouselId();
        $default_left_control_options = [
            'caption'=>'Previous',
            'icon'=>'chevron-left',
            'href'=>$href,
            'role'=>'button',
            'data-slide'=>'prev'
        ];
        
        $default_right_control_options = [
            'caption'=>'Next',
            'icon'=>'chevron-right',
            'href'=>$href,
            'role'=>'button',
            'data-slide'=>'next'
        ];
        
        $left_control_options = isset($options['left']) ? Base::getAndUnset('left', $options) : [];
        $right_control_options = isset($options['right']) ? Base::getAndUnset('right', $options) : [];
        
        Base::appendClass($left_control_options, 'left carousel-control');
        Base::appendClass($right_control_options, 'right carousel-control');
        
        Base::setOptions($default_left_control_options, $left_control_options);
        Base::setOptions($default_right_control_options, $right_control_options);
        
        return [
            $this->buildControl($left_control_options),
            $this->buildControl($right_control_options)
        ];
    }
    
    /**
     * Helper method to build icon associated with the Carousel's nav control.
     * 
     * @param array  $options options that allows to customized the nav control generation.
     * 
     * @return ContentTag ContenTag instance that represents the Carousel's nav control.
     */
    private function buildControl(array $options)
    {
        $icon = Base::getAndUnset('icon', $options);
        $caption = Base::getAndUnset('caption', $options);
        
        $control = new ContentTag(
            'a',
            $options,
            function () use ($icon, $caption) {
                return [
                    new Icon($icon, ['aria-hidden'=>true]),
                    new ContentTag('span', $caption, ['class'=>'sr-only'])
                ];
            }
        );
        
        return $control;
    }
    
    /**
     * Helper method to get an Carousel's item and its possible caption.
     * 
     * @param mixed $item_object item data.
     * 
     * @return array associativa array with the Carousel's item and its caption information.
     */
    private function getItemAndCaption($item_object)
    {
        $element=[];
        
        if (!is_array($item_object)) {
            $element = [
                'item'=>  is_callable($item_object) ? call_user_func($item_object) : $item_object,
                'caption'=>null
            ];
        } else {
            $item = is_callable($item_object[0]) ? call_user_func($item_object[0]) : $item_object[0];
            if (!isset($item_object['caption'])) {
                $caption = null;
            } else {
                if (is_callable($item_object['caption'])) {
                    $caption = call_user_func($item_object['caption']);
                } else {
                    if (is_array($item_object['caption'])) {
                        $title = Base::getAndUnset('title', $item_object['caption']);
                        $content = Base::getAndUnset('content', $item_object['caption']);
                    } else {
                        $title = null;
                        $content = $item_object['caption'];
                    }
                    $caption = array_filter(
                        [
                            !is_null($title) ? new ContentTag('h3', $title) : null,
                            !is_null($content) ? new ContentTag('p', $content) : null
                        ],
                        'strlen'
                    );
                }
            }
            $element = ['item'=>$item, 'caption'=>$caption];
        }
        
        return $element;
    }
}