UNPKG

28.4 kBJavaScriptView Raw
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">&times;</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);