jquery.contextMenu.js 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459
  1. /*
  2. * jQuery contextMenu - Plugin for simple contextMenu handling
  3. *
  4. * Version: 1.5.8
  5. *
  6. * Authors: Rodney Rehm, Addy Osmani (patches for FF)
  7. * Web: http://medialize.github.com/jQuery-contextMenu/
  8. *
  9. * Licensed under
  10. * MIT License http://www.opensource.org/licenses/mit-license
  11. * GPL v3 http://opensource.org/licenses/GPL-3.0
  12. *
  13. */
  14. (function($, undefined){
  15. // TODO: -
  16. // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio
  17. // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative
  18. // determine html5 compatibility
  19. $.support.htmlMenuitem = ('HTMLMenuItemElement' in window);
  20. $.support.htmlCommand = ('HTMLCommandElement' in window);
  21. var // currently active contextMenu trigger
  22. $currentTrigger = null,
  23. // is contextMenu initialized with at least one menu?
  24. initialized = false,
  25. // flag stating to ignore the contextmenu event
  26. ignoreThisClick = false,
  27. // window handle
  28. $win = $(window),
  29. // number of registered menus
  30. counter = 0,
  31. // mapping selector to namespace
  32. namespaces = {},
  33. // mapping namespace to options
  34. menus = {},
  35. // custom command type handlers
  36. types = {},
  37. // default values
  38. defaults = {
  39. // selector of contextMenu trigger
  40. selector: null,
  41. // where to append the menu to
  42. appendTo: null,
  43. // method to trigger context menu ["right", "left", "hover"]
  44. trigger: "right",
  45. // hide menu when mouse leaves trigger / menu elements
  46. autoHide: false,
  47. // ignore right click triggers for left, hover or custom activation
  48. ignoreRightClick: false,
  49. // ms to wait before showing a hover-triggered context menu
  50. delay: 200,
  51. // determine position to show menu at
  52. determinePosition: function($menu) {
  53. // position to the lower middle of the trigger element
  54. if ($.ui && $.ui.position) {
  55. // .position() is provided as a jQuery UI utility
  56. // (...and it won't work on hidden elements)
  57. $menu.css('display', 'block').position({
  58. my: "center top",
  59. at: "center bottom",
  60. of: this,
  61. offset: "0 5",
  62. collision: "fit"
  63. }).css('display', 'none');
  64. } else {
  65. // determine contextMenu position
  66. var offset = this.offset();
  67. offset.top += this.outerHeight();
  68. offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2;
  69. $menu.css(offset);
  70. }
  71. },
  72. // position menu
  73. position: function(opt, x, y) {
  74. var $this = this,
  75. offset;
  76. // determine contextMenu position
  77. if (!x && !y) {
  78. opt.determinePosition.call(this, opt.$menu);
  79. return;
  80. } else if (x === "maintain" && y === "maintain") {
  81. // x and y must not be changed (after re-show on command click)
  82. offset = opt.$menu.position();
  83. } else {
  84. // x and y are given (by mouse event)
  85. var triggerIsFixed = opt.$trigger.parents().andSelf()
  86. .filter(function() {
  87. return $(this).css('position') == "fixed";
  88. }).length;
  89. if (triggerIsFixed) {
  90. y -= $win.scrollTop();
  91. x -= $win.scrollLeft();
  92. }
  93. offset = {top: y, left: x};
  94. }
  95. // correct offset if viewport demands it
  96. var bottom = $win.scrollTop() + $win.height(),
  97. right = $win.scrollLeft() + $win.width(),
  98. height = opt.$menu.height(),
  99. width = opt.$menu.width();
  100. if (offset.top + height > bottom) {
  101. offset.top -= height;
  102. }
  103. if (offset.left + width > right) {
  104. offset.left -= width;
  105. }
  106. opt.$menu.css(offset);
  107. },
  108. // position the sub-menu
  109. positionSubmenu: function($menu) {
  110. if ($.ui && $.ui.position) {
  111. // .position() is provided as a jQuery UI utility
  112. // (...and it won't work on hidden elements)
  113. $menu.css('display', 'block').position({
  114. my: "left top",
  115. at: "right top",
  116. of: this,
  117. collision: "fit"
  118. }).css('display', '');
  119. } else {
  120. // determine contextMenu position
  121. var offset = this.offset();
  122. offset.top += 0;
  123. offset.left += this.outerWidth();
  124. $menu.css(offset);
  125. }
  126. },
  127. // offset to add to zIndex
  128. zIndex: 1,
  129. // show hide animation settings
  130. animation: {
  131. duration: 50,
  132. show: 'slideDown',
  133. hide: 'slideUp'
  134. },
  135. // events
  136. events: {
  137. show: $.noop,
  138. hide: $.noop
  139. },
  140. // default callback
  141. callback: null,
  142. // list of contextMenu items
  143. items: {}
  144. },
  145. // mouse position for hover activation
  146. hoveract = {
  147. timer: null,
  148. pageX: null,
  149. pageY: null
  150. },
  151. // determine zIndex
  152. zindex = function($t) {
  153. var zin = 0,
  154. $tt = $t;
  155. while (true) {
  156. zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);
  157. $tt = $tt.parent();
  158. if (!$tt || !$tt.length || $tt.prop('nodeName').toLowerCase() == 'body') {
  159. break;
  160. }
  161. }
  162. return zin;
  163. },
  164. // event handlers
  165. handle = {
  166. // abort anything
  167. abortevent: function(e){
  168. e.preventDefault();
  169. e.stopImmediatePropagation();
  170. },
  171. // contextmenu show dispatcher
  172. contextmenu: function(e) {
  173. var $this = $(this);
  174. // disable actual context-menu
  175. e.preventDefault();
  176. e.stopImmediatePropagation();
  177. // ignore right click trigger
  178. if (ignoreThisClick) {
  179. ignoreThisClick = false;
  180. return;
  181. }
  182. if (!$this.hasClass('context-menu-disabled')) {
  183. // theoretically need to fire a show event at <menu>
  184. // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
  185. // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });
  186. // e.data.$menu.trigger(evt);
  187. $currentTrigger = $this;
  188. if (e.data.build) {
  189. // dynamically build menu on invocation
  190. $.extend(true, e.data, defaults, e.data.build($currentTrigger, e) || {});
  191. op.create(e.data);
  192. }
  193. // show menu
  194. op.show.call($this, e.data, e.pageX, e.pageY);
  195. }
  196. },
  197. // contextMenu left-click trigger
  198. click: function(e) {
  199. e.preventDefault();
  200. e.stopImmediatePropagation();
  201. $(this).trigger(jQuery.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));
  202. },
  203. // contextMenu right-click trigger
  204. mousedown: function(e) {
  205. // register mouse down
  206. var $this = $(this);
  207. // hide any previous menus
  208. if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) {
  209. $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide');
  210. }
  211. // activate on right click
  212. if (e.button == 2) {
  213. $currentTrigger = $this.data('contextMenuActive', true);
  214. }
  215. },
  216. // contextMenu right-click trigger
  217. mouseup: function(e) {
  218. // show menu
  219. var $this = $(this);
  220. if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {
  221. e.preventDefault();
  222. e.stopImmediatePropagation();
  223. $currentTrigger = $this;
  224. $this.trigger(jQuery.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));
  225. }
  226. $this.removeData('contextMenuActive');
  227. },
  228. // contextMenu hover trigger
  229. mouseenter: function(e) {
  230. var $this = $(this),
  231. $related = $(e.relatedTarget),
  232. $document = $(document);
  233. // abort if we're coming from a menu
  234. if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
  235. return;
  236. }
  237. // abort if a menu is shown
  238. if ($currentTrigger && $currentTrigger.length) {
  239. return;
  240. }
  241. hoveract.pageX = e.pageX;
  242. hoveract.pageY = e.pageY;
  243. hoveract.data = e.data;
  244. $document.on('mousemove.contextMenuShow', handle.mousemove);
  245. hoveract.timer = setTimeout(function() {
  246. hoveract.timer = null;
  247. $document.off('mousemove.contextMenuShow');
  248. $currentTrigger = $this;
  249. $this.trigger(jQuery.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY }));
  250. }, e.data.delay );
  251. },
  252. // contextMenu hover trigger
  253. mousemove: function(e) {
  254. hoveract.pageX = e.pageX;
  255. hoveract.pageY = e.pageY;
  256. },
  257. // contextMenu hover trigger
  258. mouseleave: function(e) {
  259. // abort if we're leaving for a menu
  260. var $related = $(e.relatedTarget);
  261. if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
  262. return;
  263. }
  264. try {
  265. clearTimeout(hoveract.timer);
  266. } catch(e) {}
  267. hoveract.timer = null;
  268. },
  269. // ignore right click trigger
  270. ignoreRightClick: function(e) {
  271. if (e.button == 2) {
  272. ignoreThisClick = true;
  273. }
  274. },
  275. // click on layer to hide contextMenu
  276. layerClick: function(e) {
  277. var $this = $(this),
  278. root = $this.data('contextMenuRoot');
  279. e.preventDefault();
  280. e.stopImmediatePropagation();
  281. $this.remove();
  282. root.$menu.trigger('contextmenu:hide');
  283. /* (Airtime) added this to allow user to exit out of menu.
  284. * if ignoreThisClick remains false, every right click
  285. * thereafter continues to show the menu
  286. */
  287. if (handle.ignoreRightClick) {
  288. if (e.button == 2) {
  289. ignoreThisClick = true;
  290. }
  291. }
  292. },
  293. // key handled :hover
  294. keyStop: function(e, opt) {
  295. if (!opt.isInput) {
  296. e.preventDefault();
  297. }
  298. e.stopPropagation();
  299. },
  300. key: function(e) {
  301. var opt = $currentTrigger.data('contextMenu') || {},
  302. $children = opt.$menu.children(),
  303. $round;
  304. switch (e.keyCode) {
  305. case 9:
  306. case 38: // up
  307. handle.keyStop(e, opt);
  308. // if keyCode is [38 (up)] or [9 (tab) with shift]
  309. if (opt.isInput) {
  310. if (e.keyCode == 9 && e.shiftKey) {
  311. e.preventDefault();
  312. opt.$selected && opt.$selected.find('input, textarea, select').blur();
  313. opt.$menu.trigger('prevcommand');
  314. return;
  315. } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {
  316. // checkboxes don't capture this key
  317. e.preventDefault();
  318. return;
  319. }
  320. } else if (e.keyCode != 9 || e.shiftKey) {
  321. opt.$menu.trigger('prevcommand');
  322. return;
  323. }
  324. case 9: // tab
  325. case 40: // down
  326. handle.keyStop(e, opt);
  327. if (opt.isInput) {
  328. if (e.keyCode == 9) {
  329. e.preventDefault();
  330. opt.$selected && opt.$selected.find('input, textarea, select').blur();
  331. opt.$menu.trigger('nextcommand');
  332. return;
  333. } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {
  334. // checkboxes don't capture this key
  335. e.preventDefault();
  336. return;
  337. }
  338. } else {
  339. opt.$menu.trigger('nextcommand');
  340. return;
  341. }
  342. break;
  343. case 37: // left
  344. handle.keyStop(e, opt);
  345. if (opt.isInput || !opt.$selected || !opt.$selected.length) {
  346. break;
  347. }
  348. if (!opt.$selected.parent().hasClass('context-menu-root')) {
  349. var $parent = opt.$selected.parent().parent();
  350. opt.$selected.trigger('contextmenu:blur');
  351. opt.$selected = $parent;
  352. return;
  353. }
  354. break;
  355. case 39: // right
  356. handle.keyStop(e, opt);
  357. if (opt.isInput || !opt.$selected || !opt.$selected.length) {
  358. break;
  359. }
  360. var itemdata = opt.$selected.data('contextMenu') || {};
  361. if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) {
  362. opt.$selected = null;
  363. itemdata.$selected = null;
  364. itemdata.$menu.trigger('nextcommand');
  365. return;
  366. }
  367. break;
  368. case 35: // end
  369. case 36: // home
  370. if (opt.$selected && opt.$selected.find('input, textarea, select').length) {
  371. return;
  372. } else {
  373. (opt.$selected && opt.$selected.parent() || opt.$menu)
  374. .children(':not(.disabled, .not-selectable)')[e.keyCode == 36 ? 'first' : 'last']()
  375. .trigger('contextmenu:focus');
  376. e.preventDefault();
  377. return;
  378. }
  379. break;
  380. case 13: // enter
  381. handle.keyStop(e, opt);
  382. if (opt.isInput) {
  383. if (opt.$selected && !opt.$selected.is('textarea, select')) {
  384. e.preventDefault();
  385. return;
  386. }
  387. break;
  388. }
  389. opt.$selected && opt.$selected.trigger('mouseup');
  390. return;
  391. case 32: // space
  392. case 33: // page up
  393. case 34: // page down
  394. // prevent browser from scrolling down while menu is visible
  395. handle.keyStop(e, opt);
  396. return;
  397. case 27: // esc
  398. handle.keyStop(e, opt);
  399. opt.$menu.trigger('contextmenu:hide');
  400. return;
  401. default: // 0-9, a-z
  402. var k = (String.fromCharCode(e.keyCode)).toUpperCase();
  403. if (opt.accesskeys[k]) {
  404. // according to the specs accesskeys must be invoked immediately
  405. opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu
  406. ? 'contextmenu:focus'
  407. : 'mouseup'
  408. );
  409. return;
  410. }
  411. break;
  412. }
  413. // pass event to selected item,
  414. // stop propagation to avoid endless recursion
  415. e.stopPropagation();
  416. opt.$selected && opt.$selected.trigger(e);
  417. },
  418. // select previous possible command in menu
  419. prevItem: function(e) {
  420. e.stopPropagation();
  421. var opt = $(this).data('contextMenu') || {};
  422. // obtain currently selected menu
  423. if (opt.$selected) {
  424. var $s = opt.$selected;
  425. opt = opt.$selected.parent().data('contextMenu') || {};
  426. opt.$selected = $s;
  427. }
  428. var $children = opt.$menu.children(),
  429. $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(),
  430. $round = $prev;
  431. // skip disabled
  432. while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) {
  433. if ($prev.prev().length) {
  434. $prev = $prev.prev();
  435. } else {
  436. $prev = $children.last();
  437. }
  438. if ($prev.is($round)) {
  439. // break endless loop
  440. return;
  441. }
  442. }
  443. // leave current
  444. if (opt.$selected) {
  445. handle.itemMouseleave.call(opt.$selected.get(0), e);
  446. }
  447. // activate next
  448. handle.itemMouseenter.call($prev.get(0), e);
  449. // focus input
  450. var $input = $prev.find('input, textarea, select');
  451. if ($input.length) {
  452. $input.focus();
  453. }
  454. },
  455. // select next possible command in menu
  456. nextItem: function(e) {
  457. e.stopPropagation();
  458. var opt = $(this).data('contextMenu') || {};
  459. // obtain currently selected menu
  460. if (opt.$selected) {
  461. var $s = opt.$selected;
  462. opt = opt.$selected.parent().data('contextMenu') || {};
  463. opt.$selected = $s;
  464. }
  465. var $children = opt.$menu.children(),
  466. $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(),
  467. $round = $next;
  468. // skip disabled
  469. while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) {
  470. if ($next.next().length) {
  471. $next = $next.next();
  472. } else {
  473. $next = $children.first();
  474. }
  475. if ($next.is($round)) {
  476. // break endless loop
  477. return;
  478. }
  479. }
  480. // leave current
  481. if (opt.$selected) {
  482. handle.itemMouseleave.call(opt.$selected.get(0), e);
  483. }
  484. // activate next
  485. handle.itemMouseenter.call($next.get(0), e);
  486. // focus input
  487. var $input = $next.find('input, textarea, select');
  488. if ($input.length) {
  489. $input.focus();
  490. }
  491. },
  492. // flag that we're inside an input so the key handler can act accordingly
  493. focusInput: function(e) {
  494. var $this = $(this).closest('.context-menu-item'),
  495. data = $this.data(),
  496. opt = data.contextMenu,
  497. root = data.contextMenuRoot;
  498. root.$selected = opt.$selected = $this;
  499. root.isInput = opt.isInput = true;
  500. },
  501. // flag that we're inside an input so the key handler can act accordingly
  502. blurInput: function(e) {
  503. var $this = $(this).closest('.context-menu-item'),
  504. data = $this.data(),
  505. opt = data.contextMenu,
  506. root = data.contextMenuRoot;
  507. root.isInput = opt.isInput = false;
  508. },
  509. // :hover on menu
  510. menuMouseenter: function(e) {
  511. var root = $(this).data().contextMenuRoot;
  512. root.hovering = true;
  513. },
  514. // :hover on menu
  515. menuMouseleave: function(e) {
  516. var root = $(this).data().contextMenuRoot;
  517. if (root.$layer && root.$layer.is(e.relatedTarget)) {
  518. root.hovering = false;
  519. }
  520. },
  521. // :hover done manually so key handling is possible
  522. itemMouseenter: function(e) {
  523. var $this = $(this),
  524. data = $this.data(),
  525. opt = data.contextMenu,
  526. root = data.contextMenuRoot;
  527. root.hovering = true;
  528. // abort if we're re-entering
  529. if (e && root.$layer && root.$layer.is(e.relatedTarget)) {
  530. e.preventDefault();
  531. e.stopImmediatePropagation();
  532. }
  533. // make sure only one item is selected
  534. (opt.$menu ? opt : root).$menu
  535. .children('.hover').trigger('contextmenu:blur');
  536. if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) {
  537. opt.$selected = null;
  538. return;
  539. }
  540. $this.trigger('contextmenu:focus');
  541. },
  542. // :hover done manually so key handling is possible
  543. itemMouseleave: function(e) {
  544. var $this = $(this),
  545. data = $this.data(),
  546. opt = data.contextMenu,
  547. root = data.contextMenuRoot;
  548. if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) {
  549. root.$selected && root.$selected.trigger('contextmenu:blur');
  550. e.preventDefault();
  551. e.stopImmediatePropagation();
  552. root.$selected = opt.$selected = opt.$node;
  553. return;
  554. }
  555. $this.trigger('contextmenu:blur');
  556. },
  557. // contextMenu item click
  558. itemClick: function(e) {
  559. var $this = $(this),
  560. data = $this.data(),
  561. opt = data.contextMenu,
  562. root = data.contextMenuRoot,
  563. key = data.contextMenuKey,
  564. callback;
  565. // abort if the key is unknown or disabled
  566. if (!opt.items[key] || $this.hasClass('disabled')) {
  567. return;
  568. }
  569. e.preventDefault();
  570. e.stopImmediatePropagation();
  571. if ($.isFunction(root.callbacks[key])) {
  572. // item-specific callback
  573. callback = root.callbacks[key];
  574. } else if ($.isFunction(root.callback)) {
  575. // default callback
  576. callback = root.callback;
  577. } else {
  578. // no callback, no action
  579. return;
  580. }
  581. // hide menu if callback doesn't stop that
  582. if (callback.call(root.$trigger, key, root) !== false) {
  583. root.$menu.trigger('contextmenu:hide');
  584. } else {
  585. op.update.call(root.$trigger, root);
  586. }
  587. },
  588. // ignore click events on input elements
  589. inputClick: function(e) {
  590. e.stopImmediatePropagation();
  591. },
  592. // hide <menu>
  593. hideMenu: function(e) {
  594. var root = $(this).data('contextMenuRoot');
  595. op.hide.call(root.$trigger, root);
  596. },
  597. // focus <command>
  598. focusItem: function(e) {
  599. e.stopPropagation();
  600. var $this = $(this),
  601. data = $this.data(),
  602. opt = data.contextMenu,
  603. root = data.contextMenuRoot;
  604. $this.addClass('hover')
  605. .siblings('.hover').trigger('contextmenu:blur');
  606. // remember selected
  607. opt.$selected = root.$selected = $this;
  608. // position sub-menu - do after show so dumb $.ui.position can keep up
  609. if (opt.$node) {
  610. root.positionSubmenu.call(opt.$node, opt.$menu);
  611. }
  612. },
  613. // blur <command>
  614. blurItem: function(e) {
  615. e.stopPropagation();
  616. var $this = $(this),
  617. data = $this.data(),
  618. opt = data.contextMenu,
  619. root = data.contextMenuRoot;
  620. $this.removeClass('hover');
  621. opt.$selected = null;
  622. }
  623. },
  624. // operations
  625. op = {
  626. show: function(opt, x, y) {
  627. var $this = $(this),
  628. offset,
  629. css = {};
  630. // hide any open menus
  631. $('#context-menu-layer').trigger('mousedown');
  632. // show event
  633. if (opt.events.show.call($this, opt) === false) {
  634. $currentTrigger = null;
  635. return;
  636. }
  637. // backreference for callbacks
  638. opt.$trigger = $this;
  639. // create or update context menu
  640. op.update.call($this, opt);
  641. // position menu
  642. opt.position.call($this, opt, x, y);
  643. // make sure we're in front
  644. if (opt.zIndex) {
  645. css.zIndex = zindex($this) + opt.zIndex;
  646. }
  647. // add layer
  648. op.layer.call(opt.$menu, opt, css.zIndex);
  649. // adjust sub-menu zIndexes
  650. opt.$menu.find('ul').css('zIndex', css.zIndex + 1);
  651. // position and show context menu
  652. opt.$menu.css( css )[opt.animation.show](opt.animation.duration);
  653. // make options available
  654. $this.data('contextMenu', opt);
  655. // register key handler
  656. $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key);
  657. // register autoHide handler
  658. if (opt.autoHide) {
  659. // trigger element coordinates
  660. var pos = $this.position();
  661. pos.right = pos.left + $this.outerWidth();
  662. pos.bottom = pos.top + this.outerHeight();
  663. // mouse position handler
  664. $(document).on('mousemove.contextMenuAutoHide', function(e) {
  665. if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {
  666. // if mouse in menu...
  667. opt.$layer.trigger('mousedown');
  668. }
  669. });
  670. }
  671. },
  672. hide: function(opt) {
  673. var $this = $(this);
  674. if (!opt) {
  675. opt = $this.data('contextMenu') || {};
  676. }
  677. // hide event
  678. if (opt.events && opt.events.hide.call($this, opt) === false) {
  679. return;
  680. }
  681. if (opt.$layer) {
  682. try {
  683. opt.$layer.remove();
  684. delete opt.$layer;
  685. } catch(e) {
  686. opt.$layer = null;
  687. }
  688. }
  689. // remove handle
  690. $currentTrigger = null;
  691. // remove selected
  692. opt.$menu.find('.hover').trigger('contextmenu:blur');
  693. opt.$selected = null;
  694. // unregister key and mouse handlers
  695. //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705
  696. $(document).off('.contextMenuAutoHide').off('keydown.contextMenu');
  697. // hide menu
  698. opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration);
  699. // tear down dynamically built menu
  700. if (opt.build) {
  701. opt.$menu.remove();
  702. $.each(opt, function(key, value) {
  703. switch (key) {
  704. case 'ns':
  705. case 'selector':
  706. case 'build':
  707. case 'trigger':
  708. case 'ignoreRightClick':
  709. return true;
  710. default:
  711. opt[key] = undefined;
  712. try {
  713. delete opt[key];
  714. } catch (e) {}
  715. return true;
  716. }
  717. });
  718. }
  719. },
  720. create: function(opt, root) {
  721. if (root === undefined) {
  722. root = opt;
  723. }
  724. // create contextMenu
  725. opt.$menu = $('<ul class="context-menu-list ' + (opt.className || "") + '"></ul>').data({
  726. 'contextMenu': opt,
  727. 'contextMenuRoot': root
  728. });
  729. $.each(['callbacks', 'commands', 'inputs'], function(i,k){
  730. opt[k] = {};
  731. if (!root[k]) {
  732. root[k] = {};
  733. }
  734. });
  735. root.accesskeys || (root.accesskeys = {});
  736. // create contextMenu items
  737. $.each(opt.items, function(key, item){
  738. var $t = $('<li class="context-menu-item ' + (item.className || "") +'"></li>'),
  739. $label = null,
  740. $input = null;
  741. item.$node = $t.data({
  742. 'contextMenu': opt,
  743. 'contextMenuRoot': root,
  744. 'contextMenuKey': key
  745. });
  746. // register accesskey
  747. // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that
  748. if (item.accesskey) {
  749. var aks = splitAccesskey(item.accesskey);
  750. for (var i=0, ak; ak = aks[i]; i++) {
  751. if (!root.accesskeys[ak]) {
  752. root.accesskeys[ak] = item;
  753. item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '<span class="context-menu-accesskey">$1</span>');
  754. break;
  755. }
  756. }
  757. }
  758. if (typeof item == "string") {
  759. $t.addClass('context-menu-separator not-selectable');
  760. } else if (item.type && types[item.type]) {
  761. // run custom type handler
  762. types[item.type].call($t, item, opt, root);
  763. // register commands
  764. $.each([opt, root], function(i,k){
  765. k.commands[key] = item;
  766. if ($.isFunction(item.callback)) {
  767. k.callbacks[key] = item.callback;
  768. }
  769. });
  770. } else {
  771. // add label for input
  772. if (item.type == 'html') {
  773. $t.addClass('context-menu-html not-selectable');
  774. } else if (item.type) {
  775. $label = $('<label></label>').appendTo($t);
  776. $('<span></span>').html(item._name || item.name).appendTo($label);
  777. $t.addClass('context-menu-input');
  778. opt.hasTypes = true;
  779. $.each([opt, root], function(i,k){
  780. k.commands[key] = item;
  781. k.inputs[key] = item;
  782. });
  783. } else if (item.items) {
  784. item.type = 'sub';
  785. }
  786. switch (item.type) {
  787. case 'text':
  788. $input = $('<input type="text" value="1" name="context-menu-input-'+ key +'" value="">')
  789. .val(item.value || "").appendTo($label);
  790. break;
  791. case 'textarea':
  792. $input = $('<textarea name="context-menu-input-'+ key +'"></textarea>')
  793. .val(item.value || "").appendTo($label);
  794. if (item.height) {
  795. $input.height(item.height);
  796. }
  797. break;
  798. case 'checkbox':
  799. $input = $('<input type="checkbox" value="1" name="context-menu-input-'+ key +'" value="">')
  800. .val(item.value || "").prop("checked", !!item.selected).prependTo($label);
  801. break;
  802. case 'radio':
  803. $input = $('<input type="radio" value="1" name="context-menu-input-'+ item.radio +'" value="">')
  804. .val(item.value || "").prop("checked", !!item.selected).prependTo($label);
  805. break;
  806. case 'select':
  807. $input = $('<select name="context-menu-input-'+ key +'">').appendTo($label);
  808. if (item.options) {
  809. $.each(item.options, function(value, text) {
  810. $('<option></option>').val(value).text(text).appendTo($input);
  811. });
  812. $input.val(item.selected);
  813. }
  814. break;
  815. case 'sub':
  816. $('<span></span>').html(item._name || item.name).appendTo($t);
  817. item.appendTo = item.$node;
  818. op.create(item, root);
  819. $t.data('contextMenu', item).addClass('context-menu-submenu');
  820. item.callback = null;
  821. break;
  822. case 'html':
  823. $(item.html).appendTo($t);
  824. break;
  825. default:
  826. $.each([opt, root], function(i,k){
  827. k.commands[key] = item;
  828. if ($.isFunction(item.callback)) {
  829. k.callbacks[key] = item.callback;
  830. }
  831. });
  832. $('<span></span>').html(item._name || item.name || "").appendTo($t);
  833. break;
  834. }
  835. // disable key listener in <input>
  836. if (item.type && item.type != 'sub' && item.type != 'html') {
  837. $input
  838. .on('focus', handle.focusInput)
  839. .on('blur', handle.blurInput);
  840. if (item.events) {
  841. $input.on(item.events);
  842. }
  843. }
  844. // add icons
  845. if (item.icon) {
  846. $t.addClass("icon icon-" + item.icon);
  847. }
  848. }
  849. // cache contained elements
  850. item.$input = $input;
  851. item.$label = $label;
  852. // attach item to menu
  853. $t.appendTo(opt.$menu);
  854. // Disable text selection
  855. if (!opt.hasTypes) {
  856. if($.browser.msie) {
  857. $t.on('selectstart.disableTextSelect', handle.abortevent);
  858. } else if(!$.browser.mozilla) {
  859. $t.on('mousedown.disableTextSelect', handle.abortevent);
  860. }
  861. }
  862. });
  863. // attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element)
  864. if (!opt.$node) {
  865. opt.$menu.css('display', 'none').addClass('context-menu-root');
  866. }
  867. opt.$menu.appendTo(opt.appendTo || document.body);
  868. },
  869. update: function(opt, root) {
  870. var $this = this;
  871. if (root === undefined) {
  872. root = opt;
  873. // determine widths of submenus, as CSS won't grow them automatically
  874. // position:absolute > position:absolute; min-width:100; max-width:200; results in width: 100;
  875. // kinda sucks hard...
  876. opt.$menu.find('ul').andSelf().css({position: 'static', display: 'block'}).each(function(){
  877. var $this = $(this);
  878. $this.width($this.css('position', 'absolute').width())
  879. .css('position', 'static');
  880. }).css({position: '', display: ''});
  881. }
  882. // re-check disabled for each item
  883. opt.$menu.children().each(function(){
  884. var $item = $(this),
  885. key = $item.data('contextMenuKey'),
  886. item = opt.items[key],
  887. disabled = ($.isFunction(item.disabled) && item.disabled.call($this, key, root)) || item.disabled === true;
  888. // dis- / enable item
  889. $item[disabled ? 'addClass' : 'removeClass']('disabled');
  890. if (item.type) {
  891. // dis- / enable input elements
  892. $item.find('input, select, textarea').prop('disabled', disabled);
  893. // update input states
  894. switch (item.type) {
  895. case 'text':
  896. case 'textarea':
  897. item.$input.val(item.value || "");
  898. break;
  899. case 'checkbox':
  900. case 'radio':
  901. item.$input.val(item.value || "").prop('checked', !!item.selected);
  902. break;
  903. case 'select':
  904. item.$input.val(item.selected || "");
  905. break;
  906. }
  907. }
  908. if (item.$menu) {
  909. // update sub-menu
  910. op.update.call($this, item, root);
  911. }
  912. });
  913. },
  914. layer: function(opt, zIndex) {
  915. // add transparent layer for click area
  916. // filter and background for Internet Explorer, Issue #23
  917. 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>')
  918. .css({height: $win.height(), width: $win.width(), display: 'block'})
  919. .data('contextMenuRoot', opt)
  920. .insertBefore(this)
  921. .on('mousedown', handle.layerClick);
  922. }
  923. };
  924. // split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key
  925. function splitAccesskey(val) {
  926. var t = val.split(/\s+/),
  927. keys = [];
  928. for (var i=0, k; k = t[i]; i++) {
  929. k = k[0].toUpperCase(); // first character only
  930. // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.
  931. // a map to look up already used access keys would be nice
  932. keys.push(k);
  933. }
  934. return keys;
  935. }
  936. // handle contextMenu triggers
  937. $.fn.contextMenu = function(operation) {
  938. if (operation === undefined) {
  939. this.first().trigger('contextmenu');
  940. } else if (operation.x && operation.y) {
  941. this.first().trigger(jQuery.Event("contextmenu", {pageX: operation.x, pageY: operation.y}));
  942. } else if (operation === "hide") {
  943. var $menu = this.data('contextMenu').$menu;
  944. $menu && $menu.trigger('contextmenu:hide');
  945. } else if (operation) {
  946. this.removeClass('context-menu-disabled');
  947. } else if (!operation) {
  948. this.addClass('context-menu-disabled');
  949. }
  950. return this;
  951. };
  952. // manage contextMenu instances
  953. $.contextMenu = function(operation, options) {
  954. if (typeof operation != 'string') {
  955. options = operation;
  956. operation = 'create';
  957. }
  958. if (typeof options == 'string') {
  959. options = {selector: options};
  960. } else if (options === undefined) {
  961. options = {};
  962. }
  963. // merge with default options
  964. var o = $.extend(true, {}, defaults, options || {}),
  965. $body = $body = $(document);
  966. switch (operation) {
  967. case 'create':
  968. // no selector no joy
  969. if (!o.selector) {
  970. throw new Error('No selector specified');
  971. }
  972. // make sure internal classes are not bound to
  973. if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) {
  974. throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className');
  975. }
  976. if (!o.build && (!o.items || $.isEmptyObject(o.items))) {
  977. throw new Error('No Items sepcified');
  978. }
  979. counter ++;
  980. o.ns = '.contextMenu' + counter;
  981. namespaces[o.selector] = o.ns;
  982. menus[o.ns] = o;
  983. if (!initialized) {
  984. // make sure item click is registered first
  985. $body
  986. .on({
  987. 'contextmenu:hide.contextMenu': handle.hideMenu,
  988. 'prevcommand.contextMenu': handle.prevItem,
  989. 'nextcommand.contextMenu': handle.nextItem,
  990. 'contextmenu.contextMenu': handle.abortevent,
  991. 'mouseenter.contextMenu': handle.menuMouseenter,
  992. 'mouseleave.contextMenu': handle.menuMouseleave
  993. }, '.context-menu-list')
  994. .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick)
  995. .on({
  996. 'mouseup.contextMenu': handle.itemClick,
  997. 'contextmenu:focus.contextMenu': handle.focusItem,
  998. 'contextmenu:blur.contextMenu': handle.blurItem,
  999. 'contextmenu.contextMenu': handle.abortevent,
  1000. 'mouseenter.contextMenu': handle.itemMouseenter,
  1001. 'mouseleave.contextMenu': handle.itemMouseleave
  1002. }, '.context-menu-item');
  1003. initialized = true;
  1004. }
  1005. // engage native contextmenu event
  1006. $body
  1007. .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu);
  1008. switch (o.trigger) {
  1009. case 'hover':
  1010. $body
  1011. .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter)
  1012. .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave);
  1013. break;
  1014. case 'left':
  1015. $body.on('click' + o.ns, o.selector, o, handle.click);
  1016. break;
  1017. /*
  1018. default:
  1019. // http://www.quirksmode.org/dom/events/contextmenu.html
  1020. $body
  1021. .on('mousedown' + o.ns, o.selector, o, handle.mousedown)
  1022. .on('mouseup' + o.ns, o.selector, o, handle.mouseup);
  1023. break;
  1024. */
  1025. }
  1026. if (o.trigger != 'hover' && o.ignoreRightClick) {
  1027. $body.on('mousedown' + o.ns, o.selector, handle.ignoreRightClick);
  1028. }
  1029. // create menu
  1030. if (!o.build) {
  1031. op.create(o);
  1032. }
  1033. break;
  1034. case 'destroy':
  1035. if (!o.selector) {
  1036. $body.off('.contextMenu .contextMenuAutoHide');
  1037. $.each(namespaces, function(key, value) {
  1038. $body.off(value);
  1039. });
  1040. namespaces = {};
  1041. menus = {};
  1042. counter = 0;
  1043. initialized = false;
  1044. $('.context-menu-list').remove();
  1045. } else if (namespaces[o.selector]) {
  1046. try {
  1047. if (menus[namespaces[o.selector]].$menu) {
  1048. menus[namespaces[o.selector]].$menu.remove();
  1049. }
  1050. delete menus[namespaces[o.selector]];
  1051. } catch(e) {
  1052. menus[namespaces[o.selector]] = null;
  1053. }
  1054. $body.off(namespaces[o.selector]);
  1055. }
  1056. break;
  1057. case 'html5':
  1058. // if <command> or <menuitem> are not handled by the browser,
  1059. // or options was a bool true,
  1060. // initialize $.contextMenu for them
  1061. if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) {
  1062. $('menu[type="context"]').each(function() {
  1063. if (this.id) {
  1064. $.contextMenu({
  1065. selector: '[contextmenu=' + this.id +']',
  1066. items: $.contextMenu.fromMenu(this)
  1067. });
  1068. }
  1069. }).css('display', 'none');
  1070. }
  1071. break;
  1072. default:
  1073. throw new Error('Unknown operation "' + operation + '"');
  1074. }
  1075. return this;
  1076. };
  1077. // import values into <input> commands
  1078. $.contextMenu.setInputValues = function(opt, data) {
  1079. if (data === undefined) {
  1080. data = {};
  1081. }
  1082. $.each(opt.inputs, function(key, item) {
  1083. switch (item.type) {
  1084. case 'text':
  1085. case 'textarea':
  1086. item.value = data[key] || "";
  1087. break;
  1088. case 'checkbox':
  1089. item.selected = data[key] ? true : false;
  1090. break;
  1091. case 'radio':
  1092. item.selected = (data[item.radio] || "") == item.value ? true : false;
  1093. break;
  1094. case 'select':
  1095. item.selected = data[key] || "";
  1096. break;
  1097. }
  1098. });
  1099. };
  1100. // export values from <input> commands
  1101. $.contextMenu.getInputValues = function(opt, data) {
  1102. if (data === undefined) {
  1103. data = {};
  1104. }
  1105. $.each(opt.inputs, function(key, item) {
  1106. switch (item.type) {
  1107. case 'text':
  1108. case 'textarea':
  1109. case 'select':
  1110. data[key] = item.$input.val();
  1111. break;
  1112. case 'checkbox':
  1113. data[key] = item.$input.prop('checked');
  1114. break;
  1115. case 'radio':
  1116. if (item.$input.prop('checked')) {
  1117. data[item.radio] = item.value;
  1118. }
  1119. break;
  1120. }
  1121. });
  1122. return data;
  1123. };
  1124. // find <label for="xyz">
  1125. function inputLabel(node) {
  1126. return (node.id && $('label[for="'+ node.id +'"]').val()) || node.name;
  1127. }
  1128. // convert <menu> to items object
  1129. function menuChildren(items, $children, counter) {
  1130. if (!counter) {
  1131. counter = 0;
  1132. }
  1133. $children.each(function() {
  1134. var $node = $(this),
  1135. node = this,
  1136. nodeName = this.nodeName.toLowerCase(),
  1137. label,
  1138. item;
  1139. // extract <label><input>
  1140. if (nodeName == 'label' && $node.find('input, textarea, select').length) {
  1141. label = $node.text();
  1142. $node = $node.children().first();
  1143. node = $node.get(0);
  1144. nodeName = node.nodeName.toLowerCase();
  1145. }
  1146. /*
  1147. * <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items.
  1148. * Not being the sadistic kind, $.contextMenu only accepts:
  1149. * <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>.
  1150. * Everything else will be imported as an html node, which is not interfaced with contextMenu.
  1151. */
  1152. // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command
  1153. switch (nodeName) {
  1154. // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element
  1155. case 'menu':
  1156. item = {name: $node.attr('label'), items: {}};
  1157. menuChildren(item.items, $node.children(), counter);
  1158. break;
  1159. // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command
  1160. case 'a':
  1161. // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command
  1162. case 'button':
  1163. item = {
  1164. name: $node.text(),
  1165. disabled: !!$node.attr('disabled'),
  1166. callback: (function(){ return function(){ $node.click(); }; })()
  1167. };
  1168. break;
  1169. // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command
  1170. case 'menuitem':
  1171. case 'command':
  1172. switch ($node.attr('type')) {
  1173. case undefined:
  1174. case 'command':
  1175. case 'menuitem':
  1176. item = {
  1177. name: $node.attr('label'),
  1178. disabled: !!$node.attr('disabled'),
  1179. callback: (function(){ return function(){ $node.click(); }; })()
  1180. };
  1181. break;
  1182. case 'checkbox':
  1183. item = {
  1184. type: 'checkbox',
  1185. disabled: !!$node.attr('disabled'),
  1186. name: $node.attr('label'),
  1187. selected: !!$node.attr('checked')
  1188. };
  1189. break;
  1190. case 'radio':
  1191. item = {
  1192. type: 'radio',
  1193. disabled: !!$node.attr('disabled'),
  1194. name: $node.attr('label'),
  1195. radio: $node.attr('radiogroup'),
  1196. value: $node.attr('id'),
  1197. selected: !!$node.attr('checked')
  1198. };
  1199. break;
  1200. default:
  1201. item = undefined;
  1202. }
  1203. break;
  1204. case 'hr':
  1205. item = '-------';
  1206. break;
  1207. case 'input':
  1208. switch ($node.attr('type')) {
  1209. case 'text':
  1210. item = {
  1211. type: 'text',
  1212. name: label || inputLabel(node),
  1213. disabled: !!$node.attr('disabled'),
  1214. value: $node.val()
  1215. };
  1216. break;
  1217. case 'checkbox':
  1218. item = {
  1219. type: 'checkbox',
  1220. name: label || inputLabel(node),
  1221. disabled: !!$node.attr('disabled'),
  1222. selected: !!$node.attr('checked')
  1223. };
  1224. break;
  1225. case 'radio':
  1226. item = {
  1227. type: 'radio',
  1228. name: label || inputLabel(node),
  1229. disabled: !!$node.attr('disabled'),
  1230. radio: !!$node.attr('name'),
  1231. value: $node.val(),
  1232. selected: !!$node.attr('checked')
  1233. };
  1234. break;
  1235. default:
  1236. item = undefined;
  1237. break;
  1238. }
  1239. break;
  1240. case 'select':
  1241. item = {
  1242. type: 'select',
  1243. name: label || inputLabel(node),
  1244. disabled: !!$node.attr('disabled'),
  1245. selected: $node.val(),
  1246. options: {}
  1247. };
  1248. $node.children().each(function(){
  1249. item.options[this.value] = $(this).text();
  1250. });
  1251. break;
  1252. case 'textarea':
  1253. item = {
  1254. type: 'textarea',
  1255. name: label || inputLabel(node),
  1256. disabled: !!$node.attr('disabled'),
  1257. value: $node.val()
  1258. };
  1259. break;
  1260. case 'label':
  1261. break;
  1262. default:
  1263. item = {type: 'html', html: $node.clone(true)};
  1264. break;
  1265. }
  1266. if (item) {
  1267. counter++;
  1268. items['key' + counter] = item;
  1269. }
  1270. });
  1271. }
  1272. // convert html5 menu
  1273. $.contextMenu.fromMenu = function(element) {
  1274. var $this = $(element),
  1275. items = {};
  1276. menuChildren(items, $this.children());
  1277. return items;
  1278. };
  1279. // make defaults accessible
  1280. $.contextMenu.defaults = defaults;
  1281. $.contextMenu.types = types;
  1282. })(jQuery);