/*

 * jQuery contextMenu - Plugin for simple contextMenu handling

 *

 * Version: 1.5.6

 *

 * Authors: Rodney Rehm, Addy Osmani (patches for FF)

 * Web: http://medialize.github.com/jQuery-contextMenu/

 *

 * Licensed under

 *   MIT License http://www.opensource.org/licenses/mit-license

 *   GPL v3 http://opensource.org/licenses/GPL-3.0

 *

 */



(function($, undefined){

    

    // TODO: -

        // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio

        // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative



// determine html5 compatibility

$.support.htmlMenuitem = ('HTMLMenuItemElement' in window);

$.support.htmlCommand = ('HTMLCommandElement' in window);



var // currently active contextMenu trigger

    $currentTrigger = null,

    // is contextMenu initialized with at least one menu?

    initialized = false,

    // flag stating to ignore the contextmenu event

    ignoreThisClick = false,

    // window handle

    $win = $(window),

    // number of registered menus

    counter = 0,

    // mapping selector to namespace

    namespaces = {},

    // mapping namespace to options

    menus = {},

    // custom command type handlers

    types = {},

    // default values

    defaults = {

        // selector of contextMenu trigger

        selector: null,

        // where to append the menu to

        appendTo: null,

        // method to trigger context menu ["right", "left", "hover"]

        trigger: "right",

        // hide menu when mouse leaves trigger / menu elements

        autoHide: false,

        // ignore right click triggers for left, hover or custom activation

        ignoreRightClick: false,

        // ms to wait before showing a hover-triggered context menu

        delay: 200,

        // determine position to show menu at

        determinePosition: function($menu) {

            // position to the lower middle of the trigger element

            if ($.ui && $.ui.position) {

                // .position() is provided as a jQuery UI utility

                // (...and it won't work on hidden elements)

                $menu.css('display', 'block').position({

                    my: "center top",

                    at: "center bottom",

                    of: this,

                    offset: "0 5",

                    collision: "fit"

                }).css('display', 'none');

            } else {

                // determine contextMenu position

                var offset = this.offset();

                offset.top += this.outerHeight();

                offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2;

                $menu.css(offset);

            }

        },

        // position menu

        position: function(opt, x, y) {

            var $this = this,

                offset;

            // determine contextMenu position

            if (!x && !y) {

                opt.determinePosition.call(this, opt.$menu);

                return;

            } else if (x === "maintain" && y === "maintain") {

                // x and y must not be changed (after re-show on command click)

                offset = opt.$menu.position();

            } else {

                // x and y are given (by mouse event)

                var triggerIsFixed = opt.$trigger.parents().andSelf()

                    .filter(function() {

                        return $(this).css('position') == "fixed";

                    }).length;



                if (triggerIsFixed) {

                    y -= $win.scrollTop();

                    x -= $win.scrollLeft();

                }

                offset = {top: y, left: x};

            }

            

            // correct offset if viewport demands it

            var bottom = $win.scrollTop() + $win.height(),

                right = $win.scrollLeft() + $win.width(),

                height = opt.$menu.height(),

                width = opt.$menu.width();

            

            if (offset.top + height > bottom) {

                offset.top -= height;

            }

            

            if (offset.left + width > right) {

                offset.left -= width;

            }

            

            opt.$menu.css(offset);

        },

        // position the sub-menu

        positionSubmenu: function($menu) {

            if ($.ui && $.ui.position) {

                // .position() is provided as a jQuery UI utility

                // (...and it won't work on hidden elements)

                $menu.css('display', 'block').position({

                    my: "left top",

                    at: "right top",

                    of: this,

                    collision: "fit"

                }).css('display', '');

            } else {

                // determine contextMenu position

                var offset = this.offset();

                offset.top += 0;

                offset.left += this.outerWidth();

                $menu.css(offset);

            }

        },

        // offset to add to zIndex

        zIndex: 1,

        // show hide animation settings

        animation: {

            duration: 50,

            show: 'slideDown',

            hide: 'slideUp'

        },

        // events

        events: {

            show: $.noop,

            hide: $.noop

        },

        // default callback

        callback: null,

        // list of contextMenu items

        items: {}

    },

    // mouse position for hover activation

    hoveract = {

        timer: null,

        pageX: null,

        pageY: null

    },

    // determine zIndex

    zindex = function($t) {

        var zin = 0,

            $tt = $t;



        while (true) {

            zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);

            $tt = $tt.parent();

            if (!$tt || !$tt.length || $tt.prop('nodeName').toLowerCase() == 'body') {

                break;

            }

        }

        

        return zin;

    },

    // event handlers

    handle = {

        // abort anything

        abortevent: function(e){

            e.preventDefault();

            e.stopImmediatePropagation();

        },

        

        // contextmenu show dispatcher

        contextmenu: function(e) {

            var $this = $(this);

            // disable actual context-menu

            e.preventDefault();

            e.stopImmediatePropagation();

            

            // ignore right click trigger

            if (ignoreThisClick) {

                ignoreThisClick = false;

                return;

            }

            

            if (!$this.hasClass('context-menu-disabled')) {

                // theoretically need to fire a show event at <menu>

                // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus

                // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });

                // e.data.$menu.trigger(evt);

                

                $currentTrigger = $this;

                if (e.data.build) {

                    // dynamically build menu on invocation

                    $.extend(true, e.data, defaults, e.data.build($currentTrigger, e) || {});

                    op.create(e.data);

                }

                // show menu

                op.show.call($this, e.data, e.pageX, e.pageY);

            }

        },

        // contextMenu left-click trigger

        click: function(e) {

            e.preventDefault();

            e.stopImmediatePropagation();

            $(this).trigger(jQuery.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));

        },

        // contextMenu right-click trigger

        mousedown: function(e) {

            // register mouse down

            var $this = $(this);

            

            // hide any previous menus

            if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) {

                $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide');

            }

            

            // activate on right click

            if (e.button == 2) {

                $currentTrigger = $this.data('contextMenuActive', true);

            }

        },

        // contextMenu right-click trigger

        mouseup: function(e) {

            // show menu

            var $this = $(this);

            if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {

                e.preventDefault();

                e.stopImmediatePropagation();

                $currentTrigger = $this;

                $this.trigger(jQuery.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));

            }

            

            $this.removeData('contextMenuActive');

        },

        // contextMenu hover trigger

        mouseenter: function(e) {

            var $this = $(this),

                $related = $(e.relatedTarget),

                $document = $(document);

            

            // abort if we're coming from a menu

            if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {

                return;

            }

            

            // abort if a menu is shown

            if ($currentTrigger && $currentTrigger.length) {

                return;

            }

            

            hoveract.pageX = e.pageX;

            hoveract.pageY = e.pageY;

            hoveract.data = e.data;

            $document.on('mousemove.contextMenuShow', handle.mousemove);

            hoveract.timer = setTimeout(function() {

                hoveract.timer = null;

                $document.off('mousemove.contextMenuShow');

                $currentTrigger = $this;

                $this.trigger(jQuery.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY }));

            }, e.data.delay );

        },

        // contextMenu hover trigger

        mousemove: function(e) {

            hoveract.pageX = e.pageX;

            hoveract.pageY = e.pageY;

        },

        // contextMenu hover trigger

        mouseleave: function(e) {

            // abort if we're leaving for a menu

            var $related = $(e.relatedTarget);

            if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {

                return;

            }

            

            try {

                clearTimeout(hoveract.timer);

            } catch(e) {}

            

            hoveract.timer = null;

        },



        // ignore right click trigger

        ignoreRightClick: function(e) {

            if (e.button == 2) {

                ignoreThisClick = true;

            }

        },

        

        // click on layer to hide contextMenu

        layerClick: function(e) {

            var $this = $(this),

                root = $this.data('contextMenuRoot');

                

            e.preventDefault();

            e.stopImmediatePropagation();

            $this.remove();

            root.$menu.trigger('contextmenu:hide');

        },

        // key handled :hover

        keyStop: function(e, opt) {

            if (!opt.isInput) {

                e.preventDefault();

            }

            

            e.stopPropagation();

        },

        key: function(e) {

            var opt = $currentTrigger.data('contextMenu') || {},

                $children = opt.$menu.children(),

                $round;



            switch (e.keyCode) {

                case 9:

                case 38: // up

                    handle.keyStop(e, opt);

                    // if keyCode is [38 (up)] or [9 (tab) with shift]

                    if (opt.isInput) {

                        if (e.keyCode == 9 && e.shiftKey) {

                            e.preventDefault();

                            opt.$selected && opt.$selected.find('input, textarea, select').blur();

                            opt.$menu.trigger('prevcommand');

                            return;

                        } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {

                            // checkboxes don't capture this key

                            e.preventDefault();

                            return;

                        }

                    } else if (e.keyCode != 9 || e.shiftKey) {

                        opt.$menu.trigger('prevcommand');

                        return;

                    }

                    

                case 9: // tab

                case 40: // down

                    handle.keyStop(e, opt);

                    if (opt.isInput) {

                        if (e.keyCode == 9) {

                            e.preventDefault();

                            opt.$selected && opt.$selected.find('input, textarea, select').blur();

                            opt.$menu.trigger('nextcommand');

                            return;

                        } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {

                            // checkboxes don't capture this key

                            e.preventDefault();

                            return;

                        }

                    } else {

                        opt.$menu.trigger('nextcommand');

                        return;

                    }

                    break;

                

                case 37: // left

                    handle.keyStop(e, opt);

                    if (opt.isInput || !opt.$selected || !opt.$selected.length) {

                        break;

                    }

                

                    if (!opt.$selected.parent().hasClass('context-menu-root')) {

                        var $parent = opt.$selected.parent().parent();

                        opt.$selected.trigger('contextmenu:blur');

                        opt.$selected = $parent;

                        return;

                    }

                    break;

                    

                case 39: // right

                    handle.keyStop(e, opt);

                    if (opt.isInput || !opt.$selected || !opt.$selected.length) {

                        break;

                    }

                    

                    var itemdata = opt.$selected.data('contextMenu') || {};

                    if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) {

                        opt.$selected = null;

                        itemdata.$selected = null;

                        itemdata.$menu.trigger('nextcommand');

                        return;

                    }

                    break;

                    

                case 13: // enter

                    handle.keyStop(e, opt);

                    if (opt.isInput) {

                        if (opt.$selected && !opt.$selected.is(':textarea, :select')) {

                            e.preventDefault();

                            return;

                        }

                        break;

                    }

                    opt.$selected && opt.$selected.trigger('mouseup');

                    return;

                    

                case 32: // space

                    // prevent browser from scrolling down while menu is visible

                    handle.keyStop(e, opt);

                    return;

                    

                case 27: // esc

                    handle.keyStop(e, opt);

                    opt.$menu.trigger('contextmenu:hide');

                    return;

                    

                default: // 0-9, a-z

                    var k = (String.fromCharCode(e.keyCode)).toUpperCase();

                    if (opt.accesskeys[k]) {

                        // according to the specs accesskeys must be invoked immediately

                        opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu

                            ? 'contextmenu:focus'

                            : 'mouseup'

                        );

                        return;

                    }

                    break;

            }

            // pass event to selected item, 

            // stop propagation to avoid endless recursion

            e.stopPropagation();

            opt.$selected && opt.$selected.trigger(e);

        },



        // select previous possible command in menu

        prevItem: function(e) {

            e.stopPropagation();

            var opt = $(this).data('contextMenu') || {};



            // obtain currently selected menu

            if (opt.$selected) {

                var $s = opt.$selected;

                opt = opt.$selected.parent().data('contextMenu') || {};

                opt.$selected = $s;

            }

            

            var $children = opt.$menu.children(),

                $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(),

                $round = $prev;

            

            // skip disabled

            while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) {

                if ($prev.prev().length) {

                    $prev = $prev.prev();

                } else {

                    $prev = $children.last();

                }

                if ($prev.is($round)) {

                    // break endless loop

                    return;

                }

            }

            

            // leave current

            if (opt.$selected) {

                handle.itemMouseleave.call(opt.$selected.get(0), e);

            }

            

            // activate next

            handle.itemMouseenter.call($prev.get(0), e);

            

            // focus input

            var $input = $prev.find('input, textarea, select');

            if ($input.length) {

                $input.focus();

            }

        },

        // select next possible command in menu

        nextItem: function(e) {

            e.stopPropagation();

            var opt = $(this).data('contextMenu') || {};



            // obtain currently selected menu

            if (opt.$selected) {

                var $s = opt.$selected;

                opt = opt.$selected.parent().data('contextMenu') || {};

                opt.$selected = $s;

            }



            var $children = opt.$menu.children(),

                $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(),

                $round = $next;



            // skip disabled

            while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) {

                if ($next.next().length) {

                    $next = $next.next();

                } else {

                    $next = $children.first();

                }

                if ($next.is($round)) {

                    // break endless loop

                    return;

                }

            }

            

            // leave current

            if (opt.$selected) {

                handle.itemMouseleave.call(opt.$selected.get(0), e);

            }

            

            // activate next

            handle.itemMouseenter.call($next.get(0), e);

            

            // focus input

            var $input = $next.find('input, textarea, select');

            if ($input.length) {

                $input.focus();

            }

        },

        

        // flag that we're inside an input so the key handler can act accordingly

        focusInput: function(e) {

            var $this = $(this).closest('.context-menu-item'),

                data = $this.data(),

                opt = data.contextMenu,

                root = data.contextMenuRoot;



            root.$selected = opt.$selected = $this;

            root.isInput = opt.isInput = true;

        },

        // flag that we're inside an input so the key handler can act accordingly

        blurInput: function(e) {

            var $this = $(this).closest('.context-menu-item'),

                data = $this.data(),

                opt = data.contextMenu,

                root = data.contextMenuRoot;



            root.isInput = opt.isInput = false;

        },

        

        // :hover on menu

        menuMouseenter: function(e) {

            var root = $(this).data().contextMenuRoot;

            root.hovering = true;

        },

        // :hover on menu

        menuMouseleave: function(e) {

            var root = $(this).data().contextMenuRoot;

            if (root.$layer && root.$layer.is(e.relatedTarget)) {

                root.hovering = false;

            }

        },

        

        

        // :hover done manually so key handling is possible

        itemMouseenter: function(e) {

            var $this = $(this),

                data = $this.data(),

                opt = data.contextMenu,

                root = data.contextMenuRoot;

            

            root.hovering = true;



            // abort if we're re-entering

            if (e && root.$layer && root.$layer.is(e.relatedTarget)) {

                e.preventDefault();

                e.stopImmediatePropagation();

            }



            // make sure only one item is selected

            (opt.$menu ? opt : root).$menu

                .children('.hover').trigger('contextmenu:blur');



            if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) {

                opt.$selected = null;

                return;

            }

            

            $this.trigger('contextmenu:focus');

        },

        // :hover done manually so key handling is possible

        itemMouseleave: function(e) {

            var $this = $(this),

                data = $this.data(),

                opt = data.contextMenu,

                root = data.contextMenuRoot;



            if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) {

                root.$selected && root.$selected.trigger('contextmenu:blur');

                e.preventDefault();

                e.stopImmediatePropagation();

                root.$selected = opt.$selected = opt.$node;

                return;

            }

            

            $this.trigger('contextmenu:blur');

        },

        // contextMenu item click

        itemClick: function(e) {

            var $this = $(this),

                data = $this.data(),

                opt = data.contextMenu,

                root = data.contextMenuRoot,

                key = data.contextMenuKey,

                callback;



            // abort if the key is unknown or disabled

            if (!opt.items[key] || $this.hasClass('disabled')) {

                return;

            }



            e.preventDefault();

            e.stopImmediatePropagation();



            if ($.isFunction(root.callbacks[key])) {

                // item-specific callback

                callback = root.callbacks[key];

            } else if ($.isFunction(root.callback)) {

                // default callback

                callback = root.callback;                

            } else {

                // no callback, no action

                return;

            }



            // hide menu if callback doesn't stop that

            if (callback.call(root.$trigger, key, root) !== false) {

                root.$menu.trigger('contextmenu:hide');

            } else {

                op.update.call(root.$trigger, root);

            }

        },

        // ignore click events on input elements

        inputClick: function(e) {

            e.stopImmediatePropagation();

        },

        

        // hide <menu>

        hideMenu: function(e) {

            var root = $(this).data('contextMenuRoot');

            op.hide.call(root.$trigger, root);

        },

        // focus <command>

        focusItem: function(e) {

            e.stopPropagation();

            var $this = $(this),

                data = $this.data(),

                opt = data.contextMenu,

                root = data.contextMenuRoot;



            $this.addClass('hover')

                .siblings('.hover').trigger('contextmenu:blur');

            

            // remember selected

            opt.$selected = root.$selected = $this;

            

            // position sub-menu - do after show so dumb $.ui.position can keep up

            if (opt.$node) {

                root.positionSubmenu.call(opt.$node, opt.$menu);

            }

        },

        // blur <command>

        blurItem: function(e) {

            e.stopPropagation();

            var $this = $(this),

                data = $this.data(),

                opt = data.contextMenu,

                root = data.contextMenuRoot;

            

            $this.removeClass('hover');

            opt.$selected = null;

        }

    },

    // operations

    op = {

        show: function(opt, x, y) {

            var $this = $(this),

                offset,

                css = {};



            // hide any open menus

            $('#context-menu-layer').trigger('mousedown');



            // show event

            if (opt.events.show.call($this, opt) === false) {

                $currentTrigger = null;

                return;

            }

            

            // backreference for callbacks

            opt.$trigger = $this;



            // create or update context menu

            op.update.call($this, opt);

            

            // position menu

            opt.position.call($this, opt, x, y);



            // make sure we're in front

            if (opt.zIndex) {

                css.zIndex = zindex($this) + opt.zIndex;

            }

            

            // add layer

            op.layer.call(opt.$menu, opt, css.zIndex);

            

            // adjust sub-menu zIndexes

            opt.$menu.find('ul').css('zIndex', css.zIndex + 1);

            

            // position and show context menu

            opt.$menu.css( css )[opt.animation.show](opt.animation.duration);

            // make options available

            $this.data('contextMenu', opt);

            // register key handler

            $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key);

            // register autoHide handler

            if (opt.autoHide) {

                // trigger element coordinates

                var pos = $this.position();

                pos.right = pos.left + $this.outerWidth();

                pos.bottom = pos.top + this.outerHeight();

                // mouse position handler

                $(document).on('mousemove.contextMenuAutoHide', function(e) {

                    if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {

                        // if mouse in menu...¦

                        opt.$layer.trigger('mousedown');

                    }

                });

            }

        },

        hide: function(opt) {

            var $this = $(this);

            if (!opt) {

                opt = $this.data('contextMenu') || {};

            }

            

            // hide event

            if (opt.events && opt.events.hide.call($this, opt) === false) {

                return;

            }

            

            if (opt.$layer) {

                try {

                    opt.$layer.remove();

                    delete opt.$layer;

                } catch(e) {

                    opt.$layer = null;

                }

            }

            

            // remove handle

            $currentTrigger = null;

            // remove selected

            opt.$menu.find('.hover').trigger('contextmenu:blur');

            opt.$selected = null;

            // unregister key and mouse handlers

            //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705

            $(document).off('.contextMenuAutoHide').off('keydown.contextMenu');

            // hide menu

            opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration);

            

            // tear down dynamically built menu

            if (opt.build) {

                opt.$menu.remove();

                $.each(opt, function(key, value) {

                    switch (key) {

                        case 'ns':

                        case 'selector':

                        case 'build':

                        case 'trigger':

                        case 'ignoreRightClick':

                            return true;



                        default:

                            opt[key] = undefined;

                            try {

                                delete opt[key];

                            } catch (e) {}

                            return true;

                   }

                });

            }

        },

        create: function(opt, root) {

            if (root === undefined) {

                root = opt;

            }

            // create contextMenu

            opt.$menu = $('<ul class="context-menu-list ' + (opt.className || "") + '"></ul>').data({

                'contextMenu': opt,

                'contextMenuRoot': root

            });

            

            $.each(['callbacks', 'commands', 'inputs'], function(i,k){

                opt[k] = {};

                if (!root[k]) {

                    root[k] = {};

                }

            });

            

            root.accesskeys || (root.accesskeys = {});

            

            // create contextMenu items

            $.each(opt.items, function(key, item){

                var $t = $('<li class="context-menu-item ' + (item.className || "") +'"></li>'),

                    $label = null,

                    $input = null;

                

                item.$node = $t.data({

                    'contextMenu': opt,

                    'contextMenuRoot': root,

                    'contextMenuKey': key

                });

                

                // register accesskey

                // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that

                if (item.accesskey) {

                    var aks = splitAccesskey(item.accesskey);

                    for (var i=0, ak; ak = aks[i]; i++) {

                        if (!root.accesskeys[ak]) {

                            root.accesskeys[ak] = item;

                            item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '<span class="context-menu-accesskey">$1</span>');

                            break;

                        }

                    }

                }

                

                if (typeof item == "string") {

                    $t.addClass('context-menu-separator not-selectable');

                } else if (item.type && types[item.type]) {

                    // run custom type handler

                    types[item.type].call($t, item, opt, root);

                    // register commands

                    $.each([opt, root], function(i,k){

                        k.commands[key] = item;

                        if ($.isFunction(item.callback)) {

                            k.callbacks[key] = item.callback;

                        }

                    });

                } else {

                    // add label for input

                    if (item.type == 'html') {

                        $t.addClass('context-menu-html not-selectable');

                    } else if (item.type) {

                        $label = $('<label></label>').appendTo($t);

                        $('<span></span>').html(item._name || item.name).appendTo($label);

                        $t.addClass('context-menu-input');

                        opt.hasTypes = true;

                        $.each([opt, root], function(i,k){

                            k.commands[key] = item;

                            k.inputs[key] = item;

                        });

                    } else if (item.items) {

                        item.type = 'sub';

                    }

                

                    switch (item.type) {

                        case 'text':

                            $input = $('<input type="text" value="1" name="context-menu-input-'+ key +'" value="">')

                                .val(item.value || "").appendTo($label);

                            break;

                    

                        case 'textarea':

                            $input = $('<textarea name="context-menu-input-'+ key +'"></textarea>')

                                .val(item.value || "").appendTo($label);



                            if (item.height) {

                                $input.height(item.height);

                            }

                            break;



                        case 'checkbox':

                            $input = $('<input type="checkbox" value="1" name="context-menu-input-'+ key +'" value="">')

                                .val(item.value || "").prop("checked", !!item.selected).prependTo($label);

                            break;



                        case 'radio':

                            $input = $('<input type="radio" value="1" name="context-menu-input-'+ item.radio +'" value="">')

                                .val(item.value || "").prop("checked", !!item.selected).prependTo($label);

                            break;

                    

                        case 'select':

                            $input = $('<select name="context-menu-input-'+ key +'">').appendTo($label);

                            if (item.options) {

                                $.each(item.options, function(value, text) {

                                    $('<option></option>').val(value).text(text).appendTo($input);

                                });

                                $input.val(item.selected);

                            }

                            break;

                        

                        case 'sub':

                            $('<span></span>').html(item._name || item.name).appendTo($t);

                            item.appendTo = item.$node;

                            op.create(item, root);

                            $t.data('contextMenu', item).addClass('context-menu-submenu');

                            item.callback = null;

                            break;

                        

                        case 'html':

                            $(item.html).appendTo($t);

                            break;

                        

                        default:

                            $.each([opt, root], function(i,k){

                                k.commands[key] = item;

                                if ($.isFunction(item.callback)) {

                                    k.callbacks[key] = item.callback;

                                }

                            });

                            

                            $('<span></span>').html(item._name || item.name || "").appendTo($t);

                            break;

                    }

                    

                    // disable key listener in <input>

                    if (item.type && item.type != 'sub' && item.type != 'html') {

                        $input

                            .on('focus', handle.focusInput)

                            .on('blur', handle.blurInput);

                        

                        if (item.events) {

                            $input.on(item.events);

                        }

                    }

                

                    // add icons

                    if (item.icon) {

                        $t.addClass("icon icon-" + item.icon);

                    }

                }

                

                // cache contained elements

                item.$input = $input;

                item.$label = $label;



                // attach item to menu

                $t.appendTo(opt.$menu);

                

                // Disable text selection

                if (!opt.hasTypes) {

                    if($.browser.msie) {

                        $t.on('selectstart.disableTextSelect', handle.abortevent);

                    } else if(!$.browser.mozilla) {

                        $t.on('mousedown.disableTextSelect', handle.abortevent);

                    }

                }

            });

            // attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element)

            if (!opt.$node) {

                opt.$menu.css('display', 'none').addClass('context-menu-root');

            }

            opt.$menu.appendTo(opt.appendTo || document.body);

        },

        update: function(opt, root) {

            var $this = this;

            if (root === undefined) {

                root = opt;

                // determine widths of submenus, as CSS won't grow them automatically

                // position:absolute > position:absolute; min-width:100; max-width:200; results in width: 100;

                // kinda sucks hard...

                opt.$menu.find('ul').andSelf().css({position: 'static', display: 'block'}).each(function(){

                    var $this = $(this);

                    $this.width($this.css('position', 'absolute').width())

                        .css('position', 'static');

                }).css({position: '', display: ''});

            }

            // re-check disabled for each item

            opt.$menu.children().each(function(){

                var $item = $(this),

                    key = $item.data('contextMenuKey'),

                    item = opt.items[key],

                    disabled = ($.isFunction(item.disabled) && item.disabled.call($this, key, root)) || item.disabled === true;



                // dis- / enable item

                $item[disabled ? 'addClass' : 'removeClass']('disabled');

                

                if (item.type) {

                    // dis- / enable input elements

                    $item.find('input, select, textarea').prop('disabled', disabled);

                    

                    // update input states

                    switch (item.type) {

                        case 'text':

                        case 'textarea':

                            item.$input.val(item.value || "");

                            break;

                            

                        case 'checkbox':

                        case 'radio':

                            item.$input.val(item.value || "").prop('checked', !!item.selected);

                            break;

                            

                        case 'select':

                            item.$input.val(item.selected || "");

                            break;

                    }

                }

                

                if (item.$menu) {

                    // update sub-menu

                    op.update.call($this, item, root);

                }

            });

        },

        layer: function(opt, zIndex) {

            // add transparent layer for click area

            // filter and background for Internet Explorer, Issue #23

            return opt.$layer = $('<div id="context-menu-layer" style="position:fixed; z-index:' + zIndex + '; top:0; left:0; opacity: 0; filter: alpha(opacity=0); background-color: #000;"></div>')

                .css({height: $win.height(), width: $win.width(), display: 'block'})

                .data('contextMenuRoot', opt)

                .insertBefore(this)

                .on('mousedown', handle.layerClick);

        }

    };



// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key

function splitAccesskey(val) {

    var t = val.split(/\s+/),

        keys = [];

        

    for (var i=0, k; k = t[i]; i++) {

        k = k[0].toUpperCase(); // first character only

        // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.

        // a map to look up already used access keys would be nice

        keys.push(k);

    }

    

    return keys;

}



// handle contextMenu triggers

$.fn.contextMenu = function(operation) {

    if (operation === undefined) {

        this.first().trigger('contextmenu');

    } else if (operation.x && operation.y) {

        this.first().trigger(jQuery.Event("contextmenu", {pageX: operation.x, pageY: operation.y}));

    } else if (operation === "hide") {

        var $menu = this.data('contextMenu').$menu;

        $menu && $menu.trigger('contextmenu:hide');

    } else if (operation) {

        this.removeClass('context-menu-disabled');

    } else if (!operation) {

        this.addClass('context-menu-disabled');

    }

    

    return this;

};



// manage contextMenu instances

$.contextMenu = function(operation, options) {

    if (typeof operation != 'string') {

        options = operation;

        operation = 'create';

    }

    

    if (typeof options == 'string') {

        options = {selector: options};

    } else if (options === undefined) {

        options = {};

    }

    

    // merge with default options

    var o = $.extend(true, {}, defaults, options || {}),

        $body = $body = $(document);

    

    switch (operation) {

        case 'create':

            // no selector no joy

            if (!o.selector) {

                throw new Error('No selector specified');

            }

            // make sure internal classes are not bound to

            if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) {

                throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className');

            }

            if (!o.build && (!o.items || $.isEmptyObject(o.items))) {

                throw new Error('No Items sepcified');

            }

            counter ++;

            o.ns = '.contextMenu' + counter;

            namespaces[o.selector] = o.ns;

            menus[o.ns] = o;

            

            if (!initialized) {

                // make sure item click is registered first

                $body

                    .on({

                        'contextmenu:hide.contextMenu': handle.hideMenu,

                        'prevcommand.contextMenu': handle.prevItem,

                        'nextcommand.contextMenu': handle.nextItem,

                        'contextmenu.contextMenu': handle.abortevent,

                        'mouseenter.contextMenu': handle.menuMouseenter,

                        'mouseleave.contextMenu': handle.menuMouseleave

                    }, '.context-menu-list')

                    .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick)

                    .on({

                        'mouseup.contextMenu': handle.itemClick,

                        'contextmenu:focus.contextMenu': handle.focusItem,

                        'contextmenu:blur.contextMenu': handle.blurItem,

                        'contextmenu.contextMenu': handle.abortevent,

                        'mouseenter.contextMenu': handle.itemMouseenter,

                        'mouseleave.contextMenu': handle.itemMouseleave

                    }, '.context-menu-item');



                initialized = true;

            }

            

            // engage native contextmenu event

            $body

                .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu);

            

            switch (o.trigger) {

                case 'hover':

                        $body

                            .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter)

                            .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave);                    

                    break;

                    

                case 'left':

                        $body.on('click' + o.ns, o.selector, o, handle.click);

                    break;

                /*

                default:

                    // http://www.quirksmode.org/dom/events/contextmenu.html

                    $body

                        .on('mousedown' + o.ns, o.selector, o, handle.mousedown)

                        .on('mouseup' + o.ns, o.selector, o, handle.mouseup);

                    break;

                */

            }

            

            if (o.trigger != 'hover' && o.ignoreRightClick) {

                $body.on('mousedown' + o.ns, o.selector, handle.ignoreRightClick);

            }



            // create menu

            if (!o.build) {

                op.create(o);

            }

            break;

        

        case 'destroy':

            if (!o.selector) {

                $body.off('.contextMenu .contextMenuAutoHide');

                $.each(namespaces, function(key, value) {

                    $body.off(value);

                });

                

                namespaces = {};

                menus = {};

                counter = 0;

                initialized = false;

                

                $('.context-menu-list').remove();

            } else if (namespaces[o.selector]) {

                try {

                    if (menus[namespaces[o.selector]].$menu) {

                        menus[namespaces[o.selector]].$menu.remove();

                    }

                    

                    delete menus[namespaces[o.selector]];

                } catch(e) {

                    menus[namespaces[o.selector]] = null;

                }

                

                $body.off(namespaces[o.selector]);

            }

            break;

        

        case 'html5':

            // if <command> or <menuitem> are not handled by the browser,

            // or options was a bool true,

            // initialize $.contextMenu for them

            if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) {

                $('menu[type="context"]').each(function() {

                    if (this.id) {

                        $.contextMenu({

                            selector: '[contextmenu=' + this.id +']',

                            items: $.contextMenu.fromMenu(this)

                        });

                    }

                }).css('display', 'none');

            }

            break;

        

        default:

            throw new Error('Unknown operation "' + operation + '"');

    }

    

    return this;

};



// import values into <input> commands

$.contextMenu.setInputValues = function(opt, data) {

    if (data === undefined) {

        data = {};

    }

    

    $.each(opt.inputs, function(key, item) {

        switch (item.type) {

            case 'text':

            case 'textarea':

                item.value = data[key] || "";

                break;



            case 'checkbox':

                item.selected = data[key] ? true : false;

                break;

                

            case 'radio':

                item.selected = (data[item.radio] || "") == item.value ? true : false;

                break;

            

            case 'select':

                item.selected = data[key] || "";

                break;

        }

    });

};



// export values from <input> commands

$.contextMenu.getInputValues = function(opt, data) {

    if (data === undefined) {

        data = {};

    }

    

    $.each(opt.inputs, function(key, item) {

        switch (item.type) {

            case 'text':

            case 'textarea':

            case 'select':

                data[key] = item.$input.val();

                break;



            case 'checkbox':

                data[key] = item.$input.prop('checked');

                break;

                

            case 'radio':

                if (item.$input.prop('checked')) {

                    data[item.radio] = item.value;

                }

                break;

        }

    });

    

    return data;

};



// find <label for="xyz">

function inputLabel(node) {

    return (node.id && $('label[for="'+ node.id +'"]').val()) || node.name;

}



// convert <menu> to items object

function menuChildren(items, $children, counter) {

    if (!counter) {

        counter = 0;

    }

    

    $children.each(function() {

        var $node = $(this),

            node = this,

            nodeName = this.nodeName.toLowerCase(),

            label,

            item;

        

        // extract <label><input>

        if (nodeName == 'label' && $node.find('input, textarea, select').length) {

            label = $node.text();

            $node = $node.children().first();

            node = $node.get(0);

            nodeName = node.nodeName.toLowerCase();

        }

        

        /*

         * <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items.

         * Not being the sadistic kind, $.contextMenu only accepts:

         * <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>.

         * Everything else will be imported as an html node, which is not interfaced with contextMenu.

         */

        

        // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command

        switch (nodeName) {

            // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element

            case 'menu':

                item = {name: $node.attr('label'), items: {}};

                menuChildren(item.items, $node.children(), counter);

                break;

            

            // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command

            case 'a':

            // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command

            case 'button':

                item = {

                    name: $node.text(),

                    disabled: !!$node.attr('disabled'),

                    callback: (function(){ return function(){ $node.click(); }; })()

                };

                break;

            

            // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command



            case 'menuitem':

            case 'command':

                switch ($node.attr('type')) {

                    case undefined:

                    case 'command':

                    case 'menuitem':

                        item = {

                            name: $node.attr('label'),

                            disabled: !!$node.attr('disabled'),

                            callback: (function(){ return function(){ $node.click(); }; })()

                        };

                        break;

                        

                    case 'checkbox':

                        item = {

                            type: 'checkbox',

                            disabled: !!$node.attr('disabled'),

                            name: $node.attr('label'),

                            selected: !!$node.attr('checked')

                        };

                        break;

                        

                    case 'radio':

                        item = {

                            type: 'radio',

                            disabled: !!$node.attr('disabled'),

                            name: $node.attr('label'),

                            radio: $node.attr('radiogroup'),

                            value: $node.attr('id'),

                            selected: !!$node.attr('checked')

                        };

                        break;

                        

                    default:

                        item = undefined;

                }

                break;

 

            case 'hr':

                item = '-------';

                break;

                

            case 'input':

                switch ($node.attr('type')) {

                    case 'text':

                        item = {

                            type: 'text',

                            name: label || inputLabel(node),

                            disabled: !!$node.attr('disabled'),

                            value: $node.val()

                        };

                        break;

                        

                    case 'checkbox':

                        item = {

                            type: 'checkbox',

                            name: label || inputLabel(node),

                            disabled: !!$node.attr('disabled'),

                            selected: !!$node.attr('checked')

                        };

                        break;

                        

                    case 'radio':

                        item = {

                            type: 'radio',

                            name: label || inputLabel(node),

                            disabled: !!$node.attr('disabled'),

                            radio: !!$node.attr('name'),

                            value: $node.val(),

                            selected: !!$node.attr('checked')

                        };

                        break;

                    

                    default:

                        item = undefined;

                        break;

                }

                break;

                

            case 'select':

                item = {

                    type: 'select',

                    name: label || inputLabel(node),

                    disabled: !!$node.attr('disabled'),

                    selected: $node.val(),

                    options: {}

                };

                $node.children().each(function(){

                    item.options[this.value] = $(this).text();

                });

                break;

                

            case 'textarea':

                item = {

                    type: 'textarea',

                    name: label || inputLabel(node),

                    disabled: !!$node.attr('disabled'),

                    value: $node.val()

                };

                break;

            

            case 'label':

                break;

            

            default:

                item = {type: 'html', html: $node.clone(true)};

                break;

        }

        

        if (item) {

            counter++;

            items['key' + counter] = item;

        }

    });

}



// convert html5 menu

$.contextMenu.fromMenu = function(element) {

    var $this = $(element),

        items = {};

        

    menuChildren(items, $this.children());

    

    return items;

};



// make defaults accessible

$.contextMenu.defaults = defaults;

$.contextMenu.types = types;



})(jQuery);

