1 angular.module('ui.bootstrap.contextMenu', [])
3 .service('CustomService', function () {
7 initialize: function (item) {
8 console.log("got here", item);
13 .directive('contextMenu', ["$parse", "$q", "CustomService", "$sce", function ($parse, $q, custom, $sce) {
15 var contextMenus = [];
16 var $currentContextMenu = null;
17 var defaultItemText = "New Item";
19 var removeContextMenus = function (level) {
20 /// <summary>Remove context menu.</summary>
21 while (contextMenus.length && (!level || contextMenus.length > level)) {
22 contextMenus.pop().remove();
24 if (contextMenus.length == 0 && $currentContextMenu) {
25 $currentContextMenu.remove();
30 var processTextItem = function ($scope, item, text, event, model, $promises, nestedMenu, $) {
34 $a.css("padding-right", "8px");
35 $a.attr({ tabindex: '-1', href: '#' });
37 if (typeof item[0] === 'string') {
40 else if (typeof item[0] === "function") {
41 item[0].call($scope, $scope, event, model);
42 } else if (typeof item.text !== "undefined") {
46 var $promise = $q.when(text);
47 $promises.push($promise);
48 $promise.then(function (text) {
51 $a.css("cursor", "default");
52 $a.append($('<strong style="font-family:monospace;font-weight:bold;float:right;">></strong>'));
60 var processItem = function ($scope, event, model, item, $ul, $li, $promises, $q, $, level) {
61 /// <summary>Process individual item</summary>
63 var nestedMenu = angular.isArray(item[1])
64 ? item[1] : angular.isArray(item[2])
65 ? item[2] : angular.isArray(item[3])
68 // if html property is not defined, fallback to text, otherwise use default text
69 // if first item in the item array is a function then invoke .call()
70 // if first item is a string, then text should be the string.
72 var text = defaultItemText;
73 if (typeof item[0] === 'string' || typeof item.text !== "undefined") {
74 text = processTextItem($scope, item, text, event, model, $promises, nestedMenu, $);
76 else if (typeof item.html !== "undefined") {
77 // leave styling open to dev
86 // if item is object, and has enabled prop invoke the prop
87 // els if fallback to item[2]
89 var isEnabled = function () {
90 if (typeof item.enabled !== "undefined") {
91 return item.enabled.call($scope, $scope, event, model, text);
92 } else if (typeof item[2] === "function") {
93 return item[2].call($scope, $scope, event, model, text);
99 registerEnabledEvents($scope, isEnabled(), item, $ul, $li, nestedMenu, model, text, event, $, level);
102 var handlePromises = function ($ul, level, event, $promises) {
104 /// calculate if drop down menu would go out of screen at left or bottom
105 /// calculation need to be done after element has been added (and all texts are set; thus thepromises)
106 /// to the DOM the get the actual height
109 $q.all($promises).then(function () {
111 var topCoordinate = event.pageY;
112 var menuHeight = angular.element($ul[0]).prop('offsetHeight');
113 var winHeight = event.view.innerHeight;
114 if (topCoordinate > menuHeight && winHeight - topCoordinate < menuHeight) {
115 topCoordinate = event.pageY - menuHeight;
118 var leftCoordinate = event.pageX;
119 var menuWidth = angular.element($ul[0]).prop('offsetWidth');
120 var winWidth = event.view.innerWidth;
121 if (leftCoordinate > menuWidth && winWidth - leftCoordinate < menuWidth) {
122 leftCoordinate = event.pageX - menuWidth;
127 position: 'absolute',
128 left: leftCoordinate + 'px',
129 top: topCoordinate + 'px'
136 var registerEnabledEvents = function ($scope, enabled, item, $ul, $li, nestedMenu, model, text, event, $, level) {
137 /// <summary>If item is enabled, register various mouse events.</summary>
139 var openNestedMenu = function ($event) {
140 removeContextMenus(level + 1);
142 pageX: event.pageX + $ul[0].offsetWidth - 1,
143 pageY: $ul[0].offsetTop + $li[0].offsetTop - 3
145 renderContextMenu($scope, ev, nestedMenu, model, level + 1);
148 $li.on('click', function ($event) {
149 $event.preventDefault();
150 $scope.$apply(function () {
152 openNestedMenu($event);
154 $(event.currentTarget).removeClass('context');
155 removeContextMenus();
157 if (angular.isFunction(item[1])) {
158 item[1].call($scope, $scope, event, model, text)
160 item.click.call($scope, $scope, event, model, text);
166 $li.on('mouseover', function ($event) {
167 $scope.$apply(function () {
169 openNestedMenu($event);
174 $li.on('click', function ($event) {
175 $event.preventDefault();
177 $li.addClass('disabled');
183 var renderContextMenu = function ($scope, event, options, model, level) {
184 /// <summary>Render context menu recursively.</summary>
185 if (!level) { level = 0; }
186 if (!$) { var $ = angular.element; }
187 $(event.currentTarget).addClass('context');
188 var $contextMenu = $('<div>');
189 if ($currentContextMenu) {
190 $contextMenu = $currentContextMenu;
192 $currentContextMenu = $contextMenu;
194 $contextMenu.addClass('dropdown clearfix');
196 $ul.addClass('dropdown-menu');
197 $ul.attr({ 'role': 'menu' });
200 position: 'absolute',
201 left: event.pageX + 'px',
202 top: event.pageY + 'px',
208 angular.forEach(options, function (item) {
212 $li.addClass('divider');
213 } else if (typeof item[0] === "object") {
214 custom.initialize($li, item);
216 processItem($scope, event, model, item, $ul, $li, $promises, $q, $, level);
220 $contextMenu.append($ul);
221 var height = Math.max(
222 document.body.scrollHeight, document.documentElement.scrollHeight,
223 document.body.offsetHeight, document.documentElement.offsetHeight,
224 document.body.clientHeight, document.documentElement.clientHeight
228 height: height + 'px',
229 position: 'absolute',
234 $(document).find('body').append($contextMenu);
236 handlePromises($ul, level, event, $promises);
238 $contextMenu.on("mousedown", function (e) {
239 if ($(e.target).hasClass('dropdown')) {
240 $(event.currentTarget).removeClass('context');
241 removeContextMenus();
243 }).on('contextmenu', function (event) {
244 $(event.currentTarget).removeClass('context');
245 event.preventDefault();
246 removeContextMenus(level);
249 $scope.$on("$destroy", function () {
250 removeContextMenus();
253 contextMenus.push($ul);
255 return function ($scope, element, attrs) {
256 element.on('contextmenu', function (event) {
257 event.stopPropagation();
258 $scope.$apply(function () {
259 event.preventDefault();
260 var options = $scope.$eval(attrs.contextMenu);
261 var model = $scope.$eval(attrs.model);
262 if (options instanceof Array) {
263 if (options.length === 0) { return; }
264 renderContextMenu($scope, event, options, model);
266 throw '"' + attrs.contextMenu + '" not an array';