1 | (function ($) {
|
2 | // register namespace
|
3 | $.extend(true, window, {
|
4 | "Slick": {
|
5 | "Plugins": {
|
6 | "ContextMenu": ContextMenu
|
7 | }
|
8 | }
|
9 | });
|
10 |
|
11 | /***
|
12 | * A plugin to add Context Menu (mouse right+click), it subscribes to the cell "onContextMenu" event.
|
13 | * The "contextMenu" is defined in the Grid Options object
|
14 | * You can use it to change a data property (only 1) through a list of Options AND/OR through a list of Commands.
|
15 | * A good example of a Command would be an Export to CSV, that can be run from anywhere in the grid by doing a mouse right+click
|
16 | *
|
17 | * Note:
|
18 | * There is only 1 list of Options, so typically that would be use for 1 column
|
19 | * if you plan to use different Options for different columns, then the CellMenu plugin might be better suited.
|
20 | *
|
21 | * USAGE:
|
22 | *
|
23 | * Add the slick.contextmenu.(js|css) files and register it with the grid.
|
24 | *
|
25 | * To specify a menu in a column header, extend the column definition like so:
|
26 | * var contextMenuPlugin = new Slick.Plugins.ContextMenu(columns, grid, options);
|
27 | *
|
28 | * Available grid options, by defining a contextMenu object:
|
29 | *
|
30 | * var options = {
|
31 | * enableCellNavigation: true,
|
32 | * contextMenu: {
|
33 | * optionTitle: 'Change Priority',
|
34 | * optionShownOverColumnIds: ["priority"],
|
35 | * optionItems: [
|
36 | * { option: 0, title: 'none', cssClass: 'italic' },
|
37 | * { divider: true },
|
38 | * "divider" // just the string is also accepted
|
39 | * { option: 1, iconCssClass: 'fa fa-fire grey', title: 'Low' },
|
40 | * { option: 3, iconCssClass: 'fa fa-fire red', title: 'High' },
|
41 | * { option: 2, iconCssClass: 'fa fa-fire orange', title: 'Medium' },
|
42 | * { option: 4, iconCssClass: 'fa fa-fire', title: 'Extreme', disabled: true },
|
43 | * ],
|
44 | * commandTitle: 'Commands',
|
45 | * commandShownOverColumnIds: ["title", "complete", "start", "finish", "effortDriven"],
|
46 | * commandItems: [
|
47 | * { command: 'export-excel', title: 'Export to CSV', iconCssClass: 'fa fa-file-excel-o', cssClass: '' },
|
48 | * { command: 'delete-row', title: 'Delete Row', cssClass: 'bold', textCssClass: 'red' },
|
49 | * { command: 'help', title: 'Help', iconCssClass: 'fa fa-question-circle',},
|
50 | * { divider: true },
|
51 | * ],
|
52 | * }
|
53 | * };
|
54 | *
|
55 | *
|
56 | * Available contextMenu properties:
|
57 | * commandTitle: Title of the Command section (optional)
|
58 | * commandItems: Array of Command item objects (command/title pair)
|
59 | * commandShownOverColumnIds: Define which column to show the Commands list. If not defined (defaults), the menu will be shown over all columns
|
60 | * optionTitle: Title of the Option section (optional)
|
61 | * optionItems: Array of Options item objects (option/title pair)
|
62 | * optionShownOverColumnIds: Define which column to show the Options list. If not defined (defaults), the menu will be shown over all columns
|
63 | * hideCloseButton: Hide the Close button on top right (defaults to false)
|
64 | * hideCommandSection: Hide the Commands section even when the commandItems array is filled (defaults to false)
|
65 | * hideMenuOnScroll: Do we want to hide the Cell Menu when a scrolling event occurs (defaults to false)?
|
66 | * hideOptionSection: Hide the Options section even when the optionItems array is filled (defaults to false)
|
67 | * maxHeight: Maximum height that the drop menu will have, can be a number (250) or text ("none")
|
68 | * width: Width that the drop menu will have, can be a number (250) or text (defaults to "auto")
|
69 | * autoAdjustDrop: Auto-align dropup or dropdown menu to the left or right depending on grid viewport available space (defaults to true)
|
70 | * autoAdjustDropOffset: Optionally add an offset to the auto-align of the drop menu (defaults to -4)
|
71 | * autoAlignSide: Auto-align drop menu to the left or right depending on grid viewport available space (defaults to true)
|
72 | * autoAlignSideOffset: Optionally add an offset to the left/right side auto-align (defaults to 0)
|
73 | * menuUsabilityOverride: Callback method that user can override the default behavior of enabling/disabling the menu from being usable (must be combined with a custom formatter)
|
74 | *
|
75 | *
|
76 | * Available menu Command/Option item properties:
|
77 | * action: Optionally define a callback function that gets executed when item is chosen (and/or use the onCommand event)
|
78 | * command: A command identifier to be passed to the onCommand event handlers (when using "commandItems").
|
79 | * option: An option to be passed to the onOptionSelected event handlers (when using "optionItems").
|
80 | * title: Menu item text.
|
81 | * divider: Boolean which tell if the current item is a divider, not an actual command. You could also pass "divider" instead of an object
|
82 | * disabled: Whether the item/command is disabled.
|
83 | * hidden: Whether the item/command is hidden.
|
84 | * tooltip: Item tooltip.
|
85 | * cssClass: A CSS class to be added to the menu item container.
|
86 | * iconCssClass: A CSS class to be added to the menu item icon.
|
87 | * textCssClass: A CSS class to be added to the menu item text.
|
88 | * iconImage: A url to the icon image.
|
89 | * itemVisibilityOverride: Callback method that user can override the default behavior of showing/hiding an item from the list
|
90 | * itemUsabilityOverride: Callback method that user can override the default behavior of enabling/disabling an item from the list
|
91 | *
|
92 | * The plugin exposes the following events:
|
93 | *
|
94 | * onAfterMenuShow: Fired after the menu is shown. You can customize the menu or dismiss it by returning false.
|
95 | * Event args:
|
96 | * cell: Cell or column index
|
97 | * row: Row index
|
98 | * grid: Reference to the grid.
|
99 | *
|
100 | * onBeforeMenuShow: Fired before the menu is shown. You can customize the menu or dismiss it by returning false.
|
101 | * Event args:
|
102 | * cell: Cell or column index
|
103 | * row: Row index
|
104 | * grid: Reference to the grid.
|
105 | *
|
106 | * onBeforeMenuClose: Fired when the menu is closing.
|
107 | * Event args:
|
108 | * cell: Cell or column index
|
109 | * row: Row index
|
110 | * grid: Reference to the grid.
|
111 | * menu: Menu DOM element
|
112 | *
|
113 | * onCommand: Fired on menu option clicked from the Command items list
|
114 | * Event args:
|
115 | * cell: Cell or column index
|
116 | * row: Row index
|
117 | * grid: Reference to the grid.
|
118 | * command: Menu command identified.
|
119 | * item: Menu item selected
|
120 | * column: Cell Column definition
|
121 | * dataContext: Cell Data Context (data object)
|
122 | * value: Value of the cell we triggered the context menu from
|
123 | *
|
124 | * onOptionSelected: Fired on menu option clicked from the Option items list
|
125 | * Event args:
|
126 | * cell: Cell or column index
|
127 | * row: Row index
|
128 | * grid: Reference to the grid.
|
129 | * option: Menu option selected.
|
130 | * item: Menu item selected
|
131 | * column: Cell Column definition
|
132 | * dataContext: Cell Data Context (data object)
|
133 | *
|
134 | *
|
135 | * @param options {Object} Context Menu Options
|
136 | * @class Slick.Plugins.ContextMenu
|
137 | * @constructor
|
138 | */
|
139 | function ContextMenu(optionProperties) {
|
140 | var _contextMenuProperties;
|
141 | var _currentCell = -1;
|
142 | var _currentRow = -1;
|
143 | var _grid;
|
144 | var _gridOptions;
|
145 | var _gridUid = "";
|
146 | var _handler = new Slick.EventHandler();
|
147 | var _self = this;
|
148 | var $optionTitleElm;
|
149 | var $commandTitleElm;
|
150 | var $menu;
|
151 |
|
152 | var _defaults = {
|
153 | autoAdjustDrop: true, // dropup/dropdown
|
154 | autoAlignSide: true, // left/right
|
155 | autoAdjustDropOffset: -4,
|
156 | autoAlignSideOffset: 0,
|
157 | hideMenuOnScroll: false,
|
158 | maxHeight: "none",
|
159 | width: "auto",
|
160 | optionShownOverColumnIds: [],
|
161 | commandShownOverColumnIds: [],
|
162 | };
|
163 |
|
164 | function init(grid) {
|
165 | _grid = grid;
|
166 | _gridOptions = grid.getOptions();
|
167 | _contextMenuProperties = $.extend({}, _defaults, optionProperties);
|
168 | _gridUid = (grid && grid.getUID) ? grid.getUID() : "";
|
169 | _handler.subscribe(_grid.onContextMenu, handleOnContextMenu);
|
170 | if (_contextMenuProperties.hideMenuOnScroll) {
|
171 | _handler.subscribe(_grid.onScroll, destroyMenu);
|
172 | }
|
173 | }
|
174 |
|
175 | function setOptions(newOptions) {
|
176 | _contextMenuProperties = $.extend({}, _contextMenuProperties, newOptions);
|
177 |
|
178 | // on the array properties, we want to make sure to overwrite them and not just extending them
|
179 | if (newOptions.commandShownOverColumnIds) {
|
180 | _contextMenuProperties.commandShownOverColumnIds = newOptions.commandShownOverColumnIds;
|
181 | }
|
182 | if (newOptions.optionShownOverColumnIds) {
|
183 | _contextMenuProperties.optionShownOverColumnIds = newOptions.optionShownOverColumnIds;
|
184 | }
|
185 | }
|
186 |
|
187 | function destroy() {
|
188 | _self.onAfterMenuShow.unsubscribe();
|
189 | _self.onBeforeMenuShow.unsubscribe();
|
190 | _self.onBeforeMenuClose.unsubscribe();
|
191 | _self.onCommand.unsubscribe();
|
192 | _self.onOptionSelected.unsubscribe();
|
193 | _handler.unsubscribeAll();
|
194 | if ($menu && $menu.remove) {
|
195 | $menu.remove();
|
196 | }
|
197 | $commandTitleElm = null;
|
198 | $optionTitleElm = null;
|
199 | $menu = null;
|
200 | }
|
201 |
|
202 | function createMenu(e) {
|
203 | var cell = _grid.getCellFromEvent(e);
|
204 | _currentCell = cell && cell.cell;
|
205 | _currentRow = cell && cell.row;
|
206 | var columnDef = _grid.getColumns()[_currentCell];
|
207 | var dataContext = _grid.getDataItem(_currentRow);
|
208 |
|
209 | var isColumnOptionAllowed = checkIsColumnAllowed(_contextMenuProperties.optionShownOverColumnIds, columnDef.id);
|
210 | var isColumnCommandAllowed = checkIsColumnAllowed(_contextMenuProperties.commandShownOverColumnIds, columnDef.id);
|
211 | var commandItems = _contextMenuProperties.commandItems || [];
|
212 | var optionItems = _contextMenuProperties.optionItems || [];
|
213 |
|
214 | // make sure there's at least something to show before creating the Context Menu
|
215 | if (!columnDef || (!isColumnCommandAllowed && !isColumnOptionAllowed) || (!commandItems.length && optionItems.length)) {
|
216 | return;
|
217 | }
|
218 |
|
219 | // delete any prior context menu
|
220 | destroyMenu(e);
|
221 |
|
222 | // Let the user modify the menu or cancel altogether,
|
223 | // or provide alternative menu implementation.
|
224 | if (_self.onBeforeMenuShow.notify({
|
225 | "cell": _currentCell,
|
226 | "row": _currentRow,
|
227 | "grid": _grid
|
228 | }, e, _self) == false) {
|
229 | return;
|
230 | }
|
231 |
|
232 | // create a new context menu
|
233 | var maxHeight = isNaN(_contextMenuProperties.maxHeight) ? _contextMenuProperties.maxHeight : _contextMenuProperties.maxHeight + "px";
|
234 | var width = isNaN(_contextMenuProperties.width) ? _contextMenuProperties.width : _contextMenuProperties.width + "px";
|
235 | var menuStyle = "width: " + width + "; max-height: " + maxHeight;
|
236 | var menu = $('<div class="slick-context-menu ' + _gridUid + '" style="' + menuStyle + '" />')
|
237 | .css("top", e.pageY)
|
238 | .css("left", e.pageX)
|
239 | .css("display", "none");
|
240 |
|
241 | var closeButtonHtml = '<button type="button" class="close" data-dismiss="slick-context-menu" aria-label="Close">'
|
242 | + '<span class="close" aria-hidden="true">×</span></button>';
|
243 |
|
244 | // -- Option List section
|
245 | if (!_contextMenuProperties.hideOptionSection && isColumnOptionAllowed && optionItems.length > 0) {
|
246 | var $optionMenu = $('<div class="slick-context-menu-option-list" />');
|
247 | if (!_contextMenuProperties.hideCloseButton) {
|
248 | $(closeButtonHtml).on("click", handleCloseButtonClicked).appendTo(menu);
|
249 | }
|
250 | $optionMenu.appendTo(menu);
|
251 | populateOptionItems(
|
252 | _contextMenuProperties,
|
253 | $optionMenu,
|
254 | optionItems,
|
255 | { cell: _currentCell, row: _currentRow, column: columnDef, dataContext: dataContext, grid: _grid }
|
256 | );
|
257 | }
|
258 |
|
259 | // -- Command List section
|
260 | if (!_contextMenuProperties.hideCommandSection && isColumnCommandAllowed && commandItems.length > 0) {
|
261 | var $commandMenu = $('<div class="slick-context-menu-command-list" />');
|
262 | if (!_contextMenuProperties.hideCloseButton && (!isColumnOptionAllowed || optionItems.length === 0 || _contextMenuProperties.hideOptionSection)) {
|
263 | $(closeButtonHtml).on("click", handleCloseButtonClicked).appendTo(menu);
|
264 | }
|
265 | $commandMenu.appendTo(menu);
|
266 | populateCommandItems(
|
267 | _contextMenuProperties,
|
268 | $commandMenu,
|
269 | commandItems,
|
270 | { cell: _currentCell, row: _currentRow, column: columnDef, dataContext: dataContext, grid: _grid }
|
271 | );
|
272 | }
|
273 |
|
274 | menu.show();
|
275 | menu.appendTo("body");
|
276 |
|
277 | if (_self.onAfterMenuShow.notify({
|
278 | "cell": _currentCell,
|
279 | "row": _currentRow,
|
280 | "grid": _grid
|
281 | }, e, _self) == false) {
|
282 | return;
|
283 | }
|
284 |
|
285 | return menu;
|
286 | }
|
287 |
|
288 | function handleCloseButtonClicked(e) {
|
289 | if(!e.isDefaultPrevented()) {
|
290 | destroyMenu(e);
|
291 | }
|
292 | }
|
293 |
|
294 | function destroyMenu(e, args) {
|
295 | $menu = $menu || $(".slick-context-menu." + _gridUid);
|
296 |
|
297 | if ($menu && $menu.length > 0) {
|
298 | if (_self.onBeforeMenuClose.notify({
|
299 | "cell": args && args.cell,
|
300 | "row": args && args.row,
|
301 | "grid": _grid,
|
302 | "menu": $menu
|
303 | }, e, _self) == false) {
|
304 | return;
|
305 | }
|
306 | if ($menu && $menu.remove) {
|
307 | $menu.remove();
|
308 | $menu = null;
|
309 | }
|
310 | }
|
311 | }
|
312 |
|
313 | function checkIsColumnAllowed(columnIds, columnId) {
|
314 | var isAllowedColumn = false;
|
315 |
|
316 | if (columnIds && columnIds.length > 0) {
|
317 | for (var o = 0, ln = columnIds.length; o < ln; o++) {
|
318 | if (columnIds[o] === columnId) {
|
319 | isAllowedColumn = true;
|
320 | }
|
321 | }
|
322 | } else {
|
323 | isAllowedColumn = true;
|
324 | }
|
325 | return isAllowedColumn;
|
326 | }
|
327 |
|
328 | function calculateAvailableSpaceBottom(element) {
|
329 | var windowHeight = $(window).innerHeight() || 0;
|
330 | var pageScroll = $(window).scrollTop() || 0;
|
331 | if (element && element.offset && element.length > 0) {
|
332 | var elementOffsetTop = element.offset().top;
|
333 | return windowHeight - (elementOffsetTop - pageScroll);
|
334 | }
|
335 | return 0;
|
336 | }
|
337 |
|
338 | function calculateAvailableSpaceTop(element) {
|
339 | var pageScroll = $(window).scrollTop() || 0;
|
340 | if (element && element.offset && element.length > 0) {
|
341 | var elementOffsetTop = element.offset().top;
|
342 | return elementOffsetTop - pageScroll;
|
343 | }
|
344 | return 0;
|
345 | }
|
346 |
|
347 | function handleOnContextMenu(e, args) {
|
348 | e.preventDefault();
|
349 |
|
350 | var cell = _grid.getCellFromEvent(e);
|
351 | var columnDef = _grid.getColumns()[cell.cell];
|
352 | var dataContext = _grid.getDataItem(cell.row);
|
353 |
|
354 | // run the override function (when defined), if the result is false it won't go further
|
355 | if (!args) {
|
356 | args = {};
|
357 | }
|
358 | args.cell = cell.cell;
|
359 | args.row = cell.row;
|
360 | args.columnDef = columnDef;
|
361 | args.dataContext = dataContext;
|
362 | args.grid = _grid;
|
363 |
|
364 | if (!runOverrideFunctionWhenExists(_contextMenuProperties.menuUsabilityOverride, args)) {
|
365 | return;
|
366 | }
|
367 |
|
368 | // create the DOM element
|
369 | $menu = createMenu(e, args);
|
370 |
|
371 | // reposition the menu to where the user clicked
|
372 | if ($menu) {
|
373 | repositionMenu(e);
|
374 | $menu
|
375 | .data("cell", _currentCell)
|
376 | .data("row", _currentRow)
|
377 | .show();
|
378 | }
|
379 |
|
380 | $("body").one("click", function (e) {
|
381 | if(!e.isDefaultPrevented()) {
|
382 | destroyMenu(e, { cell: _currentCell, row: _currentRow });
|
383 | }
|
384 | });
|
385 | }
|
386 |
|
387 | /** Construct the Option Items section. */
|
388 | function populateOptionItems(contextMenu, optionMenuElm, optionItems, args) {
|
389 | if (!args || !optionItems || !contextMenu) {
|
390 | return;
|
391 | }
|
392 |
|
393 | // user could pass a title on top of the Options section
|
394 | if (contextMenu && contextMenu.optionTitle) {
|
395 | $optionTitleElm = $('<div class="title"/>').append(contextMenu.optionTitle);
|
396 | $optionTitleElm.appendTo(optionMenuElm);
|
397 | }
|
398 |
|
399 | for (var i = 0, ln = optionItems.length; i < ln; i++) {
|
400 | var item = optionItems[i];
|
401 |
|
402 | // run each override functions to know if the item is visible and usable
|
403 | var isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args);
|
404 | var isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args);
|
405 |
|
406 | // if the result is not visible then there's no need to go further
|
407 | if (!isItemVisible) {
|
408 | continue;
|
409 | }
|
410 |
|
411 | // when the override is defined, we need to use its result to update the disabled property
|
412 | // so that "handleMenuItemOptionClick" has the correct flag and won't trigger an option clicked event
|
413 | if (Object.prototype.hasOwnProperty.call(item, "itemUsabilityOverride")) {
|
414 | item.disabled = isItemUsable ? false : true;
|
415 | }
|
416 |
|
417 | var $li = $('<div class="slick-context-menu-item"></div>')
|
418 | .data("option", item.option || "")
|
419 | .data("item", item)
|
420 | .on("click", handleMenuItemOptionClick)
|
421 | .appendTo(optionMenuElm);
|
422 |
|
423 | if (item.divider || item === "divider") {
|
424 | $li.addClass("slick-context-menu-item-divider");
|
425 | continue;
|
426 | }
|
427 |
|
428 | // if the item is disabled then add the disabled css class
|
429 | if (item.disabled || !isItemUsable) {
|
430 | $li.addClass("slick-context-menu-item-disabled");
|
431 | }
|
432 |
|
433 | // if the item is hidden then add the hidden css class
|
434 | if (item.hidden) {
|
435 | $li.addClass("slick-context-menu-item-hidden");
|
436 | }
|
437 |
|
438 | if (item.cssClass) {
|
439 | $li.addClass(item.cssClass);
|
440 | }
|
441 |
|
442 | if (item.tooltip) {
|
443 | $li.attr("title", item.tooltip);
|
444 | }
|
445 |
|
446 | var $icon = $('<div class="slick-context-menu-icon"></div>')
|
447 | .appendTo($li);
|
448 |
|
449 | if (item.iconCssClass) {
|
450 | $icon.addClass(item.iconCssClass);
|
451 | }
|
452 |
|
453 | if (item.iconImage) {
|
454 | $icon.css("background-image", "url(" + item.iconImage + ")");
|
455 | }
|
456 |
|
457 | var $text = $('<span class="slick-context-menu-content"></span>')
|
458 | .text(item.title)
|
459 | .appendTo($li);
|
460 |
|
461 | if (item.textCssClass) {
|
462 | $text.addClass(item.textCssClass);
|
463 | }
|
464 | }
|
465 | }
|
466 |
|
467 | /** Construct the Command Items section. */
|
468 | function populateCommandItems(contextMenu, commandMenuElm, commandItems, args) {
|
469 | if (!args || !commandItems || !contextMenu) {
|
470 | return;
|
471 | }
|
472 |
|
473 | // user could pass a title on top of the Commands section
|
474 | if (contextMenu && contextMenu.commandTitle) {
|
475 | $commandTitleElm = $('<div class="title"/>').append(contextMenu.commandTitle);
|
476 | $commandTitleElm.appendTo(commandMenuElm);
|
477 | }
|
478 |
|
479 | for (var i = 0, ln = commandItems.length; i < ln; i++) {
|
480 | var item = commandItems[i];
|
481 |
|
482 | // run each override functions to know if the item is visible and usable
|
483 | var isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args);
|
484 | var isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args);
|
485 |
|
486 | // if the result is not visible then there's no need to go further
|
487 | if (!isItemVisible) {
|
488 | continue;
|
489 | }
|
490 |
|
491 | // when the override is defined, we need to use its result to update the disabled property
|
492 | // so that "handleMenuItemCommandClick" has the correct flag and won't trigger a command clicked event
|
493 | if (Object.prototype.hasOwnProperty.call(item, "itemUsabilityOverride")) {
|
494 | item.disabled = isItemUsable ? false : true;
|
495 | }
|
496 |
|
497 | var $li = $('<div class="slick-context-menu-item"></div>')
|
498 | .data("command", item.command || "")
|
499 | .data("item", item)
|
500 | .on("click", handleMenuItemCommandClick)
|
501 | .appendTo(commandMenuElm);
|
502 |
|
503 | if (item.divider || item === "divider") {
|
504 | $li.addClass("slick-context-menu-item-divider");
|
505 | continue;
|
506 | }
|
507 |
|
508 | // if the item is disabled then add the disabled css class
|
509 | if (item.disabled || !isItemUsable) {
|
510 | $li.addClass("slick-context-menu-item-disabled");
|
511 | }
|
512 |
|
513 | // if the item is hidden then add the hidden css class
|
514 | if (item.hidden) {
|
515 | $li.addClass("slick-context-menu-item-hidden");
|
516 | }
|
517 |
|
518 | if (item.cssClass) {
|
519 | $li.addClass(item.cssClass);
|
520 | }
|
521 |
|
522 | if (item.tooltip) {
|
523 | $li.attr("title", item.tooltip);
|
524 | }
|
525 |
|
526 | var $icon = $('<div class="slick-context-menu-icon"></div>')
|
527 | .appendTo($li);
|
528 |
|
529 | if (item.iconCssClass) {
|
530 | $icon.addClass(item.iconCssClass);
|
531 | }
|
532 |
|
533 | if (item.iconImage) {
|
534 | $icon.css("background-image", "url(" + item.iconImage + ")");
|
535 | }
|
536 |
|
537 | var $text = $('<span class="slick-context-menu-content"></span>')
|
538 | .text(item.title)
|
539 | .appendTo($li);
|
540 |
|
541 | if (item.textCssClass) {
|
542 | $text.addClass(item.textCssClass);
|
543 | }
|
544 | }
|
545 | }
|
546 |
|
547 | function handleMenuItemCommandClick(e) {
|
548 | var command = $(this).data("command");
|
549 | var item = $(this).data("item");
|
550 |
|
551 | if (!item || item.disabled || item.divider) {
|
552 | return;
|
553 | }
|
554 |
|
555 | var row = $menu.data("row");
|
556 | var cell = $menu.data("cell");
|
557 |
|
558 | var columnDef = _grid.getColumns()[cell];
|
559 | var dataContext = _grid.getDataItem(row);
|
560 | var cellValue;
|
561 | if (Object.prototype.hasOwnProperty.call(dataContext, columnDef && columnDef.field)) {
|
562 | cellValue = dataContext[columnDef.field];
|
563 | }
|
564 |
|
565 | if (command != null && command !== "") {
|
566 | // user could execute a callback through 2 ways
|
567 | // via the onCommand event and/or an action callback
|
568 | var callbackArgs = {
|
569 | "cell": cell,
|
570 | "row": row,
|
571 | "grid": _grid,
|
572 | "command": command,
|
573 | "item": item,
|
574 | "column": columnDef,
|
575 | "dataContext": dataContext,
|
576 | "value": cellValue
|
577 | };
|
578 | _self.onCommand.notify(callbackArgs, e, _self);
|
579 |
|
580 | // execute action callback when defined
|
581 | if (typeof item.action === "function") {
|
582 | item.action.call(this, e, callbackArgs);
|
583 | }
|
584 | }
|
585 | }
|
586 |
|
587 | function handleMenuItemOptionClick(e) {
|
588 | var option = $(this).data("option");
|
589 | var item = $(this).data("item");
|
590 |
|
591 | if (item.disabled || item.divider) {
|
592 | return;
|
593 | }
|
594 | if (!_grid.getEditorLock().commitCurrentEdit()) {
|
595 | return;
|
596 | }
|
597 |
|
598 | var row = $menu.data("row");
|
599 | var cell = $menu.data("cell");
|
600 |
|
601 | var columnDef = _grid.getColumns()[cell];
|
602 | var dataContext = _grid.getDataItem(row);
|
603 |
|
604 | if (option !== undefined) {
|
605 | // user could execute a callback through 2 ways
|
606 | // via the onOptionSelected event and/or an action callback
|
607 | var callbackArgs = {
|
608 | "cell": cell,
|
609 | "row": row,
|
610 | "grid": _grid,
|
611 | "option": option,
|
612 | "item": item,
|
613 | "column": columnDef,
|
614 | "dataContext": dataContext
|
615 | };
|
616 | _self.onOptionSelected.notify(callbackArgs, e, _self);
|
617 |
|
618 | // execute action callback when defined
|
619 | if (typeof item.action === "function") {
|
620 | item.action.call(this, e, callbackArgs);
|
621 | }
|
622 | }
|
623 | }
|
624 |
|
625 | /**
|
626 | * Reposition the menu drop (up/down) and the side (left/right)
|
627 | * @param {*} event
|
628 | */
|
629 | function repositionMenu(e) {
|
630 | var $parent = $(e.target).closest(".slick-cell");
|
631 | var menuOffsetLeft = e.pageX;
|
632 | var menuOffsetTop = $parent ? $parent.offset().top : e.pageY;
|
633 | var menuHeight = $menu.outerHeight() || 0;
|
634 | var menuWidth = $menu.outerWidth() || _contextMenuProperties.width || 0;
|
635 | var rowHeight = _gridOptions.rowHeight;
|
636 | var dropOffset = _contextMenuProperties.autoAdjustDropOffset;
|
637 | var sideOffset = _contextMenuProperties.autoAlignSideOffset;
|
638 |
|
639 | // if autoAdjustDrop is enable, we first need to see what position the drop will be located
|
640 | // without necessary toggling it's position just yet, we just want to know the future position for calculation
|
641 | if (_contextMenuProperties.autoAdjustDrop) {
|
642 | // since we reposition menu below slick cell, we need to take it in consideration and do our calculation from that element
|
643 | var spaceBottom = calculateAvailableSpaceBottom($parent);
|
644 | var spaceTop = calculateAvailableSpaceTop($parent);
|
645 | var spaceBottomRemaining = spaceBottom + dropOffset - rowHeight;
|
646 | var spaceTopRemaining = spaceTop - dropOffset + rowHeight;
|
647 | var dropPosition = (spaceBottomRemaining < menuHeight && spaceTopRemaining > spaceBottomRemaining) ? 'top' : 'bottom';
|
648 | if (dropPosition === 'top') {
|
649 | $menu.removeClass("dropdown").addClass("dropup");
|
650 | menuOffsetTop = menuOffsetTop - menuHeight - dropOffset;
|
651 | } else {
|
652 | $menu.removeClass("dropup").addClass("dropdown");
|
653 | menuOffsetTop = menuOffsetTop + rowHeight + dropOffset;
|
654 | }
|
655 | }
|
656 |
|
657 | // when auto-align is set, it will calculate whether it has enough space in the viewport to show the drop menu on the right (default)
|
658 | // if there isn't enough space on the right, it will automatically align the drop menu to the left
|
659 | // to simulate an align left, we actually need to know the width of the drop menu
|
660 | if (_contextMenuProperties.autoAlignSide) {
|
661 | var gridPos = _grid.getGridPosition();
|
662 | var dropSide = ((menuOffsetLeft + menuWidth) >= gridPos.width) ? 'left' : 'right';
|
663 | if (dropSide === 'left') {
|
664 | $menu.removeClass("dropright").addClass("dropleft");
|
665 | menuOffsetLeft = (menuOffsetLeft - menuWidth - sideOffset);
|
666 | } else {
|
667 | $menu.removeClass("dropleft").addClass("dropright");
|
668 | menuOffsetLeft = menuOffsetLeft + sideOffset;
|
669 | }
|
670 | }
|
671 |
|
672 | // ready to reposition the menu
|
673 | $menu.css("top", menuOffsetTop);
|
674 | $menu.css("left", menuOffsetLeft);
|
675 | }
|
676 |
|
677 | /**
|
678 | * Method that user can pass to override the default behavior.
|
679 | * In order word, user can choose or an item is (usable/visible/enable) by providing his own logic.
|
680 | * @param overrideFn: override function callback
|
681 | * @param args: multiple arguments provided to the override (cell, row, columnDef, dataContext, grid)
|
682 | */
|
683 | function runOverrideFunctionWhenExists(overrideFn, args) {
|
684 | if (typeof overrideFn === 'function') {
|
685 | return overrideFn.call(this, args);
|
686 | }
|
687 | return true;
|
688 | }
|
689 |
|
690 | $.extend(this, {
|
691 | "init": init,
|
692 | "closeMenu": destroyMenu,
|
693 | "destroy": destroy,
|
694 | "pluginName": "ContextMenu",
|
695 | "setOptions": setOptions,
|
696 |
|
697 | "onAfterMenuShow": new Slick.Event(),
|
698 | "onBeforeMenuShow": new Slick.Event(),
|
699 | "onBeforeMenuClose": new Slick.Event(),
|
700 | "onCommand": new Slick.Event(),
|
701 | "onOptionSelected": new Slick.Event()
|
702 | });
|
703 | }
|
704 | })(jQuery);
|