From 66519da40bd85d4110f4c06288b1612779498fd6 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Fri, 19 Jun 2015 17:04:42 +1200 Subject: [PATCH 01/25] First cut of adding support for right-click and context-menu --- angular-tree-control.js | 148 +++++++++++++++++++++++++++++++++++++--- index.html | 78 ++++++++++++++++++++- 2 files changed, 216 insertions(+), 10 deletions(-) diff --git a/angular-tree-control.js b/angular-tree-control.js index 142bee6..43c6a40 100644 --- a/angular-tree-control.js +++ b/angular-tree-control.js @@ -1,6 +1,6 @@ (function ( angular ) { 'use strict'; - + angular.module( 'treeControl', [] ) .directive( 'treecontrol', ['$compile', function( $compile ) { /** @@ -17,12 +17,12 @@ else return ""; } - + function ensureDefault(obj, prop, value) { if (!obj.hasOwnProperty(prop)) obj[prop] = value; } - + return { restrict: 'EA', require: "treecontrol", @@ -34,13 +34,15 @@ expandedNodes: "=?", onSelection: "&", onNodeToggle: "&", + onRightClick: "&", + contextMenuId: "@", options: "=?", orderBy: "@", reverseOrder: "@", filterExpression: "=?", filterComparator: "=?" }, - controller: ['$scope', function( $scope ) { + controller: ['$scope', '$attrs', function( $scope, $attrs) { function defaultIsLeaf(node) { return !node[$scope.options.nodeChildren] || node[$scope.options.nodeChildren].length === 0; @@ -150,7 +152,7 @@ index = i; } } - if (index != undefined) + if (index !== undefined) $scope.expandedNodes.splice(index, 1); } if ($scope.onNodeToggle) @@ -191,6 +193,19 @@ } }; + $scope.rightClickNodeLabel = function( targetNode ) { + + // Are are we changing the 'selected' node (as well)? + if ($scope.selectedNode != targetNode) { + this.selectNodeLabel( targetNode); + } + + // ...and do the rightClick + if($scope.onRightClick) { + $scope.onRightClick({node: targetNode}); + } + }; + $scope.selectedClass = function() { var isThisNodeSelected = isSelectedNode(this.node); var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false); @@ -203,18 +218,27 @@ //tree template var orderBy = $scope.orderBy ? ' | orderBy:orderBy:reverseOrder' : ''; + var rcLabel = $attrs.onRightClick ? ' ng-right-click="rightClickNodeLabel(node)"' : ''; + var ctxMenuId = $attrs.contextMenuId ? ' ng-context-menu="'+ $attrs.contextMenuId+'"' : ''; + var template = ''; this.template = $compile(template); }], + + compile: function(element, attrs, childTranscludeFn) { return function ( scope, element, attrs, treemodelCntr ) { @@ -274,10 +298,116 @@ // we can fix this to work with the link transclude function with angular 1.2.6. as for angular 1.2.0 we need // to keep using the compile function scope.$treeTransclude = childTranscludeFn; - } + }; + } + }; + }]) + + .directive('ngRightClick', function($parse) { + return function(scope, element, attrs) { + var fn = $parse(attrs.ngRightClick); + element.bind('contextmenu', function(event) { + scope.$apply(function() { + event.preventDefault(); // Don't show the browser's normal context menu + fn(scope, {$event:event}); // call the function inside the ng-right-click attribute + }); + }); + }; + }) + + .directive('ngContextMenu', ['$document', '$parse', function($document, $parse) { + return { + restrict : 'A', + scope : '@&', + compile: function compile(tElement, tAttrs, transclude) { + return { + post: function postLink(scope, iElement, iAttrs, controller) { + + + var ul = angular.element(document.querySelector('#' + iAttrs.ngContextMenu)); + + ul.css({ 'display' : 'none'}); + + // right-click on ng-context-menu will show the menu + iElement.bind('contextmenu', function showContextMenu(event) { + + // don't do the normal browser right-click context menu + event.preventDefault(); + + // use CSS to move and show the dropdown-menu + ul.css({ + position: "fixed", + display: "block", + left: event.clientX + 'px', + top: event.clientY + 'px' + }); + + // setup a one-time click event on the document to hide the dropdown-menu + $document.one('click', function hideContextMenu(event) { + ul.css({ + 'display' : 'none' + }); + }); + }); + } + }; + } + }; + }]) + + .directive('ngContextSubmenu', ['$document', function($document) { + return { + restrict : 'A', + scope : '@&', + compile: function compile(tElement, tAttrs, transclude) { + return { + post: function postLink(scope, iElement, iAttrs, controller) { + + var ul = angular.element(document.querySelector('#' + iAttrs.ngContextSubmenu)); + + ul.css({ 'display' : 'none'}); + + + iElement.bind('mouseover', function showContextMenu(event) { + // use CSS to move and show the sub dropdown-menu + if(ul.css("display") == 'none') { + + ul.css({ + position: "fixed", + display: "block", + left: event.target.offsetLeft + event.target.offsetWidth + 'px', + top: event.clientY + 'px' + }); + + // Each uncle/aunt menu item needs a mouseover event to make the subContext menu disappear + angular.forEach(iElement[0].parentElement.parentElement.children, function(child, ndx) { + if(child !== iElement[0].parentElement) { + angular.element(child).one('mouseover', function(event) { + if(ul.css("display") == 'block') { + ul.css({ + 'display' : 'none' + }); + } + }); + } + }); + } + + // setup a one-time click event on the document to hide the sub dropdown-menu + $document.one('click', function hideContextMenu(event) { + if(ul.css("display") == 'block') { + ul.css({ + 'display' : 'none' + }); + } + }); + }); + } + }; } }; }]) + .directive("treeitem", function() { return { restrict: 'E', @@ -288,7 +418,7 @@ element.html('').append(clone); }); } - } + }; }) .directive("treeTransclude", function() { return { @@ -332,6 +462,6 @@ element.append(clone); }); } - } + }; }); })( angular ); diff --git a/index.html b/index.html index 487aa2c..1d93521 100644 --- a/index.html +++ b/index.html @@ -1017,6 +1017,82 @@

Custom Equality (options.equality)

+ +
+ +
+
+
+
+
EXAMPLE:
+ +
+
+
+
+

The angular tree control provides for right-click and context menu functionality

+

The last right-click was on the {{lastRightClickNode}}

+
+
+
+ + +

+            
+ +

+            
+
+
+ + +
+ +
-

The angular tree control provides for right-click and context menu functionality

-

The last right-click was on the {{lastRightClickNode}}

+

The angular tree control provides right-click and context menu functionality: +

+

+

The last right-click was on the {{lastRightClickNode}} node

+

The last food to be cooked was {{lastCookedFood}}

+

The last context menu action was {{lastAction}}

@@ -1073,7 +1104,6 @@

Right Click & Context Menu

From a619564b250c04e3aa6d412f632c591c1b1697be Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Wed, 1 Jul 2015 14:28:23 +1200 Subject: [PATCH 04/25] Changed dependency for angular to 1.3.? --- bower.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 8eb1295..ecab189 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-tree-control", - "version": "0.2.9", + "version": "0.2.10", "main": [ "./angular-tree-control.js", "./css/tree-control.css" @@ -12,7 +12,7 @@ "karma.conf.js" ], "dependencies": { - "angular": "~1.2.7" + "angular": "~1.3.0" }, "devDependencies": { "angular-mocks": "~1.2.7", From c8f3dfefc7fac94c740acdb2e5a73f18e18abf19 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Wed, 1 Jul 2015 14:45:33 +1200 Subject: [PATCH 05/25] Updated dependencies to angular 1.3.x --- bower.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bower.json b/bower.json index ecab189..58e8d2c 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-tree-control", - "version": "0.2.10", + "version": "0.2.11", "main": [ "./angular-tree-control.js", "./css/tree-control.css" @@ -12,13 +12,16 @@ "karma.conf.js" ], "dependencies": { - "angular": "~1.3.0" + "angular": "~1.3.x" }, "devDependencies": { - "angular-mocks": "~1.2.7", + "angular-mocks": "~1.3.x", "jquery": "~2.0.3", "bootstrap": "~3.1.1", "angular-bootstrap": "~0.11.0", "google-code-prettify": "1.0.1" + }, + "resolutions": { + "angular": "~1.3.x" } } From 45d6f70f54caa4559584f2b017888d465b277880 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Fri, 10 Jul 2015 16:36:19 +1200 Subject: [PATCH 06/25] Actual ContextMenu stuff is now it's own compoent, attribute menu-id tells treecontrol to use it --- .gitignore | 1 + angular-tree-control.js | 153 +++------------------------------------- 2 files changed, 12 insertions(+), 142 deletions(-) diff --git a/.gitignore b/.gitignore index 94783d5..0f312c4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ bower_components coverage treecontrol.iml .DS_Store +.tern-port diff --git a/angular-tree-control.js b/angular-tree-control.js index a5a5452..f3fbb4d 100644 --- a/angular-tree-control.js +++ b/angular-tree-control.js @@ -1,19 +1,6 @@ (function ( angular ) { 'use strict'; - /* Figure out page (viewport) dimensions of current page, by - * putting an empty DIV in the bottom right, and checking its offset. - */ - function getPageDimensions() { - var bttmRight = document.createElement("div"); - bttmRight.setAttribute("style" , "visibility:hidden;position:fixed;bottom:0px;right:0px;"); - document.getElementsByTagName("body")[0].appendChild(bttmRight); - var pageWidth = bttmRight.offsetLeft; - var pageHeight = bttmRight.offsetTop; - bttmRight.parentNode.removeChild(bttmRight); - return { pageWidth:pageWidth, pageHeight:pageHeight }; - }; - angular.module( 'treeControl', [] ) .directive( 'treecontrol', ['$compile', function( $compile ) { /** @@ -48,9 +35,10 @@ onSelection: "&", onNodeToggle: "&", onRightClick: "&", - contextMenuId: "@", + menuId: "@", options: "=?", orderBy: "@", + orderByExpression: "@", reverseOrder: "@", filterExpression: "=?", filterComparator: "=?" @@ -230,9 +218,14 @@ }; //tree template - var orderBy = $scope.orderBy ? ' | orderBy:orderBy:reverseOrder' : ''; - var rcLabel = $attrs.onRightClick ? ' tree-right-click="rightClickNodeLabel(node)"' : ''; - var ctxMenuId = $attrs.contextMenuId ? ' tree-context-menu-id="'+ $attrs.contextMenuId+'"' : ''; + var orderBy = ''; + if ($scope.orderByExpression) { + orderBy = ' | orderBy:' + $scope.orderByExpression + ':reverseOrder'; + } else if ($scope.orderBy) { + orderBy = ' | orderBy:orderBy:reverseOrder'; + } + var rcLabel = $scope.onRightClick ? ' tree-right-click="rightClickNodeLabel(node)"' : ''; + var ctxMenuId = $scope.menuId ? ' context-menu-id="'+ $scope.menuId+'"' : ''; var template = '
    ' + @@ -251,7 +244,6 @@ this.template = $compile(template); }], - compile: function(element, attrs, childTranscludeFn) { return function ( scope, element, attrs, treemodelCntr ) { @@ -328,130 +320,6 @@ }; }) - .directive('treeContextMenuId', ['$document', function($document) { - - return { - restrict : 'A', - scope : '@&', - compile: function compile(tElement, tAttrs, transclude) { - return { - post: function postLink(scope, iElement, iAttrs, controller) { - - var ul = angular.element(document.querySelector('#' + iAttrs.treeContextMenuId)); - - ul.css({ 'display' : 'none'}); - - // right-click on context-menu will show the menu - iElement.bind('contextmenu', function showContextMenu(event) { - - // don't do the normal browser right-click context menu - event.preventDefault(); - var pgDim = getPageDimensions(); - - // will ctxMenu fit on screen (height-wise) ? - // TODO: figure out why we need the fudge-factor of 14 - var ulTop = event.clientY + ul.height() <= pgDim.pageHeight - 14 - ? event.clientY - : pgDim.pageHeight - ul.height() - 14; - - // will ctxMenu fit on screen (width-wise) ? - var ulLeft = event.clientX + ul.width() <= pgDim.pageWidth - 2 - ? event.clientX - : pgDim.pageWidth - ul.width() - 2; - - // use CSS to move and show the dropdown-menu - ul.css({ - position: "fixed", - display: "block", - left: ulLeft + 'px', - top: ulTop + 'px' - }); - - // setup a one-time click event on the document to hide the dropdown-menu - $document.one('click', function hideContextMenu(event) { - ul.css({ - 'display' : 'none' - }); - }); - }); - } - }; - } - }; - }]) - - .directive('contextSubmenuId', ['$document', function($document) { - return { - restrict : 'A', - scope : '@&', - compile: function compile(tElement, tAttrs, transclude) { - return { - post: function postLink(scope, iElement, iAttrs, controller) { - - var ul = angular.element(document.querySelector('#' + iAttrs.contextSubmenuId)); - - ul.css({ 'display' : 'none'}); - - - iElement.bind('mouseover', function showSubContextMenu(event) { - // use CSS to move and show the sub dropdown-menu - if(ul.css("display") == 'none') { - - // Will the ctxMenu fit on the right of the parent menu ?? - var pgDim = getPageDimensions(); - - // will ctxMenu (height-wise) ? - // TODO: figure out why we need the fudge-factor of 14 - var ulTop = event.clientY + ul.height() <= pgDim.pageHeight - 14 - ? event.clientY - : pgDim.pageHeight - ul.height() - 14; - - // will ctxMenu (width-wise) ? - var ulLeft = - (event.target.offsetParent.offsetLeft + - event.target.clientWidth + - ul.width() < pgDim.pageWidth) ? event.target.offsetParent.offsetLeft + - event.target.clientWidth - - : event.target.offsetParent.offsetLeft - - ul.width(); - - ul.css({ - position: "fixed", - display: "block", - left: ulLeft + 'px', - top: ulTop + 'px' - }) - - // Each uncle/aunt menu item needs a mouseover event to make the subContext menu disappear - angular.forEach(iElement[0].parentElement.parentElement.children, function(child, ndx) { - if(child !== iElement[0].parentElement) { - angular.element(child).one('mouseover', function(event) { - if(ul.css("display") == 'block') { - ul.css({ - 'display' : 'none' - }); - } - }); - } - }); - } - - // setup a one-time click event on the document to hide the sub dropdown-menu - $document.one('click', function hideContextMenu(event) { - if(ul.css("display") == 'block') { - ul.css({ - 'display' : 'none' - }); - } - }); - }); - } - }; - } - }; - }]) - .directive("treeitem", function() { return { restrict: 'E', @@ -464,6 +332,7 @@ } }; }) + .directive("treeTransclude", function() { return { link: function(scope, element, attrs, controller) { From 24d95287ebfda546da6df42ab022b504686f9f2d Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 16 Jul 2015 11:47:01 +1200 Subject: [PATCH 07/25] We need a context menu to play with our tree --- context-menu.js | 173 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 context-menu.js diff --git a/context-menu.js b/context-menu.js new file mode 100644 index 0000000..12b8ddb --- /dev/null +++ b/context-menu.js @@ -0,0 +1,173 @@ +(function ( angular ) { + 'use strict'; + + /* Figure out page (viewport) dimensions of current page, by + * putting an empty DIV in the bottom right, and checking its offset. + */ + function getPageDimensions() { + var bttmRight = document.createElement("div"); + bttmRight.setAttribute("style" , "visibility:hidden;position:fixed;bottom:0px;right:0px;"); + document.getElementsByTagName("body")[0].appendChild(bttmRight); + var pageWidth = bttmRight.offsetLeft; + var pageHeight = bttmRight.offsetTop; + bttmRight.parentNode.removeChild(bttmRight); + return { width:pageWidth, height:pageHeight }; + } + + angular.module( 'contextMenu', [] ) + + .directive('contextMenuId', ['$document', function($document) { + + return { + restrict : 'A', + scope : '@&', + compile: function compile(tElement, tAttrs, transclude) { + + return { + post: function postLink(scope, iElement, iAttrs, controller) { + + var ul = angular.element(document.querySelector('#' + iAttrs.contextMenuId)); + + ul.css({ 'display' : 'none'}); + + // right-click on context-menu will show the menu + iElement.bind('contextmenu', function showContextMenu(event) { + + // don't do the normal browser right-click context menu + event.preventDefault(); + + // Organise to show off the menu (in roughly the right place) + ul.css({ + visibility:"hidden", + position: "fixed", + display: "block", + left: event.clientX + 'px', + top: event.clientY + 'px' + }); + + var ulDim = { height: ul.prop("clientHeight"), + width: ul.prop("cientWidth") + }; + + var pgDim = getPageDimensions(); + + // will ctxMenu fit on screen (height-wise) ? + // TODO: figure out why we need the fudge-factor of 14 + var ulTop = event.clientY + ulDim.height <= pgDim.height - 14 + ? event.clientY + : pgDim.height - ulDim.height - 14; + + // will ctxMenu fit on screen (width-wise) ? + var ulLeft = event.clientX + ulDim.width <= pgDim.width - 2 + ? event.clientX + : pgDim.width - ulDim.width - 2; + + // Ok, now show it off in the right place + ul.css({ + visibility:"visible", + position: "fixed", + display: "block", + left: ulLeft + 'px', + top: ulTop + 'px' + }); + + // setup a one-time click event on the document to hide the dropdown-menu + $document.one('click', function hideContextMenu(event) { + ul.css({ + 'display' : 'none' + }); + }); + }); + } + }; + } + }; + }]) + + .directive('contextSubmenuId', ['$document', function($document) { + return { + restrict : 'A', + scope : '@&', + compile: function compile(tElement, tAttrs, transclude) { + return { + post: function postLink(scope, iElement, iAttrs, controller) { + + var ul = angular.element(document.querySelector('#' + iAttrs.contextSubmenuId)); + + ul.css({ 'display' : 'none'}); + + + iElement.bind('mouseover', function showSubContextMenu(event) { + // use CSS to move and show the sub dropdown-menu + if(ul.css("display") == 'none') { + + // Organise to show off the sub-menu (in roughly the right place) + ul.css({ + visibility:"hidden", + position: "fixed", + display: "block", + left: event.clientX + 'px', + top: event.clientY + 'px' + }); + + var ulDim = { height: ul.prop("clientHeight"), + width: ul.prop("clientWidth") + }; + + var pgDim = getPageDimensions(); + + + // Will ctxSubMenu fit (height-wise) ? + // TODO: figure out why we need the fudge-factor of 14 + var ulTop = event.clientY + ulDim.height <= pgDim.height - 14 + ? event.clientY + : pgDim.height - ulDim.height - 14; + + // Will ctxSubMenu fit (on the right of parent menu) ? + var ulLeft = + (event.target.offsetParent.offsetLeft + + event.target.clientWidth + ulDim.width < pgDim.width) + ? event.target.offsetParent.offsetLeft + + event.target.clientWidth + + : event.target.offsetParent.offsetLeft - ulDim.width; + + // OK, now show it off in the right place + ul.css({ + visibility:"visible", + position: "fixed", + display: "block", + left: ulLeft + 'px', + top: ulTop + 'px' + }); + + // Each uncle/aunt menu item needs a mouseover event to make the subContext menu disappear + angular.forEach(iElement[0].parentElement.parentElement.children, function(child, ndx) { + if(child !== iElement[0].parentElement) { + angular.element(child).one('mouseover', function(event) { + if(ul.css("display") == 'block') { + ul.css({ + 'display' : 'none' + }); + } + }); + } + }); + } + + // setup a one-time click event on the document to hide the sub dropdown-menu + $document.one('click', function hideContextMenu(event) { + if(ul.css("display") == 'block') { + ul.css({ + 'display' : 'none' + }); + } + }); + }); + } + }; + } + }; + }]); + +})( angular ); From b0d48f1f564cfa336bd37e6d713e5c12e133da96 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 16 Jul 2015 11:47:57 +1200 Subject: [PATCH 08/25] Added dependency on context-menu so w e can have context menu on the tree --- angular-tree-control.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/angular-tree-control.js b/angular-tree-control.js index f3fbb4d..3db52ca 100644 --- a/angular-tree-control.js +++ b/angular-tree-control.js @@ -1,7 +1,7 @@ (function ( angular ) { 'use strict'; - angular.module( 'treeControl', [] ) + angular.module( 'treeControl', ['contextMenu'] ) .directive( 'treecontrol', ['$compile', function( $compile ) { /** * @param cssClass - the css class From 39f61f95110dc5902f9217eb54729849cf2bf075 Mon Sep 17 00:00:00 2001 From: Stuart McGrigor Date: Thu, 16 Jul 2015 11:48:34 +1200 Subject: [PATCH 09/25] Updated documentation in README and index.html --- .gitignore | 6 +++--- README.md | 8 ++++++++ index.html | 18 +++++++----------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 0f312c4..4a4db42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ -.idea +.* node_modules bower_components coverage treecontrol.iml -.DS_Store -.tern-port + + diff --git a/README.md b/README.md index c539836..e8d730e 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,13 @@ Copy the script and css into your project and add a script and link tag to your ```html + + + + + ``` @@ -112,6 +117,7 @@ Attributes of angular treecontrol - `expanded-nodes` : [Array[Node]] binding for the expanded nodes in the tree. Updating this value updates the nodes that are expanded in the tree. - `on-selection` : `(node, selected)` callback called whenever selecting a node in the tree. The callback expression can use the selected node (`node`) and a boolean which indicates if the node was selected or deselected (`selected`). - `on-node-toggle` : `(node, expanded)` callback called whenever a node expands or collapses in the tree. The callback expression can use the toggled node (`node`) and a boolean which indicates expansion or collapse (`expanded`). +- `on-right-click` : `(node)` callback called whenever a node is right-clicked. - `options` : different options to customize the tree control. - `multiSelection` : [Boolean] enable multiple nodes selection in the tree. - `nodeChildren` : the name of the property of each node that holds the node children. Defaults to 'children'. @@ -128,9 +134,11 @@ Attributes of angular treecontrol - `label` : inhject classes into the div element around the label - `labelSelected` : inject classes into the div element around the label only when the node is selected - `order-by` : value for ng-repeat to use for ordering sibling nodes +- `order-by-expression` : expression for ng-repeat to use for ordering sibling nodes - `reverse-order` : whether or not to reverse the ordering of sibling nodes based on the value of `order-by` - `filter-expression` : value for ng-repeat to use for filtering the sibling nodes - `filter-comparator` : value for ng-repeat to use for comparing nodes with the filter expression +- `menu-id` : the id of an ul element which will be displayed after a right-click ### The tree labels diff --git a/index.html b/index.html index 11e59dc..02e1b8c 100644 --- a/index.html +++ b/index.html @@ -12,6 +12,7 @@ +