--- /dev/null
+/*!\r
+ * jQuery contextMenu - Plugin for simple contextMenu handling\r
+ *\r
+ * Version: 1.5.13\r
+ *\r
+ * Authors: Rodney Rehm, Addy Osmani (patches for FF)\r
+ * Web: http://medialize.github.com/jQuery-contextMenu/\r
+ *\r
+ * Licensed under\r
+ * MIT License http://www.opensource.org/licenses/mit-license\r
+ * GPL v3 http://opensource.org/licenses/GPL-3.0\r
+ *\r
+ */\r
+\r
+(function($, undefined){\r
+ \r
+ // TODO: -\r
+ // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio\r
+ // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative\r
+\r
+// determine html5 compatibility\r
+$.support.htmlMenuitem = ('HTMLMenuItemElement' in window);\r
+$.support.htmlCommand = ('HTMLCommandElement' in window);\r
+\r
+var // currently active contextMenu trigger\r
+ $currentTrigger = null,\r
+ // is contextMenu initialized with at least one menu?\r
+ initialized = false,\r
+ // window handle\r
+ $win = $(window),\r
+ // number of registered menus\r
+ counter = 0,\r
+ // mapping selector to namespace\r
+ namespaces = {},\r
+ // mapping namespace to options\r
+ menus = {},\r
+ // custom command type handlers\r
+ types = {},\r
+ // default values\r
+ defaults = {\r
+ // selector of contextMenu trigger\r
+ selector: null,\r
+ // where to append the menu to\r
+ appendTo: null,\r
+ // method to trigger context menu ["right", "left", "hover"]\r
+ trigger: "right",\r
+ // hide menu when mouse leaves trigger / menu elements\r
+ autoHide: false,\r
+ // ms to wait before showing a hover-triggered context menu\r
+ delay: 200,\r
+ // determine position to show menu at\r
+ determinePosition: function($menu) {\r
+ // position to the lower middle of the trigger element\r
+ if ($.ui && $.ui.position) {\r
+ // .position() is provided as a jQuery UI utility\r
+ // (...and it won't work on hidden elements)\r
+ $menu.css('display', 'block').position({\r
+ my: "center top",\r
+ at: "center bottom",\r
+ of: this,\r
+ offset: "0 5",\r
+ collision: "fit"\r
+ }).css('display', 'none');\r
+ } else {\r
+ // determine contextMenu position\r
+ var offset = this.offset();\r
+ offset.top += this.outerHeight();\r
+ offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2;\r
+ $menu.css(offset);\r
+ }\r
+ },\r
+ // position menu\r
+ position: function(opt, x, y) {\r
+ var $this = this,\r
+ offset;\r
+ // determine contextMenu position\r
+ if (!x && !y) {\r
+ opt.determinePosition.call(this, opt.$menu);\r
+ return;\r
+ } else if (x === "maintain" && y === "maintain") {\r
+ // x and y must not be changed (after re-show on command click)\r
+ offset = opt.$menu.position();\r
+ } else {\r
+ // x and y are given (by mouse event)\r
+ var triggerIsFixed = opt.$trigger.parents().andSelf()\r
+ .filter(function() {\r
+ return $(this).css('position') == "fixed";\r
+ }).length;\r
+\r
+ if (triggerIsFixed) {\r
+ y -= $win.scrollTop();\r
+ x -= $win.scrollLeft();\r
+ }\r
+ offset = {top: y, left: x};\r
+ }\r
+ \r
+ // correct offset if viewport demands it\r
+ var bottom = $win.scrollTop() + $win.height(),\r
+ right = $win.scrollLeft() + $win.width(),\r
+ height = opt.$menu.height(),\r
+ width = opt.$menu.width();\r
+ \r
+ if (offset.top + height > bottom) {\r
+ offset.top -= height;\r
+ }\r
+ \r
+ if (offset.left + width > right) {\r
+ offset.left -= width;\r
+ }\r
+ \r
+ opt.$menu.css(offset);\r
+ },\r
+ // position the sub-menu\r
+ positionSubmenu: function($menu) {\r
+ if ($.ui && $.ui.position) {\r
+ // .position() is provided as a jQuery UI utility\r
+ // (...and it won't work on hidden elements)\r
+ $menu.css('display', 'block').position({\r
+ my: "left top",\r
+ at: "right top",\r
+ of: this,\r
+ collision: "fit"\r
+ }).css('display', '');\r
+ } else {\r
+ // determine contextMenu position\r
+ var offset = this.offset();\r
+ offset.top += 0;\r
+ offset.left += this.outerWidth();\r
+ $menu.css(offset);\r
+ }\r
+ },\r
+ // offset to add to zIndex\r
+ zIndex: 1,\r
+ // show hide animation settings\r
+ animation: {\r
+ duration: 50,\r
+ show: 'slideDown',\r
+ hide: 'slideUp'\r
+ },\r
+ // events\r
+ events: {\r
+ show: $.noop,\r
+ hide: $.noop\r
+ },\r
+ // default callback\r
+ callback: null,\r
+ // list of contextMenu items\r
+ items: {}\r
+ },\r
+ // mouse position for hover activation\r
+ hoveract = {\r
+ timer: null,\r
+ pageX: null,\r
+ pageY: null\r
+ },\r
+ // determine zIndex\r
+ zindex = function($t) {\r
+ var zin = 0,\r
+ $tt = $t;\r
+\r
+ while (true) {\r
+ zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);\r
+ $tt = $tt.parent();\r
+ if (!$tt || !$tt.length || $tt.prop('nodeName').toLowerCase() == 'body') {\r
+ break;\r
+ }\r
+ }\r
+ \r
+ return zin;\r
+ },\r
+ // event handlers\r
+ handle = {\r
+ // abort anything\r
+ abortevent: function(e){\r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+ },\r
+ \r
+ // contextmenu show dispatcher\r
+ contextmenu: function(e) {\r
+ var $this = $(this);\r
+ \r
+ // disable actual context-menu\r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+ \r
+ // abort native-triggered events unless we're triggering on right click\r
+ if (e.data.trigger != 'right' && e.originalEvent) {\r
+ return;\r
+ }\r
+ \r
+ if (!$this.hasClass('context-menu-disabled')) {\r
+ // theoretically need to fire a show event at <menu>\r
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus\r
+ // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });\r
+ // e.data.$menu.trigger(evt);\r
+ \r
+ $currentTrigger = $this;\r
+ if (e.data.build) {\r
+ var built = e.data.build($currentTrigger, e);\r
+ // abort if build() returned false\r
+ if (built === false) {\r
+ return;\r
+ }\r
+ \r
+ // dynamically build menu on invocation\r
+ e.data = $.extend(true, defaults, e.data, built || {});\r
+\r
+ // abort if there are no items to display\r
+ if (!e.data.items || $.isEmptyObject(e.data.items)) {\r
+ // Note: jQuery captures and ignores errors from event handlers\r
+ if (window.console) {\r
+ (console.error || console.log)("No items specified to show in contextMenu");\r
+ }\r
+ \r
+ throw new Error('No Items sepcified');\r
+ }\r
+ \r
+ // backreference for custom command type creation\r
+ e.data.$trigger = $currentTrigger;\r
+ \r
+ op.create(e.data);\r
+ }\r
+ // show menu\r
+ op.show.call($this, e.data, e.pageX, e.pageY);\r
+ }\r
+ },\r
+ // contextMenu left-click trigger\r
+ click: function(e) {\r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+ $(this).trigger(jQuery.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));\r
+ },\r
+ // contextMenu right-click trigger\r
+ mousedown: function(e) {\r
+ // register mouse down\r
+ var $this = $(this);\r
+ \r
+ // hide any previous menus\r
+ if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) {\r
+ $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide');\r
+ }\r
+ \r
+ // activate on right click\r
+ if (e.button == 2) {\r
+ $currentTrigger = $this.data('contextMenuActive', true);\r
+ }\r
+ },\r
+ // contextMenu right-click trigger\r
+ mouseup: function(e) {\r
+ // show menu\r
+ var $this = $(this);\r
+ if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {\r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+ $currentTrigger = $this;\r
+ $this.trigger(jQuery.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));\r
+ }\r
+ \r
+ $this.removeData('contextMenuActive');\r
+ },\r
+ // contextMenu hover trigger\r
+ mouseenter: function(e) {\r
+ var $this = $(this),\r
+ $related = $(e.relatedTarget),\r
+ $document = $(document);\r
+ \r
+ // abort if we're coming from a menu\r
+ if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {\r
+ return;\r
+ }\r
+ \r
+ // abort if a menu is shown\r
+ if ($currentTrigger && $currentTrigger.length) {\r
+ return;\r
+ }\r
+ \r
+ hoveract.pageX = e.pageX;\r
+ hoveract.pageY = e.pageY;\r
+ hoveract.data = e.data;\r
+ $document.on('mousemove.contextMenuShow', handle.mousemove);\r
+ hoveract.timer = setTimeout(function() {\r
+ hoveract.timer = null;\r
+ $document.off('mousemove.contextMenuShow');\r
+ $currentTrigger = $this;\r
+ $this.trigger(jQuery.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY }));\r
+ }, e.data.delay );\r
+ },\r
+ // contextMenu hover trigger\r
+ mousemove: function(e) {\r
+ hoveract.pageX = e.pageX;\r
+ hoveract.pageY = e.pageY;\r
+ },\r
+ // contextMenu hover trigger\r
+ mouseleave: function(e) {\r
+ // abort if we're leaving for a menu\r
+ var $related = $(e.relatedTarget);\r
+ if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {\r
+ return;\r
+ }\r
+ \r
+ try {\r
+ clearTimeout(hoveract.timer);\r
+ } catch(e) {}\r
+ \r
+ hoveract.timer = null;\r
+ },\r
+ \r
+ // click on layer to hide contextMenu\r
+ layerClick: function(e) {\r
+ var $this = $(this),\r
+ root = $this.data('contextMenuRoot');\r
+ \r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+ \r
+ if ((root.trigger == 'left' && e.button == 0) || (root.trigger == 'right' && e.button == 2)) {\r
+ var offset = root.$trigger.offset();\r
+ \r
+ // while this looks kinda awful, it's the best way to avoid\r
+ // unnecessarily calculating any positions\r
+ offset.top += $(window).scrollTop();\r
+ if (offset.top <= e.pageY) {\r
+ offset.left += $(window).scrollLeft();\r
+ if (offset.left <= e.pageX) {\r
+ offset.bottom = offset.top + root.$trigger.outerHeight();\r
+ if (offset.bottom >= e.pageY) {\r
+ offset.right = offset.left + root.$trigger.outerWidth();\r
+ if (offset.right >= e.pageX) {\r
+ // reposition\r
+ root.position.call(root.$trigger, root, e.pageX, e.pageY);\r
+ return;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ } \r
+ \r
+ // remove only after mouseup has completed\r
+ $this.on('mouseup', function(e) {\r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+ root.$menu.trigger('contextmenu:hide');\r
+ });\r
+ },\r
+ // key handled :hover\r
+ keyStop: function(e, opt) {\r
+ if (!opt.isInput) {\r
+ e.preventDefault();\r
+ }\r
+ \r
+ e.stopPropagation();\r
+ },\r
+ key: function(e) {\r
+ var opt = $currentTrigger.data('contextMenu') || {},\r
+ $children = opt.$menu.children(),\r
+ $round;\r
+\r
+ switch (e.keyCode) {\r
+ case 9:\r
+ case 38: // up\r
+ handle.keyStop(e, opt);\r
+ // if keyCode is [38 (up)] or [9 (tab) with shift]\r
+ if (opt.isInput) {\r
+ if (e.keyCode == 9 && e.shiftKey) {\r
+ e.preventDefault();\r
+ opt.$selected && opt.$selected.find('input, textarea, select').blur();\r
+ opt.$menu.trigger('prevcommand');\r
+ return;\r
+ } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {\r
+ // checkboxes don't capture this key\r
+ e.preventDefault();\r
+ return;\r
+ }\r
+ } else if (e.keyCode != 9 || e.shiftKey) {\r
+ opt.$menu.trigger('prevcommand');\r
+ return;\r
+ }\r
+ \r
+ case 9: // tab\r
+ case 40: // down\r
+ handle.keyStop(e, opt);\r
+ if (opt.isInput) {\r
+ if (e.keyCode == 9) {\r
+ e.preventDefault();\r
+ opt.$selected && opt.$selected.find('input, textarea, select').blur();\r
+ opt.$menu.trigger('nextcommand');\r
+ return;\r
+ } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {\r
+ // checkboxes don't capture this key\r
+ e.preventDefault();\r
+ return;\r
+ }\r
+ } else {\r
+ opt.$menu.trigger('nextcommand');\r
+ return;\r
+ }\r
+ break;\r
+ \r
+ case 37: // left\r
+ handle.keyStop(e, opt);\r
+ if (opt.isInput || !opt.$selected || !opt.$selected.length) {\r
+ break;\r
+ }\r
+ \r
+ if (!opt.$selected.parent().hasClass('context-menu-root')) {\r
+ var $parent = opt.$selected.parent().parent();\r
+ opt.$selected.trigger('contextmenu:blur');\r
+ opt.$selected = $parent;\r
+ return;\r
+ }\r
+ break;\r
+ \r
+ case 39: // right\r
+ handle.keyStop(e, opt);\r
+ if (opt.isInput || !opt.$selected || !opt.$selected.length) {\r
+ break;\r
+ }\r
+ \r
+ var itemdata = opt.$selected.data('contextMenu') || {};\r
+ if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) {\r
+ opt.$selected = null;\r
+ itemdata.$selected = null;\r
+ itemdata.$menu.trigger('nextcommand');\r
+ return;\r
+ }\r
+ break;\r
+ \r
+ case 35: // end\r
+ case 36: // home\r
+ if (opt.$selected && opt.$selected.find('input, textarea, select').length) {\r
+ return;\r
+ } else {\r
+ (opt.$selected && opt.$selected.parent() || opt.$menu)\r
+ .children(':not(.disabled, .not-selectable)')[e.keyCode == 36 ? 'first' : 'last']()\r
+ .trigger('contextmenu:focus');\r
+ e.preventDefault();\r
+ return;\r
+ }\r
+ break;\r
+ \r
+ case 13: // enter\r
+ handle.keyStop(e, opt);\r
+ if (opt.isInput) {\r
+ if (opt.$selected && !opt.$selected.is('textarea, select')) {\r
+ e.preventDefault();\r
+ return;\r
+ }\r
+ break;\r
+ }\r
+ opt.$selected && opt.$selected.trigger('mouseup');\r
+ return;\r
+ \r
+ case 32: // space\r
+ case 33: // page up\r
+ case 34: // page down\r
+ // prevent browser from scrolling down while menu is visible\r
+ handle.keyStop(e, opt);\r
+ return;\r
+ \r
+ case 27: // esc\r
+ handle.keyStop(e, opt);\r
+ opt.$menu.trigger('contextmenu:hide');\r
+ return;\r
+ \r
+ default: // 0-9, a-z\r
+ var k = (String.fromCharCode(e.keyCode)).toUpperCase();\r
+ if (opt.accesskeys[k]) {\r
+ // according to the specs accesskeys must be invoked immediately\r
+ opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu\r
+ ? 'contextmenu:focus'\r
+ : 'mouseup'\r
+ );\r
+ return;\r
+ }\r
+ break;\r
+ }\r
+ // pass event to selected item, \r
+ // stop propagation to avoid endless recursion\r
+ e.stopPropagation();\r
+ opt.$selected && opt.$selected.trigger(e);\r
+ },\r
+\r
+ // select previous possible command in menu\r
+ prevItem: function(e) {\r
+ e.stopPropagation();\r
+ var opt = $(this).data('contextMenu') || {};\r
+\r
+ // obtain currently selected menu\r
+ if (opt.$selected) {\r
+ var $s = opt.$selected;\r
+ opt = opt.$selected.parent().data('contextMenu') || {};\r
+ opt.$selected = $s;\r
+ }\r
+ \r
+ var $children = opt.$menu.children(),\r
+ $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(),\r
+ $round = $prev;\r
+ \r
+ // skip disabled\r
+ while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) {\r
+ if ($prev.prev().length) {\r
+ $prev = $prev.prev();\r
+ } else {\r
+ $prev = $children.last();\r
+ }\r
+ if ($prev.is($round)) {\r
+ // break endless loop\r
+ return;\r
+ }\r
+ }\r
+ \r
+ // leave current\r
+ if (opt.$selected) {\r
+ handle.itemMouseleave.call(opt.$selected.get(0), e);\r
+ }\r
+ \r
+ // activate next\r
+ handle.itemMouseenter.call($prev.get(0), e);\r
+ \r
+ // focus input\r
+ var $input = $prev.find('input, textarea, select');\r
+ if ($input.length) {\r
+ $input.focus();\r
+ }\r
+ },\r
+ // select next possible command in menu\r
+ nextItem: function(e) {\r
+ e.stopPropagation();\r
+ var opt = $(this).data('contextMenu') || {};\r
+\r
+ // obtain currently selected menu\r
+ if (opt.$selected) {\r
+ var $s = opt.$selected;\r
+ opt = opt.$selected.parent().data('contextMenu') || {};\r
+ opt.$selected = $s;\r
+ }\r
+\r
+ var $children = opt.$menu.children(),\r
+ $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(),\r
+ $round = $next;\r
+\r
+ // skip disabled\r
+ while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) {\r
+ if ($next.next().length) {\r
+ $next = $next.next();\r
+ } else {\r
+ $next = $children.first();\r
+ }\r
+ if ($next.is($round)) {\r
+ // break endless loop\r
+ return;\r
+ }\r
+ }\r
+ \r
+ // leave current\r
+ if (opt.$selected) {\r
+ handle.itemMouseleave.call(opt.$selected.get(0), e);\r
+ }\r
+ \r
+ // activate next\r
+ handle.itemMouseenter.call($next.get(0), e);\r
+ \r
+ // focus input\r
+ var $input = $next.find('input, textarea, select');\r
+ if ($input.length) {\r
+ $input.focus();\r
+ }\r
+ },\r
+ \r
+ // flag that we're inside an input so the key handler can act accordingly\r
+ focusInput: function(e) {\r
+ var $this = $(this).closest('.context-menu-item'),\r
+ data = $this.data(),\r
+ opt = data.contextMenu,\r
+ root = data.contextMenuRoot;\r
+\r
+ root.$selected = opt.$selected = $this;\r
+ root.isInput = opt.isInput = true;\r
+ },\r
+ // flag that we're inside an input so the key handler can act accordingly\r
+ blurInput: function(e) {\r
+ var $this = $(this).closest('.context-menu-item'),\r
+ data = $this.data(),\r
+ opt = data.contextMenu,\r
+ root = data.contextMenuRoot;\r
+\r
+ root.isInput = opt.isInput = false;\r
+ },\r
+ \r
+ // :hover on menu\r
+ menuMouseenter: function(e) {\r
+ var root = $(this).data().contextMenuRoot;\r
+ root.hovering = true;\r
+ },\r
+ // :hover on menu\r
+ menuMouseleave: function(e) {\r
+ var root = $(this).data().contextMenuRoot;\r
+ if (root.$layer && root.$layer.is(e.relatedTarget)) {\r
+ root.hovering = false;\r
+ }\r
+ },\r
+ \r
+ // :hover done manually so key handling is possible\r
+ itemMouseenter: function(e) {\r
+ var $this = $(this),\r
+ data = $this.data(),\r
+ opt = data.contextMenu,\r
+ root = data.contextMenuRoot;\r
+ \r
+ root.hovering = true;\r
+\r
+ // abort if we're re-entering\r
+ if (e && root.$layer && root.$layer.is(e.relatedTarget)) {\r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+ }\r
+\r
+ // make sure only one item is selected\r
+ (opt.$menu ? opt : root).$menu\r
+ .children('.hover').trigger('contextmenu:blur');\r
+\r
+ if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) {\r
+ opt.$selected = null;\r
+ return;\r
+ }\r
+ \r
+ $this.trigger('contextmenu:focus');\r
+ },\r
+ // :hover done manually so key handling is possible\r
+ itemMouseleave: function(e) {\r
+ var $this = $(this),\r
+ data = $this.data(),\r
+ opt = data.contextMenu,\r
+ root = data.contextMenuRoot;\r
+\r
+ if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) {\r
+ root.$selected && root.$selected.trigger('contextmenu:blur');\r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+ root.$selected = opt.$selected = opt.$node;\r
+ return;\r
+ }\r
+ \r
+ $this.trigger('contextmenu:blur');\r
+ },\r
+ // contextMenu item click\r
+ itemClick: function(e) {\r
+ var $this = $(this),\r
+ data = $this.data(),\r
+ opt = data.contextMenu,\r
+ root = data.contextMenuRoot,\r
+ key = data.contextMenuKey,\r
+ callback;\r
+\r
+ // abort if the key is unknown or disabled or is a menu\r
+ if (!opt.items[key] || $this.hasClass('disabled') || $this.hasClass('context-menu-submenu')) {\r
+ return;\r
+ }\r
+\r
+ e.preventDefault();\r
+ e.stopImmediatePropagation();\r
+\r
+ if ($.isFunction(root.callbacks[key])) {\r
+ // item-specific callback\r
+ callback = root.callbacks[key];\r
+ } else if ($.isFunction(root.callback)) {\r
+ // default callback\r
+ callback = root.callback; \r
+ } else {\r
+ // no callback, no action\r
+ return;\r
+ }\r
+\r
+ // hide menu if callback doesn't stop that\r
+ if (callback.call(root.$trigger, key, root) !== false) {\r
+ root.$menu.trigger('contextmenu:hide');\r
+ } else {\r
+ op.update.call(root.$trigger, root);\r
+ }\r
+ },\r
+ // ignore click events on input elements\r
+ inputClick: function(e) {\r
+ e.stopImmediatePropagation();\r
+ },\r
+ \r
+ // hide <menu>\r
+ hideMenu: function(e) {\r
+ var root = $(this).data('contextMenuRoot');\r
+ op.hide.call(root.$trigger, root);\r
+ },\r
+ // focus <command>\r
+ focusItem: function(e) {\r
+ e.stopPropagation();\r
+ var $this = $(this),\r
+ data = $this.data(),\r
+ opt = data.contextMenu,\r
+ root = data.contextMenuRoot;\r
+\r
+ $this.addClass('hover')\r
+ .siblings('.hover').trigger('contextmenu:blur');\r
+ \r
+ // remember selected\r
+ opt.$selected = root.$selected = $this;\r
+ \r
+ // position sub-menu - do after show so dumb $.ui.position can keep up\r
+ if (opt.$node) {\r
+ root.positionSubmenu.call(opt.$node, opt.$menu);\r
+ }\r
+ },\r
+ // blur <command>\r
+ blurItem: function(e) {\r
+ e.stopPropagation();\r
+ var $this = $(this),\r
+ data = $this.data(),\r
+ opt = data.contextMenu,\r
+ root = data.contextMenuRoot;\r
+ \r
+ $this.removeClass('hover');\r
+ opt.$selected = null;\r
+ }\r
+ },\r
+ // operations\r
+ op = {\r
+ show: function(opt, x, y) {\r
+ var $this = $(this),\r
+ offset,\r
+ css = {};\r
+\r
+ // hide any open menus\r
+ $('#context-menu-layer').trigger('mousedown');\r
+\r
+ // backreference for callbacks\r
+ opt.$trigger = $this;\r
+\r
+ // show event\r
+ if (opt.events.show.call($this, opt) === false) {\r
+ $currentTrigger = null;\r
+ return;\r
+ }\r
+ \r
+ // create or update context menu\r
+ op.update.call($this, opt);\r
+ \r
+ // position menu\r
+ opt.position.call($this, opt, x, y);\r
+\r
+ // make sure we're in front\r
+ if (opt.zIndex) {\r
+ css.zIndex = zindex($this) + opt.zIndex;\r
+ }\r
+ \r
+ // add layer\r
+ op.layer.call(opt.$menu, opt, css.zIndex);\r
+ \r
+ // adjust sub-menu zIndexes\r
+ opt.$menu.find('ul').css('zIndex', css.zIndex + 1);\r
+ \r
+ // position and show context menu\r
+ opt.$menu.css( css )[opt.animation.show](opt.animation.duration);\r
+ // make options available\r
+ $this.data('contextMenu', opt);\r
+ // register key handler\r
+ $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key);\r
+ // register autoHide handler\r
+ if (opt.autoHide) {\r
+ // trigger element coordinates\r
+ var pos = $this.position();\r
+ pos.right = pos.left + $this.outerWidth();\r
+ pos.bottom = pos.top + this.outerHeight();\r
+ // mouse position handler\r
+ $(document).on('mousemove.contextMenuAutoHide', function(e) {\r
+ if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {\r
+ // if mouse in menu...\r
+ opt.$layer.trigger('mousedown');\r
+ }\r
+ });\r
+ }\r
+ },\r
+ hide: function(opt) {\r
+ var $this = $(this);\r
+ if (!opt) {\r
+ opt = $this.data('contextMenu') || {};\r
+ }\r
+ \r
+ // hide event\r
+ if (opt.events && opt.events.hide.call($this, opt) === false) {\r
+ return;\r
+ }\r
+ \r
+ if (opt.$layer) {\r
+ // keep layer for a bit so the contextmenu event can be aborted properly by opera\r
+ setTimeout((function($layer){ return function(){\r
+ $layer.remove();\r
+ };\r
+ })(opt.$layer), 10);\r
+ \r
+ try {\r
+ delete opt.$layer;\r
+ } catch(e) {\r
+ opt.$layer = null;\r
+ }\r
+ }\r
+ \r
+ // remove handle\r
+ $currentTrigger = null;\r
+ // remove selected\r
+ opt.$menu.find('.hover').trigger('contextmenu:blur');\r
+ opt.$selected = null;\r
+ // unregister key and mouse handlers\r
+ //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705\r
+ $(document).off('.contextMenuAutoHide').off('keydown.contextMenu');\r
+ // hide menu\r
+ opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration);\r
+ \r
+ // tear down dynamically built menu\r
+ if (opt.build) {\r
+ opt.$menu.remove();\r
+ $.each(opt, function(key, value) {\r
+ switch (key) {\r
+ case 'ns':\r
+ case 'selector':\r
+ case 'build':\r
+ case 'trigger':\r
+ return true;\r
+\r
+ default:\r
+ opt[key] = undefined;\r
+ try {\r
+ delete opt[key];\r
+ } catch (e) {}\r
+ return true;\r
+ }\r
+ });\r
+ }\r
+ },\r
+ create: function(opt, root) {\r
+ if (root === undefined) {\r
+ root = opt;\r
+ }\r
+ // create contextMenu\r
+ opt.$menu = $('<ul class="context-menu-list ' + (opt.className || "") + '"></ul>').data({\r
+ 'contextMenu': opt,\r
+ 'contextMenuRoot': root\r
+ });\r
+ \r
+ $.each(['callbacks', 'commands', 'inputs'], function(i,k){\r
+ opt[k] = {};\r
+ if (!root[k]) {\r
+ root[k] = {};\r
+ }\r
+ });\r
+ \r
+ root.accesskeys || (root.accesskeys = {});\r
+ \r
+ // create contextMenu items\r
+ $.each(opt.items, function(key, item){\r
+ var $t = $('<li class="context-menu-item ' + (item.className || "") +'"></li>'),\r
+ $label = null,\r
+ $input = null;\r
+ \r
+ item.$node = $t.data({\r
+ 'contextMenu': opt,\r
+ 'contextMenuRoot': root,\r
+ 'contextMenuKey': key\r
+ });\r
+ \r
+ // register accesskey\r
+ // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that\r
+ if (item.accesskey) {\r
+ var aks = splitAccesskey(item.accesskey);\r
+ for (var i=0, ak; ak = aks[i]; i++) {\r
+ if (!root.accesskeys[ak]) {\r
+ root.accesskeys[ak] = item;\r
+ item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '<span class="context-menu-accesskey">$1</span>');\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ \r
+ if (typeof item == "string") {\r
+ $t.addClass('context-menu-separator not-selectable');\r
+ } else if (item.type && types[item.type]) {\r
+ // run custom type handler\r
+ types[item.type].call($t, item, opt, root);\r
+ // register commands\r
+ $.each([opt, root], function(i,k){\r
+ k.commands[key] = item;\r
+ if ($.isFunction(item.callback)) {\r
+ k.callbacks[key] = item.callback;\r
+ }\r
+ });\r
+ } else {\r
+ // add label for input\r
+ if (item.type == 'html') {\r
+ $t.addClass('context-menu-html not-selectable');\r
+ } else if (item.type) {\r
+ $label = $('<label></label>').appendTo($t);\r
+ $('<span></span>').html(item._name || item.name).appendTo($label);\r
+ $t.addClass('context-menu-input');\r
+ opt.hasTypes = true;\r
+ $.each([opt, root], function(i,k){\r
+ k.commands[key] = item;\r
+ k.inputs[key] = item;\r
+ });\r
+ } else if (item.items) {\r
+ item.type = 'sub';\r
+ }\r
+ \r
+ switch (item.type) {\r
+ case 'text':\r
+ $input = $('<input type="text" value="1" name="context-menu-input-'+ key +'" value="">')\r
+ .val(item.value || "").appendTo($label);\r
+ break;\r
+ \r
+ case 'textarea':\r
+ $input = $('<textarea name="context-menu-input-'+ key +'"></textarea>')\r
+ .val(item.value || "").appendTo($label);\r
+\r
+ if (item.height) {\r
+ $input.height(item.height);\r
+ }\r
+ break;\r
+\r
+ case 'checkbox':\r
+ $input = $('<input type="checkbox" value="1" name="context-menu-input-'+ key +'" value="">')\r
+ .val(item.value || "").prop("checked", !!item.selected).prependTo($label);\r
+ break;\r
+\r
+ case 'radio':\r
+ $input = $('<input type="radio" value="1" name="context-menu-input-'+ item.radio +'" value="">')\r
+ .val(item.value || "").prop("checked", !!item.selected).prependTo($label);\r
+ break;\r
+ \r
+ case 'select':\r
+ $input = $('<select name="context-menu-input-'+ key +'">').appendTo($label);\r
+ if (item.options) {\r
+ $.each(item.options, function(value, text) {\r
+ $('<option></option>').val(value).text(text).appendTo($input);\r
+ });\r
+ $input.val(item.selected);\r
+ }\r
+ break;\r
+ \r
+ case 'sub':\r
+ $('<span></span>').html(item._name || item.name).appendTo($t);\r
+ item.appendTo = item.$node;\r
+ op.create(item, root);\r
+ $t.data('contextMenu', item).addClass('context-menu-submenu');\r
+ item.callback = null;\r
+ break;\r
+ \r
+ case 'html':\r
+ $(item.html).appendTo($t);\r
+ break;\r
+ \r
+ default:\r
+ $.each([opt, root], function(i,k){\r
+ k.commands[key] = item;\r
+ if ($.isFunction(item.callback)) {\r
+ k.callbacks[key] = item.callback;\r
+ }\r
+ });\r
+ \r
+ $('<span></span>').html(item._name || item.name || "").appendTo($t);\r
+ break;\r
+ }\r
+ \r
+ // disable key listener in <input>\r
+ if (item.type && item.type != 'sub' && item.type != 'html') {\r
+ $input\r
+ .on('focus', handle.focusInput)\r
+ .on('blur', handle.blurInput);\r
+ \r
+ if (item.events) {\r
+ $input.on(item.events);\r
+ }\r
+ }\r
+ \r
+ // add icons\r
+ if (item.icon) {\r
+ $t.addClass("icon icon-" + item.icon);\r
+ }\r
+ }\r
+ \r
+ // cache contained elements\r
+ item.$input = $input;\r
+ item.$label = $label;\r
+\r
+ // attach item to menu\r
+ $t.appendTo(opt.$menu);\r
+ \r
+ // Disable text selection\r
+ if (!opt.hasTypes) {\r
+ if($.browser.msie) {\r
+ $t.on('selectstart.disableTextSelect', handle.abortevent);\r
+ } else if(!$.browser.mozilla) {\r
+ $t.on('mousedown.disableTextSelect', handle.abortevent);\r
+ }\r
+ }\r
+ });\r
+ // attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element)\r
+ if (!opt.$node) {\r
+ opt.$menu.css('display', 'none').addClass('context-menu-root');\r
+ }\r
+ opt.$menu.appendTo(opt.appendTo || document.body);\r
+ },\r
+ update: function(opt, root) {\r
+ var $this = this;\r
+ if (root === undefined) {\r
+ root = opt;\r
+ // determine widths of submenus, as CSS won't grow them automatically\r
+ // position:absolute > position:absolute; min-width:100; max-width:200; results in width: 100;\r
+ // kinda sucks hard...\r
+ opt.$menu.find('ul').andSelf().css({position: 'static', display: 'block'}).each(function(){\r
+ var $this = $(this);\r
+ $this.width($this.css('position', 'absolute').width())\r
+ .css('position', 'static');\r
+ }).css({position: '', display: ''});\r
+ }\r
+ // re-check disabled for each item\r
+ opt.$menu.children().each(function(){\r
+ var $item = $(this),\r
+ key = $item.data('contextMenuKey'),\r
+ item = opt.items[key],\r
+ disabled = ($.isFunction(item.disabled) && item.disabled.call($this, key, root)) || item.disabled === true;\r
+\r
+ // dis- / enable item\r
+ $item[disabled ? 'addClass' : 'removeClass']('disabled');\r
+ \r
+ if (item.type) {\r
+ // dis- / enable input elements\r
+ $item.find('input, select, textarea').prop('disabled', disabled);\r
+ \r
+ // update input states\r
+ switch (item.type) {\r
+ case 'text':\r
+ case 'textarea':\r
+ item.$input.val(item.value || "");\r
+ break;\r
+ \r
+ case 'checkbox':\r
+ case 'radio':\r
+ item.$input.val(item.value || "").prop('checked', !!item.selected);\r
+ break;\r
+ \r
+ case 'select':\r
+ item.$input.val(item.selected || "");\r
+ break;\r
+ }\r
+ }\r
+ \r
+ if (item.$menu) {\r
+ // update sub-menu\r
+ op.update.call($this, item, root);\r
+ }\r
+ });\r
+ },\r
+ layer: function(opt, zIndex) {\r
+ // add transparent layer for click area\r
+ // filter and background for Internet Explorer, Issue #23\r
+ 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>')\r
+ .css({height: $win.height(), width: $win.width(), display: 'block'})\r
+ .data('contextMenuRoot', opt)\r
+ .insertBefore(this)\r
+ .on('contextmenu', handle.abortevent)\r
+ .on('mousedown', handle.layerClick);\r
+ }\r
+ };\r
+\r
+// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key\r
+function splitAccesskey(val) {\r
+ var t = val.split(/\s+/),\r
+ keys = [];\r
+ \r
+ for (var i=0, k; k = t[i]; i++) {\r
+ k = k[0].toUpperCase(); // first character only\r
+ // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.\r
+ // a map to look up already used access keys would be nice\r
+ keys.push(k);\r
+ }\r
+ \r
+ return keys;\r
+}\r
+\r
+// handle contextMenu triggers\r
+$.fn.contextMenu = function(operation) {\r
+ if (operation === undefined) {\r
+ this.first().trigger('contextmenu');\r
+ } else if (operation.x && operation.y) {\r
+ this.first().trigger(jQuery.Event("contextmenu", {pageX: operation.x, pageY: operation.y}));\r
+ } else if (operation === "hide") {\r
+ var $menu = this.data('contextMenu').$menu;\r
+ $menu && $menu.trigger('contextmenu:hide');\r
+ } else if (operation) {\r
+ this.removeClass('context-menu-disabled');\r
+ } else if (!operation) {\r
+ this.addClass('context-menu-disabled');\r
+ }\r
+ \r
+ return this;\r
+};\r
+\r
+// manage contextMenu instances\r
+$.contextMenu = function(operation, options) {\r
+ if (typeof operation != 'string') {\r
+ options = operation;\r
+ operation = 'create';\r
+ }\r
+ \r
+ if (typeof options == 'string') {\r
+ options = {selector: options};\r
+ } else if (options === undefined) {\r
+ options = {};\r
+ }\r
+ \r
+ // merge with default options\r
+ var o = $.extend(true, {}, defaults, options || {}),\r
+ $body = $body = $(document);\r
+ \r
+ switch (operation) {\r
+ case 'create':\r
+ // no selector no joy\r
+ if (!o.selector) {\r
+ throw new Error('No selector specified');\r
+ }\r
+ // make sure internal classes are not bound to\r
+ if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) {\r
+ throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className');\r
+ }\r
+ if (!o.build && (!o.items || $.isEmptyObject(o.items))) {\r
+ throw new Error('No Items sepcified');\r
+ }\r
+ counter ++;\r
+ o.ns = '.contextMenu' + counter;\r
+ namespaces[o.selector] = o.ns;\r
+ menus[o.ns] = o;\r
+ \r
+ if (!initialized) {\r
+ // make sure item click is registered first\r
+ $body\r
+ .on({\r
+ 'contextmenu:hide.contextMenu': handle.hideMenu,\r
+ 'prevcommand.contextMenu': handle.prevItem,\r
+ 'nextcommand.contextMenu': handle.nextItem,\r
+ 'contextmenu.contextMenu': handle.abortevent,\r
+ 'mouseenter.contextMenu': handle.menuMouseenter,\r
+ 'mouseleave.contextMenu': handle.menuMouseleave\r
+ }, '.context-menu-list')\r
+ .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick)\r
+ .on({\r
+ 'mouseup.contextMenu': handle.itemClick,\r
+ 'contextmenu:focus.contextMenu': handle.focusItem,\r
+ 'contextmenu:blur.contextMenu': handle.blurItem,\r
+ 'contextmenu.contextMenu': handle.abortevent,\r
+ 'mouseenter.contextMenu': handle.itemMouseenter,\r
+ 'mouseleave.contextMenu': handle.itemMouseleave\r
+ }, '.context-menu-item');\r
+\r
+ initialized = true;\r
+ }\r
+ \r
+ // engage native contextmenu event\r
+ $body\r
+ .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu);\r
+ \r
+ switch (o.trigger) {\r
+ case 'hover':\r
+ $body\r
+ .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter)\r
+ .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); \r
+ break;\r
+ \r
+ case 'left':\r
+ $body.on('click' + o.ns, o.selector, o, handle.click);\r
+ break;\r
+ /*\r
+ default:\r
+ // http://www.quirksmode.org/dom/events/contextmenu.html\r
+ $body\r
+ .on('mousedown' + o.ns, o.selector, o, handle.mousedown)\r
+ .on('mouseup' + o.ns, o.selector, o, handle.mouseup);\r
+ break;\r
+ */\r
+ }\r
+ \r
+ // create menu\r
+ if (!o.build) {\r
+ op.create(o);\r
+ }\r
+ break;\r
+ \r
+ case 'destroy':\r
+ if (!o.selector) {\r
+ $body.off('.contextMenu .contextMenuAutoHide');\r
+ $.each(namespaces, function(key, value) {\r
+ $body.off(value);\r
+ });\r
+ \r
+ namespaces = {};\r
+ menus = {};\r
+ counter = 0;\r
+ initialized = false;\r
+ \r
+ $('.context-menu-list').remove();\r
+ } else if (namespaces[o.selector]) {\r
+ try {\r
+ if (menus[namespaces[o.selector]].$menu) {\r
+ menus[namespaces[o.selector]].$menu.remove();\r
+ }\r
+ \r
+ delete menus[namespaces[o.selector]];\r
+ } catch(e) {\r
+ menus[namespaces[o.selector]] = null;\r
+ }\r
+ \r
+ $body.off(namespaces[o.selector]);\r
+ }\r
+ break;\r
+ \r
+ case 'html5':\r
+ // if <command> or <menuitem> are not handled by the browser,\r
+ // or options was a bool true,\r
+ // initialize $.contextMenu for them\r
+ if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) {\r
+ $('menu[type="context"]').each(function() {\r
+ if (this.id) {\r
+ $.contextMenu({\r
+ selector: '[contextmenu=' + this.id +']',\r
+ items: $.contextMenu.fromMenu(this)\r
+ });\r
+ }\r
+ }).css('display', 'none');\r
+ }\r
+ break;\r
+ \r
+ default:\r
+ throw new Error('Unknown operation "' + operation + '"');\r
+ }\r
+ \r
+ return this;\r
+};\r
+\r
+// import values into <input> commands\r
+$.contextMenu.setInputValues = function(opt, data) {\r
+ if (data === undefined) {\r
+ data = {};\r
+ }\r
+ \r
+ $.each(opt.inputs, function(key, item) {\r
+ switch (item.type) {\r
+ case 'text':\r
+ case 'textarea':\r
+ item.value = data[key] || "";\r
+ break;\r
+\r
+ case 'checkbox':\r
+ item.selected = data[key] ? true : false;\r
+ break;\r
+ \r
+ case 'radio':\r
+ item.selected = (data[item.radio] || "") == item.value ? true : false;\r
+ break;\r
+ \r
+ case 'select':\r
+ item.selected = data[key] || "";\r
+ break;\r
+ }\r
+ });\r
+};\r
+\r
+// export values from <input> commands\r
+$.contextMenu.getInputValues = function(opt, data) {\r
+ if (data === undefined) {\r
+ data = {};\r
+ }\r
+ \r
+ $.each(opt.inputs, function(key, item) {\r
+ switch (item.type) {\r
+ case 'text':\r
+ case 'textarea':\r
+ case 'select':\r
+ data[key] = item.$input.val();\r
+ break;\r
+\r
+ case 'checkbox':\r
+ data[key] = item.$input.prop('checked');\r
+ break;\r
+ \r
+ case 'radio':\r
+ if (item.$input.prop('checked')) {\r
+ data[item.radio] = item.value;\r
+ }\r
+ break;\r
+ }\r
+ });\r
+ \r
+ return data;\r
+};\r
+\r
+// find <label for="xyz">\r
+function inputLabel(node) {\r
+ return (node.id && $('label[for="'+ node.id +'"]').val()) || node.name;\r
+}\r
+\r
+// convert <menu> to items object\r
+function menuChildren(items, $children, counter) {\r
+ if (!counter) {\r
+ counter = 0;\r
+ }\r
+ \r
+ $children.each(function() {\r
+ var $node = $(this),\r
+ node = this,\r
+ nodeName = this.nodeName.toLowerCase(),\r
+ label,\r
+ item;\r
+ \r
+ // extract <label><input>\r
+ if (nodeName == 'label' && $node.find('input, textarea, select').length) {\r
+ label = $node.text();\r
+ $node = $node.children().first();\r
+ node = $node.get(0);\r
+ nodeName = node.nodeName.toLowerCase();\r
+ }\r
+ \r
+ /*\r
+ * <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items.\r
+ * Not being the sadistic kind, $.contextMenu only accepts:\r
+ * <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>.\r
+ * Everything else will be imported as an html node, which is not interfaced with contextMenu.\r
+ */\r
+ \r
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command\r
+ switch (nodeName) {\r
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element\r
+ case 'menu':\r
+ item = {name: $node.attr('label'), items: {}};\r
+ menuChildren(item.items, $node.children(), counter);\r
+ break;\r
+ \r
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command\r
+ case 'a':\r
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command\r
+ case 'button':\r
+ item = {\r
+ name: $node.text(),\r
+ disabled: !!$node.attr('disabled'),\r
+ callback: (function(){ return function(){ $node.click(); }; })()\r
+ };\r
+ break;\r
+ \r
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command\r
+\r
+ case 'menuitem':\r
+ case 'command':\r
+ switch ($node.attr('type')) {\r
+ case undefined:\r
+ case 'command':\r
+ case 'menuitem':\r
+ item = {\r
+ name: $node.attr('label'),\r
+ disabled: !!$node.attr('disabled'),\r
+ callback: (function(){ return function(){ $node.click(); }; })()\r
+ };\r
+ break;\r
+ \r
+ case 'checkbox':\r
+ item = {\r
+ type: 'checkbox',\r
+ disabled: !!$node.attr('disabled'),\r
+ name: $node.attr('label'),\r
+ selected: !!$node.attr('checked')\r
+ };\r
+ break;\r
+ \r
+ case 'radio':\r
+ item = {\r
+ type: 'radio',\r
+ disabled: !!$node.attr('disabled'),\r
+ name: $node.attr('label'),\r
+ radio: $node.attr('radiogroup'),\r
+ value: $node.attr('id'),\r
+ selected: !!$node.attr('checked')\r
+ };\r
+ break;\r
+ \r
+ default:\r
+ item = undefined;\r
+ }\r
+ break;\r
+ \r
+ case 'hr':\r
+ item = '-------';\r
+ break;\r
+ \r
+ case 'input':\r
+ switch ($node.attr('type')) {\r
+ case 'text':\r
+ item = {\r
+ type: 'text',\r
+ name: label || inputLabel(node),\r
+ disabled: !!$node.attr('disabled'),\r
+ value: $node.val()\r
+ };\r
+ break;\r
+ \r
+ case 'checkbox':\r
+ item = {\r
+ type: 'checkbox',\r
+ name: label || inputLabel(node),\r
+ disabled: !!$node.attr('disabled'),\r
+ selected: !!$node.attr('checked')\r
+ };\r
+ break;\r
+ \r
+ case 'radio':\r
+ item = {\r
+ type: 'radio',\r
+ name: label || inputLabel(node),\r
+ disabled: !!$node.attr('disabled'),\r
+ radio: !!$node.attr('name'),\r
+ value: $node.val(),\r
+ selected: !!$node.attr('checked')\r
+ };\r
+ break;\r
+ \r
+ default:\r
+ item = undefined;\r
+ break;\r
+ }\r
+ break;\r
+ \r
+ case 'select':\r
+ item = {\r
+ type: 'select',\r
+ name: label || inputLabel(node),\r
+ disabled: !!$node.attr('disabled'),\r
+ selected: $node.val(),\r
+ options: {}\r
+ };\r
+ $node.children().each(function(){\r
+ item.options[this.value] = $(this).text();\r
+ });\r
+ break;\r
+ \r
+ case 'textarea':\r
+ item = {\r
+ type: 'textarea',\r
+ name: label || inputLabel(node),\r
+ disabled: !!$node.attr('disabled'),\r
+ value: $node.val()\r
+ };\r
+ break;\r
+ \r
+ case 'label':\r
+ break;\r
+ \r
+ default:\r
+ item = {type: 'html', html: $node.clone(true)};\r
+ break;\r
+ }\r
+ \r
+ if (item) {\r
+ counter++;\r
+ items['key' + counter] = item;\r
+ }\r
+ });\r
+}\r
+\r
+// convert html5 menu\r
+$.contextMenu.fromMenu = function(element) {\r
+ var $this = $(element),\r
+ items = {};\r
+ \r
+ menuChildren(items, $this.children());\r
+ \r
+ return items;\r
+};\r
+\r
+// make defaults accessible\r
+$.contextMenu.defaults = defaults;\r
+$.contextMenu.types = types;\r
+\r
+})(jQuery);\r
--- /dev/null
+/*!
+ * jQuery UI Position 1.8.13
+ *
+ * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Position
+ */
+(function( $, undefined ) {
+
+$.ui = $.ui || {};
+
+var horizontalPositions = /left|center|right/,
+ verticalPositions = /top|center|bottom/,
+ center = "center",
+ _position = $.fn.position,
+ _offset = $.fn.offset;
+
+$.fn.position = function( options ) {
+ if ( !options || !options.of ) {
+ return _position.apply( this, arguments );
+ }
+
+ // make a copy, we don't want to modify arguments
+ options = $.extend( {}, options );
+
+ var target = $( options.of ),
+ targetElem = target[0],
+ collision = ( options.collision || "flip" ).split( " " ),
+ offset = options.offset ? options.offset.split( " " ) : [ 0, 0 ],
+ targetWidth,
+ targetHeight,
+ basePosition;
+
+ if ( targetElem.nodeType === 9 ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ basePosition = { top: 0, left: 0 };
+ // TODO: use $.isWindow() in 1.9
+ } else if ( targetElem.setTimeout ) {
+ targetWidth = target.width();
+ targetHeight = target.height();
+ basePosition = { top: target.scrollTop(), left: target.scrollLeft() };
+ } else if ( targetElem.preventDefault ) {
+ // force left top to allow flipping
+ options.at = "left top";
+ targetWidth = targetHeight = 0;
+ basePosition = { top: options.of.pageY, left: options.of.pageX };
+ } else {
+ targetWidth = target.outerWidth();
+ targetHeight = target.outerHeight();
+ basePosition = target.offset();
+ }
+
+ // force my and at to have valid horizontal and veritcal positions
+ // if a value is missing or invalid, it will be converted to center
+ $.each( [ "my", "at" ], function() {
+ var pos = ( options[this] || "" ).split( " " );
+ if ( pos.length === 1) {
+ pos = horizontalPositions.test( pos[0] ) ?
+ pos.concat( [center] ) :
+ verticalPositions.test( pos[0] ) ?
+ [ center ].concat( pos ) :
+ [ center, center ];
+ }
+ pos[ 0 ] = horizontalPositions.test( pos[0] ) ? pos[ 0 ] : center;
+ pos[ 1 ] = verticalPositions.test( pos[1] ) ? pos[ 1 ] : center;
+ options[ this ] = pos;
+ });
+
+ // normalize collision option
+ if ( collision.length === 1 ) {
+ collision[ 1 ] = collision[ 0 ];
+ }
+
+ // normalize offset option
+ offset[ 0 ] = parseInt( offset[0], 10 ) || 0;
+ if ( offset.length === 1 ) {
+ offset[ 1 ] = offset[ 0 ];
+ }
+ offset[ 1 ] = parseInt( offset[1], 10 ) || 0;
+
+ if ( options.at[0] === "right" ) {
+ basePosition.left += targetWidth;
+ } else if ( options.at[0] === center ) {
+ basePosition.left += targetWidth / 2;
+ }
+
+ if ( options.at[1] === "bottom" ) {
+ basePosition.top += targetHeight;
+ } else if ( options.at[1] === center ) {
+ basePosition.top += targetHeight / 2;
+ }
+
+ basePosition.left += offset[ 0 ];
+ basePosition.top += offset[ 1 ];
+
+ return this.each(function() {
+ var elem = $( this ),
+ elemWidth = elem.outerWidth(),
+ elemHeight = elem.outerHeight(),
+ marginLeft = parseInt( $.curCSS( this, "marginLeft", true ) ) || 0,
+ marginTop = parseInt( $.curCSS( this, "marginTop", true ) ) || 0,
+ collisionWidth = elemWidth + marginLeft +
+ ( parseInt( $.curCSS( this, "marginRight", true ) ) || 0 ),
+ collisionHeight = elemHeight + marginTop +
+ ( parseInt( $.curCSS( this, "marginBottom", true ) ) || 0 ),
+ position = $.extend( {}, basePosition ),
+ collisionPosition;
+
+ if ( options.my[0] === "right" ) {
+ position.left -= elemWidth;
+ } else if ( options.my[0] === center ) {
+ position.left -= elemWidth / 2;
+ }
+
+ if ( options.my[1] === "bottom" ) {
+ position.top -= elemHeight;
+ } else if ( options.my[1] === center ) {
+ position.top -= elemHeight / 2;
+ }
+
+ // prevent fractions (see #5280)
+ position.left = Math.round( position.left );
+ position.top = Math.round( position.top );
+
+ collisionPosition = {
+ left: position.left - marginLeft,
+ top: position.top - marginTop
+ };
+
+ $.each( [ "left", "top" ], function( i, dir ) {
+ if ( $.ui.position[ collision[i] ] ) {
+ $.ui.position[ collision[i] ][ dir ]( position, {
+ targetWidth: targetWidth,
+ targetHeight: targetHeight,
+ elemWidth: elemWidth,
+ elemHeight: elemHeight,
+ collisionPosition: collisionPosition,
+ collisionWidth: collisionWidth,
+ collisionHeight: collisionHeight,
+ offset: offset,
+ my: options.my,
+ at: options.at
+ });
+ }
+ });
+
+ if ( $.fn.bgiframe ) {
+ elem.bgiframe();
+ }
+ elem.offset( $.extend( position, { using: options.using } ) );
+ });
+};
+
+$.ui.position = {
+ fit: {
+ left: function( position, data ) {
+ var win = $( window ),
+ over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft();
+ position.left = over > 0 ? position.left - over : Math.max( position.left - data.collisionPosition.left, position.left );
+ },
+ top: function( position, data ) {
+ var win = $( window ),
+ over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop();
+ position.top = over > 0 ? position.top - over : Math.max( position.top - data.collisionPosition.top, position.top );
+ }
+ },
+
+ flip: {
+ left: function( position, data ) {
+ if ( data.at[0] === center ) {
+ return;
+ }
+ var win = $( window ),
+ over = data.collisionPosition.left + data.collisionWidth - win.width() - win.scrollLeft(),
+ myOffset = data.my[ 0 ] === "left" ?
+ -data.elemWidth :
+ data.my[ 0 ] === "right" ?
+ data.elemWidth :
+ 0,
+ atOffset = data.at[ 0 ] === "left" ?
+ data.targetWidth :
+ -data.targetWidth,
+ offset = -2 * data.offset[ 0 ];
+ position.left += data.collisionPosition.left < 0 ?
+ myOffset + atOffset + offset :
+ over > 0 ?
+ myOffset + atOffset + offset :
+ 0;
+ },
+ top: function( position, data ) {
+ if ( data.at[1] === center ) {
+ return;
+ }
+ var win = $( window ),
+ over = data.collisionPosition.top + data.collisionHeight - win.height() - win.scrollTop(),
+ myOffset = data.my[ 1 ] === "top" ?
+ -data.elemHeight :
+ data.my[ 1 ] === "bottom" ?
+ data.elemHeight :
+ 0,
+ atOffset = data.at[ 1 ] === "top" ?
+ data.targetHeight :
+ -data.targetHeight,
+ offset = -2 * data.offset[ 1 ];
+ position.top += data.collisionPosition.top < 0 ?
+ myOffset + atOffset + offset :
+ over > 0 ?
+ myOffset + atOffset + offset :
+ 0;
+ }
+ }
+};
+
+// offset setter from jQuery 1.4
+if ( !$.offset.setOffset ) {
+ $.offset.setOffset = function( elem, options ) {
+ // set position first, in-case top/left are set even on static elem
+ if ( /static/.test( $.curCSS( elem, "position" ) ) ) {
+ elem.style.position = "relative";
+ }
+ var curElem = $( elem ),
+ curOffset = curElem.offset(),
+ curTop = parseInt( $.curCSS( elem, "top", true ), 10 ) || 0,
+ curLeft = parseInt( $.curCSS( elem, "left", true ), 10) || 0,
+ props = {
+ top: (options.top - curOffset.top) + curTop,
+ left: (options.left - curOffset.left) + curLeft
+ };
+
+ if ( 'using' in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ };
+
+ $.fn.offset = function( options ) {
+ var elem = this[ 0 ];
+ if ( !elem || !elem.ownerDocument ) { return null; }
+ if ( options ) {
+ return this.each(function() {
+ $.offset.setOffset( this, options );
+ });
+ }
+ return _offset.call( this );
+ };
+}
+
+}( jQuery ));