Repository: medialize/jQuery-contextMenu Branch: master Commit: 39805525db8d Files: 150 Total size: 1.3 MB Directory structure: gitextract_y3pnf_da/ ├── .editorconfig ├── .gitignore ├── .jscsrc ├── .sauce.yml ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── contextMenu.jquery.json ├── dist/ │ ├── jquery.contextMenu.css │ ├── jquery.contextMenu.js │ └── jquery.ui.position.js ├── documentation/ │ ├── CONTRIBUTE.md │ ├── couscous.yml │ ├── demo/ │ │ ├── accesskeys.md │ │ ├── accesskeys_test.md │ │ ├── async-create.md │ │ ├── async-promise.md │ │ ├── callback.md │ │ ├── callback_test.md │ │ ├── custom-command.md │ │ ├── custom-command_test.md │ │ ├── disabled-callback.md │ │ ├── disabled-callback_test.md │ │ ├── disabled-changing.md │ │ ├── disabled-changing_test.md │ │ ├── disabled-menu.md │ │ ├── disabled.md │ │ ├── disabled_test.md │ │ ├── dynamic-create.md │ │ ├── dynamic.md │ │ ├── fontawesome-icons.md │ │ ├── html5-import.md │ │ ├── html5-polyfill-firefox8.md │ │ ├── html5-polyfill.md │ │ ├── input.md │ │ ├── keeping-contextmenu-open.md │ │ ├── menu-promise.md │ │ ├── menu-title.md │ │ ├── on-dom-element.md │ │ ├── sub-menus-promise.md │ │ ├── sub-menus.md │ │ ├── sub-menus_test.md │ │ ├── trigger-custom.md │ │ ├── trigger-hover-autohide.md │ │ ├── trigger-hover.md │ │ ├── trigger-left-click.md │ │ └── trigger-swipe.md │ ├── demo.md │ ├── docs/ │ │ ├── custom-command-types.md │ │ ├── customize.md │ │ ├── events.md │ │ ├── font-awesome.md │ │ ├── html5-polyfill.md │ │ ├── input-helpers.md │ │ ├── items.md │ │ ├── plugin-commands.md │ │ └── runtime-options.md │ ├── docs.md │ ├── index.md │ └── website/ │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── couscous.yml │ ├── css/ │ │ ├── screen.css │ │ ├── theme-fixes.css │ │ └── theme.css │ ├── default.twig │ ├── fonts/ │ │ └── FontAwesome.otf │ ├── js/ │ │ ├── main.js │ │ └── theme.js │ └── original-theme/ │ ├── bower.json │ ├── css/ │ │ └── badge_only.css │ └── sass/ │ ├── _theme_badge.sass │ ├── _theme_badge_fa.sass │ ├── _theme_breadcrumbs.sass │ ├── _theme_font_awesome_compatibility.sass │ ├── _theme_layout.sass │ ├── _theme_mathjax.sass │ ├── _theme_rst.sass │ ├── _theme_variables.sass │ ├── badge_only.sass │ └── theme.sass ├── gulpfile.js ├── karma-saucelabs.conf.js ├── karma.conf.js ├── package.json ├── src/ │ ├── .csscomb.json │ ├── .csslintrc │ ├── .jshintrc │ ├── jquery.contextMenu.js │ ├── jquery.ui.position.js │ └── sass/ │ ├── _icons.scss │ ├── _variables.scss │ ├── icons/ │ │ ├── _icon_classes.scss.tpl │ │ ├── _mixins.scss │ │ ├── _variables.scss │ │ └── _variables.scss.tpl │ └── jquery.contextMenu.scss ├── test/ │ ├── index.html │ ├── integration/ │ │ ├── custom-command.js │ │ ├── disabled-callback.js │ │ ├── disabled-changing.js │ │ ├── disabled-menu.js │ │ ├── disabled.js │ │ ├── dynamic-create.js │ │ ├── dynamic.js │ │ ├── html/ │ │ │ ├── accesskeys.html │ │ │ ├── accesskeys_test.html │ │ │ ├── async-create.html │ │ │ ├── callback.html │ │ │ ├── callback_test.html │ │ │ ├── custom-command.html │ │ │ ├── custom-command_test.html │ │ │ ├── disabled-callback.html │ │ │ ├── disabled-callback_test.html │ │ │ ├── disabled-changing.html │ │ │ ├── disabled-changing_test.html │ │ │ ├── disabled-menu.html │ │ │ ├── disabled.html │ │ │ ├── disabled_test.html │ │ │ ├── dynamic-create.html │ │ │ ├── dynamic.html │ │ │ ├── html5-import.html │ │ │ ├── html5-polyfill-firefox8.html │ │ │ ├── html5-polyfill.html │ │ │ ├── input.html │ │ │ ├── keeping-contextmenu-open.html │ │ │ ├── menu-title.html │ │ │ ├── on-dom-element.html │ │ │ ├── sub-menus.html │ │ │ ├── sub-menus_test.html │ │ │ ├── trigger-custom.html │ │ │ ├── trigger-hover-autohide.html │ │ │ ├── trigger-hover.html │ │ │ ├── trigger-left-click.html │ │ │ └── trigger-swipe.html │ │ ├── input.js │ │ ├── keeping-contextmenu-open.js │ │ ├── on-dom-element.js │ │ ├── trigger-custom.js │ │ ├── trigger-left-click.js │ │ └── trigger-right-click.js │ ├── specs/ │ │ ├── accesskeys.js │ │ ├── aync-create.js │ │ ├── callback.js │ │ └── submenu.js │ └── unit/ │ └── contextmenu.test.js └── wdio.conf.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [**] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true ================================================ FILE: .gitignore ================================================ .DS_Store node_modules/ bower_components/ ### Node template # Logs logs *.log npm-debug.log* # Runtime data pids *.pid *.seed # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # node-waf configuration .lock-wscript # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directory # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git node_modules ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio *.iml ## Directory-based project format: .idea/ # if you remove the above rule, at least ignore the following: # User-specific stuff: # .idea/workspace.xml # .idea/tasks.xml # .idea/dictionaries # Sensitive or high-churn files: # .idea/dataSources.ids # .idea/dataSources.xml # .idea/sqlDataSources.xml # .idea/dynamic.xml # .idea/uiDesigner.xml # Gradle: # .idea/gradle.xml # .idea/libraries # Mongo Explorer plugin: # .idea/mongoSettings.xml ## File-based project format: *.ipr *.iws ## Plugin-specific files: # IntelliJ /out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties ================================================ FILE: .jscsrc ================================================ { "requireCurlyBraces": [ "if", "else", "for", "while", "do", "try", "catch" ], "requireOperatorBeforeLineBreak": true, "requireParenthesesAroundIIFE": true, "requireCommaBeforeLineBreak": true, "requireCamelCaseOrUpperCaseIdentifiers": true, "requireDotNotation": true, "requireSpacesInForStatement": true, "requireSpaceBetweenArguments": true, "maximumLineLength": { "value": 100, "tabSize": 4, "allExcept": ["urlComments", "regex"] }, "validateQuoteMarks": { "mark": "\"", "escape": true }, "disallowMixedSpacesAndTabs": "smart", "disallowTrailingWhitespace": true, "disallowMultipleLineStrings": true, "disallowTrailingComma": true, "disallowSpaceBeforeComma": true, "requireSpaceAfterComma": true, "requireSpaceBeforeBlockStatements": true, "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true }, "requireSpaceAfterKeywords": [ "if", "else", "for", "while", "do", "switch", "return", "try", "catch" ], "requireSpacesInsideObjectBrackets": "all", "requireSemicolons": true, "requireSpaceAfterBinaryOperators": true, "requireLineFeedAtFileEnd": true, "requireSpaceBeforeBinaryOperators": [ "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "|=", "^=", "+=", "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", "|", "^", "&&", "||", "===", "==", ">=", "<=", "<", ">", "!=", "!==" ], "requireSpacesInAnonymousFunctionExpression": { "beforeOpeningCurlyBrace": true }, "requireSpacesInNamedFunctionExpression": { "beforeOpeningCurlyBrace": true }, "requirePaddingNewLinesBeforeLineComments": true, "validateLineBreaks": "LF", "disallowKeywords": [ "with" ], "disallowKeywordsOnNewLine": [ "else" ], "disallowSpacesInFunctionExpression": { "beforeOpeningRoundBrace": true }, "disallowSpacesInNamedFunctionExpression": { "beforeOpeningRoundBrace": true }, "disallowSpacesInAnonymousFunctionExpression": { "beforeOpeningRoundBrace": true }, "disallowSpacesInCallExpression": true, "disallowSpaceAfterObjectKeys": true, "disallowSpaceAfterPrefixUnaryOperators": true, "disallowSpaceBeforePostfixUnaryOperators": true, "disallowSpaceBeforeBinaryOperators": [ "," ], "disallowMultipleLineBreaks": true } ================================================ FILE: .sauce.yml ================================================ --- language: "javascript" framework: "webdriverio" configPath: "wdio.conf.js" ================================================ FILE: .travis.yml ================================================ sudo: false cache: directories: - node_modules env: global: - GIT_NAME: "'Couscous auto deploy'" - GIT_EMAIL: couscous@couscous.io - GH_REF: github.com/swisnl/jQuery-contextMenu - SAUCE_USERNAME: "bbrala-contextmenu" # GH_TOKEN - secure: gWIh9j5sUgVn9DmVDYnmS4OT2GhHmsndLTPKRLgAF8LM4sa65SkSCNB4Xu5drcb1a6e4egOe0bXNgXSl70ApBoQIxPvjvWxLBCjPQBPD6kJjo2ovOsfxLARMSGAqlGN7hiocLi+s8qa7RP3uuJ373z+Ge+bLIV6vFxEjK9U3Iz4= # SAUCE_ACCESS_KEY - secure: UOse3txRLxLQKsPVQf6OKZZP3c0nLaPJ+4G2vR/qJqBXCTTCQ84+9qx9ih/40FDFcjVXwabJsdn0EhkqDw4h50OGdc58V1UfSbk7g1RiuvvRakOPTK0J9h7bEkBPb7QQXCvAVfOZ81DN6l5lMjmH1tiC2T/h/MNOLHPXsbzXElg= matrix: include: - language: node_js node_js: "10" env: JQUERY=1 - language: node_js node_js: "10" env: JQUERY=2 - language: node_js node_js: "10" env: JQUERY=3 - language: php php: 7.4 env: DOCUMENTATION=1 allow_failures: - env: DOCUMENTATION=1 install: - if [ "$JQUERY" ] ; then yarn add jquery@$JQUERY ; fi - if [ "$DOCUMENTATION" ] ; then cd documentation && composer global require couscous/couscous ; fi script: - if [ "$JQUERY" ] ; then yarn run test ; else /home/travis/.config/composer/vendor/bin/couscous travis-auto-deploy --php-version=7.4 ; fi - if [ "$JQUERY" ] && [ "$SAUCE_USERNAME" ] ; then yarn run test-sauce ; fi deploy: provider: npm email: bjorn@swis.nl api_key: secure: YvSWphr8aTtwhvzO99jAVl4KoGHFEwwYVf1R7mklO3ZvU4yM1AYQ7m/gwpgkO3vBl0U6C1ixoE5VZzZHHDx3U1UAWeKktVdTvCp3uBDlnRVJdPIQ2gR5hT8X6j6LLTBc1iN/gaf5rT8xTeeeJp/M9gK6f757k88ZLm1DP/mdU3w= on: tags: true repo: swisnl/jQuery-contextMenu branch: master condition: $JQUERY = 3 ================================================ FILE: CHANGELOG.md ================================================ ## Changelog ## ### Unreleased * Context menu no longer jumps to the top of the screen #749 ### 2.9.2 * Fix reflow when adding click layer to page (fixed #721, thanks @Rhain) ### 2.9.1 * Fix error when closing the menu by clicking on the page without any element under that click point. (fixes #717) * Upgrades dependencies ### 2.9.0 #### Added * Added `dataAttr` option to add arbitrary data attributes to menu items. #### Changed * Updated dev dependencies. ### 2.8.1 #### Fixed * Added FontAwesome `fab` class to known classes. #### Documentation * Updated documentation for `callback` (thanks @arashdalir) ### 2.8.0 #### Added * Added support for `events.preShow` so you can enable default browser menu if needed (thanks @terwarf) ### 2.7.1 #### Fixed * A context menu appears outside the screen Under certain conditions (thanks @so-susa) * No font-awesome icons visible in submenu ([Issue #659](https://github.com/swisnl/jQuery-contextMenu/issues/659)) thanks @betafritz and @klues ### 2.7.0 #### Documentation * Add `getting started` to the documentation. * Fixed typo in documentation which breaks the `callback` demo. * Fixed typo `promis` => `promise` ([Issue #633](https://github.com/swisnl/jQuery-contextMenu/issues/633)). * Fixed arguments for callback option in documentation ([Issue #571](https://github.com/swisnl/jQuery-contextMenu/issues/571)). #### Added * Added support for Font Awesome 5 ([Issue #593](https://github.com/swisnl/jQuery-contextMenu/issues/593)), ([Issue #593](https://github.com/swisnl/jQuery-contextMenu/issues/593)) ### 2.6.4 #### Fixed * `events.activated` is called without `options`as argument ([Issue #580](https://github.com/swisnl/jQuery-contextMenu/issues/580)). * LayerClick sometimes breaks when the source is not a mouseevent ([Issue #132](https://github.com/swisnl/jQuery-contextMenu/issues/132)). * The contextmenu now checks `visible` on items once instead of twice. Fixes [issue 612](https://github.com/swisnl/jQuery-contextMenu/issues/612). * Font awesome li height is now consistent again ([Issue #610](https://github.com/swisnl/jQuery-contextMenu/issues/610)). ### 2.6.3 #### Fixed * Broke build script after 2.5.0 which ment no updates to dist folder ([Issue #578](https://github.com/swisnl/jQuery-contextMenu/issues/578)). ### 2.6.2 #### Fixed * Dev dependency ended up in normal dependencies. ### 2.6.1 #### Added * Ability to define touchstart as trigger (thanks @npuser) * Extra event `activated` that triggers after the menu is activated (thanks @AliShahrivarian) * Flag denoting if a second trigger should close the menu (thanks @OliverColeman) * Added update call to update visibility, disabled, icon and form value stats for items. Fixes issue ([Issue #555](https://github.com/swisnl/jQuery-contextMenu/issues/555)). ```javascript $('.context-menu-one').contextMenu('update'); // update single menu $.contextMenu('update') // update all open menus ``` #### Fixed * Fix for out of bounds problem on window edges (thanks @AliShahrivarian) ### 2.5.0 #### Added * Callback function now supplies original event ([Issue #211](https://github.com/swisnl/jQuery-contextMenu/issues/211)) thanks @wizzard0 ### 2.4.5 #### Fixed * ContextMenu appears with wrong position ([Issue #502](https://github.com/swisnl/jQuery-contextMenu/issues/502) thanks @apptaro * Check if given selected value is a 0, if it is a zero so return it as is. Thanks @Falseee * Events are never trigger when opening a contextMenu right after the other ([Issue #454](https://github.com/swisnl/jQuery-contextMenu/issues/454) thanks @kagant15 * Accesskey jQuery Modal Dialog not working ([Issue #506](https://github.com/swisnl/jQuery-contextMenu/issues/506) thanks @CiTRO33 * Fix submenu hover not always staying active if hovering over a submenu item. ([Issue #523](https://github.com/swisnl/jQuery-contextMenu/issues/523) thanks @tim-nz * Change $node.click() to $node.get(0).click() to allow native event in HTML5 ([Issue #517](https://github.com/swisnl/jQuery-contextMenu/issues/517) ### 2.4.4 #### Fixed * trigger is sometimes called on undefined objects because of typecheck on null. thanks @andreasrosdal ### 2.4.3 #### Changed * The inline style causes a Content Security Policy violation if style-src 'unsafe-inline' is not defined in the policy. [PR 498](https://github.com/swisnl/jQuery-contextMenu/pull/498) thanks @StealthDuck * Removed GPL license from the comment in the plugin. Was already removed everywhere else. Only MIT applies now. #### Added * Added SauceLabs tests for common browsers. ### 2.4.2 ### ### Fixed * Focus not set on content editable element when right clicking the second time ([Issue #482](https://github.com/swisnl/jQuery-contextMenu/issues/482)) * `selectableSubMenu` broke disabling click menu (fixes ([Issue #493](https://github.com/swisnl/jQuery-contextMenu/issues/493)) ### 2.4.1 ### #### Fixed * Quick fix for error in visible check ([Issue #484](https://github.com/swisnl/jQuery-contextMenu/issues/484)) #### Updated * Tweaked positioning of submenu ([Issue #387](https://github.com/swisnl/jQuery-contextMenu/issues/387)) ### 2.4.0 ### #### Added * Selectable Sub Menus ([Issue #483](https://github.com/swisnl/jQuery-contextMenu/issues/483)) thanks @zyuhel #### Fixed * The contextmenu shows even if all items are set to visible:false ([Issue #473](https://github.com/swisnlhttps://github.com/swisnl/jQuery-contextMenu/issues/482/jQuery-contextMenu/issues/473)) #### Documentation * Update documentation to include demo for async promise fixes ([Issue #470](https://github.com/swisnl/jQuery-contextMenu/issues/470)) ### 2.3.0 ### #### Added * Asynchronous promise support for submenu's ([Issue #429](https://github.com/swisnl/jQuery-contextMenu/issues/429)) thanks @Ruud-cb for the hard work. * Include dist and src in package.json to easily use SCSS files ([PR #467](https://github.com/swisnl/jQuery-contextMenu/pull/467)) thanks @RoachMech #### Fixed * Font family when using font awesome ([Issue #433](https://github.com/swisnl/jQuery-contextMenu/issues/433)) * Add check for `opt.$menu` is null when handling callbacks. ([Issue #462](https://github.com/swisnl/jQuery-contextMenu/issues/462)) thanks @andreasrosdal #### Changed * Make `` and `') .attr('name', 'context-menu-input-' + key) .val(item.value || '') .appendTo($label); break; case 'textarea': $input = $('') .attr('name', 'context-menu-input-' + key) .val(item.value || '') .appendTo($label); if (item.height) { $input.height(item.height); } break; case 'checkbox': $input = $('') .attr('name', 'context-menu-input-' + key) .val(item.value || '') .prop('checked', !!item.selected) .prependTo($label); // prevent checkbox default action on fast click-through $input.on('click', handleFastInputClick); break; case 'radio': $input = $('') .attr('name', 'context-menu-input-' + item.radio) .val(item.value || '') .prop('checked', !!item.selected) .prependTo($label); // prevent radio default action on fast click-through $input.on('click', handleFastInputClick); break; case 'select': $input = $('') .attr('name', 'context-menu-input-' + key) .appendTo($label); if (item.options) { $.each(item.options, function (value, text) { $('').val(value).text(text).appendTo($input); }); $input.val(item.selected); } break; case 'sub': createNameNode(item).appendTo($t); item.appendTo = item.$node; $t.data('contextMenu', item).addClass('context-menu-submenu'); item.callback = null; // If item contains items, and this is a promise, we should create it later // check if subitems is of type promise. If it is a promise we need to create // it later, after promise has been resolved. if ('function' === typeof item.items.then) { // probably a promise, process it, when completed it will create the sub menu's. op.processPromises(item, root, item.items); } else { // normal submenu. op.create(item, root); } break; case 'html': $(item.html).appendTo($t); break; default: $.each([opt, root], function (i, k) { k.commands[key] = item; // Overwrite only if undefined or the item is appended to the root. This so it // doesn't overwrite callbacks of root elements if the name is the same. if ($.isFunction(item.callback) && (typeof k.callbacks[key] === 'undefined' || typeof opt.type === 'undefined')) { k.callbacks[key] = item.callback; } }); createNameNode(item).appendTo($t); break; } // disable key listener in if (item.type && item.type !== 'sub' && item.type !== 'html' && item.type !== 'cm_seperator') { $input .on('focus', handle.focusInput) .on('blur', handle.blurInput); if (item.events) { $input.on(item.events, opt); } } // add icons if (item.icon) { if ($.isFunction(item.icon)) { item._icon = item.icon.call(this, this, $t, key, item); } else { if (typeof(item.icon) === 'string' && ( item.icon.substring(0, 4) === 'fab ' || item.icon.substring(0, 4) === 'fas ' || item.icon.substring(0, 4) === 'fad ' || item.icon.substring(0, 4) === 'far ' || item.icon.substring(0, 4) === 'fal ') ) { // to enable font awesome $t.addClass(root.classNames.icon + ' ' + root.classNames.icon + '--fa5'); item._icon = $(''); } else if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') { item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon; } else { item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon; } } if(typeof(item._icon) === "string"){ $t.addClass(item._icon); } else { $t.prepend(item._icon); } } } // cache contained elements item.$input = $input; item.$label = $label; // attach item to menu $t.appendTo(opt.$menu); // Disable text selection if (!opt.hasTypes && $.support.eventSelectstart) { // browsers support user-select: none, // IE has a special event for text-selection // browsers supporting neither will not be preventing text-selection $t.on('selectstart.disableTextSelect', handle.abortevent); } }); // attach contextMenu to (to bypass any possible overflow:hidden issues on parents of the trigger element) if (!opt.$node) { opt.$menu.css('display', 'none').addClass('context-menu-root'); } opt.$menu.appendTo(opt.appendTo || document.body); }, resize: function ($menu, nested) { var domMenu; // determine widths of submenus, as CSS won't grow them automatically // position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100; // kinda sucks hard... // determine width of absolutely positioned element $menu.css({position: 'absolute', display: 'block'}); // don't apply yet, because that would break nested elements' widths $menu.data('width', (domMenu = $menu.get(0)).getBoundingClientRect ? Math.ceil(domMenu.getBoundingClientRect().width) : $menu.outerWidth() + 1); // outerWidth() returns rounded pixels // reset styles so they allow nested elements to grow/shrink naturally $menu.css({ position: 'static', minWidth: '0px', maxWidth: '100000px' }); // identify width of nested menus $menu.find('> li > ul').each(function () { op.resize($(this), true); }); // reset and apply changes in the end because nested // elements' widths wouldn't be calculatable otherwise if (!nested) { $menu.find('ul').addBack().css({ position: '', display: '', minWidth: '', maxWidth: '' }).outerWidth(function () { return $(this).data('width'); }); } }, update: function (opt, root) { var $trigger = this; if (typeof root === 'undefined') { root = opt; op.resize(opt.$menu); } var hasVisibleItems = false; // re-check disabled for each item opt.$menu.children().each(function () { var $item = $(this), key = $item.data('contextMenuKey'), item = opt.items[key], disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true, visible; if ($.isFunction(item.visible)) { visible = item.visible.call($trigger, key, root); } else if (typeof item.visible !== 'undefined') { visible = item.visible === true; } else { visible = true; } if (visible) { hasVisibleItems = true; } $item[visible ? 'show' : 'hide'](); // dis- / enable item $item[disabled ? 'addClass' : 'removeClass'](root.classNames.disabled); if ($.isFunction(item.icon)) { $item.removeClass(item._icon); var iconResult = item.icon.call(this, $trigger, $item, key, item); if(typeof(iconResult) === "string"){ $item.addClass(iconResult); } else { $item.prepend(iconResult); } } if (item.type) { // dis- / enable input elements $item.find('input, select, textarea').prop('disabled', disabled); // update input states switch (item.type) { case 'text': case 'textarea': item.$input.val(item.value || ''); break; case 'checkbox': case 'radio': item.$input.val(item.value || '').prop('checked', !!item.selected); break; case 'select': item.$input.val((item.selected === 0 ? "0" : item.selected) || ''); break; } } if (item.$menu) { // update sub-menu var subMenuHasVisibleItems = op.update.call($trigger, item, root); if (subMenuHasVisibleItems) { hasVisibleItems = true; } } }); return hasVisibleItems; }, layer: function (opt, zIndex) { if (!opt.useModal) { var listener = function (ev) { handle.layerClick(ev, opt, function() { document.removeEventListener('mousedown', listener, true); }); }; document.addEventListener('mousedown', listener, true); return; } // add transparent layer for click area // filter and background for Internet Explorer, Issue #23 var $layer = opt.$layer = $('
') .css({ height: $win.height(), width: $win.width(), display: 'block', position: 'fixed', 'z-index': zIndex - 1, top: 0, left: 0, opacity: 0, filter: 'alpha(opacity=0)', 'background-color': '#000' }) .data('contextMenuRoot', opt) .appendTo(document.body) .on('contextmenu', handle.abortevent) .on('mousedown', handle.layerClick); // IE6 doesn't know position:fixed; if (typeof document.body.style.maxWidth === 'undefined') { // IE6 doesn't support maxWidth $layer.css({ 'position': 'absolute', 'height': $(document).height() }); } return $layer; }, processPromises: function (opt, root, promise) { // Start opt.$node.addClass(root.classNames.iconLoadingClass); function completedPromise(opt, root, items) { // Completed promise (dev called promise.resolve). We now have a list of items which can // be used to create the rest of the context menu. if (typeof items === 'undefined') { // Null result, dev should have checked errorPromise(undefined);//own error object } finishPromiseProcess(opt, root, items); } function errorPromise(opt, root, errorItem) { // User called promise.reject() with an error item, if not, provide own error item. if (typeof errorItem === 'undefined') { errorItem = { "error": { name: "No items and no error item", icon: "context-menu-icon context-menu-icon-quit" } }; if (window.console) { (console.error || console.log).call(console, 'When you reject a promise, provide an "items" object, equal to normal sub-menu items'); } } else if (typeof errorItem === 'string') { errorItem = {"error": {name: errorItem}}; } finishPromiseProcess(opt, root, errorItem); } function finishPromiseProcess(opt, root, items) { if (typeof root.$menu === 'undefined' || !root.$menu.is(':visible')) { return; } opt.$node.removeClass(root.classNames.iconLoadingClass); opt.items = items; op.create(opt, root, true); // Create submenu op.update(opt, root); // Correctly update position if user is already hovered over menu item root.positionSubmenu.call(opt.$node, opt.$menu); // positionSubmenu, will only do anything if user already hovered over menu item that just got new subitems. } // Wait for promise completion. .then(success, error, notify) (we don't track notify). Bind the opt // and root to avoid scope problems promise.then(completedPromise.bind(this, opt, root), errorPromise.bind(this, opt, root)); }, // operation that will run after contextMenu showed on screen activated: function(opt){ var $menu = opt.$menu; var $menuOffset = $menu.offset(); var winHeight = $(window).height(); var winWidth = $(window).width(); var winScrollTop = $(window).scrollTop(); var winScrollLeft = $(window).scrollLeft(); var menuHeight = $menu.height(); var outerHeight = $menu.outerHeight(); var outerWidth = $menu.outerWidth(); if(menuHeight > winHeight){ $menu.css({ 'height' : winHeight + 'px', 'overflow-x': 'hidden', 'overflow-y': 'auto', 'top': winScrollTop + 'px' }); } else if($menuOffset.top < winScrollTop){ $menu.css({ 'top': winScrollTop + 'px' }); } else if($menuOffset.top + outerHeight > winScrollTop + winHeight){ $menu.css({ 'top': $menuOffset.top - (($menuOffset.top + outerHeight) - (winScrollTop + winHeight)) + "px" }); } if($menuOffset.left + outerWidth > winScrollLeft + winWidth){ $menu.css({ 'left': $menuOffset.left - (($menuOffset.left + outerWidth) - (winScrollLeft + winWidth)) + "px" }); } } }; // split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key function splitAccesskey(val) { var t = val.split(/\s+/); var keys = []; for (var i = 0, k; k = t[i]; i++) { k = k.charAt(0).toUpperCase(); // first character only // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it. // a map to look up already used access keys would be nice keys.push(k); } return keys; } // handle contextMenu triggers $.fn.contextMenu = function (operation) { var $t = this, $o = operation; if (this.length > 0) { // this is not a build on demand menu if (typeof operation === 'undefined') { this.first().trigger('contextmenu'); } else if (typeof operation.x !== 'undefined' && typeof operation.y !== 'undefined') { this.first().trigger($.Event('contextmenu', { pageX: operation.x, pageY: operation.y, mouseButton: operation.button })); } else if (operation === 'hide') { var $menu = this.first().data('contextMenu') ? this.first().data('contextMenu').$menu : null; if ($menu) { $menu.trigger('contextmenu:hide'); } } else if (operation === 'destroy') { $.contextMenu('destroy', {context: this}); } else if ($.isPlainObject(operation)) { operation.context = this; $.contextMenu('create', operation); } else if (operation) { this.removeClass('context-menu-disabled'); } else if (!operation) { this.addClass('context-menu-disabled'); } } else { $.each(menus, function () { if (this.selector === $t.selector) { $o.data = this; $.extend($o.data, {trigger: 'demand'}); } }); handle.contextmenu.call($o.target, $o); } return this; }; // manage contextMenu instances $.contextMenu = function (operation, options) { if (typeof operation !== 'string') { options = operation; operation = 'create'; } if (typeof options === 'string') { options = {selector: options}; } else if (typeof options === 'undefined') { options = {}; } // merge with default options var o = $.extend(true, {}, defaults, options || {}); var $document = $(document); var $context = $document; var _hasContext = false; if (!o.context || !o.context.length) { o.context = document; } else { // you never know what they throw at you... $context = $(o.context).first(); o.context = $context.get(0); _hasContext = !$(o.context).is(document); } switch (operation) { case 'update': // Updates visibility and such if(_hasContext){ op.update($context); } else { for(var menu in menus){ if(menus.hasOwnProperty(menu)){ op.update(menus[menu]); } } } break; case 'create': // no selector no joy if (!o.selector) { throw new Error('No selector specified'); } // make sure internal classes are not bound to if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) { throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className'); } if (!o.build && (!o.items || $.isEmptyObject(o.items))) { throw new Error('No Items specified'); } counter++; o.ns = '.contextMenu' + counter; if (!_hasContext) { namespaces[o.selector] = o.ns; } menus[o.ns] = o; // default to right click if (!o.trigger) { o.trigger = 'right'; } if (!initialized) { var itemClick = o.itemClickEvent === 'click' ? 'click.contextMenu' : 'mouseup.contextMenu'; var contextMenuItemObj = { // 'mouseup.contextMenu': handle.itemClick, // 'click.contextMenu': handle.itemClick, 'contextmenu:focus.contextMenu': handle.focusItem, 'contextmenu:blur.contextMenu': handle.blurItem, 'contextmenu.contextMenu': handle.abortevent, 'mouseenter.contextMenu': handle.itemMouseenter, 'mouseleave.contextMenu': handle.itemMouseleave }; contextMenuItemObj[itemClick] = handle.itemClick; // make sure item click is registered first $document .on({ 'contextmenu:hide.contextMenu': handle.hideMenu, 'prevcommand.contextMenu': handle.prevItem, 'nextcommand.contextMenu': handle.nextItem, 'contextmenu.contextMenu': handle.abortevent, 'mouseenter.contextMenu': handle.menuMouseenter, 'mouseleave.contextMenu': handle.menuMouseleave }, '.context-menu-list') .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick) .on(contextMenuItemObj, '.context-menu-item'); initialized = true; } // engage native contextmenu event $context .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu); if (_hasContext) { // add remove hook, just in case $context.on('remove' + o.ns, function () { $(this).contextMenu('destroy'); }); } switch (o.trigger) { case 'hover': $context .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter) .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); break; case 'left': $context.on('click' + o.ns, o.selector, o, handle.click); break; case 'touchstart': $context.on('touchstart' + o.ns, o.selector, o, handle.click); break; /* default: // http://www.quirksmode.org/dom/events/contextmenu.html $document .on('mousedown' + o.ns, o.selector, o, handle.mousedown) .on('mouseup' + o.ns, o.selector, o, handle.mouseup); break; */ } // create menu if (!o.build) { op.create(o); } break; case 'destroy': var $visibleMenu; if (_hasContext) { // get proper options var context = o.context; $.each(menus, function (ns, o) { if (!o) { return true; } // Is this menu equest to the context called from if (!$(context).is(o.selector)) { return true; } $visibleMenu = $('.context-menu-list').filter(':visible'); if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) { $visibleMenu.trigger('contextmenu:hide', {force: true}); } try { if (menus[o.ns].$menu) { menus[o.ns].$menu.remove(); } delete menus[o.ns]; } catch (e) { menus[o.ns] = null; } $(o.context).off(o.ns); return true; }); } else if (!o.selector) { $document.off('.contextMenu .contextMenuAutoHide'); $.each(menus, function (ns, o) { $(o.context).off(o.ns); }); namespaces = {}; menus = {}; counter = 0; initialized = false; $('#context-menu-layer, .context-menu-list').remove(); } else if (namespaces[o.selector]) { $visibleMenu = $('.context-menu-list').filter(':visible'); if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) { $visibleMenu.trigger('contextmenu:hide', {force: true}); } try { if (menus[namespaces[o.selector]].$menu) { menus[namespaces[o.selector]].$menu.remove(); } delete menus[namespaces[o.selector]]; } catch (e) { menus[namespaces[o.selector]] = null; } $document.off(namespaces[o.selector]); } break; case 'html5': // if and are not handled by the browser, // or options was a bool true, // initialize $.contextMenu for them if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options === 'boolean' && options)) { $('menu[type="context"]').each(function () { if (this.id) { $.contextMenu({ selector: '[contextmenu=' + this.id + ']', items: $.contextMenu.fromMenu(this) }); } }).css('display', 'none'); } break; default: throw new Error('Unknown operation "' + operation + '"'); } return this; }; // import values into commands $.contextMenu.setInputValues = function (opt, data) { if (typeof data === 'undefined') { data = {}; } $.each(opt.inputs, function (key, item) { switch (item.type) { case 'text': case 'textarea': item.value = data[key] || ''; break; case 'checkbox': item.selected = data[key] ? true : false; break; case 'radio': item.selected = (data[item.radio] || '') === item.value; break; case 'select': item.selected = data[key] || ''; break; } }); }; // export values from commands $.contextMenu.getInputValues = function (opt, data) { if (typeof data === 'undefined') { data = {}; } $.each(opt.inputs, function (key, item) { switch (item.type) { case 'text': case 'textarea': case 'select': data[key] = item.$input.val(); break; case 'checkbox': data[key] = item.$input.prop('checked'); break; case 'radio': if (item.$input.prop('checked')) { data[item.radio] = item.value; } break; } }); return data; }; // find