<?php
/* vim: set number autoindent tabstop=2 shiftwidth=2 softtabstop=2: */

/**
* This package to  add combobox type to HTML_QuickForm
*
* A combobox is an element composed by an input text and a dropdown list:
* you can either select an option from the list or  write into the text field.

* PHP versions 4 and 5
*
* 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.
*
* @category   HTML
* @package    HTML_QuickForm_ComboBox
* @author     Fabio Ambrosanio <fabio@ambrosanio.com>
* @license    http://www.php.net/license/3_01.txt  PHP
* @version    @package_version@
*/
require_once 'HTML/QuickForm/text.php';

/**
* This class represents a combobox element of HTML_QuickForm framework.
*
* @author     Fabio Ambrosanio <fabio@ambrosanio.com>
* @category   HTML
* @package    HTML_QuickForm_ComboBox
* @version    @package_version@
* @license    http://www.php.net/license/3_01.txt  PHP
*/
class HTML_QuickForm_ComboBox extends HTML_QuickForm_text
{
    
// {{{ properties

    /**
     * Elements of the dropdown list
     *
     * @var array
     * @access private
     */
    
var $_elements = null;
    
    
/**
     * Options for element's UI.
     * You can modify the appearence of combobox (see below for the list of elements and classes.
     *
     * If options contains a value for arrowImage key, an image is used instead of a button.
     *
     * @var array
     * @access private
     */
    
var $_options = array(
        
'boxClass' => 'comboBox',                    // style class of external box
        
'inputClass' => 'comboBoxInput',            // style class of input text field
        
'buttonClass' => 'comboBoxButton',            // style class of dropdown button
        
'arrowClass' => 'comboBoxArrow',            // style class of dropdown image
        
'containerClass' => 'comboBoxContainer',    // style class of dropdown container
        
'optionClass' => 'comboBoxOption',            // style class of dropdown options
        
'optionClassOver' => 'comboBoxOptionOver',    // style class of dropdown options when highlighted
        
'buttonValue' => '*'                        // value of dropdown button
    
);

    
// }}}
    
    // {{{ constructor

    /**
     * Class constructor
     *
     * @param     string    $elementName    (required)Input field name attribute.
     * @param     string    $elementLabel   (required)Input field label in form.
     * @param     array     $elements       (optional)Combobox elements.
     * @param     array     $options        (optional)An associative array which elements specify different
     *                                        aspects of the combobox.
     * @param     mixed     $attributes     (optional)Either a typical HTML attribute string
     *                                      or an associative array. Date format is passed along the attributes.
     * @access    public
     * @return    void
     */
    
function HTML_QuickForm_ComboBox($elementName = null, $elementLabel = null, $elements = null, $options = null, $attributes = null)
    {
        
$this->HTML_QuickForm_text($elementName, $elementLabel, $attributes);
        
$this->_persistantFreeze = true;
        if (isset(
$elements)) {
          
$this->_elements = $elements;
        }
        
$id = $this->getAttribute('id');
        if (
"$id" == '') {
            
$this->updateAttributes(array(
              
'id' => $elementName
            
));
        }
        
        
// update element options
        
if (isset($options)) {
            
$this->_options = array_merge($this->_options, $options);
          }
                
    }
//end constructor
    
    // }}}
        
    // {{{ toHtml()

    /**
     * Returns Html for the Combobox input element
     *
     * @access      public
     * @return      string    the HTML string representing the combobox
     */
    
function toHtml()
    {            
        
// generate an unique id for all UI elements
        
$uid = uniqid('cb');
        
        if (
$this->_flagFrozen) {
            
// input froze: return parent html
            
$html = parent::toHtml();
        } else {
        
            
// eventually write style classes and javascript functions
            
if (!defined('HTML_QuickForm_ComboBox_EXISTS')) {        
                
$html = $this->_getCSS();
                
$html .= $this->_getJS();
                
define('HTML_QuickForm_ComboBox_EXISTS', true);
            }    
        
            
// build input text field
            
$this->updateAttributes(array(
                
'class' => $this->_options['inputClass']
            ));
            
$input = parent::toHtml();

            
// wich type of button? input-button or image?
            
if (!$this->_options['arrowImage']) {
                
// build simple button
                
$buttonClass = $this->_options['buttonClass'];
                
$buttonValue = $this->_options['buttonValue'];
                
$button = <<<html
<input value="$buttonValue" type="button" id="$uid.button" class="$buttonClass" onClick="comboBoxShowOptions('$uid')" onblur="comboBoxHideOptions('$uid')"/>
html;

            } else {
                
// build button implemented with an image
                
$arrowImage = $this->_options['arrowImage'];
                
                
// have we an image for mouse-over?
                
$arrowImageOver = $this->_options['arrowImageOver'];                
                if (!
$arrowImageOver) $arrowImageOver = $arrowImage;
                
                
// have we an image for mouse-down?
                
$arrowImageDown = $this->_options['arrowImageDown'];                
                if (!
$arrowImageDown) $arrowImageDown = $arrowImage;
                
                
$arrowClass = $this->_options['arrowClass'];
                
$button = <<<html
<input type="image" src="$arrowImage" id="$uid.arrow" class="$arrowClass" align="top" onClick="comboBoxShowOptions('$uid'); return false;" onMouseOver="comboBoxSwitchImage('$uid')" onMouseOut="comboBoxSwitchImage('$uid')" arrowimage="$arrowImage" arrowimageover="$arrowImageOver" arrowimagedown="$arrowImageDown" onblur="comboBoxHideOptions('$uid')" />
html;

            }
            
            
// build elements list
            
$options = '';
            if (
is_array($this->_elements)) {
                
$optionClass = $this->_options['optionClass'];
                if (
$this->_is_assoc_array($this->_elements)) {        
                    foreach (
$this->_elements as $k => $e) {
                        
$options .= <<<html
<div class="$optionClass" width="500px" value="$k">$e</div>
html;
                    }
                } else {
                    foreach (
$this->_elements as $e) {
                        
$options .= <<<html
<div class="$optionClass" width="500px">$e</div>
html;
                    }
                }
            }

            
// build the whole combobox
            
$boxClass = $this->_options['boxClass'];
            
$containerClass = $this->_options['containerClass'];
            
$html .= <<<html
<div id="$uid.box" class="$boxClass" width="500px">
    $input$button
</div>    
    <div id="$uid.container" class="$containerClass"  width="500px">
        $options
    </div>
html;

            
// add this combobox to a javascript array:
            // when the page is loaded a script will resize each combobox
            
$html .= <<<html
<script type="text/javascript">
var aCombo = new Object();
aCombo
["id"] = '$uid';
combos
[combos.length++] = aCombo;
</script>
html;

        }
        
        return
$html;
    }
// end func toHtml

    // }}}
    
    /**
     * Returns CSS styles
     *
     * @return string
     * @access private
     */
    
function _getCSS()
    {
        
        
$css = <<<CSS
<style type="text/css">
.comboBox
{
    border: 1px solid #7f9db9;
    width: 100%;
    float: left;
    position: relative;
    padding: 0px;
}

.comboBoxInput
{
    border: 0px;
    padding-left: 1px;
    position: relative;
    top: 0px;
    left:0px;
}

.comboBoxButton
{
    margin-top: 0px;
    position: relative;
    right: 0px;
    padding-left: 1px;
    padding-right: 1px;
}    

.comboBoxArrow
{
    margin-top: 1px;
    position: relative;
    right: -1px;
    top: 0px;
    padding-left: 1px;
    padding-right: 1px;
}    

.comboBoxContainer
{
    position: absolute;
    border: 1px solid #7f9db9;
    background-color: #FFF;
    visibility: hidden;
    display: block;
    overflow: auto;
    width: 100%;
    height: 100px;
}

.comboBoxOption
{
    font-family: arial;
    font-size: 12px;
    cursor: default;
    margin: 1px;
    overflow: hidden;
    white-space: nowrap;
    padding-left: 2px;
    width: 100%;
}

.comboBoxOptionOver
{
    font-family: arial;
    font-size: 12px;
    cursor: default;
    margin: 1px;
    overflow: hidden;
    white-space: nowrap;
    padding-left: 2px;
    width: 100%;
    background-color: #316AC5;
    color: #FFF;
}

</style>
CSS;
        
        return
$css;
    }
    
    
/**
     * Returns javascript functions
     *
     * @return string
     * @access private
     */
    
function _getJS()
    {
        
$js = <<<JS
<script type="text/javascript">
<!--
// Array of all comboboxes
var combos = new Array();


// Shows options of a combobox
var currentContainer = false;
function comboBoxShowOptions(uid)
{
    var container = document.getElementById(uid + ".container");
    var img = document.getElementById(uid + ".arrow");
    
    if (container.style.display != 'block')
{
        container.style.display = 'block';
        if (img != null)
{
            img.src = img.getAttribute('arrowimagedown');
        
}
        
        if (currentContainer && currentContainer != container)
{
            currentContainer.style.display = 'none';    
        
}
        
        currentContainer = container;        
    
} else {
        container.style.display = 'none';
        currentContainer = false;
        
        if (img != null)
{
            img.src = img.getAttribute('arrowimageover');
        
}
    
}    
}

// Hides options of a combobox
function comboBoxHideOptions(uid)
{
    var container = document.getElementById(uid + ".container");
    var img = document.getElementById(uid + ".arrow");
    
    container.style.display = 'none';
    currentContainer = false;
    
    if (img != null)
{
        img.src = img.getAttribute('arrowimageover');
    
}
}


// Switches "highlight" if current option
var currentOption = false;
function comboBoxHighlightOption()
{
    if (currentOption && currentOption == this)
{
        this.className = this.className.replace(/Over
$/, '');
        currentOption = false;
    
} else {
        this.className = this.className + 'Over';
        currentOption = this;
    
}
}

// Put current option's value into input field
function comboBoxSelectOptionValue()
{
    var parentNode = this.parentNode.parentNode;
    var textInput = parentNode.getElementsByTagName('INPUT')
[0];
    var value = this.getAttribute('value');
    textInput.value = (value != null) ? value : this.innerHTML;
    this.parentNode.style.display='none';    
        
    var img = parentNode.getElementsByTagName('IMAGE')
[0];
    if (img != null)
{
        img.src = img.getAttribute('arrowimage');
    
}
}

// Switches images of the button
function comboBoxSwitchImage(uid)
{
    var img = document.getElementById(uid + ".arrow");
    if (img.src.indexOf(img.getAttribute('arrowimage')) != -1)
{
        img.src = img.getAttribute('arrowimageover');    
    
} else {
        img.src = img.getAttribute('arrowimage');
    
}    
}


/**
* Fixes dimensions of a combobox and its elements
* and adds event handlers to options
*/
function fixComboBox(index)
{
    var aCombo = combos
[index];
    var id = aCombo
["id"];

    // fix width end z-index of box around field and button
    var box = document.getElementById(id + ".box");
    box.style.zIndex = 10000 - index;
    var input = box.getElementsByTagName("INPUT").item(0);
    var width = input.offsetWidth + 3;
    var button = document.getElementById(id + ".button");
    if (button != null)
{
        width += button.offsetWidth;
    
} else {
        var arrow = document.getElementById(id + ".arrow");
        width += arrow.offsetWidth;
    
}
    box.style.width = width + 'px';
        
    // fix width and z-index of options container
    var container = document.getElementById(id + ".container");    
    container.style.zIndex = 10000 - index;
    container.style.width = width + 'px';
    container.style.left = box.offsetLeft + 'px';
    container.style.top = (box.offsetTop + box.offsetHeight - 2) + 'px';

    // fix width and set event handlers of each option
    var height = 0;
    width -= 4;
    var divs = container.getElementsByTagName("DIV");
    for (var i = 0; i < divs.length; i++)
{
        var option = divs.item(i);
        option.onmouseover = comboBoxHighlightOption;
        option.onmouseout = comboBoxHighlightOption;
        option.onmousedown = comboBoxSelectOptionValue;
        option.style.width = width + 'px';    
        height += option.offsetHeight
    
}
    
    // fix height of options container
    if (height > container.offsetHeight)
{
        // shrink options' width to create place for scrollbar
        width -= 20;
        for (var i = 0; i < divs.length; i++)
{
            divs.item(i).style.width = width  + 'px';
        
}            
    
} else {
        // beautify container adding a space below options
        container.style.height = (height + 16) + 'px';
    
}    

    // container was an hidden block so browser was able to render it
    // now it'll be visible but not painted :-)
    container.style.display = 'none';
    container.style.visibility = 'visible';
}

/**
* Fixes all comboboxes
*/
function fixAllCombos()
{
    for (var i = 0; i < combos.length; i++)
{
        fixComboBox(i);
    
}
}

/**
* Due to IE, comboboxess cannot be "resized" during page renderization:
* when they are placed insede table cells (HTML_Quickform normal behavior)
* IE does not provide offsets of input field and divs, so we have to
* resize them after body load
*/
var oldonload = window.onload;
if (typeof window.onload != 'function')
{
    window.onload = fixAllCombos;
} else {
    window.onload = function()
{
        oldonload();
        fixAllCombos();
    
}
}

//-->
</script>
JS;
        
        return
$js;    
    }
    
    
/**
     * check if an array is associative
     */
    
function _is_assoc_array( $php_val )
    {
           if( !
is_array( $php_val ) ){
              
# Neither an associative, nor non-associative array.
              
return false;
           }

        
$given_keys = array_keys( $php_val );
        
$non_assoc_keys = range( 0, count( $php_val ) );

        if (
function_exists( 'array_diff_assoc' ) ) { # PHP > 4.3.0
              
return array_diff_assoc( $given_keys, $non_assoc_keys );
        } else {
              return
array_diff( $given_keys, $non_assoc_keys ) and array_diff( $non_assoc_keys, $given_keys );
        }
    }
    
}
// end class HTML_QuickForm_ComboBox

if (class_exists('HTML_QuickForm')) {
    
HTML_QuickForm::registerElementType('combobox', 'HTML/QuickForm/combobox.php', 'HTML_QuickForm_ComboBox');
}
?>