* @link http://www.pradosoft.com/ * @copyright Copyright © 2005-2014 PradoSoft * @license http://www.pradosoft.com/license/ * @package Prado\Web\UI\WebControls * @since 3.2 */ namespace Prado\Web\UI\WebControls; /** * Class TAccordion. * * TAccordion displays an accordion control. Users can click on the view headers to switch among * different accordion views. Each accordion view is an independent panel that can contain arbitrary content. * * A TAccordion control consists of one or several {@link TAccordionView} controls representing the possible * accordion views. At any time, only one accordion view is visible (active), which is specified by any of * the following properties: * - {@link setActiveViewIndex ActiveViewIndex} - the zero-based integer index of the view in the view collection. * - {@link setActiveViewID ActiveViewID} - the text ID of the visible view. * - {@link setActiveView ActiveView} - the visible view instance. * If both {@link setActiveViewIndex ActiveViewIndex} and {@link setActiveViewID ActiveViewID} * are set, the latter takes precedence. * * TAccordion uses CSS to specify the appearance of the accordion headers and panel. By default, * an embedded CSS file will be published which contains the default CSS for TTabPanel. * You may also use your own CSS file by specifying the {@link setCssUrl CssUrl} property. * The following properties specify the CSS classes used for elements in a TAccordion: * - {@link setCssClass CssClass} - the CSS class name for the outer-most div element (defaults to 'accordion'); * - {@link setHeaderCssClass HeaderCssClass} - the CSS class name for nonactive accordion div elements (defaults to 'accordion-header'); * - {@link setActiveHeaderCssClass ActiveHeaderCssClass} - the CSS class name for the active accordion div element (defaults to 'accordion-header-active'); * - {@link setViewCssClass ViewCssClass} - the CSS class for the div element enclosing view content (defaults to 'accordion-view'); * * When the user clicks on a view header, the switch between the old visible view and the clicked one is animated. * You can use the {@link setAnimationDuration AnimationDuration} property to set the animation length in seconds; * it defaults to 1 second, and when set to 0 it will produce an immediate switch with no animation. * * The TAccordion auto-sizes itself to the largest of all views, so it can encompass all of them without scrolling. * If you want to specify a fixed height (in pixels), use the {@link setViewHeight ViewHeight} property. * When a TAccordion is nested inside another, it's adviced to manually specify a {@link setViewHeight ViewHeight} for the internal TAccordion * * To use TAccordion, write a template like following: * * * * content for view 1 * * * content for view 2 * * * content for view 3 * * * * * @author Gabor Berczi, DevWorx Hungary * @package Prado\Web\UI\WebControls * @since 3.2 */ class TAccordion extends TWebControl implements IPostBackDataHandler { private $_dataChanged=false; /** * @return string tag name for the control */ protected function getTagName() { return 'div'; } /** * Adds object parsed from template to the control. * This method adds only {@link TAccordionView} objects into the {@link getViews Views} collection. * All other objects are ignored. * @param mixed object parsed from template */ public function addParsedObject($object) { if($object instanceof TAccordionView) $this->getControls()->add($object); } /** * Returns the index of the active accordion view. * Note, this property may not return the correct index. * To ensure the correctness, call {@link getActiveView()} first. * @return integer the zero-based index of the active accordion view. If -1, it means no active accordion view. Default is 0 (the first view is active). */ public function getActiveViewIndex() { return $this->getViewState('ActiveViewIndex',0); } /** * @param integer the zero-based index of the current view in the view collection. -1 if no active view. * @throws TInvalidDataValueException if the view index is invalid */ public function setActiveViewIndex($value) { $this->setViewState('ActiveViewIndex',TPropertyValue::ensureInteger($value),0); $this->setActiveViewID(''); } /** * Returns the ID of the active accordion view. * Note, this property may not return the correct ID. * To ensure the correctness, call {@link getActiveView()} first. * @return string The ID of the active accordion view. Defaults to '', meaning not set. */ public function getActiveViewID() { return $this->getViewState('ActiveViewID',''); } /** * @param string The ID of the active accordion view. */ public function setActiveViewID($value) { $this->setViewState('ActiveViewID',$value,''); } /** * Returns the currently active view. * This method will examin the ActiveViewID, ActiveViewIndex and Views collection to * determine which view is currently active. It will update ActiveViewID and ActiveViewIndex accordingly. * @return TAccordionView the currently active view, null if no active view * @throws TInvalidDataValueException if the active view ID or index set previously is invalid */ public function getActiveView() { $activeView=null; $views=$this->getViews(); if(($id=$this->getActiveViewID())!=='') { if(($index=$views->findIndexByID($id))>=0) $activeView=$views->itemAt($index); else throw new TInvalidDataValueException('tabpanel_activeviewid_invalid',$id); } else if(($index=$this->getActiveViewIndex())>=0) { if($index<$views->getCount()) $activeView=$views->itemAt($index); else throw new TInvalidDataValueException('tabpanel_activeviewindex_invalid',$index); } else { foreach($views as $index=>$view) { if($view->getActive()) { $activeView=$view; break; } } } if($activeView!==null) $this->activateView($activeView); return $activeView; } /** * @param TAccordionView the view to be activated * @throws TInvalidOperationException if the view is not in the view collection */ public function setActiveView($view) { if($this->getViews()->indexOf($view)>=0) $this->activateView($view); else throw new TInvalidOperationException('tabpanel_view_inexistent'); } /** * @return string URL for the CSS file including all relevant CSS class definitions. Defaults to ''. */ public function getCssUrl() { return $this->getViewState('CssUrl','default'); } /** * @param string URL for the CSS file including all relevant CSS class definitions. */ public function setCssUrl($value) { $this->setViewState('CssUrl',TPropertyValue::ensureString($value),''); } /** * @return string CSS class for the whole accordion control div. */ public function getCssClass() { $cssClass=parent::getCssClass(); return $cssClass===''?'accordion':$cssClass; } /** * @return string CSS class for the currently displayed view div. Defaults to 'accordion-view'. */ public function getViewCssClass() { return $this->getViewStyle()->getCssClass(); } /** * @param string CSS class for the currently displayed view div. */ public function setViewCssClass($value) { $this->getViewStyle()->setCssClass($value); } /** * @return string CSS class for the currently displayed view div. Defaults to 'accordion-view'. */ public function getAnimationDuration() { return $this->getViewState('AnimationDuration','1'); } /** * @param string CSS class for the currently displayed view div. */ public function setAnimationDuration($value) { $this->setViewState('AnimationDuration',$value); } /** * @return TStyle the style for all the view div */ public function getViewStyle() { if(($style=$this->getViewState('ViewStyle',null))===null) { $style=new TStyle; $style->setCssClass('accordion-view'); $this->setViewState('ViewStyle',$style,null); } return $style; } /** * @return string CSS class for view headers. Defaults to 'accordion-header'. */ public function getHeaderCssClass() { return $this->getHeaderStyle()->getCssClass(); } /** * @param string CSS class for view headers. */ public function setHeaderCssClass($value) { $this->getHeaderStyle()->setCssClass($value); } /** * @return TStyle the style for all the inactive header div */ public function getHeaderStyle() { if(($style=$this->getViewState('HeaderStyle',null))===null) { $style=new TStyle; $style->setCssClass('accordion-header'); $this->setViewState('HeaderStyle',$style,null); } return $style; } /** * @return string Extra CSS class for the active header. Defaults to 'accordion-header-active'. */ public function getActiveHeaderCssClass() { return $this->getActiveHeaderStyle()->getCssClass(); } /** * @param string Extra CSS class for the active header. Will be added to the normal header specified by HeaderCssClass. */ public function setActiveHeaderCssClass($value) { $this->getActiveHeaderStyle()->setCssClass($value); } /** * @return TStyle the style for the active header div */ public function getActiveHeaderStyle() { if(($style=$this->getViewState('ActiveHeaderStyle',null))===null) { $style=new TStyle; $style->setCssClass('accordion-header-active'); $this->setViewState('ActiveHeaderStyle',$style,null); } return $style; } /** * @return integer Maximum height for the accordion views. If non specified, the accordion will auto-sized to the largest of all views, so it can encompass all of them without scrolling */ public function getViewHeight() { return TPropertyValue::ensureInteger($this->getViewState('ViewHeight')); } /** * @param integer Maximum height for the accordion views. If any of the accordion's views' content is larger, those views will be made scrollable when activated */ public function setViewHeight($value) { $this->setViewState('ViewHeight', TPropertyValue::ensureInteger($value)); } /** * Activates the specified view. * If there is any other view currently active, it will be deactivated. * @param TAccordionView the view to be activated. If null, all views will be deactivated. */ protected function activateView($view) { $this->setActiveViewIndex(-1); $this->setActiveViewID(''); foreach($this->getViews() as $index=>$v) { if($view===$v) { $this->setActiveViewIndex($index); $this->setActiveViewID($view->getID(false)); $view->setActive(true); } else $v->setActive(false); } } /** * Loads user input data. * This method is primarly used by framework developers. * @param string the key that can be used to retrieve data from the input data collection * @param array the input data collection * @return boolean whether the data of the control has been changed */ public function loadPostData($key,$values) { if(($index=$values[$this->getClientID().'_1'])!==null) { $index=(int)$index; $currentIndex=$this->getActiveViewIndex(); if($currentIndex!==$index) { $this->setActiveViewID(''); // clear up view ID $this->setActiveViewIndex($index); return $this->_dataChanged=true; } } return false; } /** * Raises postdata changed event. * This method is required by {@link IPostBackDataHandler} interface. * It is invoked by the framework when {@link getActiveViewIndex ActiveViewIndex} property * is changed on postback. * This method is primarly used by framework developers. */ public function raisePostDataChangedEvent() { // do nothing } /** * Returns a value indicating whether postback has caused the control data change. * This method is required by the IPostBackDataHandler interface. * @return boolean whether postback has caused the control data change. False if the page is not in postback mode. */ public function getDataChanged() { return $this->_dataChanged; } /** * Adds attributes to renderer. * @param THtmlWriter the renderer */ protected function addAttributesToRender($writer) { $writer->addAttribute('id',$this->getClientID()); $this->setCssClass($this->getCssClass()); parent::addAttributesToRender($writer); } /** * Registers CSS and JS. * This method is invoked right before the control rendering, if the control is visible. * @param mixed event parameter */ public function onPreRender($param) { parent::onPreRender($param); $this->getActiveView(); // determine the active view $this->registerStyleSheet(); } /** * Registers the CSS relevant to the TAccordion. * It will register the CSS file specified by {@link getCssUrl CssUrl}. * If that is not set, it will use the default CSS. */ protected function registerStyleSheet() { $url = $this->getCssUrl(); if($url === '') { return; } if($url === 'default') { $url = $this->getApplication()->getAssetManager()->publishFilePath(dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'accordion.css'); } if($url !== '') { $this->getPage()->getClientScript()->registerStyleSheetFile($url, $url); } } /** * Registers the relevant JavaScript. */ protected function registerClientScript() { $id=$this->getClientID(); $options=TJavaScript::encode($this->getClientOptions()); $className=$this->getClientClassName(); $page=$this->getPage(); $cs=$page->getClientScript(); $cs->registerPradoScript('accordion'); $code="new $className($options);"; $cs->registerEndScript("prado:$id", $code); // ensure an item is always active and visible $index = $this->getActiveViewIndex(); if(!$this->getViews()->itemAt($index)->Visible) $index=0; $cs->registerHiddenField($id.'_1', $index); $page->registerRequiresPostData($this); $page->registerRequiresPostData($id."_1"); } /** * Gets the name of the javascript class responsible for performing postback for this control. * This method overrides the parent implementation. * @return string the javascript class name */ protected function getClientClassName() { return 'Prado.WebUI.TAccordion'; } /** * @return array the options for JavaScript */ protected function getClientOptions() { $options['ID'] = $this->getClientID(); $options['ActiveHeaderCssClass'] = $this->getActiveHeaderCssClass(); $options['HeaderCssClass'] = $this->getHeaderCssClass(); $options['Duration'] = $this->getAnimationDuration(); if (($viewheight = $this->getViewHeight())>0) $options['maxHeight'] = $viewheight; $views = array(); foreach($this->getViews() as $view) $views[$view->getClientID()] = $view->getVisible() ? '1': '0'; $options['Views'] = $views; return $options; } /** * Creates a control collection object that is to be used to hold child controls * @return TAccordionViewCollection control collection */ protected function createControlCollection() { return new TAccordionViewCollection($this); } /** * @return TAccordionViewCollection list of {@link TAccordionView} controls */ public function getViews() { return $this->getControls(); } public function render($writer) { $this->registerClientScript(); parent::render($writer); } /** * Renders body contents of the accordion control. * @param THtmlWriter the writer used for the rendering purpose. */ public function renderContents($writer) { $views=$this->getViews(); if($views->getCount()>0) { $writer->writeLine(); foreach($views as $view) { $view->renderHeader($writer); $view->renderControl($writer); $writer->writeLine(); } } } }