<?php
/**
* Hierarchical select element
*
* PHP version 5
*
* LICENSE:
*
* Copyright (c) 2006-2014, Alexey Borzov <avb@php.net>,
* Bertrand Mansion <golgote@mamasam.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * The names of the authors may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @category HTML
* @package HTML_QuickForm2
* @author Alexey Borzov <avb@php.net>
* @author Bertrand Mansion <golgote@mamasam.com>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/HTML_QuickForm2
*/
/**
* Base class for HTML_QuickForm2 groups
*/
require_once 'HTML/QuickForm2/Container/Group.php';
/**
* Classes for <select> elements
*/
require_once 'HTML/QuickForm2/Element/Select.php';
/**
* Class for adding inline javascript to the form
*/
require_once 'HTML/QuickForm2/Element/Script.php';
/**
* Hierarchical select element
*
* Class to dynamically create two or more HTML Select elements
* The first select changes the content of the second select and so on.
* This element is considered as a group. Selects will be named
* groupName[0], groupName[1], groupName[2]...
*
* @category HTML
* @package HTML_QuickForm2
* @author Herim Vasquez <vasquezh@iro.umontreal.ca>
* @author Bertrand Mansion <bmansion@mamasam.com>
* @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version Release: 2.0.2
* @link http://pear.php.net/package/HTML_QuickForm2
*/
class HTML_QuickForm2_Element_Hierselect extends HTML_QuickForm2_Container_Group
{
/**
* Options for all the select elements
*
* @see loadOptions()
* @var array
*/
protected $options = array();
/**
* PHP callback function for getting additional options
*
* @see loadOptions()
* @var callback
*/
protected $callback = null;
/**
* Javascript callback function for getting additional options
*
* @see loadOptions()
* @var string
*/
protected $jsCallback = null;
/**
* Number of select elements in hierselect
* @var int
*/
protected $size = 0;
/**
* Values for child selects, needed for form reset handling in JS
* @var array
* @see _loadChildOptions()
* @see _generateInlineScript()
*/
private $_values = array();
public function getType()
{
return 'hierselect';
}
/**
* Class constructor
*
* Hierselect element can understand the following keys in $data parameter:
* - 'options': data to populate child elements' options with. Passed to
* {@link loadOptions()} method.
* - 'size': number of selects in hierselect. If not given will be set
* from size of options array or size of array passed to setValue()
* $data is propagated to created Select elements with these keys removed.
*
* @param string $name Element name
* @param string|array $attributes Attributes (either a string or an array)
* @param array $data Additional element data
*/
public function __construct($name = null, $attributes = null, array $data = array())
{
if (!empty($data['size'])) {
$this->size = $data['size'];
}
$options = isset($data['options'])? $data['options']: array();
unset($data['options'], $data['size']);
parent::__construct($name, $attributes, $data);
$this->loadOptions($options);
}
/**
* Initializes the the options for each select element.
*
* Format is a bit more complex than for a simple select as we need to know
* which options are related to the ones in the previous select:
*
* Ex:
* <code>
* // first select
* $select1[0] = 'Pop';
* $select1[1] = 'Classical';
* $select1[2] = 'Funeral doom';
*
* // second select
* $select2[0][0] = 'Red Hot Chilly Peppers';
* $select2[0][1] = 'The Pixies';
* $select2[1][0] = 'Wagner';
* $select2[1][1] = 'Strauss';
* $select2[2][0] = 'Pantheist';
* $select2[2][1] = 'Skepticism';
*
* // Two selects
* $sel = $form->addElement('hierselect', 'cds')->setLabel('Choose CD:');
* $sel->loadOptions(array($select1, $select2));
*
* // If you have a third select with prices for the cds
* $select3[0][0][0] = '15.00$';
* $select3[0][0][1] = '17.00$';
* // etc
*
* // You can now use
* $sel = $form->addElement('hierselect', 'cds')->setLabel('Choose CD:');
* $sel->loadOptions(array($select1, $select2, $select3));
* </code>
*
* @param array $options Array of options defining each element
* @param callback $callback Callback function to load additional options.
* It will receive an array of keys and should return associative
* array ('option value' => 'option text')
* @param string $jsCallback Javascript function to load additional options
* (presumably via some sort of AJAX request). It will receive an
* array of keys and should return {'values': [...], 'texts': [...]}
*
* @return $this
* @throws HTML_QuickForm2_InvalidArgumentException
*/
public function loadOptions(array $options, $callback = null, $jsCallback = null)
{
if (null !== $callback && !is_callable($callback, false, $callbackName)) {
throw new HTML_QuickForm2_InvalidArgumentException(
'Hierselect expects a valid callback for loading options, \'' .
$callbackName . '\' was given'
);
}
$this->options = $options;
$this->callback = $callback;
$this->jsCallback = $jsCallback;
$this->size = max($this->size, count($options));
$this->_createSelects();
$this->_loadChildOptions();
return $this;
}
/**
* Populates hierselect with Select elements
*/
private function _createSelects()
{
for ($i = count($this); $i < $this->size; $i++) {
$data = $this->getData();
unset($data['label']);
$this->appendChild(new HTML_QuickForm2_Element_Select(
$i, array('id' => self::generateId($this->getName() . "[{$i}]")) + $this->getAttributes(), $data
));
}
}
/**
* Loads options for child Select elements
*/
private function _loadChildOptions()
{
$idx = 0;
$this->_values = array();
/* @var $select HTML_QuickForm2_Element_Select */
foreach ($this as $select) {
if (empty($this->options[$idx])) {
$this->options[$idx] = array();
}
$keys = $this->_values;
$array =& $this->options[$idx++];
while (!empty($keys)) {
$key = array_shift($keys);
if (!isset($array[$key])) {
if (!empty($keys)) {
$array[$key] = array();
} elseif (!empty($this->callback)) {
$array[$key] = call_user_func($this->callback, $this->_values);
} else {
// Most probably called from constructor with neither
// options nor callback provided
return;
}
}
$array =& $array[$key];
}
$select->loadOptions($array);
$this->_values[] = null !== ($v = $select->getValue())? $v: key($array);
}
}
/**
* Sets the element's value
*
* This also creates missing selects and loads their options, in addition
* to {@link HTML_QuickForm2_Container_Group::setValue()} behaviour
*
* @param array $value
*
* @return $this
*/
public function setValue($value)
{
$this->size = max($this->size, count($value));
$this->_createSelects();
parent::setValue($value);
$this->_loadChildOptions();
return $this;
}
/**
* Sets the element's name
*
* Need to override group's implementation due to overridden updateValue()
*
* @param string $name
*
* @return $this
*/
public function setName($name)
{
parent::setName($name);
$this->updateValue();
return $this;
}
/**
* Called when the element needs to update its value from form's data sources
*
* Hierselect uses the Element's implementation of updateValue() since its
* values need to be passed through setValue() to properly update options of
* its child selects.
*/
protected function updateValue()
{
$name = $this->getName();
/* @var $ds HTML_QuickForm2_DataSource_NullAware */
foreach ($this->getDataSources() as $ds) {
if (null !== ($value = $ds->getValue($name))
|| $ds instanceof HTML_QuickForm2_DataSource_NullAware && $ds->hasValue($name)
) {
$this->setValue($value);
return;
}
}
}
/**
* Prepares options for JS encoding
*
* We need to preserve order of options when adding them via javascript, so
* cannot use object literal and for/in loop (see bug #16603). Therefore we
* convert an associative array of options to two arrays of their values
* and texts.
*
* @param array $ary Options array
* @param int $depth Depth within options array
*
* @link http://pear.php.net/bugs/bug.php?id=16603
* @return array Array with separate options and texts
*/
private function _prepareOptions($ary, $depth)
{
if (!is_array($ary)) {
$ret = $ary;
} elseif (0 == $depth) {
$ret = array('values' => array_keys($ary), 'texts' => array_values($ary));
} else {
$ret = array();
foreach ($ary as $k => $v) {
$ret[$k] = $this->_prepareOptions($v, $depth - 1);
}
}
return $ret;
}
/**
* Generates inline javascript containing element's defaults and (available) options
*
* @return string
*/
private function _generateInlineScript()
{
// we store values and options with id of first select rather than with
// the element's name since the former has more chances to be unique
$selectId = reset($this->elements)->getId();
$cr = HTML_Common2::getOption('linebreak');
$js = "qf.elements.hierselect.defaults['{$selectId}'] = " .
HTML_QuickForm2_JavascriptBuilder::encode($this->_values) . ";{$cr}";
$jsParts = array();
for ($i = 1; $i < count($this->options); $i++) {
$jsParts[] = empty($this->options[$i])
? '{}' : HTML_QuickForm2_JavascriptBuilder::encode($this->_prepareOptions(
$this->options[$i], $i
));
}
$js .= "qf.elements.hierselect.options['{$selectId}'] = [{$cr}" . implode(",{$cr}", $jsParts) . "{$cr}];";
return $js;
}
/**
* Generates a javascript function call to initialize hierselect behaviour
*
* @return string
*/
private function _generateInitScript()
{
HTML_QuickForm2_Loader::loadClass('HTML_QuickForm2_JavascriptBuilder');
$ids = array();
/* @var $element HTML_QuickForm2_Element */
foreach ($this as $element) {
$ids[] = $element->getId();
}
return 'qf.elements.hierselect.init(' . HTML_QuickForm2_JavascriptBuilder::encode($ids)
. (empty($this->jsCallback)? '': ", {$this->jsCallback}") . ');';
}
/**
* Renders the hierselect using the given renderer
*
* @param HTML_QuickForm2_Renderer $renderer
*
* @return HTML_QuickForm2_Renderer
* @throws HTML_QuickForm2_Exception if number of selects in hierselect cannot
* be determined
*/
public function render(HTML_QuickForm2_Renderer $renderer)
{
if (0 == $this->size) {
throw new HTML_QuickForm2_Exception(
'Unable to determine number of selects in hierselect'
);
}
if ($this->toggleFrozen()) {
// frozen hierselect does not need any javascript
return parent::render($renderer);
}
$jsBuilder = $renderer->getJavascriptBuilder();
$jsBuilder->addLibrary('hierselect', 'quickform-hierselect.js');
$jsBuilder->addElementJavascript($this->_generateInitScript());
$script = $this->appendChild(new HTML_QuickForm2_Element_Script('script'))
->setContent($this->_generateInlineScript());
parent::render($renderer);
$this->removeChild($script);
return $renderer;
}
}
?>
Copyright 2K16 - 2K18 Indonesian Hacker Rulez