Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | espaco | 1 | /*! |
| 2 | * Bootstrap Context Menu |
||
| 3 | * Author: @sydcanem |
||
| 4 | * https://github.com/sydcanem/bootstrap-contextmenu |
||
| 5 | * |
||
| 6 | * Inspired by Bootstrap's dropdown plugin. |
||
| 7 | * Bootstrap (http://getbootstrap.com). |
||
| 8 | * |
||
| 9 | * Licensed under MIT |
||
| 10 | * ========================================================= */ |
||
| 11 | |||
| 12 | ;(function($) { |
||
| 13 | |||
| 14 | 'use strict'; |
||
| 15 | |||
| 16 | /* CONTEXTMENU CLASS DEFINITION |
||
| 17 | * ============================ */ |
||
| 18 | var toggle = '[data-toggle="context"]'; |
||
| 19 | |||
| 20 | var ContextMenu = function (element, options) { |
||
| 21 | this.$element = $(element); |
||
| 22 | |||
| 23 | this.before = options.before || this.before; |
||
| 24 | this.onItem = options.onItem || this.onItem; |
||
| 25 | this.scopes = options.scopes || null; |
||
| 26 | |||
| 27 | if (options.target) { |
||
| 28 | this.$element.data('target', options.target); |
||
| 29 | } |
||
| 30 | |||
| 31 | this.listen(); |
||
| 32 | }; |
||
| 33 | |||
| 34 | ContextMenu.prototype = { |
||
| 35 | |||
| 36 | constructor: ContextMenu |
||
| 37 | ,show: function(e) { |
||
| 38 | |||
| 39 | var $menu |
||
| 40 | , evt |
||
| 41 | , tp |
||
| 42 | , items |
||
| 43 | , relatedTarget = { relatedTarget: this, target: e.currentTarget }; |
||
| 44 | |||
| 45 | if (this.isDisabled()) return; |
||
| 46 | |||
| 47 | this.closemenu(); |
||
| 48 | |||
| 49 | if (!this.before.call(this,e,$(e.currentTarget))) return; |
||
| 50 | |||
| 51 | $menu = this.getMenu(); |
||
| 52 | $menu.trigger(evt = $.Event('show.bs.context', relatedTarget)); |
||
| 53 | |||
| 54 | tp = this.getPosition(e, $menu); |
||
| 55 | items = 'li:not(.divider)'; |
||
| 56 | $menu.attr('style', '') |
||
| 57 | .css(tp) |
||
| 58 | .addClass('open') |
||
| 59 | .on('click.context.data-api', items, $.proxy(this.onItem, this, $(e.currentTarget))) |
||
| 60 | .trigger('shown.bs.context', relatedTarget); |
||
| 61 | |||
| 62 | // Delegating the `closemenu` only on the currently opened menu. |
||
| 63 | // This prevents other opened menus from closing. |
||
| 64 | $('html') |
||
| 65 | .on('click.context.data-api', $menu.selector, $.proxy(this.closemenu, this)); |
||
| 66 | |||
| 67 | return false; |
||
| 68 | } |
||
| 69 | |||
| 70 | ,closemenu: function(e) { |
||
| 71 | var $menu |
||
| 72 | , evt |
||
| 73 | , items |
||
| 74 | , relatedTarget; |
||
| 75 | |||
| 76 | $menu = this.getMenu(); |
||
| 77 | |||
| 78 | if(!$menu.hasClass('open')) return; |
||
| 79 | |||
| 80 | relatedTarget = { relatedTarget: this }; |
||
| 81 | $menu.trigger(evt = $.Event('hide.bs.context', relatedTarget)); |
||
| 82 | |||
| 83 | items = 'li:not(.divider)'; |
||
| 84 | $menu.removeClass('open') |
||
| 85 | .off('click.context.data-api', items) |
||
| 86 | .trigger('hidden.bs.context', relatedTarget); |
||
| 87 | |||
| 88 | $('html') |
||
| 89 | .off('click.context.data-api', $menu.selector); |
||
| 90 | // Don't propagate click event so other currently |
||
| 91 | // opened menus won't close. |
||
| 92 | return false; |
||
| 93 | } |
||
| 94 | |||
| 95 | ,keydown: function(e) { |
||
| 96 | if (e.which == 27) this.closemenu(e); |
||
| 97 | } |
||
| 98 | |||
| 99 | ,before: function(e) { |
||
| 100 | return true; |
||
| 101 | } |
||
| 102 | |||
| 103 | ,onItem: function(e) { |
||
| 104 | return true; |
||
| 105 | } |
||
| 106 | |||
| 107 | ,listen: function () { |
||
| 108 | this.$element.on('contextmenu.context.data-api', this.scopes, $.proxy(this.show, this)); |
||
| 109 | $('html').on('click.context.data-api', $.proxy(this.closemenu, this)); |
||
| 110 | $('html').on('keydown.context.data-api', $.proxy(this.keydown, this)); |
||
| 111 | } |
||
| 112 | |||
| 113 | ,destroy: function() { |
||
| 114 | this.$element.off('.context.data-api').removeData('context'); |
||
| 115 | $('html').off('.context.data-api'); |
||
| 116 | } |
||
| 117 | |||
| 118 | ,isDisabled: function() { |
||
| 119 | return this.$element.hasClass('disabled') || |
||
| 120 | this.$element.attr('disabled'); |
||
| 121 | } |
||
| 122 | |||
| 123 | ,getMenu: function () { |
||
| 124 | var selector = this.$element.data('target') |
||
| 125 | , $menu; |
||
| 126 | |||
| 127 | if (!selector) { |
||
| 128 | selector = this.$element.attr('href'); |
||
| 129 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, ''); //strip for ie7 |
||
| 130 | } |
||
| 131 | |||
| 132 | $menu = $(selector); |
||
| 133 | |||
| 134 | return $menu && $menu.length ? $menu : this.$element.find(selector); |
||
| 135 | } |
||
| 136 | |||
| 137 | ,getPosition: function(e, $menu) { |
||
| 138 | var mouseX = e.clientX |
||
| 139 | , mouseY = e.clientY |
||
| 140 | , boundsX = $(window).width() |
||
| 141 | , boundsY = $(window).height() |
||
| 142 | , menuWidth = $menu.find('.dropdown-menu').outerWidth() |
||
| 143 | , menuHeight = $menu.find('.dropdown-menu').outerHeight() |
||
| 144 | , tp = {"position":"absolute","z-index":9999} |
||
| 145 | , Y, X, parentOffset; |
||
| 146 | |||
| 147 | if (mouseY + menuHeight > boundsY) { |
||
| 148 | Y = {"top": mouseY - menuHeight + $(window).scrollTop()}; |
||
| 149 | } else { |
||
| 150 | Y = {"top": mouseY + $(window).scrollTop()}; |
||
| 151 | } |
||
| 152 | |||
| 153 | if ((mouseX + menuWidth > boundsX) && ((mouseX - menuWidth) > 0)) { |
||
| 154 | X = {"left": mouseX - menuWidth + $(window).scrollLeft()}; |
||
| 155 | } else { |
||
| 156 | X = {"left": mouseX + $(window).scrollLeft()}; |
||
| 157 | } |
||
| 158 | |||
| 159 | // If context-menu's parent is positioned using absolute or relative positioning, |
||
| 160 | // the calculated mouse position will be incorrect. |
||
| 161 | // Adjust the position of the menu by its offset parent position. |
||
| 162 | parentOffset = $menu.offsetParent().offset(); |
||
| 163 | X.left = X.left - parentOffset.left; |
||
| 164 | Y.top = Y.top - parentOffset.top; |
||
| 165 | |||
| 166 | return $.extend(tp, Y, X); |
||
| 167 | } |
||
| 168 | |||
| 169 | }; |
||
| 170 | |||
| 171 | /* CONTEXT MENU PLUGIN DEFINITION |
||
| 172 | * ========================== */ |
||
| 173 | |||
| 174 | $.fn.contextmenu = function (option,e) { |
||
| 175 | return this.each(function () { |
||
| 176 | var $this = $(this) |
||
| 177 | , data = $this.data('context') |
||
| 178 | , options = (typeof option == 'object') && option; |
||
| 179 | |||
| 180 | if (!data) $this.data('context', (data = new ContextMenu($this, options))); |
||
| 181 | if (typeof option == 'string') data[option].call(data, e); |
||
| 182 | }); |
||
| 183 | }; |
||
| 184 | |||
| 185 | $.fn.contextmenu.Constructor = ContextMenu; |
||
| 186 | |||
| 187 | /* APPLY TO STANDARD CONTEXT MENU ELEMENTS |
||
| 188 | * =================================== */ |
||
| 189 | |||
| 190 | $(document) |
||
| 191 | .on('contextmenu.context.data-api', function() { |
||
| 192 | $(toggle).each(function () { |
||
| 193 | var data = $(this).data('context'); |
||
| 194 | if (!data) return; |
||
| 195 | data.closemenu(); |
||
| 196 | }); |
||
| 197 | }) |
||
| 198 | .on('contextmenu.context.data-api', toggle, function(e) { |
||
| 199 | $(this).contextmenu('show', e); |
||
| 200 | |||
| 201 | e.preventDefault(); |
||
| 202 | e.stopPropagation(); |
||
| 203 | }); |
||
| 204 | |||
| 205 | }(jQuery)); |