/**********************************************************************************
 * $URL: $
 * $Id: $
 ***********************************************************************************
 *
 * Copyright (c) 2010 The Sakai Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.osedu.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *********************************************************************************/
/*
 * a11ySelectHandler.js -- Brian Richwine, Indiana University
 * Based on code by Cameron Adams from 19.October.2004 as found on at:
 * themaninblue.com/writing/perspective/2004/10/19/
 *
 * Interaction manager for select elements with onchange event handlers that
 * attempts to ensure their accessibility for keyboard only users. Models
 * its user interaction behavior after Mozilla Firefox 3.6 on the PC.
 *
 * Call with the following code from the bottom of the HTML body:
 *  <script type="text/javascript" defer="defer" src="/library/js/a11ySelectHandler.js"></script>
 * Be sure to load jQuery first!
 *
 * This code and the results of interactions testing performed on 11.August.2010 are
 * documented on the Sakai Accessibility Working Group confluence space. 
 *
 * Tested against:
 *  XP: IE 6, IE 7, IE 8, FF 2.0, FF 3.0, FF 3.5, FF 3.6.8, Opera 10.5, Opera 10.6
 *  Win7: IE 9 (Preview 3)
 *  OS X: Safari 5, FF 3.6.8
 */

(function($) {
$(document).ready(function () { // Scan document and install handler once document is loaded and ready
    $("select").each(function (i, o) { // "select[onchange]" didn't work, old version of jQuery?
        if (o && o.onchange) {
            o.a11ySelect = {
                allowOnchangeEvent : false,
                wasKeyed : false,
                initValue : null,
                lastInteractionWasAClick : true,
                allowPossibleOpenMenuChange : false,
                oSelect : null,
                origOnfocus : null,
                origOnchange : null,
                origOnkeydown : null,
                origOnclick : null,
                origMousedown : null,
                origOnblur : null,
                isSafari : null,
                isOpera : null,
                handleChanged : function(ob) {
                    var oSelect = (ob && ob.a11ySelect) ? ob : this,
                        regx=/^function (onchange|anonymous)\((event)*\)\s*\{\s*blur\(\);\s*\}$/im,
                        a11ySelect = oSelect.a11ySelect;
                    
                    if (!a11ySelect.allowOnchangeEvent && !a11ySelect.allowPossibleOpenMenuChange && !a11ySelect.isSafari) {
                        return false; // Abort calling the original onchange event handler
                    }
                    a11ySelect.allowOnchangeEvent = false; // Prevent redundant calls to orig onchange event
                    a11ySelect.initValue = a11ySelect.oSelect.value; // Store current value as init since calling onchange

                    if (a11ySelect.origOnchange) {
                        //console.log("changed keyed="+a11ySelect.wasKeyed+"-"+"test:"+regx.test(a11ySelect.origOnchange.toString())+" f:"+a11ySelect.origOnchange);
                        if(a11ySelect.wasKeyed === false || regx.test(a11ySelect.origOnchange.toString()) === false ) {
                            return a11ySelect.origOnchange (ob);
                        }
                    }

                    return true;
                },
                handleMousedown : function(e) {
                    // Needed for Opera. For some reason this prevents certain false onclick events on keypresses
                    if (this.a11ySelect.origOnmousedown) {
                      return this.a11ySelect.origOnmousedown(e);
                    }

                    return true;
                },
                handleClick : function(e) {
                    // Handles clicks on menus opened by keyboard (alt+down arrow)
                    // Disabled this for Opera because Opera generates false click events on keypresses
                    if (this.value != this.a11ySelect.initValue && !this.a11ySelect.lastInteractionWasAClick && !this.a11ySelect.isOpera) {
                        this.a11ySelect.allowOnchangeEvent = true;
                        this.a11ySelect.handleChanged(this);
                    }

                    this.a11ySelect.lastInteractionWasAClick = true;
                    this.a11ySelect.allowOnchangeEvent = true;
                    this.a11ySelect.wasKeyed = false;
                    this.a11ySelect.allowPossibleOpenMenuChange = false;

                    if (this.a11ySelect.origOnclick) {
                        return this.a11ySelect.origOnclick (e);
                    }

                    return true;
                },
                handleFocus : function(e) {
                    // Reset everything...
                    this.a11ySelect.initValue = this.value;
                    this.a11ySelect.allowOnchangeEvent = false;
                    this.a11ySelect.wasKeyed = false;
                    this.a11ySelect.allowPossibleOpenMenuChange = false;

                    if (this.a11ySelect.origOnfocus) {
                        return this.a11ySelect.origOnfocus(e);
                    }

                    return true;
                },
                handleBlur : function(e) {
                    // Call orig onChange event when focus is lost if needed
                    if (this.value !== this.a11ySelect.initValue && !this.a11ySelect.wasKeyed) {
                        this.a11ySelect.allowOnchangeEvent = true;
                        this.a11ySelect.handleChanged(this); 
                    }
                    
                    if (this.a11ySelect.origOnblur) {
                        return this.a11ySelect.origOnblur(e);
                    }

                    return true;
                },
                handleKeydown : function(e) {
                    var evt = (e) ? e : window.event,
                        keyCodeEnter = 13,
                        keyCodeEsc = 27;

                    this.a11ySelect.allowPossibleOpenMenuChange = false;
                    this.a11ySelect.lastInteractionWasAClick = false;

                    if (evt.keyCode === keyCodeEnter && this.value !== this.a11ySelect.initValue && !this.a11ySelect.isSafari) {
                        this.a11ySelect.allowOnchangeEvent = true;
                        this.a11ySelect.wasKeyed = true;
                        this.a11ySelect.handleChanged(this);
                    }
                    else if (evt.keyCode === keyCodeEnter) {
                        // Expanded select menus (alt+down arrow key or by click) don't update value until after keypress event
                        // This allows menus opened by a mouse, but set with an enter key to work
                        this.a11ySelect.allowPossibleOpenMenuChange = true;
                        this.a11ySelect.wasKeyed = true;
                    }
                    else {
                        this.a11ySelect.wasKeyed = false;
                    }

                    if (evt.keyCode === keyCodeEsc) {
                        this.value = this.a11ySelect.initValue; // restore original value when escape is pressed
                    }
                    else {
                        if (this.a11ySelect.isSafari) {
                            this.a11ySelect.wasKeyed = true;
                        }
                    }

                    this.a11ySelect.allowOnchangeEvent = false;
                    return true;
                },
                init : function(oSel) {
                    oSel.a11ySelect.isSafari = $.browser.webkit;
                    oSel.a11ySelect.isOpera = $.browser.opera;
                    oSel.a11ySelect.oSelect = oSel;
                    
                    oSel.a11ySelect.initValue = oSel.value;
                    oSel.a11ySelect.allowOnchangeEvent = false;
                    oSel.a11ySelect.wasKeyed = false;
                    oSel.a11ySelect.allowPossibleOpenMenuChange = false;

                    // Store original handlers
                    oSel.a11ySelect.origOnfocus = oSel.onfocus;
                    oSel.a11ySelect.origOnkeydown = oSel.onkeydown;
                    oSel.a11ySelect.origMousedown = oSel.onmousedown;
                    oSel.a11ySelect.origOnclick = oSel.onclick;
                    oSel.a11ySelect.origOnchange = oSel.onchange;
                    oSel.a11ySelect.origOnblur = oSel.onblur;

                    // Install handlers
                    oSel.onfocus = oSel.a11ySelect.handleFocus;
                    oSel.onkeydown = oSel.a11ySelect.handleKeydown;
                    oSel.onmousedown = oSel.a11ySelect.handleMousedown;
                    oSel.onclick = oSel.a11ySelect.handleClick;
                    oSel.onchange = oSel.a11ySelect.handleChanged;
                    oSel.onblur = oSel.a11ySelect.handleBlur;
                    
//                    $(oSel).css("outline", "1px solid blue");
                }
            }
            o.a11ySelect.init(o);   
        }           
      });
 });
})(jQuery);
