/*
Datagraf jQuery selectbox plugin
Author: Lars Danielsen

Replaces a HTML select element with a construction of html, that reacts in a similar
way as the original element. The selection made in the constructed element is
propagated to the original select element, and changes made to the original
element is propagated to the constructed element.

The original element's onchange events are fired appropiately;

Will not work for select-multiple elements

Required: jQuery 1.4 or above

Synopsis:

jQuery(elem).DGselectbox();

Resulting html example:

<div class="DGselectbox-fieldwrapper DGselectbox-test" style="position: static; display: inline; float: none; top: auto; left: auto; right: auto; width: 196px; height: 18px; margin: 0px; padding: 0px; font-family: MS Shell Dlg; font-size: 13.3333px; color: rgb(0, 0, 0);">
    <select name="sel1" id="test" style="display: none;">
		<option value="a">First option</option>
		<option value="b">Second option, very long text, that will wrap ...</option>
		<option value="c">Third option</option>
	</select>
    <div class="DGselectbox-inputwrapper" style="width: 196px; height: 18px; overflow: hidden; position: relative;">
        <input value="" name="DGselectbox_test" class="DGselectbox" autocomplete="off" readonly="" style="left: -10px; top: 0pt; position: absolute; height: 1px; width: 1px;">
        <label class="DGselectbox" style="display: block; overflow: hidden; white-space: nowrap; width: 174px; font-family: MS Shell Dlg; font-size: 13.3333px; color: rgb(0, 0, 0);">First option</label>
    </div>
</div>
<ul class="DGselectbox" style="position: absolute; width: 196px; display: none; font-family: MS Shell Dlg; font-size: 13.3333px; color: rgb(0, 0, 0);">
    <li value="0" class="active selected">First option</li>
    <li value="0" class="">Second option, very long text, that will wrap ...</li>
    <li value="0" class="">Third option</li>
</ul>

You can to do further positioning and styling yourself.
Use margin on the ul.DGselectbox to do exact positioning

*/

jQuery.fn.DGselectbox = function(init) {
    var defaults = {
        autoStyle:              true,
        setSize:                true,
        outerWidth:             undefined,
        innerWidth:             undefined,
        outerHeight:            undefined,
        innerHeight:            undefined,
        frameWidth:             undefined,
        borderWidth:            undefined,
        debug:                  false,
        selectCallback:         false,
        charsEntered:           new Array(),
        nearestMatchTimeout:    null,
        advanced:               true
    };
    
    return this.each(function() {
        var action;
        if (typeof init != 'object') {
            action = init;
        }
        var options = jQuery.extend(defaults, init);
        if (!options.text) {
            options.text = jQuery(this).attr('alt');
        }
        
        
        options.outerWidth  = (options.outerWidth === undefined)    ?  jQuery(this).outerWidth()    : options.outerWidth;
        options.innerWidth  = (options.innerWidth === undefined)    ?  jQuery(this).innerWidth()    : options.innerWidth;
        options.innerHeight = (options.innerHeight === undefined)   ?  jQuery(this).innerHeight()   : options.innerHeight;
        options.outerHeight = (options.outerHeight === undefined)   ?  jQuery(this).outerHeight()   : options.outerHeight;
        options.frameWidth  = options.frameWidth || 0;
        options.buttonWidth = (options.buttonWidth === undefined)   ?  jQuery(this).outerHeight()   : options.buttonWidth;
        
        options.name = options.name || jQuery(this).attr('id') || jQuery(this).attr('name');
        
        if (!jQuery.DG_selectbox_uls) {
            jQuery.DG_selectbox_uls = new Array();
            jQuery(document).bind('click.DGselectbox', function(event) {
                //Say('document click');
                HideAllDGselectbox_uls();
            });
        }
        if (options.debug) {
            if (!jQuery('#DGselectbox_debug').size()) {
                jQuery('body').append('<div id="DGselectbox_debug"></div>');
            }
        }
        
        MakeSelectbox(jQuery(this), options);
    });
    
    function MakeSelectbox (o, options) {
        options.initDone = false;
        o.trigger('RemoveDGselectbox');
        //options.originalChange = o.get(0).onchange;
        
        var fieldwrapper    = GetFieldwrapper(o, options);
        var input           = GetInput(o, options);
        var label           = GetLabel(o, options);
        var ul              = GetUl(o, options);
        var inputwrapper    = GetInputwrapper(o, options);
        
        if (options.advanced) {
            inputwrapper.append(input);
            o.hide();
        } else {
            //alert(o.outerHeight());
            o.css('top', o.outerHeight() * -1);//hideSelect(o);
        }
        
        inputwrapper.append(label);
        
        o.wrap(fieldwrapper)
            .after(inputwrapper);
        
        
            
        jQuery('body').append(ul);
        jQuery.DG_selectbox_uls.push(ul);
        
        o.bind('change.DGselectbox', function (event) {
                event.stopPropagation();
                event.preventDefault();
                options.runningInternalChange = true;
                if (options.runningOriginalChange) {
                } else {
                    var newli = jQuery(ul.find('li').get(o.get(0).selectedIndex)).addClass('selected');
                    SelectOption(null, options, ul, newli, input, label);
                }
                options.runningInternalChange = false;
            })
            .bind('RemoveDGselectbox', function () {
                RemoveDGselectbox (o, options, ul);
            });
        
        
        inputwrapper.bind('click.DGselectbox', function(event) {
            event.stopPropagation();
            ul.empty().toggle();
            input.trigger('focus.DGselectbox');
            if (ul.is(':visible')) {
                ul.trigger('position');
            }
        });
        
        input.bind('keydown.DGselectbox', function (event) {
            InputKeyDown(event, o, options, ul, input, inputwrapper);
        });
        input.bind('keypress.DGselectbox', function (event) {
            InputKeyPress(event, o, options, ul, input);
        });
        
        input.bind('blur.DGselectbox', function(event) {
            if (ul.is(':visible')) {
                input.trigger('focus.DGselectbox');
            } else {
                inputwrapper.removeClass('focus');
            }
        });
        
        input.bind('focus.DGselectbox', function(event) {
            HideAllDGselectbox_uls (ul);
            inputwrapper.addClass('focus');
            FillOptions(o, options, ul, input, label);
        });
        
        ul.bind('position', function () {PositionUl(o, options, ul, inputwrapper, label);})
            .bind('focusout', function () {inputwrapper.removeClass('focus');})
        
        FillOptions(o, options, ul, input, label);
        SetSelected (o, options, ul, input, label);
        input.blur();
        if (options.debug) {
            jQuery('#DGselectbox_debug').text('stopped');
        }
        options.initDone = true;
    }
    
    function FillOptions (o, options, ul, input, label) {
        if (ul.find('li').size() == 0) {
            o.find('option').each(
                function () {
                    var op = jQuery(this);
                    var li = GetLi(o, options, ul, input, label);
                    li.text(op.text());
                    if (op.is(':selected')) {
                        li.addClass('selected');
                        li.addClass('active');
                    }
                    li.bind('click.DGselectbox',
                        function (event) {
                            event.stopPropagation();
                            SelectOption(o, options, ul, jQuery(this), input, label);
                            input.trigger('focus.DGselectbox');
                        });
                    ul.append(li);
                }
            );
        }
    }
    
    function SetSelected (o, options, ul, input, label) {
        SelectOption(o, options, ul, ul.find('li.selected'), input, label);
    }
    
    function PositionUl (o, options, ul, inputwrapper, label) {
        var ofs = inputwrapper.offset();
        ul.css('top', ofs.top + options.outerHeight);
        ul.css('left', ofs.left);
        ul.height('');
        if (ofs.top + ul.outerHeight() + 30 > jQuery(window).height()){
            ul.height(0);
            ofs = inputwrapper.offset();
            ul.height(jQuery(window).height() - ofs.top - options.outerHeight - 30);
            ul.css('top', ofs.top + options.outerHeight);
            ul.css('left', ofs.left);
        } else {
            ul.height('');
        }
        var selectedposition = ul.find('li.selected').position();
        if (selectedposition) {
            ul.get(0).scrollTop = ul.find('li.selected').position().top;
        }
    }
    
    function SelectOption (o, options, ul, li, input, label) {
        var newselection = true;
        if (li.is('.selected')) {
            newselection = false;
        }
        li.addClass('selected')
            .siblings().removeClass('selected');
        var index = li.index();
        if (options.initDone && o) {
            o.get(0).selectedIndex = index;
            if (newselection && !options.runningInternalChange) {
                options.runningOriginalChange = true;
                o.trigger('change');
                options.runningOriginalChange = false;
            }
        }
        label.text(li.text());
        input.trigger('focus.DGselectbox');
        HideAllDGselectbox_uls();
        if (options.initDone && options.selectCallback) {
            options.selectCallback(o);
        }
    }
    
    function InputKeyDown (event, o, options, ul, input, inputwrapper) {
        //Say('InputKeyDown');
        var keycode = event.keyCode;
        
        if (keycode == 40) { // down arrow
            if (event.altKey) {
                inputwrapper.trigger('click.DGselectbox');
            } else {
                MoveFocusDown (o, options, ul);
            }
        } else if (keycode == 38) { // up arrow
            MoveFocusUp (o, options, ul);
        } else if (keycode == 13) { // enter
            event.preventDefault();
            ul.find('li.active').trigger('click.DGselectbox');
        } else if (keycode == 27) { // esc
            ul.hide();
            input.blur();
        } else if (keycode == 9) { // tab
            ul.hide();
        }
        
        if (keycode != 9 && keycode != 16) { // shift and tab is allowed to pass
            //event.preventDefault();
            return true;
        }
        return false;
    }
    
    function InputKeyPress (event, o, options, ul, input) {
        //event.preventDefault();
        //Say('InputKeyPress: ' + event.keyCode);
        var charcode = event.charCode || event.keyCode;
        var keycode = event.keyCode;
        FindAndSelectNearest(o, options, ul, charcode);
        return false;
    }
    
    
    function FindAndSelectNearest (o, options, ul, charcode) {
        var charToMatch = String.fromCharCode(charcode);
        //if (!charToMatch.match(/[\w|\d|æøå]/i)) return;
        //Say(charcode + ' - ' + charToMatch);
        if (charToMatch != options.charsEntered[0]) {
            options.charsEntered.push(charToMatch);
            window.clearTimeout(options.nearestMatchTimeout);
            options.nearestMatchTimeout = window.setTimeout(function () {
                options.charsEntered = new Array();
            }, 1000);   
        }
        var indexToUse = 0;
        var matchString = options.charsEntered.join('');
        var matchLength = matchString.length;
        var matchExp = new RegExp(matchString, 'i');
        var matchingLis = new Array();
        ul.find('li').each( function () {
            var li = jQuery(this);
            var text = li.text().substring(0, matchLength);
            if (text && text.match(matchExp)) {
                matchingLis.push(li);
                if (li.is('.selected') || li.is('.active')) {
                    indexToUse = matchingLis.length;
                }
            } 
        });
        if (indexToUse >= matchingLis.length) {
            indexToUse = 0;
        }
        var foundLi = matchingLis[indexToUse];
        if (foundLi && foundLi.size()) {
            SetFocusTo (options, ul, foundLi);
        }
    }
    
    function MoveFocusDown (o, options, ul) {
        MoveFocus (o, options, ul, 1);
    }
    function MoveFocusUp (o, options, ul) {
        MoveFocus (o, options, ul, -1);
    }
        
    function MoveFocus (o, options, ul, dir) {
        var currentActive = ul.find('li:first');
        var counter = 0;
        ul.find('li').each( function () {
            var li = jQuery(this);
            if (li.is('.active')) {
                currentActive = li;
            }
            counter++;
        });
        if (currentActive.size()) {
            var newActive = null;
            if (dir == 1) {
                newActive = currentActive.next();
            } else if (dir == -1) {
                newActive = currentActive.prev();
            }
            if (newActive.size()) {
                SetFocusTo (options, ul, newActive);
            }
        }
    }
    
    function SetFocusTo (options, ul, li) {
        ul.children('li').removeClass('active');
        li.addClass('active');
        if (!ul.is(':visible')) {  
            li.trigger('click.DGselectbox');
        }
        if (li.position().top < 0) {
            ul.get(0).scrollTop += li.position().top; // scroll up
        } else if (li.position().top + li.outerHeight(true) > ul.innerHeight()) {
            ul.get(0).scrollTop += li.outerHeight(true) + li.position().top - ul.innerHeight(); // scroll down
        }
    }
    
    function HideAllDGselectbox_uls (exceptMe) {
        for (var i = 0; i < jQuery.DG_selectbox_uls.length; i++) {
            var ul = jQuery(jQuery.DG_selectbox_uls[i]);
            if (ul.is(':visible') && (exceptMe && exceptMe.get(0)) !== ul.get(0)) {
                ul.hide()
                    .trigger('focusout');
            }
        }
    }
        
    function RemoveDGselectbox (o, options, ul) {
        if (o.size() && ul.size()) {
            ul.remove();
            var wrapper = o.parent('.DGselectbox-fieldwrapper');
            if (wrapper.size()) {
                wrapper.before(o);
                wrapper.remove();
            }
            if (!options.advanced) {
                o.unbind('keydown.DGselectbox')
                    .unbind('keypress.DGselectbox')
                    .unbind('blur.DGselectbox')
                    .unbind('focus.DGselectbox');
                
                for (var key in options.originalStyles) {
                    //alert(key + ': ' + options.originalStyles[key]);
                    o.css(key, options.originalStyles[key]);
                }
            }
            o.unbind('RemoveDGselectbox')
                .unbind('change.DGselectbox')
                .show();
        }
    }
    
    function GetInputwrapper (o, options) {
        var inputwrapper = jQuery('<div class="DGselectbox-inputwrapper"></div>')
            .css('position', 'relative');
        if (options.autoStyle) {
            inputwrapper.css('color', o.css('color'));
        }
        if (options.setSize) {
            inputwrapper.width(options.outerWidth - (options.frameWidth * 2))
                .height(options.outerHeight - (options.frameWidth * 2));
        }
        return inputwrapper
    }
    
    function GetFieldwrapper (o, options) {
        var fieldwrapper = jQuery('<div class="DGselectbox-fieldwrapper DGselectbox-' + options.name + '"></div>');
        fieldwrapper.css('position', 'relative')
            .css('display', o.css('display'))
            .css('overflow', 'hidden')
            .css('float', o.css('float'))
            .css('position', o.css('position'))
            .css('top', o.css('top'))
            .css('left', o.css('left'))
            .css('right', o.css('right'))
        
        if (options.autoStyle) {
            fieldwrapper.css('font-family', o.css('font-family'))
                .css('font-size', o.css('font-size'))
                .css('color', o.css('color'))
                .css('margin-left', o.css('margin-left'))
                .css('margin-right', o.css('margin-right'))
                .css('margin-top', o.css('margin-top'))
                .css('margin-bottom', o.css('margin-bottom'))
                .css('padding-left', o.css('padding-left'))
                .css('padding-right', o.css('padding-right'))
                .css('padding-top', o.css('padding-top'))
                .css('padding-bottom', o.css('padding-bottom'));
        }
        if (options.setSize) {
            fieldwrapper.width(options.outerWidth)
                .height(options.outerHeight);
        }
        return fieldwrapper;
    }
    
    function GetInput (o, options) {
        if (options.advanced) {
            var input = jQuery('<input class="DGselectbox" name="DGselectbox_' + options.name + '" value="" />')
                .attr('autocomplete','off')
                .attr('readonly', 'readonly')
                .css('left', '-100px')
                .css('top', '-100px')
                .css('border', '0px none')
                .css('position', 'absolute')
                .height(1)
                .width(1);
            return input;
        } else {
            options.originalStyles = {
                top: o.css('top'),
                position: o.css('position'),
                marginTop: o.css('marginTop'),
                marginBottom: o.css('marginBottom'),
                marginLeft: o.css('marginLeft'),
                marginRight: o.css('marginRight')
            };
            o.css('position', 'absolute')
                .css('marginTop', '0')
                .css('marginBottom', '0')
                .css('marginLeft', '0')
                .css('marginRight', '0');
            return o;
        }
        
    }
    
    function GetLabel (o, options) {
        var label = jQuery('<label class="DGselectbox"></label>');
        if (options.autoStyle) {
            label.css('font-family', o.css('font-family'))
                .css('font-size', o.css('font-size'))
                .css('display', 'block')
                .css('overflow', 'hidden')
                .css('white-space', 'nowrap');
        }
        
        if (options.setSize) {
            label.width('auto')
                .height(options.innerHeight)
                .css('margin-right', options.buttonWidth)
        }
        return label;
    }
    
    function GetUl (o, options) {
        var ul = jQuery('<ul class="DGselectbox DGselectbox-' + options.name + '"></ul>')
            .css('position', 'absolute')
            .css('overflow', 'auto')
            .hide();
            
        if (options.autoStyle) {
            ul.css('font-family', o.css('font-family'))
                .css('font-size', o.css('font-size'))
                .css('color', o.css('color'))
                .css('background-color', o.css('background-color'));
        }
        if (options.setSize) {
            ul.width(options.outerWidth - (options.frameWidth * 2))
        }
        
        return ul;
    }
    
    function GetLi (o, options, ul, input, label) {
        var li = jQuery('<li></li>')
            .css('position', 'relative');
        return li;
    }
    
    function Say (str) {
        var sayDiv = jQuery('#DGselectbox_Say');
        if (!sayDiv.size()) {
            sayDiv = jQuery('<div id="DGselectbox_Say"></div>').appendTo(jQuery('body'));
        }
        sayDiv.html(sayDiv.html() + '<br/>' + str);
    }
};

