| 5 |
lars |
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));
|