3 * TAccordion class file.
5 * @author Gabor Berczi, DevWorx Hungary <gabor.berczi@devworx.hu>
6 * @link https://github.com/pradosoft/prado
7 * @copyright Copyright © 2005-2016 The PRADO Group
8 * @license https://github.com/pradosoft/prado/blob/master/COPYRIGHT
9 * @package System.Web.UI.WebControls
16 * TAccordion displays an accordion control. Users can click on the view headers to switch among
17 * different accordion views. Each accordion view is an independent panel that can contain arbitrary content.
19 * A TAccordion control consists of one or several {@link TAccordionView} controls representing the possible
20 * accordion views. At any time, only one accordion view is visible (active), which is specified by any of
21 * the following properties:
22 * - {@link setActiveViewIndex ActiveViewIndex} - the zero-based integer index of the view in the view collection.
23 * - {@link setActiveViewID ActiveViewID} - the text ID of the visible view.
24 * - {@link setActiveView ActiveView} - the visible view instance.
25 * If both {@link setActiveViewIndex ActiveViewIndex} and {@link setActiveViewID ActiveViewID}
26 * are set, the latter takes precedence.
28 * TAccordion uses CSS to specify the appearance of the accordion headers and panel. By default,
29 * an embedded CSS file will be published which contains the default CSS for TTabPanel.
30 * You may also use your own CSS file by specifying the {@link setCssUrl CssUrl} property.
31 * The following properties specify the CSS classes used for elements in a TAccordion:
32 * - {@link setCssClass CssClass} - the CSS class name for the outer-most div element (defaults to 'accordion');
33 * - {@link setHeaderCssClass HeaderCssClass} - the CSS class name for nonactive accordion div elements (defaults to 'accordion-header');
34 * - {@link setActiveHeaderCssClass ActiveHeaderCssClass} - the CSS class name for the active accordion div element (defaults to 'accordion-header-active');
35 * - {@link setViewCssClass ViewCssClass} - the CSS class for the div element enclosing view content (defaults to 'accordion-view');
37 * When the user clicks on a view header, the switch between the old visible view and the clicked one is animated.
38 * You can use the {@link setAnimationDuration AnimationDuration} property to set the animation length in seconds;
39 * it defaults to 1 second, and when set to 0 it will produce an immediate switch with no animation.
41 * The TAccordion auto-sizes itself to the largest of all views, so it can encompass all of them without scrolling.
42 * If you want to specify a fixed height (in pixels), use the {@link setViewHeight ViewHeight} property.
43 * When a TAccordion is nested inside another, it's adviced to manually specify a {@link setViewHeight ViewHeight} for the internal TAccordion
45 * To use TAccordion, write a template like following:
48 * <com:TAccordionView Caption="View 1">
50 * </com:TAccordionView>
51 * <com:TAccordionView Caption="View 2">
53 * </com:TAccordionView>
54 * <com:TAccordionView Caption="View 3">
56 * </com:TAccordionView>
60 * @author Gabor Berczi, DevWorx Hungary <gabor.berczi@devworx.hu>
61 * @package System.Web.UI.WebControls
65 class TAccordion extends TWebControl implements IPostBackDataHandler
67 private $_dataChanged=false;
70 * @return string tag name for the control
72 protected function getTagName()
78 * Adds object parsed from template to the control.
79 * This method adds only {@link TAccordionView} objects into the {@link getViews Views} collection.
80 * All other objects are ignored.
81 * @param mixed object parsed from template
83 public function addParsedObject($object)
85 if($object instanceof TAccordionView)
86 $this->getControls()->add($object);
90 * Returns the index of the active accordion view.
91 * Note, this property may not return the correct index.
92 * To ensure the correctness, call {@link getActiveView()} first.
93 * @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).
95 public function getActiveViewIndex()
97 return $this->getViewState('ActiveViewIndex',0);
101 * @param integer the zero-based index of the current view in the view collection. -1 if no active view.
102 * @throws TInvalidDataValueException if the view index is invalid
104 public function setActiveViewIndex($value)
106 $this->setViewState('ActiveViewIndex',TPropertyValue::ensureInteger($value),0);
107 $this->setActiveViewID('');
111 * Returns the ID of the active accordion view.
112 * Note, this property may not return the correct ID.
113 * To ensure the correctness, call {@link getActiveView()} first.
114 * @return string The ID of the active accordion view. Defaults to '', meaning not set.
116 public function getActiveViewID()
118 return $this->getViewState('ActiveViewID','');
122 * @param string The ID of the active accordion view.
124 public function setActiveViewID($value)
126 $this->setViewState('ActiveViewID',$value,'');
130 * Returns the currently active view.
131 * This method will examin the ActiveViewID, ActiveViewIndex and Views collection to
132 * determine which view is currently active. It will update ActiveViewID and ActiveViewIndex accordingly.
133 * @return TAccordionView the currently active view, null if no active view
134 * @throws TInvalidDataValueException if the active view ID or index set previously is invalid
136 public function getActiveView()
139 $views=$this->getViews();
140 if(($id=$this->getActiveViewID())!=='')
142 if(($index=$views->findIndexByID($id))>=0)
143 $activeView=$views->itemAt($index);
145 throw new TInvalidDataValueException('accordion_activeviewid_invalid',$id);
147 else if(($index=$this->getActiveViewIndex())>=0)
149 if($index<$views->getCount())
150 $activeView=$views->itemAt($index);
152 throw new TInvalidDataValueException('accordion_activeviewindex_invalid',$index);
156 foreach($views as $index=>$view)
158 if($view->getActive())
165 if($activeView!==null)
166 $this->activateView($activeView);
171 * @param TAccordionView the view to be activated
172 * @throws TInvalidOperationException if the view is not in the view collection
174 public function setActiveView($view)
176 if($this->getViews()->indexOf($view)>=0)
177 $this->activateView($view);
179 throw new TInvalidOperationException('accordion_view_inexistent');
183 * @return string URL for the CSS file including all relevant CSS class definitions. Defaults to ''.
185 public function getCssUrl()
187 return $this->getViewState('CssUrl','default');
191 * @param string URL for the CSS file including all relevant CSS class definitions.
193 public function setCssUrl($value)
195 $this->setViewState('CssUrl',TPropertyValue::ensureString($value),'');
199 * @return string CSS class for the whole accordion control div.
201 public function getCssClass()
203 $cssClass=parent::getCssClass();
204 return $cssClass===''?'accordion':$cssClass;
208 * @return string CSS class for the currently displayed view div. Defaults to 'accordion-view'.
210 public function getViewCssClass()
212 return $this->getViewStyle()->getCssClass();
216 * @param string CSS class for the currently displayed view div.
218 public function setViewCssClass($value)
220 $this->getViewStyle()->setCssClass($value);
224 * @return string CSS class for the currently displayed view div. Defaults to 'accordion-view'.
226 public function getAnimationDuration()
228 return $this->getViewState('AnimationDuration','1');
232 * @param string CSS class for the currently displayed view div.
234 public function setAnimationDuration($value)
236 $this->setViewState('AnimationDuration',$value);
240 * @return TStyle the style for all the view div
242 public function getViewStyle()
244 if(($style=$this->getViewState('ViewStyle',null))===null)
247 $style->setCssClass('accordion-view');
248 $this->setViewState('ViewStyle',$style,null);
254 * @return string CSS class for view headers. Defaults to 'accordion-header'.
256 public function getHeaderCssClass()
258 return $this->getHeaderStyle()->getCssClass();
262 * @param string CSS class for view headers.
264 public function setHeaderCssClass($value)
266 $this->getHeaderStyle()->setCssClass($value);
270 * @return TStyle the style for all the inactive header div
272 public function getHeaderStyle()
274 if(($style=$this->getViewState('HeaderStyle',null))===null)
277 $style->setCssClass('accordion-header');
278 $this->setViewState('HeaderStyle',$style,null);
284 * @return string Extra CSS class for the active header. Defaults to 'accordion-header-active'.
286 public function getActiveHeaderCssClass()
288 return $this->getActiveHeaderStyle()->getCssClass();
292 * @param string Extra CSS class for the active header. Will be added to the normal header specified by HeaderCssClass.
294 public function setActiveHeaderCssClass($value)
296 $this->getActiveHeaderStyle()->setCssClass($value);
300 * @return TStyle the style for the active header div
302 public function getActiveHeaderStyle()
304 if(($style=$this->getViewState('ActiveHeaderStyle',null))===null)
307 $style->setCssClass('accordion-header-active');
308 $this->setViewState('ActiveHeaderStyle',$style,null);
314 * @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
316 public function getViewHeight()
318 return TPropertyValue::ensureInteger($this->getViewState('ViewHeight'));
322 * @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
324 public function setViewHeight($value)
326 $this->setViewState('ViewHeight', TPropertyValue::ensureInteger($value));
330 * Activates the specified view.
331 * If there is any other view currently active, it will be deactivated.
332 * @param TAccordionView the view to be activated. If null, all views will be deactivated.
334 protected function activateView($view)
336 $this->setActiveViewIndex(-1);
337 $this->setActiveViewID('');
338 foreach($this->getViews() as $index=>$v)
342 $this->setActiveViewIndex($index);
343 $this->setActiveViewID($view->getID(false));
344 $view->setActive(true);
347 $v->setActive(false);
352 * Loads user input data.
353 * This method is primarly used by framework developers.
354 * @param string the key that can be used to retrieve data from the input data collection
355 * @param array the input data collection
356 * @return boolean whether the data of the control has been changed
358 public function loadPostData($key,$values)
360 if(($index=$values[$this->getClientID().'_1'])!==null)
363 $currentIndex=$this->getActiveViewIndex();
364 if($currentIndex!==$index)
366 $this->setActiveViewID(''); // clear up view ID
367 $this->setActiveViewIndex($index);
368 return $this->_dataChanged=true;
375 * Raises postdata changed event.
376 * This method is required by {@link IPostBackDataHandler} interface.
377 * It is invoked by the framework when {@link getActiveViewIndex ActiveViewIndex} property
378 * is changed on postback.
379 * This method is primarly used by framework developers.
381 public function raisePostDataChangedEvent()
387 * Returns a value indicating whether postback has caused the control data change.
388 * This method is required by the IPostBackDataHandler interface.
389 * @return boolean whether postback has caused the control data change. False if the page is not in postback mode.
391 public function getDataChanged()
393 return $this->_dataChanged;
397 * Adds attributes to renderer.
398 * @param THtmlWriter the renderer
400 protected function addAttributesToRender($writer)
402 $writer->addAttribute('id',$this->getClientID());
403 $this->setCssClass($this->getCssClass());
404 parent::addAttributesToRender($writer);
408 * Registers CSS and JS.
409 * This method is invoked right before the control rendering, if the control is visible.
410 * @param mixed event parameter
412 public function onPreRender($param)
414 parent::onPreRender($param);
415 $this->getActiveView(); // determine the active view
416 $this->registerStyleSheet();
420 * Registers the CSS relevant to the TAccordion.
421 * It will register the CSS file specified by {@link getCssUrl CssUrl}.
422 * If that is not set, it will use the default CSS.
424 protected function registerStyleSheet()
426 $url = $this->getCssUrl();
432 if($url === 'default') {
433 $url = $this->getApplication()->getAssetManager()->publishFilePath(dirname(__FILE__).DIRECTORY_SEPARATOR.'assets'.DIRECTORY_SEPARATOR.'accordion.css');
437 $this->getPage()->getClientScript()->registerStyleSheetFile($url, $url);
442 * Registers the relevant JavaScript.
444 protected function registerClientScript()
446 $id=$this->getClientID();
447 $options=TJavaScript::encode($this->getClientOptions());
448 $className=$this->getClientClassName();
449 $page=$this->getPage();
450 $cs=$page->getClientScript();
451 $cs->registerPradoScript('accordion');
452 $code="new $className($options);";
453 $cs->registerEndScript("prado:$id", $code);
454 // ensure an item is always active and visible
455 $index = $this->getActiveViewIndex();
456 if(!$this->getViews()->itemAt($index)->Visible)
458 $cs->registerHiddenField($id.'_1', $index);
459 $page->registerRequiresPostData($this);
460 $page->registerRequiresPostData($id."_1");
464 * Gets the name of the javascript class responsible for performing postback for this control.
465 * This method overrides the parent implementation.
466 * @return string the javascript class name
468 protected function getClientClassName()
470 return 'Prado.WebUI.TAccordion';
474 * @return array the options for JavaScript
476 protected function getClientOptions()
478 $options['ID'] = $this->getClientID();
479 $options['ActiveHeaderCssClass'] = $this->getActiveHeaderCssClass();
480 $options['HeaderCssClass'] = $this->getHeaderCssClass();
481 $options['Duration'] = $this->getAnimationDuration();
483 if (($viewheight = $this->getViewHeight())>0)
484 $options['maxHeight'] = $viewheight;
486 foreach($this->getViews() as $view)
487 $views[$view->getClientID()] = $view->getVisible() ? '1': '0';
488 $options['Views'] = $views;
494 * Creates a control collection object that is to be used to hold child controls
495 * @return TAccordionViewCollection control collection
497 protected function createControlCollection()
499 return new TAccordionViewCollection($this);
503 * @return TAccordionViewCollection list of {@link TAccordionView} controls
505 public function getViews()
507 return $this->getControls();
510 public function render($writer)
512 $this->registerClientScript();
513 parent::render($writer);
517 * Renders body contents of the accordion control.
518 * @param THtmlWriter the writer used for the rendering purpose.
520 public function renderContents($writer)
522 $views=$this->getViews();
523 if($views->getCount()>0)
525 $writer->writeLine();
526 foreach($views as $view)
528 $view->renderHeader($writer);
529 $view->renderControl($writer);
530 $writer->writeLine();
538 * Class TAccordionView.
540 * TAccordionView represents a single view in a {@link TAccordion}.
542 * TAccordionView is represented inside the {@link TAccordion} with an header label whose text is defined by
543 * the {@link setCaption Caption} property; optionally the label can be an hyperlink: use the
544 * {@link setNavigateUrl NavigateUrl} property to define the destination url.
546 * @author Gabor Berczi, DevWorx Hungary <gabor.berczi@devworx.hu>
547 * @package System.Web.UI.WebControls
550 class TAccordionView extends TWebControl
552 private $_active=false;
555 * @return the tag name for the view element
557 protected function getTagName()
563 * Adds attributes to renderer.
564 * @param THtmlWriter the renderer
566 protected function addAttributesToRender($writer)
568 if(!$this->getActive() && $this->getPage()->getClientSupportsJavaScript())
569 $this->getStyle()->setStyleField('display','none');
571 $this->getStyle()->mergeWith($this->getParent()->getViewStyle());
573 parent::addAttributesToRender($writer);
575 $writer->addAttribute('id',$this->getClientID());
579 * @return string the caption displayed on this header. Defaults to ''.
581 public function getCaption()
583 return $this->getViewState('Caption','');
587 * @param string the caption displayed on this header
589 public function setCaption($value)
591 $this->setViewState('Caption',TPropertyValue::ensureString($value),'');
595 * @return string the URL of the target page. Defaults to ''.
597 public function getNavigateUrl()
599 return $this->getViewState('NavigateUrl','');
603 * Sets the URL of the target page.
604 * If not empty, clicking on this header will redirect the browser to the specified URL.
605 * @param string the URL of the target page.
607 public function setNavigateUrl($value)
609 $this->setViewState('NavigateUrl',TPropertyValue::ensureString($value),'');
613 * @return string the text content displayed on this view. Defaults to ''.
615 public function getText()
617 return $this->getViewState('Text','');
621 * Sets the text content to be displayed on this view.
622 * If this is not empty, the child content of the view will be ignored.
623 * @param string the text content displayed on this view
625 public function setText($value)
627 $this->setViewState('Text',TPropertyValue::ensureString($value),'');
631 * @return boolean whether this accordion view is active. Defaults to false.
633 public function getActive()
635 return $this->_active;
639 * @param boolean whether this accordion view is active.
641 public function setActive($value)
643 $this->_active=TPropertyValue::ensureBoolean($value);
647 * Renders body contents of the accordion view.
648 * @param THtmlWriter the writer used for the rendering purpose.
650 public function renderContents($writer)
652 if(($text=$this->getText())!=='')
653 $writer->write($text);
654 else if($this->getHasControls())
655 parent::renderContents($writer);
659 * Renders the header associated with the accordion view.
660 * @param THtmlWriter the writer for rendering purpose.
662 public function renderHeader($writer)
664 if($this->getVisible(false) && $this->getPage()->getClientSupportsJavaScript())
666 $writer->addAttribute('id',$this->getClientID().'_0');
668 $style=$this->getActive()?$this->getParent()->getActiveHeaderStyle():$this->getParent()->getHeaderStyle();
670 $style->addAttributesToRender($writer);
672 $writer->renderBeginTag($this->getTagName());
674 $this->renderHeaderContent($writer);
676 $writer->renderEndTag();
681 * Renders the content in the header.
682 * By default, a hyperlink is displayed.
683 * @param THtmlWriter the HTML writer
685 protected function renderHeaderContent($writer)
687 $url = $this->getNavigateUrl();
688 if(($caption=$this->getCaption())==='')
692 $writer->write("<a href=\"{$url}\">");
693 $writer->write("{$caption}");
695 $writer->write("</a>");
700 * Class TAccordionViewCollection.
702 * TAccordionViewCollection is a collection of {@link TAccordionView} to be used inside a {@link TAccordion}.
704 * @author Gabor Berczi, DevWorx Hungary <gabor.berczi@devworx.hu>
705 * @package System.Web.UI.WebControls
708 class TAccordionViewCollection extends TControlCollection
711 * Inserts an item at the specified position.
712 * This overrides the parent implementation by performing sanity check on the type of new item.
713 * @param integer the speicified position.
714 * @param mixed new item
715 * @throws TInvalidDataTypeException if the item to be inserted is not a {@link TAccordionView} object.
717 public function insertAt($index,$item)
719 if($item instanceof TAccordionView)
720 parent::insertAt($index,$item);
722 throw new TInvalidDataTypeException('tabviewcollection_tabview_required');
726 * Finds the index of the accordion view whose ID is the same as the one being looked for.
727 * @param string the explicit ID of the accordion view to be looked for
728 * @return integer the index of the accordion view found, -1 if not found.
730 public function findIndexByID($id)
732 foreach($this as $index=>$view)
734 if($view->getID(false)===$id)