From 95793b7b87b02d097f4c84ab75bd155cfd61ea0f Mon Sep 17 00:00:00 2001 From: colemas1 Date: Mon, 28 Nov 2016 09:07:24 +0000 Subject: [PATCH 1/2] dynamically enable/disabled and updated docs --- README.md | 8 + contextMenu.js | 577 +++++++++++++++++++++++++----------------------- demo/demo.js | 34 +-- demo/index.html | 9 +- 4 files changed, 337 insertions(+), 291 deletions(-) diff --git a/README.md b/README.md index 6ef04f4..0c2d0e8 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,14 @@ Sometimes you'll want to catch the same event that's being used by this library ``` Note that if you set this to true, and don't catch it with something else the browser's context menu will be shown on top of this library's context menu. +## Dynamically enable/disable context menu +``` + +``` + ## Nested Menus (v0.9.5+) Nested lists work by appending an array as the last index of any list of menu items. diff --git a/contextMenu.js b/contextMenu.js index 6daefb3..ad2b5b1 100644 --- a/contextMenu.js +++ b/contextMenu.js @@ -1,331 +1,358 @@ angular.module('ui.bootstrap.contextMenu', []) .service('CustomService', function () { - "use strict"; + "use strict"; - return { - initialize: function (item) { - console.log("got here", item); - } - }; + return { + initialize: function (item) { + console.log("got here", item); + } + }; -}) -.directive('contextMenu', ["$parse", "$q", "CustomService", "$sce", function ($parse, $q, custom, $sce) { + }) + .directive('contextMenu', ["$parse", "$q", "CustomService", "$sce", function ($parse, $q, custom, $sce) { - var contextMenus = []; - var defaultItemText = "New Item"; + var contextMenus = []; + var defaultItemText = "New Item"; - var removeContextMenus = function (level) { - /// Remove context menu. - while (contextMenus.length && (!level || contextMenus.length > level)) { - contextMenus.pop().remove(); - } - }; + var removeContextMenus = function (level) { + /// Remove context menu. + while (contextMenus.length && (!level || contextMenus.length > level)) { + contextMenus.pop().remove(); + } + }; - var processTextItem = function ($scope, item, text, event, modelValue, $promises, nestedMenu, $) { - "use strict"; + var processTextItem = function ($scope, item, text, event, modelValue, $promises, nestedMenu, $) { + "use strict"; - var $a = $(''); - $a.css("padding-right", "8px"); - $a.attr({ tabindex: '-1', href: '#' }); + var $a = $(''); + $a.css("padding-right", "8px"); + $a.attr({ + tabindex: '-1', + href: '#' + }); - if (typeof item[0] === 'string') { - text = item[0]; - } - else if (typeof item[0] === "function") { - text = item[0].call($scope, $scope, event, modelValue); - } else if (typeof item.text !== "undefined") { - text = item.text; - } + if (typeof item[0] === 'string') { + text = item[0]; + } else if (typeof item[0] === "function") { + text = item[0].call($scope, $scope, event, modelValue); + } else if (typeof item.text !== "undefined") { + text = item.text; + } - var $promise = $q.when(text); - $promises.push($promise); - $promise.then(function (text) { - if (nestedMenu) { - $a.css("cursor", "default"); - $a.append($('>')); + var $promise = $q.when(text); + $promises.push($promise); + $promise.then(function (text) { + if (nestedMenu) { + $a.css("cursor", "default"); + $a.append($('>')); + } + $a.append(text); + }); + + return $a; + + }; + + var processItem = function ($scope, event, modelValue, item, $ul, $li, $promises, $q, $, level) { + /// Process individual item + "use strict"; + // nestedMenu is either an Array or a Promise that will return that array. + var nestedMenu = angular.isArray(item[1]) || + (item[1] && angular.isFunction(item[1].then)) ? item[1] : angular.isArray(item[2]) || + (item[2] && angular.isFunction(item[2].then)) ? item[2] : angular.isArray(item[3]) || + (item[3] && angular.isFunction(item[3].then)) ? item[3] : null; + + // if html property is not defined, fallback to text, otherwise use default text + // if first item in the item array is a function then invoke .call() + // if first item is a string, then text should be the string. + + var text = defaultItemText; + if (typeof item[0] === 'function' || typeof item[0] === 'string' || typeof item.text !== "undefined") { + text = processTextItem($scope, item, text, event, modelValue, $promises, nestedMenu, $); + } else if (typeof item.html === 'function') { + // leave styling open to dev + text = item.html($scope); + } else if (typeof item.html !== "undefined") { + // leave styling open to dev + text = item.html; } - $a.append(text); - }); - return $a; + $li.append(text); - }; - var processItem = function ($scope, event, modelValue, item, $ul, $li, $promises, $q, $, level) { - /// Process individual item - "use strict"; - // nestedMenu is either an Array or a Promise that will return that array. - var nestedMenu = angular.isArray(item[1]) || - (item[1] && angular.isFunction(item[1].then)) ? item[1] : angular.isArray(item[2]) || - (item[2] && angular.isFunction(item[2].then)) ? item[2] : angular.isArray(item[3]) || - (item[3] && angular.isFunction(item[3].then)) ? item[3] : null; - - // if html property is not defined, fallback to text, otherwise use default text - // if first item in the item array is a function then invoke .call() - // if first item is a string, then text should be the string. - - var text = defaultItemText; - if (typeof item[0] === 'function' || typeof item[0] === 'string' || typeof item.text !== "undefined") { - text = processTextItem($scope, item, text, event, modelValue, $promises, nestedMenu, $); - } - else if (typeof item.html === 'function') { - // leave styling open to dev - text = item.html($scope); - } - else if (typeof item.html !== "undefined") { - // leave styling open to dev - text = item.html; - } - $li.append(text); + // if item is object, and has enabled prop invoke the prop + // els if fallback to item[2] + var isEnabled = function () { + if (typeof item.enabled !== "undefined") { + return item.enabled.call($scope, $scope, event, modelValue, text); + } else if (typeof item[2] === "function") { + return item[2].call($scope, $scope, event, modelValue, text); + } else { + return true; + } + }; + + registerEnabledEvents($scope, isEnabled(), item, $ul, $li, nestedMenu, modelValue, text, event, $, level); + }; + + var handlePromises = function ($ul, level, event, $promises) { + /// + /// calculate if drop down menu would go out of screen at left or bottom + /// calculation need to be done after element has been added (and all texts are set; thus thepromises) + /// to the DOM the get the actual height + /// + "use strict"; + $q.all($promises).then(function () { + var topCoordinate = event.pageY; + var menuHeight = angular.element($ul[0]).prop('offsetHeight'); + var winHeight = window.scrollY + event.view.innerHeight; + /// the 20 pixels in second condition are considering the browser status bar that sometimes overrides the element + if (topCoordinate > menuHeight && winHeight - topCoordinate < menuHeight + 20) { + topCoordinate = event.pageY - menuHeight; + /// If the element is a nested menu, adds the height of the parent li to the topCoordinate to align with the parent + if (level && level > 0) { + topCoordinate += event.event.currentTarget.offsetHeight; + } + } else if (winHeight <= menuHeight) { + // If it really can't fit, reset the height of the menu to one that will fit + angular.element($ul[0]).css({ + "height": winHeight - 5, + "overflow-y": "scroll" + }); + // ...then set the topCoordinate height to 0 so the menu starts from the top + topCoordinate = 0; + } else if (winHeight - topCoordinate < menuHeight) { + var reduceThresholdY = 5; + if (topCoordinate < reduceThresholdY) { + reduceThresholdY = topCoordinate; + } + topCoordinate = winHeight - menuHeight - reduceThresholdY; + } + + var leftCoordinate = event.pageX; + var menuWidth = angular.element($ul[0]).prop('offsetWidth'); + var winWidth = event.view.innerWidth; + var rightPadding = 5; + if (leftCoordinate > menuWidth && winWidth - leftCoordinate - rightPadding < menuWidth) { + leftCoordinate = winWidth - menuWidth - rightPadding; + } else if (winWidth - leftCoordinate < menuWidth) { + var reduceThresholdX = 5; + if (leftCoordinate < reduceThresholdX + rightPadding) { + reduceThresholdX = leftCoordinate + rightPadding; + } + leftCoordinate = winWidth - menuWidth - reduceThresholdX - rightPadding; + } + $ul.css({ + display: 'block', + position: 'absolute', + left: leftCoordinate + 'px', + top: topCoordinate + 'px' + }); + }); + }; - // if item is object, and has enabled prop invoke the prop - // els if fallback to item[2] + var registerEnabledEvents = function ($scope, enabled, item, $ul, $li, nestedMenu, modelValue, text, event, $, level) { + /// If item is enabled, register various mouse events. + if (enabled) { + var openNestedMenu = function ($event) { + removeContextMenus(level + 1); + /* + * The object here needs to be constructed and filled with data + * on an "as needed" basis. Copying the data from event directly + * or cloning the event results in unpredictable behavior. + */ + /// adding the original event in the object to use the attributes of the mouse over event in the promises + var ev = { + pageX: event.pageX + $ul[0].offsetWidth - 1, + pageY: $ul[0].offsetTop + $li[0].offsetTop - 3, + view: event.view || window, + event: $event + }; + + /* + * At this point, nestedMenu can only either be an Array or a promise. + * Regardless, passing them to when makes the implementation singular. + */ + $q.when(nestedMenu).then(function (promisedNestedMenu) { + renderContextMenu($scope, ev, promisedNestedMenu, modelValue, level + 1); + }); + }; - var isEnabled = function () { - if (typeof item.enabled !== "undefined") { - return item.enabled.call($scope, $scope, event, modelValue, text); - } else if (typeof item[2] === "function") { - return item[2].call($scope, $scope, event, modelValue, text); + $li.on('click', function ($event) { + if ($event.which == 1) { + $event.preventDefault(); + $scope.$apply(function () { + if (nestedMenu) { + openNestedMenu($event); + } else { + $(event.currentTarget).removeClass('context'); + removeContextMenus(); + + if (angular.isFunction(item[1])) { + item[1].call($scope, $scope, event, modelValue, text, $li); + } else { + item.click.call($scope, $scope, event, modelValue, text, $li); + } + } + }); + } + }); + + $li.on('mouseover', function ($event) { + $scope.$apply(function () { + if (nestedMenu) { + openNestedMenu($event); + /// Implementation made by dashawk + } else { + removeContextMenus(level + 1); + } + }); + }); } else { - return true; + $li.on('click', function ($event) { + $event.preventDefault(); + }); + $li.addClass('disabled'); } + }; - registerEnabledEvents($scope, isEnabled(), item, $ul, $li, nestedMenu, modelValue, text, event, $, level); - }; - var handlePromises = function ($ul, level, event, $promises) { - /// - /// calculate if drop down menu would go out of screen at left or bottom - /// calculation need to be done after element has been added (and all texts are set; thus thepromises) - /// to the DOM the get the actual height - /// - "use strict"; - $q.all($promises).then(function () { - var topCoordinate = event.pageY; - var menuHeight = angular.element($ul[0]).prop('offsetHeight'); - var winHeight = window.scrollY + event.view.innerHeight; - /// the 20 pixels in second condition are considering the browser status bar that sometimes overrides the element - if (topCoordinate > menuHeight && winHeight - topCoordinate < menuHeight + 20) { - topCoordinate = event.pageY - menuHeight; - /// If the element is a nested menu, adds the height of the parent li to the topCoordinate to align with the parent - if(level && level > 0) { - topCoordinate += event.event.currentTarget.offsetHeight; - } - } else if(winHeight <= menuHeight) { - // If it really can't fit, reset the height of the menu to one that will fit - angular.element($ul[0]).css({"height": winHeight - 5, "overflow-y": "scroll"}); - // ...then set the topCoordinate height to 0 so the menu starts from the top - topCoordinate = 0; - } else if(winHeight - topCoordinate < menuHeight) { - var reduceThresholdY = 5; - if(topCoordinate < reduceThresholdY) { - reduceThresholdY = topCoordinate; - } - topCoordinate = winHeight - menuHeight - reduceThresholdY; + var renderContextMenu = function ($scope, event, options, modelValue, level, customClass) { + /// Render context menu recursively. + if (!level) { + level = 0; } - - var leftCoordinate = event.pageX; - var menuWidth = angular.element($ul[0]).prop('offsetWidth'); - var winWidth = event.view.innerWidth; - var rightPadding = 5; - if (leftCoordinate > menuWidth && winWidth - leftCoordinate - rightPadding < menuWidth) { - leftCoordinate = winWidth - menuWidth - rightPadding; - } else if(winWidth - leftCoordinate < menuWidth) { - var reduceThresholdX = 5; - if(leftCoordinate < reduceThresholdX + rightPadding) { - reduceThresholdX = leftCoordinate + rightPadding; - } - leftCoordinate = winWidth - menuWidth - reduceThresholdX - rightPadding; + if (!$) { + var $ = angular.element; } - + $(event.currentTarget).addClass('context'); + var $ul = $('