1
|
/**
|
2
|
* Redmine - ContextMenu (JQuery plugin)
|
3
|
*
|
4
|
* @author: Jan Strnadek <jan.strnadek@gmail.com, jan.strnadek@netbrick.eu>
|
5
|
*/
|
6
|
(function($) {
|
7
|
"use strict"; // jshint
|
8
|
|
9
|
/** Context Menu Manager (SINGLETON) */
|
10
|
var ContextMenuManager = function() {
|
11
|
if ( ContextMenuManager.prototype._singletonInstance )
|
12
|
return ContextMenuManager.prototype._singletonInstance;
|
13
|
|
14
|
ContextMenuManager.prototype._singletonInstance = this;
|
15
|
|
16
|
this.init();
|
17
|
};
|
18
|
|
19
|
ContextMenuManager.prototype = {
|
20
|
|
21
|
init: function() {
|
22
|
var that = this;
|
23
|
this.stack = [];
|
24
|
this.$el = $("<div></div>").attr('id', 'context-menu').hide();
|
25
|
$("#content").append(this.$el);
|
26
|
|
27
|
/** Click event handler */
|
28
|
$(document).click(function(evt) {
|
29
|
that.click(evt);
|
30
|
});
|
31
|
|
32
|
/** Escape event handler */
|
33
|
$(document).keyup(function(evt) {
|
34
|
if (evt.keyCode == 27)
|
35
|
that.hideContexMenu();
|
36
|
});
|
37
|
},
|
38
|
|
39
|
click: function(evt) {
|
40
|
var target = $(evt.target);
|
41
|
if (target.is('a') && target.hasClass('submenu')) {
|
42
|
event.preventDefault();
|
43
|
return;
|
44
|
}
|
45
|
this.hideContexMenu();
|
46
|
},
|
47
|
|
48
|
hideContexMenu: function() {
|
49
|
this.$el.hide();
|
50
|
},
|
51
|
|
52
|
add: function(contextMenu) {
|
53
|
this.stack.push(contextMenu);
|
54
|
},
|
55
|
|
56
|
getElement: function() {
|
57
|
return this.$el;
|
58
|
},
|
59
|
|
60
|
showContextMenu: function(evt, data) {
|
61
|
var mouse_x = evt.pageX;
|
62
|
var mouse_y = evt.pageY;
|
63
|
this.$el.css('left', (mouse_x + 'px'));
|
64
|
this.$el.css('top', (mouse_y + 'px'));
|
65
|
this.$el.html(data);
|
66
|
this.resizeContextMenu(mouse_x, mouse_y);
|
67
|
this.$el.show();
|
68
|
},
|
69
|
|
70
|
resizeContextMenu: function(mouse_x, mouse_y) {
|
71
|
var dims;
|
72
|
var menu_width;
|
73
|
var menu_height;
|
74
|
var window_width;
|
75
|
var window_height;
|
76
|
var max_width;
|
77
|
var max_height;
|
78
|
|
79
|
menu_width = this.$el.width();
|
80
|
menu_height = this.$el.height();
|
81
|
max_width = mouse_x + 2 * menu_width;
|
82
|
max_height = mouse_y + menu_height;
|
83
|
|
84
|
var ws = this.windowSize();
|
85
|
window_width = ws.width;
|
86
|
window_height = ws.height;
|
87
|
|
88
|
/* display the menu above and/or to the left of the click if needed */
|
89
|
if (max_width > window_width) {
|
90
|
mouse_x -= menu_width;
|
91
|
this.$el.addClass('reverse-x');
|
92
|
} else {
|
93
|
this.$el.removeClass('reverse-x');
|
94
|
}
|
95
|
if (max_height > window_height) {
|
96
|
mouse_y -= menu_height;
|
97
|
this.$el.addClass('reverse-y');
|
98
|
} else {
|
99
|
this.$el.removeClass('reverse-y');
|
100
|
}
|
101
|
if (mouse_x <= 0) mouse_x = 1;
|
102
|
if (mouse_y <= 0) mouse_y = 1;
|
103
|
this.$el.css('left', (mouse_x + 'px'));
|
104
|
this.$el.css('top', (mouse_y + 'px'));
|
105
|
},
|
106
|
|
107
|
windowSize: function() {
|
108
|
var w;
|
109
|
var h;
|
110
|
if (window.innerWidth) {
|
111
|
w = window.innerWidth;
|
112
|
h = window.innerHeight;
|
113
|
} else if (document.documentElement) {
|
114
|
w = document.documentElement.clientWidth;
|
115
|
h = document.documentElement.clientHeight;
|
116
|
} else {
|
117
|
w = document.body.clientWidth;
|
118
|
h = document.body.clientHeight;
|
119
|
}
|
120
|
return {width: w, height: h};
|
121
|
},
|
122
|
};
|
123
|
|
124
|
/* ContextMenu class definition */
|
125
|
var ContextMenu = function (element, options) {
|
126
|
this.init (element, options);
|
127
|
};
|
128
|
|
129
|
ContextMenu.prototype = {
|
130
|
constructor: ContextMenu,
|
131
|
|
132
|
init: function (element, options) {
|
133
|
var that = this;
|
134
|
this.options = options;
|
135
|
|
136
|
/** Add menu to manager */
|
137
|
this.manager = new ContextMenuManager();
|
138
|
this.manager.add ( this );
|
139
|
|
140
|
/** Start observing element */
|
141
|
this.$el = $(element);
|
142
|
this.$el.contextmenu(function(evt) {
|
143
|
that.contextMenuClick(evt);
|
144
|
});
|
145
|
this.$el.click(function(evt) {
|
146
|
that.click(evt);
|
147
|
});
|
148
|
},
|
149
|
|
150
|
click: function(evt) {
|
151
|
var target = $(evt.target);
|
152
|
|
153
|
if (target.is('a') && target.hasClass('submenu')) {
|
154
|
event.preventDefault();
|
155
|
return;
|
156
|
}
|
157
|
if (target.is('a') || target.is('img')) { return; }
|
158
|
|
159
|
/** Check if row was clicked */
|
160
|
var tr = target.parents('tr').first();
|
161
|
if (tr.length && tr.hasClass('hascontextmenu')) {
|
162
|
/** Input was clicked - do not remove selection from all */
|
163
|
if (target.is('input')) {
|
164
|
if (target.is(':checked')) {
|
165
|
tr.addClass('context-menu-selection');
|
166
|
} else {
|
167
|
tr.removeClass('context-menu-selection');
|
168
|
}
|
169
|
} else {
|
170
|
/** Disable all selected items */
|
171
|
this.$el.find('tr.hascontextmenu').each(function() {
|
172
|
var element = $(this);
|
173
|
var input = element.find('td.checkbox input[type="checkbox"]').first();
|
174
|
if ( input ) input.prop('checked', false);
|
175
|
element.removeClass('context-menu-selection');
|
176
|
});
|
177
|
|
178
|
/** Set checked to true and add tr context class */
|
179
|
tr.addClass('context-menu-selection');
|
180
|
var input = tr.find('td.checkbox input[type="checkbox"]').first();
|
181
|
if ( input ) input.prop('checked', true);
|
182
|
}
|
183
|
}
|
184
|
},
|
185
|
|
186
|
contextMenuClick: function(evt) {
|
187
|
var target = $(evt.target);
|
188
|
|
189
|
if (target.is('a')) { return; }
|
190
|
|
191
|
var tr = target.parents('tr').first();
|
192
|
if (!tr.hasClass('hascontextmenu')) { return; }
|
193
|
|
194
|
/** Clicked on tr - ensure it is checked */
|
195
|
var input = tr.find('td.checkbox input[type="checkbox"]');
|
196
|
if ( input ) {
|
197
|
if ( !input.is(':checked') ) {
|
198
|
/** Input is not checked - remove selection of other elements */
|
199
|
this.$el.find('tr.hascontextmenu').each(function() {
|
200
|
var element = $(this);
|
201
|
var input = element.find('td.checkbox input[type="checkbox"]').first();
|
202
|
if ( input ) input.prop('checked', false);
|
203
|
element.removeClass('context-menu-selection');
|
204
|
});
|
205
|
}
|
206
|
input.prop('checked', true);
|
207
|
tr.addClass('context-menu-selection');
|
208
|
}
|
209
|
|
210
|
evt.preventDefault();
|
211
|
this.show(evt);
|
212
|
},
|
213
|
|
214
|
show: function(evt) {
|
215
|
var manager = this.manager;
|
216
|
var form = this.$el.children('form').first();
|
217
|
|
218
|
$.ajax({
|
219
|
url: this.options.url,
|
220
|
data: form.serialize(),
|
221
|
success: function(data, textStatus, jqXHR) {
|
222
|
manager.showContextMenu(evt, data);
|
223
|
}
|
224
|
});
|
225
|
}
|
226
|
};
|
227
|
|
228
|
$.fn.contextMenu = function(options) {
|
229
|
var defaults = {
|
230
|
url: null
|
231
|
};
|
232
|
var settings = $.extend( {}, defaults, options );
|
233
|
|
234
|
/* Element for bind is table / parent DIV */
|
235
|
return this.each(function() {
|
236
|
new ContextMenu(this, settings);
|
237
|
});
|
238
|
};
|
239
|
|
240
|
})( jQuery );
|