diff --git a/.eslintrc b/.eslintrc index 0e98a92f..1f883e38 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,7 +5,7 @@ ], "env": { "browser": true, - "es2015": true, + "es6": true, "node": true }, "globals": { diff --git a/controls/slick.columnpicker.js b/controls/slick.columnpicker.js index 3f50e70c..281afdc3 100644 --- a/controls/slick.columnpicker.js +++ b/controls/slick.columnpicker.js @@ -110,7 +110,7 @@ } function handleBodyMouseDown(e) { - if ((_menuElm !== e.target && !_menuElm.contains(e.target)) || e.target.className === 'close') { + if ((_menuElm !== e.target && !(_menuElm && _menuElm.contains(e.target))) || e.target.className === 'close') { _menuElm.setAttribute('aria-expanded', 'false'); _menuElm.style.display = 'none'; } diff --git a/cypress/integration/example-plugin-contextmenu.spec.js b/cypress/integration/example-plugin-contextmenu.spec.js index 76af42e6..a99eeded 100644 --- a/cypress/integration/example-plugin-contextmenu.spec.js +++ b/cypress/integration/example-plugin-contextmenu.spec.js @@ -56,6 +56,12 @@ describe('Example - Context Menu & Cell Menu', () => { .find('.slick-row .slick-cell:nth(1)') .rightclick(); + cy.get('.slick-context-menu-command-list') + .should('exist'); + + cy.get('.slick-context-menu-option-list') + .should('not.exist'); + cy.window().then((win) => { expect(win.console.log).to.have.callCount(2); expect(win.console.log).to.be.calledWith('Before the global Context Menu is shown'); @@ -118,13 +124,16 @@ describe('Example - Context Menu & Cell Menu', () => { .find('.slick-row .slick-cell:nth(5)') .rightclick(); + cy.get('.slick-context-menu-command-list') + .should('not.exist'); + + cy.get('.slick-context-menu-option-list') + .should('exist'); + cy.get('.slick-context-menu .slick-context-menu-option-list') .contains('High') .click(); - cy.get('.slick-context-menu-command-list') - .should('not.exist'); - cy.get('#myGrid') .find('.slick-row .slick-cell:nth(7)') .contains('Action') diff --git a/examples/example-plugin-contextmenu.html b/examples/example-plugin-contextmenu.html index 90c97279..4be0aee5 100644 --- a/examples/example-plugin-contextmenu.html +++ b/examples/example-plugin-contextmenu.html @@ -158,9 +158,6 @@

View Source:

- - - @@ -209,10 +206,12 @@

View Source:

window.clipboardData.setData("Text", textToCopy); } else { var range = document.createRange(); - var tmpElem = $('
') - .css({ position: "absolute", left: "-1000px", top: "-1000px" }) - .text(textToCopy); - $("body").append(tmpElem); + var tmpElem = document.createElement('div'); + tmpElem.style.position = 'absolute'; + tmpElem.style.left = '-1000px'; + tmpElem.style.top = '-1000px'; + tmpElem.textContent = textToCopy; + document.body.appendChild(tmpElem); range.selectNodeContents(tmpElem.get(0)); var selection = window.getSelection(); selection.removeAllRanges(); @@ -432,7 +431,7 @@

View Source:

] }; - $(function () { + (function () { dataView = new Slick.Data.DataView(); grid = new Slick.Grid("#myGrid", dataView, columns, gridOptions); cellMenuPlugin = new Slick.Plugins.CellMenu({ hideMenuOnScroll: true }); @@ -534,7 +533,7 @@

View Source:

grid.updateRow(args.row); } }); - }); + })(); diff --git a/plugins/slick.cellmenu.js b/plugins/slick.cellmenu.js index f180b18f..a73a024f 100644 --- a/plugins/slick.cellmenu.js +++ b/plugins/slick.cellmenu.js @@ -1,6 +1,6 @@ -(function ($) { +(function (window) { // register namespace - $.extend(true, window, { + Slick.Utils.extend(true, window, { "Slick": { "Plugins": { "CellMenu": CellMenu @@ -130,19 +130,20 @@ * @constructor */ function CellMenu(optionProperties) { - var _cellMenuProperties; - var _currentCell = -1; - var _currentRow = -1; - var _grid; - var _gridOptions; - var _gridUid = ""; - var _handler = new Slick.EventHandler(); - var _self = this; - var $commandTitleElm; - var $optionTitleElm; - var $menu; - - var _defaults = { + let _cellMenuProperties; + let _currentCell = -1; + let _currentRow = -1; + let _grid; + let _gridOptions; + let _gridUid = ""; + let _handler = new Slick.EventHandler(); + let _self = this; + let _commandTitleElm; + let _optionTitleElm; + let _menuElm; + let _bindingEventService = new Slick.BindingEventService(); + + let _defaults = { autoAdjustDrop: true, // dropup/dropdown autoAlignSide: true, // left/right autoAdjustDropOffset: 0, @@ -155,7 +156,7 @@ function init(grid) { _grid = grid; _gridOptions = grid.getOptions(); - _cellMenuProperties = $.extend({}, _defaults, optionProperties); + _cellMenuProperties = Slick.Utils.extend({}, _defaults, optionProperties); _gridUid = (grid && grid.getUID) ? grid.getUID() : ""; _handler.subscribe(_grid.onClick, handleCellClick); if (_cellMenuProperties.hideMenuOnScroll) { @@ -164,7 +165,7 @@ } function setOptions(newOptions) { - _cellMenuProperties = $.extend({}, _cellMenuProperties, newOptions); + _cellMenuProperties = Slick.Utils.extend({}, _cellMenuProperties, newOptions); } function destroy() { @@ -174,23 +175,25 @@ _self.onCommand.unsubscribe(); _self.onOptionSelected.unsubscribe(); _handler.unsubscribeAll(); - if ($menu && $menu.remove) { - $menu.remove(); + _bindingEventService.unbindAll(); + + if (_menuElm && _menuElm.remove) { + _menuElm.remove(); } - $commandTitleElm = null; - $optionTitleElm = null; - $menu = null; + _commandTitleElm = null; + _optionTitleElm = null; + _menuElm = null; } function createMenu(e) { - var cell = _grid.getCellFromEvent(e); + let cell = _grid.getCellFromEvent(e); _currentCell = cell && cell.cell; _currentRow = cell && cell.row; - var columnDef = _grid.getColumns()[_currentCell]; - var dataContext = _grid.getDataItem(_currentRow); + let columnDef = _grid.getColumns()[_currentCell]; + let dataContext = _grid.getDataItem(_currentRow); - var commandItems = _cellMenuProperties.commandItems || []; - var optionItems = _cellMenuProperties.optionItems || []; + let commandItems = _cellMenuProperties.commandItems || []; + let optionItems = _cellMenuProperties.optionItems || []; // make sure there's at least something to show before creating the Cell Menu if (!columnDef || !columnDef.cellMenu || (!commandItems.length && !optionItems.length)) { @@ -211,27 +214,43 @@ } // create a new cell menu - var maxHeight = isNaN(_cellMenuProperties.maxHeight) ? _cellMenuProperties.maxHeight : _cellMenuProperties.maxHeight + "px"; - var width = isNaN(_cellMenuProperties.width) ? _cellMenuProperties.width : _cellMenuProperties.width + "px"; - var menuStyle = "width: " + width + "; max-height: " + maxHeight; - var menu = $('
') - .css("top", e.pageY + 5) - .css("left", e.pageX) - .css("display", "none"); - - var closeButtonHtml = ''; + let maxHeight = isNaN(_cellMenuProperties.maxHeight) ? _cellMenuProperties.maxHeight : _cellMenuProperties.maxHeight + "px"; + let width = isNaN(_cellMenuProperties.width) ? _cellMenuProperties.width : _cellMenuProperties.width + "px"; + + _menuElm = document.createElement('div'); + _menuElm.className = `slick-cell-menu ${_gridUid}`; + _menuElm.style.width = width; + _menuElm.style.maxHeight = maxHeight; + _menuElm.style.top = `${e.pageY + 5}px`; + _menuElm.style.left = `${e.pageX}px`; + _menuElm.style.display = 'none'; + + const closeButtonElm = document.createElement('button'); + closeButtonElm.type = 'button'; + closeButtonElm.className = 'close'; + closeButtonElm.dataset.dismiss = 'slick-cell-menu'; + closeButtonElm.ariaLabel = 'Close'; + + const spanCloseElm = document.createElement('span'); + spanCloseElm.className = 'close'; + spanCloseElm.ariaHidden = 'true'; + spanCloseElm.innerHTML = '×'; + closeButtonElm.appendChild(spanCloseElm); // -- Option List section if (!_cellMenuProperties.hideOptionSection && optionItems.length > 0) { - var $optionMenu = $('
'); + const optionMenuElm = document.createElement('div'); + optionMenuElm.className = 'slick-cell-menu-option-list'; + if (!_cellMenuProperties.hideCloseButton) { - $(closeButtonHtml).on("click", handleCloseButtonClicked).appendTo(menu); + _bindingEventService.bind(closeButtonElm, 'click', handleCloseButtonClicked); + _menuElm.appendChild(closeButtonElm); } - $optionMenu.appendTo(menu); + _menuElm.appendChild(optionMenuElm) + populateOptionItems( _cellMenuProperties, - $optionMenu, + optionMenuElm, optionItems, { cell: _currentCell, row: _currentRow, column: columnDef, dataContext: dataContext, grid: _grid } ); @@ -239,21 +258,25 @@ // -- Command List section if (!_cellMenuProperties.hideCommandSection && commandItems.length > 0) { - var $commandMenu = $('
'); + const commandMenuElm = document.createElement('div'); + commandMenuElm.className = 'slick-cell-menu-command-list'; + if (!_cellMenuProperties.hideCloseButton && (optionItems.length === 0 || _cellMenuProperties.hideOptionSection)) { - $(closeButtonHtml).on("click", handleCloseButtonClicked).appendTo(menu); + _bindingEventService.bind(closeButtonElm, 'click', handleCloseButtonClicked); + _menuElm.appendChild(closeButtonElm); } - $commandMenu.appendTo(menu); + + _menuElm.appendChild(commandMenuElm); populateCommandItems( _cellMenuProperties, - $commandMenu, + commandMenuElm, commandItems, { cell: _currentCell, row: _currentRow, column: columnDef, dataContext: dataContext, grid: _grid } ); } - menu.show(); - menu.appendTo("body"); + _menuElm.style.display = 'block'; + document.body.appendChild(_menuElm); if (_self.onAfterMenuShow.notify({ "cell": _currentCell, @@ -263,50 +286,29 @@ return; } - return menu; - } - - function calculateAvailableSpaceBottom(element) { - var windowHeight = $(window).innerHeight() || 0; - var pageScroll = $(window).scrollTop() || 0; - if (element && element.offset && element.length > 0) { - var elementOffsetTop = element.offset().top; - return windowHeight - (elementOffsetTop - pageScroll); - } - return 0; - } - - function calculateAvailableSpaceTop(element) { - var pageScroll = $(window).scrollTop() || 0; - if (element && element.offset && element.length > 0) { - var elementOffsetTop = element.offset().top; - return elementOffsetTop - pageScroll; - } - return 0; + return _menuElm; } function handleCloseButtonClicked(e) { - if(!e.isDefaultPrevented()) { + if (!e.defaultPrevented) { destroyMenu(e); } } function destroyMenu(e, args) { - $menu = $menu || $(".slick-cell-menu." + _gridUid); - - if ($menu && $menu.remove) { - if ($menu.length > 0) { - if (_self.onBeforeMenuClose.notify({ - "cell": args && args.cell, - "row": args && args.row, - "grid": _grid, - "menu": $menu - }, e, _self).getReturnValue() == false) { - return; - } - } - $menu.remove(); - $menu = null; + _menuElm = _menuElm || document.querySelector(".slick-cell-menu." + _gridUid); + + if (_menuElm && _menuElm.remove) { + if (_self.onBeforeMenuClose.notify({ + "cell": args && args.cell, + "row": args && args.row, + "grid": _grid, + "menu": _menuElm + }, e, _self).getReturnValue() == false) { + return; + } + _menuElm.remove(); + _menuElm = null; } } @@ -315,30 +317,33 @@ * @param {*} event */ function repositionMenu(e) { - var $parent = $(e.target).closest(".slick-cell"); - var menuOffsetLeft = $parent ? $parent.offset().left : e.pageX; - var menuOffsetTop = $parent ? $parent.offset().top : e.pageY; - var parentCellWidth = $parent.outerWidth(); - var menuHeight = $menu.outerHeight() || 0; - var menuWidth = $menu.outerWidth() || _cellMenuProperties.width || 0; - var rowHeight = _gridOptions.rowHeight; - var dropOffset = _cellMenuProperties.autoAdjustDropOffset; - var sideOffset = _cellMenuProperties.autoAlignSideOffset; + const parentElm = e.target.closest('.slick-cell'); + const parentOffset = parentElm && Slick.Utils.offset(parentElm); + let menuOffsetLeft = parentElm ? parentOffset.left : e.pageX; + let menuOffsetTop = parentElm ? parentOffset.top : e.pageY; + const parentCellWidth = parentElm.offsetWidth || 0; + const menuHeight = _menuElm && _menuElm.offsetHeight || 0; + const menuWidth = _menuElm && _menuElm.offsetWidth || _cellMenuProperties.width || 0; + const rowHeight = _gridOptions.rowHeight; + const dropOffset = _cellMenuProperties.autoAdjustDropOffset; + const sideOffset = _cellMenuProperties.autoAlignSideOffset; // if autoAdjustDrop is enable, we first need to see what position the drop will be located (defaults to bottom) // without necessary toggling it's position just yet, we just want to know the future position for calculation if (_cellMenuProperties.autoAdjustDrop) { // since we reposition menu below slick cell, we need to take it in consideration and do our calculation from that element - var spaceBottom = calculateAvailableSpaceBottom($parent); - var spaceTop = calculateAvailableSpaceTop($parent); - var spaceBottomRemaining = spaceBottom + dropOffset - rowHeight; - var spaceTopRemaining = spaceTop - dropOffset + rowHeight; - var dropPosition = (spaceBottomRemaining < menuHeight && spaceTopRemaining > spaceBottomRemaining) ? 'top' : 'bottom'; + const spaceBottom = Slick.Utils.calculateAvailableSpace(parentElm).bottom; + const spaceTop = Slick.Utils.calculateAvailableSpace(parentElm).top; + const spaceBottomRemaining = spaceBottom + dropOffset - rowHeight; + const spaceTopRemaining = spaceTop - dropOffset + rowHeight; + const dropPosition = (spaceBottomRemaining < menuHeight && spaceTopRemaining > spaceBottomRemaining) ? 'top' : 'bottom'; if (dropPosition === 'top') { - $menu.removeClass("dropdown").addClass("dropup"); + _menuElm.classList.remove('dropdown'); + _menuElm.classList.add('dropup'); menuOffsetTop = menuOffsetTop - menuHeight - dropOffset; } else { - $menu.removeClass("dropup").addClass("dropdown"); + _menuElm.classList.remove('dropup'); + _menuElm.classList.add('dropdown'); menuOffsetTop = menuOffsetTop + rowHeight + dropOffset; } } @@ -347,29 +352,31 @@ // if there isn't enough space on the right, it will automatically align the drop menu to the left (defaults to the right) // to simulate an align left, we actually need to know the width of the drop menu if (_cellMenuProperties.autoAlignSide) { - var gridPos = _grid.getGridPosition(); - var dropSide = ((menuOffsetLeft + menuWidth) >= gridPos.width) ? 'left' : 'right'; + let gridPos = _grid.getGridPosition(); + let dropSide = ((menuOffsetLeft + menuWidth) >= gridPos.width) ? 'left' : 'right'; if (dropSide === 'left') { - $menu.removeClass("dropright").addClass("dropleft"); + _menuElm.classList.remove('dropright'); + _menuElm.classList.add('dropleft'); menuOffsetLeft = (menuOffsetLeft - (menuWidth - parentCellWidth) - sideOffset); } else { - $menu.removeClass("dropleft").addClass("dropright"); + _menuElm.classList.remove('dropleft'); + _menuElm.classList.add('dropright'); menuOffsetLeft = menuOffsetLeft + sideOffset; } } // ready to reposition the menu - $menu.css("top", menuOffsetTop); - $menu.css("left", menuOffsetLeft); + _menuElm.style.top = `${menuOffsetTop}px`; + _menuElm.style.left = `${menuOffsetLeft}px`; } function handleCellClick(e, args) { if(e instanceof Slick.EventData) e = e.getNativeEvent(); - var cell = _grid.getCellFromEvent(e); - var dataContext = _grid.getDataItem(cell.row); - var columnDef = _grid.getColumns()[cell.cell]; + let cell = _grid.getCellFromEvent(e); + let dataContext = _grid.getDataItem(cell.row); + let columnDef = _grid.getColumns()[cell.cell]; // prevent event from bubbling but only on column that has a cell menu defined if (columnDef && columnDef.cellMenu) { @@ -377,7 +384,7 @@ } // merge the cellMenu of the column definition with the default properties - _cellMenuProperties = $.extend({}, _cellMenuProperties, columnDef.cellMenu); + _cellMenuProperties = Slick.Utils.extend({}, _cellMenuProperties, columnDef.cellMenu); // run the override function (when defined), if the result is false it won't go further if (!args) { @@ -391,42 +398,40 @@ } // create the DOM element - $menu = createMenu(e, args); + _menuElm = createMenu(e, args); // reposition the menu to where the user clicked - if ($menu) { + if (_menuElm) { repositionMenu(e); - $menu - .data("cell", _currentCell) - .data("row", _currentRow) - .show(); + _menuElm.setAttribute('aria-expanded', 'true'); + _menuElm.style.display = 'block'; } // Hide the menu on outside click. - $("body").on("mousedown." + _gridUid, handleBodyMouseDown); + _bindingEventService.bind(document.body, 'mousedown', handleBodyMouseDown.bind(this)); } function handleBodyMouseDown(e) { - if ($menu && $menu[0] != e.target && !$.contains($menu[0], e.target)) { - if(!e.isDefaultPrevented()) { + if (_menuElm != e.target && !(_menuElm && _menuElm.contains(e.target))) { + if (!e.defaultPrevented) { closeMenu(e, { cell: _currentCell, row: _currentRow }); } } } function closeMenu(e, args) { - if ($menu && $menu.length > 0) { + if (_menuElm) { if (_self.onBeforeMenuClose.notify({ "cell": args && args.cell, "row": args && args.row, "grid": _grid, - "menu": $menu + "menu": _menuElm }, e, _self).getReturnValue() == false) { return; } - if ($menu && $menu.remove) { - $menu.remove(); - $menu = null; + if (_menuElm && _menuElm.remove) { + _menuElm.remove(); + _menuElm = null; } } } @@ -438,17 +443,20 @@ } // user could pass a title on top of the Options section - if (cellMenu && cellMenu.optionTitle) { - $optionTitleElm = $('
').append(cellMenu.optionTitle); - $optionTitleElm.appendTo(optionMenuElm); + if (cellMenu && cellMenu.optionTitle) { + _optionTitleElm = document.createElement('div'); + _optionTitleElm.className = 'title'; + _optionTitleElm.textContent = cellMenu.optionTitle; + optionMenuElm.appendChild(_optionTitleElm); } - for (var i = 0, ln = optionItems.length; i < ln; i++) { - var item = optionItems[i]; + for (let i = 0, ln = optionItems.length; i < ln; i++) { + let addClickListener = true; + let item = optionItems[i]; // run each override functions to know if the item is visible and usable - var isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args); - var isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args); + let isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args); + let isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args); // if the result is not visible then there's no need to go further if (!isItemVisible) { @@ -461,52 +469,59 @@ item.disabled = isItemUsable ? false : true; } - var $li = $('
') - .data("option", item.option !== undefined ? item.option : "") - .data("item", item) - .on("click", handleMenuItemOptionClick) - .appendTo(optionMenuElm); + const liElm = document.createElement('div'); + liElm.className = 'slick-cell-menu-item'; if (item.divider || item === "divider") { - $li.addClass("slick-cell-menu-item-divider"); - continue; + liElm.classList.add("slick-cell-menu-item-divider"); + addClickListener = false; } // if the item is disabled then add the disabled css class if (item.disabled || !isItemUsable) { - $li.addClass("slick-cell-menu-item-disabled"); + liElm.classList.add("slick-cell-menu-item-disabled"); } // if the item is hidden then add the hidden css class if (item.hidden) { - $li.addClass("slick-cell-menu-item-hidden"); + liElm.classList.add("slick-cell-menu-item-hidden"); } if (item.cssClass) { - $li.addClass(item.cssClass); + liElm.classList.add(item.cssClass); } if (item.tooltip) { - $li.attr("title", item.tooltip); + liElm.title = item.tooltip; } - var $icon = $('
') - .appendTo($li); + const iconElm = document.createElement('div'); + iconElm.className = 'slick-cell-menu-icon'; + + liElm.appendChild(iconElm); if (item.iconCssClass) { - $icon.addClass(item.iconCssClass); + iconElm.classList.add(item.iconCssClass); } if (item.iconImage) { - $icon.css("background-image", "url(" + item.iconImage + ")"); + iconElm.style.backgroundImage = "url(" + item.iconImage + ")"; } - var $text = $('') - .text(item.title) - .appendTo($li); + const textElm = document.createElement('span'); + textElm.className = 'slick-cell-menu-content'; + textElm.textContent = item.title; + + liElm.appendChild(textElm); if (item.textCssClass) { - $text.addClass(item.textCssClass); + textElm.classList.add(item.textCssClass); + } + + optionMenuElm.appendChild(liElm); + + if (addClickListener) { + _bindingEventService.bind(liElm, 'click', handleMenuItemOptionClick.bind(this, item)); } } } @@ -519,16 +534,19 @@ // user could pass a title on top of the Commands section if (cellMenu && cellMenu.commandTitle) { - $commandTitleElm = $('
').append(cellMenu.commandTitle); - $commandTitleElm.appendTo(commandMenuElm); + _commandTitleElm = document.createElement('div'); + _commandTitleElm.className = 'title'; + _commandTitleElm.textContent = cellMenu.commandTitle; + commandMenuElm.appendChild(_commandTitleElm); } - for (var i = 0, ln = commandItems.length; i < ln; i++) { - var item = commandItems[i]; + for (let i = 0, ln = commandItems.length; i < ln; i++) { + let addClickListener = true; + let item = commandItems[i]; // run each override functions to know if the item is visible and usable - var isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args); - var isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args); + let isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args); + let isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args); // if the result is not visible then there's no need to go further if (!isItemVisible) { @@ -541,74 +559,78 @@ item.disabled = isItemUsable ? false : true; } - var $li = $('
') - .data("command", item.command !== undefined ? item.command : "") - .data("item", item) - .on("click", handleMenuItemCommandClick) - .appendTo(commandMenuElm); + const liElm = document.createElement('div'); + liElm.className = 'slick-cell-menu-item'; if (item.divider || item === "divider") { - $li.addClass("slick-cell-menu-item-divider"); - continue; + liElm.classList.add("slick-cell-menu-item-divider"); + addClickListener = false; } // if the item is disabled then add the disabled css class if (item.disabled || !isItemUsable) { - $li.addClass("slick-cell-menu-item-disabled"); + liElm.classList.add("slick-cell-menu-item-disabled"); } // if the item is hidden then add the hidden css class if (item.hidden) { - $li.addClass("slick-cell-menu-item-hidden"); + liElm.classList.add("slick-cell-menu-item-hidden"); } if (item.cssClass) { - $li.addClass(item.cssClass); + liElm.classList.add(item.cssClass); } if (item.tooltip) { - $li.attr("title", item.tooltip); + liElm.title = item.tooltip; } - var $icon = $('
') - .appendTo($li); + const iconElm = document.createElement('div'); + iconElm.className = 'slick-cell-menu-icon'; + + liElm.appendChild(iconElm); if (item.iconCssClass) { - $icon.addClass(item.iconCssClass); + iconElm.classList.add(item.iconCssClass); } if (item.iconImage) { - $icon.css("background-image", "url(" + item.iconImage + ")"); + iconElm.style.backgroundImage = "url(" + item.iconImage + ")"; } - var $text = $('') - .text(item.title) - .appendTo($li); + const textElm = document.createElement('span'); + textElm.className = 'slick-cell-menu-content'; + textElm.textContent = item.title; + + liElm.appendChild(textElm); if (item.textCssClass) { - $text.addClass(item.textCssClass); + textElm.classList.add(item.textCssClass); + } + + commandMenuElm.appendChild(liElm); + + if (addClickListener) { + _bindingEventService.bind(liElm, 'click', handleMenuItemCommandClick.bind(this, item)); } } } - function handleMenuItemCommandClick(e) { - var command = $(this).data("command"); - var item = $(this).data("item"); - + function handleMenuItemCommandClick(item, e) { if (!item || item.disabled || item.divider || item === "divider") { return; } - var row = $menu.data("row"); - var cell = $menu.data("cell"); - - var columnDef = _grid.getColumns()[cell]; - var dataContext = _grid.getDataItem(row); + const command = item.command || ''; + const row = _currentRow; + const cell = _currentCell; + let columnDef = _grid.getColumns()[cell]; + let dataContext = _grid.getDataItem(row); if (command !== null && command !== "") { // user could execute a callback through 2 ways // via the onCommand event and/or an action callback - var callbackArgs = { + let callbackArgs = { "cell": cell, "row": row, "grid": _grid, @@ -624,16 +646,13 @@ item.action.call(this, e, callbackArgs); } - if(!e.isDefaultPrevented()) { + if (!e.defaultPrevented) { closeMenu(e, { cell: cell, row: row }); } } } - function handleMenuItemOptionClick(e) { - var option = $(this).data("option"); - var item = $(this).data("item"); - + function handleMenuItemOptionClick(item, e) { if (!item || item.disabled || item.divider || item === "divider") { return; } @@ -641,16 +660,16 @@ return; } - var row = $menu.data("row"); - var cell = $menu.data("cell"); - - var columnDef = _grid.getColumns()[cell]; - var dataContext = _grid.getDataItem(row); + const option = item.option !== undefined ? item.option : ''; + const row = _currentRow; + const cell = _currentCell; + const columnDef = _grid.getColumns()[cell]; + const dataContext = _grid.getDataItem(row); if (option !== undefined) { // user could execute a callback through 2 ways // via the onOptionSelected event and/or an action callback - var callbackArgs = { + const callbackArgs = { "cell": cell, "row": row, "grid": _grid, @@ -666,7 +685,7 @@ item.action.call(this, e, callbackArgs); } - if(!e.isDefaultPrevented()) { + if (!e.defaultPrevented) { closeMenu(e, { cell: cell, row: row }); } } @@ -685,7 +704,7 @@ return true; } - $.extend(this, { + Slick.Utils.extend(this, { "init": init, "closeMenu": destroyMenu, "destroy": destroy, @@ -699,4 +718,4 @@ "onOptionSelected": new Slick.Event() }); } -})(jQuery); +})(window); diff --git a/plugins/slick.contextmenu.js b/plugins/slick.contextmenu.js index 5f1783a4..a31980e7 100644 --- a/plugins/slick.contextmenu.js +++ b/plugins/slick.contextmenu.js @@ -1,6 +1,6 @@ -(function ($) { +(function (window) { // register namespace - $.extend(true, window, { + Slick.Utils.extend(true, window, { "Slick": { "Plugins": { "ContextMenu": ContextMenu @@ -137,19 +137,20 @@ * @constructor */ function ContextMenu(optionProperties) { - var _contextMenuProperties; - var _currentCell = -1; - var _currentRow = -1; - var _grid; - var _gridOptions; - var _gridUid = ""; - var _handler = new Slick.EventHandler(); - var _self = this; - var $optionTitleElm; - var $commandTitleElm; - var $menu; - - var _defaults = { + let _contextMenuProperties; + let _currentCell = -1; + let _currentRow = -1; + let _grid; + let _gridOptions; + let _gridUid = ""; + let _handler = new Slick.EventHandler(); + let _self = this; + let _optionTitleElm; + let _commandTitleElm; + let _menuElm; + let _bindingEventService = new Slick.BindingEventService(); + + let _defaults = { autoAdjustDrop: true, // dropup/dropdown autoAlignSide: true, // left/right autoAdjustDropOffset: -4, @@ -164,7 +165,7 @@ function init(grid) { _grid = grid; _gridOptions = grid.getOptions(); - _contextMenuProperties = $.extend({}, _defaults, optionProperties); + _contextMenuProperties = Slick.Utils.extend({}, _defaults, optionProperties); _gridUid = (grid && grid.getUID) ? grid.getUID() : ""; _handler.subscribe(_grid.onContextMenu, handleOnContextMenu); if (_contextMenuProperties.hideMenuOnScroll) { @@ -173,7 +174,7 @@ } function setOptions(newOptions) { - _contextMenuProperties = $.extend({}, _contextMenuProperties, newOptions); + _contextMenuProperties = Slick.Utils.extend({}, _contextMenuProperties, newOptions); // on the array properties, we want to make sure to overwrite them and not just extending them if (newOptions.commandShownOverColumnIds) { @@ -191,29 +192,32 @@ _self.onCommand.unsubscribe(); _self.onOptionSelected.unsubscribe(); _handler.unsubscribeAll(); - if ($menu && $menu.remove) { - $menu.remove(); + _bindingEventService.unbindAll(); + + if (_menuElm && _menuElm.remove) { + _menuElm.remove(); } - $commandTitleElm = null; - $optionTitleElm = null; - $menu = null; + _commandTitleElm = null; + _optionTitleElm = null; + _menuElm = null; } function createMenu(e) { - if(e instanceof Slick.EventData) + if (e instanceof Slick.EventData) { e = e.getNativeEvent(); + } - var targetEvent = e.touches ? e.touches[0] : e; - var cell = _grid.getCellFromEvent(e); + let targetEvent = e.touches ? e.touches[0] : e; + let cell = _grid.getCellFromEvent(e); _currentCell = cell && cell.cell; _currentRow = cell && cell.row; - var columnDef = _grid.getColumns()[_currentCell]; - var dataContext = _grid.getDataItem(_currentRow); + let columnDef = _grid.getColumns()[_currentCell]; + let dataContext = _grid.getDataItem(_currentRow); - var isColumnOptionAllowed = checkIsColumnAllowed(_contextMenuProperties.optionShownOverColumnIds, columnDef.id); - var isColumnCommandAllowed = checkIsColumnAllowed(_contextMenuProperties.commandShownOverColumnIds, columnDef.id); - var commandItems = _contextMenuProperties.commandItems || []; - var optionItems = _contextMenuProperties.optionItems || []; + let isColumnOptionAllowed = checkIsColumnAllowed(_contextMenuProperties.optionShownOverColumnIds, columnDef.id); + let isColumnCommandAllowed = checkIsColumnAllowed(_contextMenuProperties.commandShownOverColumnIds, columnDef.id); + let commandItems = _contextMenuProperties.commandItems || []; + let optionItems = _contextMenuProperties.optionItems || []; // make sure there's at least something to show before creating the Context Menu if (!columnDef || (!isColumnCommandAllowed && !isColumnOptionAllowed) || (!commandItems.length && !optionItems.length)) { @@ -234,27 +238,43 @@ } // create a new context menu - var maxHeight = isNaN(_contextMenuProperties.maxHeight) ? _contextMenuProperties.maxHeight : _contextMenuProperties.maxHeight + "px"; - var width = isNaN(_contextMenuProperties.width) ? _contextMenuProperties.width : _contextMenuProperties.width + "px"; - var menuStyle = "width: " + width + "; max-height: " + maxHeight; - var menu = $('
') - .css("top", targetEvent.pageY) - .css("left", targetEvent.pageX) - .css("display", "none"); - - var closeButtonHtml = ''; + let maxHeight = isNaN(_contextMenuProperties.maxHeight) ? _contextMenuProperties.maxHeight : _contextMenuProperties.maxHeight + "px"; + let width = isNaN(_contextMenuProperties.width) ? _contextMenuProperties.width : _contextMenuProperties.width + "px"; + + _menuElm = document.createElement('div'); + _menuElm.className = `slick-context-menu ${_gridUid}`; + _menuElm.style.width = width; + _menuElm.style.maxHeight = maxHeight; + _menuElm.style.top = `${targetEvent.pageY}px`; + _menuElm.style.left = `${targetEvent.pageX}px`; + _menuElm.style.display = 'none'; + + const closeButtonElm = document.createElement('button'); + closeButtonElm.type = 'button'; + closeButtonElm.className = 'close'; + closeButtonElm.dataset.dismiss = 'slick-context-menu'; + closeButtonElm.ariaLabel = 'Close'; + + const spanCloseElm = document.createElement('span'); + spanCloseElm.className = 'close'; + spanCloseElm.ariaHidden = 'true'; + spanCloseElm.innerHTML = '×'; + closeButtonElm.appendChild(spanCloseElm); // -- Option List section if (!_contextMenuProperties.hideOptionSection && isColumnOptionAllowed && optionItems.length > 0) { - var $optionMenu = $('
'); + const optionMenuElm = document.createElement('div'); + optionMenuElm.className = 'slick-context-menu-option-list'; + if (!_contextMenuProperties.hideCloseButton) { - $(closeButtonHtml).on("click", handleCloseButtonClicked).appendTo(menu); + _bindingEventService.bind(closeButtonElm, 'click', handleCloseButtonClicked); + _menuElm.appendChild(closeButtonElm); } - $optionMenu.appendTo(menu); + _menuElm.appendChild(optionMenuElm) + populateOptionItems( _contextMenuProperties, - $optionMenu, + optionMenuElm, optionItems, { cell: _currentCell, row: _currentRow, column: columnDef, dataContext: dataContext, grid: _grid } ); @@ -262,21 +282,25 @@ // -- Command List section if (!_contextMenuProperties.hideCommandSection && isColumnCommandAllowed && commandItems.length > 0) { - var $commandMenu = $('
'); + const commandMenuElm = document.createElement('div'); + commandMenuElm.className = 'slick-context-menu-command-list'; + if (!_contextMenuProperties.hideCloseButton && (!isColumnOptionAllowed || optionItems.length === 0 || _contextMenuProperties.hideOptionSection)) { - $(closeButtonHtml).on("click", handleCloseButtonClicked).appendTo(menu); + _bindingEventService.bind(closeButtonElm, 'click', handleCloseButtonClicked); + _menuElm.appendChild(closeButtonElm); } - $commandMenu.appendTo(menu); + + _menuElm.appendChild(commandMenuElm); populateCommandItems( _contextMenuProperties, - $commandMenu, + commandMenuElm, commandItems, { cell: _currentCell, row: _currentRow, column: columnDef, dataContext: dataContext, grid: _grid } ); } - menu.show(); - menu.appendTo("body"); + _menuElm.style.display = 'block'; + document.body.appendChild(_menuElm); if (_self.onAfterMenuShow.notify({ "cell": _currentCell, @@ -286,39 +310,37 @@ return; } - return menu; + return _menuElm; } function handleCloseButtonClicked(e) { - if(!e.isDefaultPrevented()) { + if (!e.defaultPrevented) { destroyMenu(e); } } function destroyMenu(e, args) { - $menu = $menu || $(".slick-context-menu." + _gridUid); + _menuElm = _menuElm || document.querySelector(".slick-context-menu." + _gridUid); - if ($menu && $menu.length > 0) { + if (_menuElm && _menuElm.remove) { if (_self.onBeforeMenuClose.notify({ "cell": args && args.cell, "row": args && args.row, "grid": _grid, - "menu": $menu + "menu": _menuElm }, e, _self).getReturnValue() == false) { return; } - if ($menu && $menu.remove) { - $menu.remove(); - $menu = null; - } + _menuElm.remove(); + _menuElm = null; } } function checkIsColumnAllowed(columnIds, columnId) { - var isAllowedColumn = false; + let isAllowedColumn = false; if (columnIds && columnIds.length > 0) { - for (var o = 0, ln = columnIds.length; o < ln; o++) { + for (let o = 0, ln = columnIds.length; o < ln; o++) { if (columnIds[o] === columnId) { isAllowedColumn = true; } @@ -329,34 +351,15 @@ return isAllowedColumn; } - function calculateAvailableSpaceBottom(element) { - var windowHeight = $(window).innerHeight() || 0; - var pageScroll = $(window).scrollTop() || 0; - if (element && element.offset && element.length > 0) { - var elementOffsetTop = element.offset().top; - return windowHeight - (elementOffsetTop - pageScroll); - } - return 0; - } - - function calculateAvailableSpaceTop(element) { - var pageScroll = $(window).scrollTop() || 0; - if (element && element.offset && element.length > 0) { - var elementOffsetTop = element.offset().top; - return elementOffsetTop - pageScroll; - } - return 0; - } - function handleOnContextMenu(e, args) { if(e instanceof Slick.EventData) e = e.getNativeEvent(); e.preventDefault(); - var cell = _grid.getCellFromEvent(e); - var columnDef = _grid.getColumns()[cell.cell]; - var dataContext = _grid.getDataItem(cell.row); + let cell = _grid.getCellFromEvent(e); + let columnDef = _grid.getColumns()[cell.cell]; + let dataContext = _grid.getDataItem(cell.row); // run the override function (when defined), if the result is false it won't go further if (!args) { @@ -373,19 +376,16 @@ } // create the DOM element - $menu = createMenu(e, args); + _menuElm = createMenu(e, args); // reposition the menu to where the user clicked - if ($menu) { + if (_menuElm) { repositionMenu(e); - $menu - .data("cell", _currentCell) - .data("row", _currentRow) - .show(); + _menuElm.style.display = 'block'; } - $("body").one("click", function (e) { - if(!e.isDefaultPrevented()) { + _bindingEventService.bind(document.body, 'click', (e) => { + if (!e.defaultPrevented) { destroyMenu(e, { cell: _currentCell, row: _currentRow }); } }); @@ -399,16 +399,19 @@ // user could pass a title on top of the Options section if (contextMenu && contextMenu.optionTitle) { - $optionTitleElm = $('
').append(contextMenu.optionTitle); - $optionTitleElm.appendTo(optionMenuElm); + _optionTitleElm = document.createElement('div'); + _optionTitleElm.className = 'title'; + _optionTitleElm.textContent = contextMenu.optionTitle; + optionMenuElm.appendChild(_optionTitleElm); } - for (var i = 0, ln = optionItems.length; i < ln; i++) { - var item = optionItems[i]; + for (let i = 0, ln = optionItems.length; i < ln; i++) { + let addClickListener = true; + let item = optionItems[i]; // run each override functions to know if the item is visible and usable - var isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args); - var isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args); + let isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args); + let isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args); // if the result is not visible then there's no need to go further if (!isItemVisible) { @@ -421,52 +424,59 @@ item.disabled = isItemUsable ? false : true; } - var $li = $('
') - .data("option", item.option !== undefined ? item.option : "") - .data("item", item) - .on("click", handleMenuItemOptionClick) - .appendTo(optionMenuElm); + const liElm = document.createElement('div'); + liElm.className = 'slick-context-menu-item'; if (item.divider || item === "divider") { - $li.addClass("slick-context-menu-item-divider"); - continue; + liElm.classList.add("slick-context-menu-item-divider"); + addClickListener = false; } // if the item is disabled then add the disabled css class if (item.disabled || !isItemUsable) { - $li.addClass("slick-context-menu-item-disabled"); + liElm.classList.add("slick-context-menu-item-disabled"); } // if the item is hidden then add the hidden css class if (item.hidden) { - $li.addClass("slick-context-menu-item-hidden"); + liElm.classList.add("slick-context-menu-item-hidden"); } if (item.cssClass) { - $li.addClass(item.cssClass); + liElm.classList.add(item.cssClass); } if (item.tooltip) { - $li.attr("title", item.tooltip); + liElm.title = item.tooltip; } - var $icon = $('
') - .appendTo($li); + const iconElm = document.createElement('div'); + iconElm.className = 'slick-context-menu-icon'; + + liElm.appendChild(iconElm); if (item.iconCssClass) { - $icon.addClass(item.iconCssClass); + iconElm.classList.add(item.iconCssClass); } if (item.iconImage) { - $icon.css("background-image", "url(" + item.iconImage + ")"); + iconElm.style.backgroundImage = "url(" + item.iconImage + ")"; } - var $text = $('') - .text(item.title) - .appendTo($li); + const textElm = document.createElement('span'); + textElm.className = 'slick-context-menu-content'; + textElm.textContent = item.title; + + liElm.appendChild(textElm); if (item.textCssClass) { - $text.addClass(item.textCssClass); + textElm.classList.add(item.textCssClass); + } + + optionMenuElm.appendChild(liElm); + + if (addClickListener) { + _bindingEventService.bind(liElm, 'click', handleMenuItemOptionClick.bind(this, item)); } } } @@ -479,16 +489,19 @@ // user could pass a title on top of the Commands section if (contextMenu && contextMenu.commandTitle) { - $commandTitleElm = $('
').append(contextMenu.commandTitle); - $commandTitleElm.appendTo(commandMenuElm); + _commandTitleElm = document.createElement('div'); + _commandTitleElm.className = 'title'; + _commandTitleElm.textContent = contextMenu.commandTitle; + commandMenuElm.appendChild(_commandTitleElm); } - for (var i = 0, ln = commandItems.length; i < ln; i++) { - var item = commandItems[i]; + for (let i = 0, ln = commandItems.length; i < ln; i++) { + let addClickListener = true; + let item = commandItems[i]; // run each override functions to know if the item is visible and usable - var isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args); - var isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args); + let isItemVisible = runOverrideFunctionWhenExists(item.itemVisibilityOverride, args); + let isItemUsable = runOverrideFunctionWhenExists(item.itemUsabilityOverride, args); // if the result is not visible then there's no need to go further if (!isItemVisible) { @@ -501,70 +514,75 @@ item.disabled = isItemUsable ? false : true; } - var $li = $('
') - .data("command", item.command !== undefined ? item.command : "") - .data("item", item) - .on("click", handleMenuItemCommandClick) - .appendTo(commandMenuElm); + const liElm = document.createElement('div'); + liElm.className = 'slick-context-menu-item'; if (item.divider || item === "divider") { - $li.addClass("slick-context-menu-item-divider"); - continue; + liElm.classList.add("slick-context-menu-item-divider"); + addClickListener = false; } // if the item is disabled then add the disabled css class if (item.disabled || !isItemUsable) { - $li.addClass("slick-context-menu-item-disabled"); + liElm.classList.add("slick-context-menu-item-disabled"); } // if the item is hidden then add the hidden css class if (item.hidden) { - $li.addClass("slick-context-menu-item-hidden"); + liElm.classList.add("slick-context-menu-item-hidden"); } if (item.cssClass) { - $li.addClass(item.cssClass); + liElm.classList.add(item.cssClass); } if (item.tooltip) { - $li.attr("title", item.tooltip); + liElm.title = item.tooltip; } - var $icon = $('
') - .appendTo($li); + const iconElm = document.createElement('div'); + iconElm.className = 'slick-context-menu-icon'; + + liElm.appendChild(iconElm); if (item.iconCssClass) { - $icon.addClass(item.iconCssClass); + iconElm.classList.add(item.iconCssClass); } if (item.iconImage) { - $icon.css("background-image", "url(" + item.iconImage + ")"); + iconElm.style.backgroundImage = "url(" + item.iconImage + ")"; } - var $text = $('') - .text(item.title) - .appendTo($li); + const textElm = document.createElement('span'); + textElm.className = 'slick-context-menu-content'; + textElm.textContent = item.title; + + liElm.appendChild(textElm); if (item.textCssClass) { - $text.addClass(item.textCssClass); + textElm.classList.add(item.textCssClass); + } + + commandMenuElm.appendChild(liElm); + + if (addClickListener) { + _bindingEventService.bind(liElm, 'click', handleMenuItemCommandClick.bind(this, item)); } } } - function handleMenuItemCommandClick(e) { - var command = $(this).data("command"); - var item = $(this).data("item"); - + function handleMenuItemCommandClick(item, e) { if (!item || item.disabled || item.divider) { return; } - var row = $menu.data("row"); - var cell = $menu.data("cell"); + const command = item.command || ''; + const row = _currentRow; + const cell = _currentCell; + let columnDef = _grid.getColumns()[cell]; + let dataContext = _grid.getDataItem(row); + let cellValue; - var columnDef = _grid.getColumns()[cell]; - var dataContext = _grid.getDataItem(row); - var cellValue; if (Object.prototype.hasOwnProperty.call(dataContext, columnDef && columnDef.field)) { cellValue = dataContext[columnDef.field]; } @@ -572,7 +590,7 @@ if (command != null && command !== "") { // user could execute a callback through 2 ways // via the onCommand event and/or an action callback - var callbackArgs = { + let callbackArgs = { "cell": cell, "row": row, "grid": _grid, @@ -591,10 +609,7 @@ } } - function handleMenuItemOptionClick(e) { - var option = $(this).data("option"); - var item = $(this).data("item"); - + function handleMenuItemOptionClick(item, e) { if (item.disabled || item.divider) { return; } @@ -602,16 +617,16 @@ return; } - var row = $menu.data("row"); - var cell = $menu.data("cell"); - - var columnDef = _grid.getColumns()[cell]; - var dataContext = _grid.getDataItem(row); + const option = item.option !== undefined ? item.option : ''; + const row = _currentRow; + const cell = _currentCell; + const columnDef = _grid.getColumns()[cell]; + const dataContext = _grid.getDataItem(row); if (option !== undefined) { // user could execute a callback through 2 ways // via the onOptionSelected event and/or an action callback - var callbackArgs = { + let callbackArgs = { "cell": cell, "row": row, "grid": _grid, @@ -634,30 +649,32 @@ * @param {*} event */ function repositionMenu(e) { - var targetEvent = e.touches ? e.touches[0] : e; - var $parent = $(e.target).closest(".slick-cell"); - var menuOffsetLeft = targetEvent.pageX; - var menuOffsetTop = $parent ? $parent.offset().top : targetEvent.pageY; - var menuHeight = $menu.outerHeight() || 0; - var menuWidth = $menu.outerWidth() || _contextMenuProperties.width || 0; - var rowHeight = _gridOptions.rowHeight; - var dropOffset = _contextMenuProperties.autoAdjustDropOffset; - var sideOffset = _contextMenuProperties.autoAlignSideOffset; + const targetEvent = e.touches ? e.touches[0] : e; + const parentElm = e.target.closest(".slick-cell"); + let menuOffsetLeft = targetEvent.pageX; + let menuOffsetTop = parentElm ? Slick.Utils.offset(parentElm).top : targetEvent.pageY; + const menuHeight = _menuElm && _menuElm.offsetHeight || 0; + const menuWidth = _menuElm && _menuElm.offsetWidth || _contextMenuProperties.width || 0; + let rowHeight = _gridOptions.rowHeight; + let dropOffset = _contextMenuProperties.autoAdjustDropOffset; + let sideOffset = _contextMenuProperties.autoAlignSideOffset; // if autoAdjustDrop is enable, we first need to see what position the drop will be located // without necessary toggling it's position just yet, we just want to know the future position for calculation if (_contextMenuProperties.autoAdjustDrop) { // since we reposition menu below slick cell, we need to take it in consideration and do our calculation from that element - var spaceBottom = calculateAvailableSpaceBottom($parent); - var spaceTop = calculateAvailableSpaceTop($parent); - var spaceBottomRemaining = spaceBottom + dropOffset - rowHeight; - var spaceTopRemaining = spaceTop - dropOffset + rowHeight; - var dropPosition = (spaceBottomRemaining < menuHeight && spaceTopRemaining > spaceBottomRemaining) ? 'top' : 'bottom'; + let spaceBottom = Slick.Utils.calculateAvailableSpace(parentElm).bottom; + let spaceTop = Slick.Utils.calculateAvailableSpace(parentElm).top; + let spaceBottomRemaining = spaceBottom + dropOffset - rowHeight; + let spaceTopRemaining = spaceTop - dropOffset + rowHeight; + let dropPosition = (spaceBottomRemaining < menuHeight && spaceTopRemaining > spaceBottomRemaining) ? 'top' : 'bottom'; if (dropPosition === 'top') { - $menu.removeClass("dropdown").addClass("dropup"); + _menuElm.classList.remove('dropdown'); + _menuElm.classList.add('dropup'); menuOffsetTop = menuOffsetTop - menuHeight - dropOffset; } else { - $menu.removeClass("dropup").addClass("dropdown"); + _menuElm.classList.remove('dropup'); + _menuElm.classList.add('dropdown'); menuOffsetTop = menuOffsetTop + rowHeight + dropOffset; } } @@ -666,20 +683,22 @@ // if there isn't enough space on the right, it will automatically align the drop menu to the left // to simulate an align left, we actually need to know the width of the drop menu if (_contextMenuProperties.autoAlignSide) { - var gridPos = _grid.getGridPosition(); - var dropSide = ((menuOffsetLeft + menuWidth) >= gridPos.width) ? 'left' : 'right'; + let gridPos = _grid.getGridPosition(); + let dropSide = ((menuOffsetLeft + menuWidth) >= gridPos.width) ? 'left' : 'right'; if (dropSide === 'left') { - $menu.removeClass("dropright").addClass("dropleft"); + _menuElm.classList.remove('dropright'); + _menuElm.classList.add('dropleft'); menuOffsetLeft = (menuOffsetLeft - menuWidth - sideOffset); } else { - $menu.removeClass("dropleft").addClass("dropright"); + _menuElm.classList.remove('dropleft'); + _menuElm.classList.add('dropright'); menuOffsetLeft = menuOffsetLeft + sideOffset; } } // ready to reposition the menu - $menu.css("top", menuOffsetTop); - $menu.css("left", menuOffsetLeft); + _menuElm.style.top = `${menuOffsetTop}px`; + _menuElm.style.left = `${menuOffsetLeft}px`; } /** @@ -695,7 +714,7 @@ return true; } - $.extend(this, { + Slick.Utils.extend(this, { "init": init, "closeMenu": destroyMenu, "destroy": destroy, @@ -709,4 +728,4 @@ "onOptionSelected": new Slick.Event() }); } -})(jQuery); +})(window); diff --git a/slick.core.js b/slick.core.js index a712c880..2778c2a1 100644 --- a/slick.core.js +++ b/slick.core.js @@ -737,6 +737,28 @@ return dirtyHtml.replace(/(\b)(on[a-z]+)(\s*)=|javascript:([^>]*)[^>]*|(<\s*)(\/*)script([<>]*).*(<\s*)(\/*)script(>*)|(<)(\/*)(script|script defer)(.*)(>|>">)/gi, ''); } + function calculateAvailableSpace(element) { + let bottom = 0, top = 0, left = 0, right = 0; + + const windowHeight = window.innerHeight || 0; + const windowWidth = window.innerWidth || 0; + const scrollPosition = windowScrollPosition(); + const pageScrollTop = scrollPosition.top; + const pageScrollLeft = scrollPosition.left; + const elmOffset = offset(element); + + if (elmOffset) { + const elementOffsetTop = elmOffset.top || 0; + const elementOffsetLeft = elmOffset.left || 0; + top = elementOffsetTop - pageScrollTop; + bottom = windowHeight - (elementOffsetTop - pageScrollTop); + left = elementOffsetLeft - pageScrollLeft; + right = windowWidth - (elementOffsetLeft - pageScrollLeft); + } + + return { top, bottom, left, right }; + } + // With help from https://youmightnotneedjquery.com/ function grep(elems, callback, invert) { var callbackInverse, @@ -785,6 +807,13 @@ }; } + function windowScrollPosition() { + return { + left: window.pageXOffset || document.documentElement.scrollLeft || 0, + top: window.pageYOffset || document.documentElement.scrollTop || 0, + }; + } + function width(el, value) { if (value === undefined) { return el.getBoundingClientRect().width; @@ -1040,6 +1069,7 @@ "Utils": { "extend": extend, + "calculateAvailableSpace": calculateAvailableSpace, "grep": grep, "emptyElement": emptyElement, "isEmptyObject": isEmptyObject,