/* Editable Select Boxes 0.5.2

Copyright 2005 Sandy McArthur: http://Sandy.McArthur.org/
http://sandy.mcarthur.org/javascript/select/select.html

You are free to use this code however you please as long as the
above copyright is preserved. It would be nice if you sent me
any bug fixes or improvements you make.

A select box is made editable by adding the string "editable" to the element's class. When the page load event is called, any select elements with the "editable" class will be activated as an editable select box.

TODO: Support optgroup - this will be hard, at least in IE.

CSS
select.editable, input.editable {width:15em;}
option.activateEdit {font-style:italic; color:#800;}

HTML
<form>
<select id="a" name="a" class="editable"> (or class="keepSorted editable")
<option value="!!!edit!!!" class="activateEdit">Other Value</option>
<option value="a@b" selected="selected">a@b</option>
<option value="c@d">c@d</option>
</select>
</form>


*/ 

var EditableSelect = {
    
    /** The value used to indicate an option is the "edit" value. */
    "editValue": "!!!edit!!!",
    
    /** The text used when creating an edit option for a select box. */
    "editText": "(Other...)",
    //"editText": "(Other\u2026)", // Doesn't work in IE's select box
    //"editText": "(Other" + unescape("%85") + ")", // Doesn't work in Safari
    
    /** The text used when creating an edit option for a select box. */
    "editClass": "activateEdit",
    
    /**
     * Finds all select elements and if they have the "editable" CSS class then
     * it makes that select be editable.
     */
    "activateAll": function () {
        var selects = document.getElementsByTagName("select");
        for (var i=0; i < selects.length; i++) {
            var select = selects[i];
            if (EditableSelect.hasClass(select, "editable")) {
                EditableSelect.activate(select);
            }
        }
    },
    
    /** Makes the select element editable. */
    "activate": function (select) {
        if (!EditableSelect.selectHasEditOption(select)) {
            EditableSelect.selectAddEditOption(select);
        }
        select.oldSelection = select.options.selectedIndex;
        EditableSelect.addEvent(select, "change", EditableSelect.selectOnChage);
        EditableSelect.addClass(select, "editable");
    },
    
    /** Does the select box have an edit option. */
    "selectHasEditOption": function (select) {
        var options = select.options;
        for (var i=0; i < options.length; i++) {
            if (options.item(i).value == EditableSelect.editValue) {
                return true;
            }
        }
        return false;
    },
    
    /** Add an edit option to the select box. */
    "selectAddEditOption": function (select) {
        var option = document.createElement("option");
        option.value = EditableSelect.editValue;
        option.text = EditableSelect.editText;
        option.className = EditableSelect.editClass;
        EditableSelect.selectAddOption(select, option, 0);
    },
    
    /**
     * Add an option to the select box at specified postion.
     * "index" is optionial, if left undefined then the end is assumed.
     */
    "selectAddOption": function (select, option, index) {
        if (select.options.add) {
            if (typeof index == "undefined") {
                select.options.add(option);
            } else {
                select.options.add(option,index);
            }
        } else {
            if (typeof index == "undefined") {
                select.insertBefore(option);
            } else {
                var before = select.options.item(index);
                select.insertBefore(option, before);
            }
        }
    },
    
    /**
     * Event handler for select box. If the edit option is selected it
     * switches to the edit input field.
     */
    "selectOnChage": function (evt) {
        var select = this;
        if (evt.srcElement) select = evt.srcElement; // For IE
        
        if (select.value == EditableSelect.editValue) {
            var input = document.createElement("input");
            input.type = "text";
            input.value = select.options.item(select.oldSelection).value;
            input.className = select.className;
		    input.name = select.name;
		    input.id = select.id;
            input.selectOnChange = select.onchange;
            EditableSelect.addEvent(input, "blur", EditableSelect.inputOnBlur);
            EditableSelect.addEvent(input, "keypress", EditableSelect.inputOnKeyPress);
    
            var oldOptions = [];
            for (var i=0; i < select.options.length; i++) {
                var o = select.options.item(i);
                var sn = o;
                var oo = EditableSelect.serializeOption(o);
                oldOptions[oldOptions.length] = oo;
            }
            
            select.parentNode.replaceChild(input, select);
            input.focus();
            input.select();
            input.oldOptions = oldOptions;
            
        } else {
            select.oldSelection = select.options.selectedIndex;
        }
    },
    
    /**
     * Event handler for the input field when the field has lost focus.
     * This rebuilds the select box possibly adding a new option for what
     * the user typed.
     */
    "inputOnBlur": function (evt) {
        var input = this;
        if (evt.srcElement) input = evt.srcElement; // For IE
        var keepSorted = EditableSelect.hasClass(input, "keepSorted");
        var value = input.value;
        var select = document.createElement("select");
        select.className = input.className;
		select.name = input.name;
		select.id = input.id;
        select.onchange = input.selectOnChange;
        
        var selectedIndex = -1;
        var optionIndex = 0;
        var oldOptions = input.oldOptions;
        var newOption = {"text": value, "value": value };
        for (var i=0; i < oldOptions.length; i++) {
            var n = oldOptions[i];

            if (newOption != null && EditableSelect.inputCompare(n, newOption) == 0) {
                newOption = null;
            } else if (keepSorted && newOption != null && EditableSelect.inputCompare(n, newOption) > 0) {
                EditableSelect.selectAddOption(select, EditableSelect.deserializeOption(newOption));
                
                selectedIndex = optionIndex;
                optionIndex++;
                newOption = null;
            }
            
            if (selectedIndex == -1 && n.value == value) {
                selectedIndex = optionIndex;
            }
            
            var opt = EditableSelect.deserializeOption(n);
            EditableSelect.selectAddOption(select, opt);
            optionIndex++;
            input.oldOptions[i] = null;
        }
        if (newOption != null) {
            var opt = EditableSelect.deserializeOption(newOption);
            EditableSelect.selectAddOption(select, opt);
            
            select.options.selectedIndex = optionIndex;
            select.oldSelection = select.options.selectedIndex;
        } else {
            select.options.selectedIndex = selectedIndex;
            select.oldSelection = select.options.selectedIndex;
        }
        
        EditableSelect.activate(select);
        input.parentNode.replaceChild(select, input);
        select.blur();
        if (select.onchange) select.onchange();
    },
    
    "inputCompare": function (x, y) {
        if (x.value ==  EditableSelect.editValue && y.value == EditableSelect.editValue) {
            return 0;
        }
        if (x.value ==  EditableSelect.editValue) {
            return -1;
        }
        if (y.value ==  EditableSelect.editValue) {
            return 1;
        }
	var xText = x.text ? x.text.toUpperCase() : "";
	var yText = y.text ? y.text.toUpperCase() : "";
        if (xText < yText) {
            return -1;
        } else if (xText == yText) {
            return 0;
        } else {
            return 1;
        }
    },
    
    /** Intercept enter key presses to prevent form submit but still update the field. */
    "inputOnKeyPress": function (evt) {
        var e;
        if (evt) {
            e = evt;
        } else if (window.event) {
            e = window.event;
        } else {
            throw "EditableSelect.inputOnKeyPress: Unable to find the event.";
        }
        if (e.keyCode == 13) {
            if (e.currentTarget) {
                e.currentTarget.blur();
                return false; // Prevent form submit
            } else if (e.srcElement) {
                e.srcElement.blur();
                return false; // Prevent form submit
            } else {
                throw "EditableSelect.inputOnKeyPress: Unknown event type.";
            }
        }
        return true;
    },
    
    /** Convert an option element to a form that can be attached to the input element. */
    "serializeOption": function (option) {
        var ser = {};
        if (option.text) ser.text = option.text;
        if (option.value) ser.value = option.value;
        if (option.disabled) ser.disabled = option.disabled;
        if (option.label) ser.label = option.label;
        if (option.className) ser.className = option.className;
        if (option.title) ser.title = option.title;
        if (option.id) ser.id = option.id;
        return ser;
    },
    
    /** Reverse the serializeOption function into an option element. */
    "deserializeOption": function (ser) {
        var option = document.createElement("option");
        if (ser.text) option.text = ser.text;
        if (ser.value) {
            option.value = ser.value;
        } else if (ser.text) {
            option.value = ser.text;
        }
        if (ser.disabled) option.disabled = ser.disabled;
        if (ser.label) option.label = ser.label;
        if (ser.className) option.className = ser.className;
        if (ser.title) option.title = ser.value;
        if (ser.id) option.id = ser.id;
        return option;
    },
    
    /** Does this element have the CSS class? */
    "hasClass": function (element, clazz) {
        var regex = new RegExp('\\b'+clazz+'\\b');
        return regex.test(element.className);
    },
    
    /** Append the CSS class to the element if it doesn't exist. */
    "addClass": function (element, clazz) {
        if (!EditableSelect.hasClass(element, clazz)) {
            element.className = element.className + " " + clazz;
        }
    },
    
    /** Remove the CSS class from the element if it exist. */
    "removeClass": function (element, clazz) {
        if (EditableSelect.hasClass(element, clazz)) {
            element.className = element.className.replace(clazz, "");
        }
    },
    
    // From: http://www.scottandrew.com/weblog/articles/cbs-events
    /** Add an event in a cross browser way. */
    "addEvent": function (obj, evType, fn, useCapture) {
        if (obj.addEventListener){
            obj.addEventListener(evType, fn, useCapture);
            return true;
        } else if (obj.attachEvent){
            var r = obj.attachEvent("on"+evType, fn);
            return r;
        } else {
            alert("Handler could not be attached");
        }
    },
    
    /** Remove an event in a cross browser way. */
    "removeEvent": function (obj, evType, fn, useCapture){
        if (obj.removeEventListener){
            obj.removeEventListener(evType, fn, useCapture);
            return true;
        } else if (obj.detachEvent){
            var r = obj.detachEvent("on"+evType, fn);
            return r;
        } else {
            alert("Handler could not be removed");
        }
    }
}

EditableSelect.addEvent(window, 'load', EditableSelect.activateAll);

