Source for file advmultiselect.php
Documentation is available at advmultiselect.php
* Element for HTML_QuickForm that emulate a multi-select.
* The HTML_QuickForm_advmultiselect package adds an element to the
* HTML_QuickForm package that is two select boxes next to each other
* emulating a multi-select.
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
* @package HTML_QuickForm_advmultiselect
* @author Laurent Laville <pear@laurent-laville.org>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version CVS: $Id: advmultiselect.php 7739 2006-02-12 17:21:34Z turboke $
* @link http://pear.php.net/package/HTML_QuickForm_advmultiselect
require_once 'HTML/QuickForm/select.php';
* Replace PHP_EOL constant
* @link http://php.net/reserved.constants.core
* @author Aidan Lister <aidan@php.net>
* Element for HTML_QuickForm that emulate a multi-select.
* The HTML_QuickForm_advmultiselect package adds an element to the
* HTML_QuickForm package that is two select boxes next to each other
* emulating a multi-select.
* LICENSE: This source file is subject to version 3.0 of the PHP license
* that is available through the world-wide-web at the following URI:
* http://www.php.net/license/3_0.txt. If you did not receive a copy of
* the PHP License and are unable to obtain it through the web, please
* send a note to license@php.net so we can mail you a copy immediately.
* @package HTML_QuickForm_advmultiselect
* @author Laurent Laville <pear@laurent-laville.org>
* @copyright 1997-2005 The PHP Group
* @license http://www.php.net/license/3_0.txt PHP License 3.0
* @version Release: 0.5.1
* @link http://pear.php.net/package/HTML_QuickForm_advmultiselect
* Prefix function name in javascript move selections
* Postfix function name in javascript move selections
* Associative array of the multi select container attributes
* Associative array of the add button attributes
* Associative array of the remove button attributes
* Associative array of the move up button attributes
* Associative array of the move up button attributes
* Defines if both list (unselected, selected) will have their elements be
* arranged from lowest to highest (or reverse) depending on comparaison function.
* SORT_ASC is used to sort in ascending order
* SORT_DESC is used to sort in descending order
* Associative array of the unselected item box attributes
* Associative array of the selected item box attributes
* Associative array of the internal hidden box attributes
* Default Element template string
<!-- BEGIN label_2 --><tr><th>{label_2}</th><!-- END label_2 -->
<!-- BEGIN label_3 --><th> </th><th>{label_3}</th></tr><!-- END label_3 -->
<td valign="top">{unselected}</td>
<td align="center">{add}{remove}</td>
<td valign="top">{selected}</td>
* Default Element stylesheet string
border-left: 1px solid #404040;
border-top: 1px solid #404040;
border-bottom: 1px solid #d4d0c8;
border-right: 1px solid #d4d0c8;
* @param string $elementName Dual Select name attribute
* @param mixed $elementLabel Label(s) for the select boxes
* @param mixed $options Data to be used to populate options
* @param mixed $attributes Either a typical HTML attribute string or an associative array
* @param integer $sortOptions Either SORT_ASC for auto ascending arrange,
* SORT_DESC for auto descending arrange, or
* NULL for no sort (append at end: default)
$options = null, $attributes = null,
// add multiple selection attribute by default if missing
// default size is ten item on each select box (left and right)
// default width of each select box
$attr = array('border' => '0', 'cellpadding' => '10', 'cellspacing' => '0');
// set default add button attributes
// set default remove button attributes
// set default move up button attributes
// set default move up button attributes
// defines javascript functions names
// set select boxes sort order (none by default)
if (isset ($sortOptions)) {
$this->_sort = $sortOptions;
* Sets the button attributes
* In <b>custom example 1</b>, the <i>add</i> and <i>remove</i> buttons have look set
* by the css class <i>inputCommand</i>. See especially lines 43-48 and 98-103.
* In <b>custom example 2</b>, the basic text <i>add</i> and <i>remove</i> buttons
* are now replaced by images. See lines 43-44.
* In <b>custom example 5</b>, we have ability to sort the selection list (on right side)
* - <b>user-end</b>: with <i>Up</i> and <i>Down</i> buttons
* (see lines 65,65,76 and 128-130)
* - <b>programming</b>: with the QF element constructor $sort option
* (see lines 34,36,38 and 59)
* @example examples/qfams_custom_5.php Custom example 5: source code
* @link http://www.laurent-laville.org/img/qfams/screenshot/custom5.png Custom example 5: screenshot
* @example examples/qfams_custom_2.php Custom example 2: source code
* @link http://www.laurent-laville.org/img/qfams/screenshot/custom2.png Custom example 2: screenshot
* @example examples/qfams_custom_1.php Custom example 1: source code
* @link http://www.laurent-laville.org/img/qfams/screenshot/custom1.png Custom example 1: screenshot
* @param string $button Button identifier, either 'add', 'remove', 'moveup' or 'movedown'
* @param mixed $attributes (optional) Either a typical HTML attribute string
* or an associative array
return PEAR::raiseError('Argument 1 of advmultiselect::setButtonAttributes'
return PEAR::raiseError('Argument 1 of advmultiselect::setButtonAttributes'
. ' has unexpected value');
* @param string $html The HTML surrounding select boxes and buttons
* Sets JavaScript function name parts. Maybe usefull to avoid conflict names
* In <b>multiple example 1</b>, the javascript function prefix is set to not null
* @example examples/qfams_multiple_1.php Multiple example 1: source code
* @link http://www.laurent-laville.org/img/qfams/screenshot/multiple1.png Multiple example 1: screenshot
* @param string $pref (optional) Prefix name
* @param string $post (optional) Postfix name
function setJsElement($pref = null, $post = 'moveSelections')
* Gets default element stylesheet for a single multi-select shape render
* In <b>custom example 4</b>, the template defined lines 80-87 allows
* a single multi-select checkboxes shape. Useful when javascript is disabled
* (or when browser is not js compliant). In our example, no need to add javascript code
* (see lines 170-172), but css is mandatory (see line 142).
* @example qfams_custom_4.php Custom example 4: source code
* @link http://www.laurent-laville.org/img/qfams/screenshot/custom4.png Custom example 4: screenshot
* @param boolean $raw (optional) html output with style tags or just raw data
$css = '<style type="text/css">' . PHP_EOL
* Returns the HTML generated for the advanced mutliple select component
$selectName = $this->getName() . '[]';
// placeholder {unselected} existence determines if we will render
// ... a single multi-select with checkboxes
$strHtmlSelected = $tab . '<div id="'. $id. 'amsSelected">' . PHP_EOL;
$_labelAttributes = array('style', 'class', 'onmouseover', 'onmouseout');
$labelAttributes = array();
foreach ($_labelAttributes as $attr) {
if (isset ($option['attr'][$attr])) {
$labelAttributes[$attr] = $option['attr'][$attr];
unset ($option['attr'][$attr]);
// The items is *selected*
$checked = ' checked="checked"';
// The item is *unselected* so we want to put it
. '<input type="checkbox"'
. ' name="'. $selectName. '"'
. ' />' . $option['text'] . '</label>'
$strHtmlSelected .= $tab . '</div>'. PHP_EOL;
// ... or a dual multi-select
// set name of Select From Box
$this->_attributesUnselected = array('name' => '__'. $selectName, 'ondblclick' => "{ $this->_jsPrefix}{ $this->_jsPostfix}(this.form.elements['__ " . $selectName . "'], this.form.elements['_" . $selectName . "'], this.form.elements['" . $selectName . "'], 'add')");
// set name of Select To Box
$this->_attributesSelected = array('name' => '_'. $selectName, 'ondblclick' => "{ $this->_jsPrefix}{ $this->_jsPostfix}(this.form.elements['__ " . $selectName . "'], this.form.elements['_" . $selectName . "'], this.form.elements['" . $selectName . "'], 'remove')");
// set name of Select hidden Box
$this->_attributesHidden = array('name' => $selectName, 'style' => 'overflow: hidden; visibility: hidden; width: 1px; height: 0;');
// prepare option tables to be displayed as in POST order
$arrHtmlSelected = array_fill(0, $append, ' ');
$arrHtmlHidden = array_fill(0, count($this->_options), ' ');
in_array((string) $option['attr']['value'], $this->_values)) {
$key = array_search($option['attr']['value'], $this->_values);
// The items is *selected* so we want to put it in the 'selected' multi-select
$arrHtmlSelected[$key] = $option;
// Add it to the 'hidden' multi-select and set it as 'selected'
$option['attr']['selected'] = 'selected';
$arrHtmlHidden[$key] = $option;
// The item is *unselected* so we want to put it in the 'unselected' multi-select
$arrHtmlUnselected[] = $option;
// Add it to the hidden multi-select as 'unselected'
$arrHtmlHidden[$append] = $option;
// The 'unselected' multi-select which appears on the left
$strHtmlUnselected = "<select$attrUnselected>". PHP_EOL;
foreach ($arrHtmlUnselected as $data) {
$strHtmlUnselected .= $tabs . $tab
. $data['text'] . '</option>' . PHP_EOL;
$strHtmlUnselected .= '</select>';
// The 'selected' multi-select which appears on the right
$strHtmlSelected = "<select$attrSelected>". PHP_EOL;
if (isset($arrHtmlSelected)) {
foreach ($arrHtmlSelected as $data) {
$strHtmlSelected .= $tabs . $tab
. $data['text'] . '</option>' . PHP_EOL;
$strHtmlSelected .= '</select>';
// The 'hidden' multi-select
$strHtmlHidden = "<select$attrHidden>". PHP_EOL;
foreach ($arrHtmlHidden as $data) {
$strHtmlHidden .= $tabs . $tab
. $data['text'] . '</option>' . PHP_EOL;
$strHtmlHidden .= '</select>';
// build the remove button with all its attributes
$attributes = array('onclick' => "{ $this->_jsPrefix}{ $this->_jsPostfix}(this.form.elements['__ " . $selectName . "'], this.form.elements['_" . $selectName . "'], this.form.elements['" . $selectName . "'], 'remove'); return false;");
$strHtmlRemove = "<input$attrStrRemove />". PHP_EOL;
// build the add button with all its attributes
$attributes = array('onclick' => "{ $this->_jsPrefix}{ $this->_jsPostfix}(this.form.elements['__ " . $selectName . "'], this.form.elements['_" . $selectName . "'], this.form.elements['" . $selectName . "'], 'add'); return false;");
$strHtmlAdd = "<input$attrStrAdd />". PHP_EOL;
// build the move up button with all its attributes
$attributes = array('onclick' => "{ $this->_jsPrefix}moveUp(this.form.elements['_ " . $selectName . "'], this.form.elements['" . $selectName . "']); return false;");
$strHtmlMoveUp = "<input$attrStrUp />". PHP_EOL;
// build the move down button with all its attributes
$attributes = array('onclick' => "{ $this->_jsPrefix}moveDown(this.form.elements['_ " . $selectName . "'], this.form.elements['" . $selectName . "']); return false;");
$strHtmlMoveDown = "<input$attrStrDown />". PHP_EOL;
// render all part of the multi select component with the template
// Prepare multiple labels
// render extra labels, if any
foreach($labels as $key => $text) {
$key = is_int($key)? $key + 2: $key;
$strHtml = str_replace("{label_{ $key}}", $text, $strHtml);
$strHtml = str_replace("<!-- BEGIN label_{ $key} -->", '', $strHtml);
$strHtml = str_replace("<!-- END label_{ $key} -->", '', $strHtml);
// clean up useless label tags
if (strpos($strHtml, '{label_')) {
$strHtml = preg_replace('/\s*<!-- BEGIN label_(\S+) -->.*<!-- END label_\1 -->\s*/i', '', $strHtml);
'{stylesheet}', '{javascript}', '{class}',
'{unselected}', '{selected}',
$strHtmlUnselected, $strHtmlSelected . $strHtmlHidden,
$strHtmlAdd, $strHtmlRemove,
$strHtmlMoveUp, $strHtmlMoveDown
$strHtml = str_replace($placeHolders, $htmlElements, $strHtml);
* Returns the javascript code generated to handle this element
* @param boolean $raw (optional) html output with script tags or just raw data
function getElementJs($raw = true)
if (!defined('HTML_QUICKFORM_ADVMULTISELECT_'. $jsfuncName. '_EXISTS')) {
// We only want to include the javascript code once per form
define('HTML_QUICKFORM_ADVMULTISELECT_'.$jsfuncName.'_EXISTS', true);
/* begin javascript for HTML_QuickForm_advmultiselect */
function { $jsfuncName}(selectLeft, selectRight, selectHidden, action) {
// Don't do anything if nothing selected. Otherwise we throw javascript errors.
if (menuFrom.selectedIndex == -1) {
// Add items to the 'TO' list.
for (i=0; i < menuFrom.length; i++) {
if (menuFrom.options[i].selected == true ) {
menuTo.options[menuTo.length]= new Option(menuFrom.options[i].text, menuFrom.options[i].value);
// Remove items from the 'FROM' list.
for (i=(menuFrom.length - 1); i>=0; i--){
if (menuFrom.options[i].selected == true ) {
menuFrom.options[i] = null;
if ($this->_sort === false) {
// Set the appropriate items as 'selected in the hidden select.
// These are the values that will actually be posted with the form.
{ $this->_jsPrefix}updateHidden(selectHidden, selectRight);
$reverse = ($this->_sort === SORT_DESC) ? 'options.reverse();' : '';
// Set the appropriate items as 'selected in the hidden select.
// These are the values that will actually be posted with the form.
{ $this->_jsPrefix}updateHidden(selectHidden, selectRight);
function { $this->_jsPrefix}sortList(list, compareFunction) {
var options = new Array (list.options.length);
for (var i = 0; i < options.length; i++) {
options[i] = new Option (
list.options[i].defaultSelected,
options.sort(compareFunction);
for (var i = 0; i < options.length; i++) {
list.options[i] = options[i];
function { $this->_jsPrefix}compareText(option1, option2) {
if (option1.text == option2.text) {
return option1.text < option2.text ? -1 : 1;
function { $this->_jsPrefix}updateHidden(h,r) {
for (i=0; i < h.length; i++) {
h.options[i].selected = false;
for (i=0; i < r.length; i++) {
h.options[h.length] = new Option(r.options[i].text, r.options[i].value);
h.options[h.length-1].selected = true;
var indice = l.selectedIndex;
{ $this->_jsPrefix}moveSwap(l, indice, indice-1);
var indice = l.selectedIndex;
if (indice < l.options.length-1) {
{ $this->_jsPrefix}moveSwap(l, indice, indice+1);
var valeur = l.options[i].value;
var texte = l.options[i].text;
l.options[i].value = l.options[j].value;
l.options[i].text = l.options[j].text;
l.options[j].value = valeur;
l.options[j].text = texte;
/* end javascript for HTML_QuickForm_advmultiselect */
$js = '<script type="text/javascript">' . PHP_EOL
. '/* <![CDATA[ */' . $js . '/* ]]> */' . PHP_EOL
if (class_exists('HTML_QuickForm')) {
HTML_QuickForm::registerElementType('advmultiselect', 'HTML/QuickForm/advmultiselect.php', 'HTML_QuickForm_advmultiselect');
|