Skip to content

ContextMenu and RightClick done properly. #148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Feb 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
66519da
First cut of adding support for right-click and context-menu
Jun 19, 2015
10ef96d
When showing contextMenu and contextSubMenu, workout if the menu will…
stueynz Jun 30, 2015
3497cf2
Tidied up right-click and context-menu section
stueynz Jun 30, 2015
a619564
Changed dependency for angular to 1.3.?
Jul 1, 2015
c8f3dfe
Updated dependencies to angular 1.3.x
Jul 1, 2015
45d6f70
Actual ContextMenu stuff is now it's own compoent, attribute menu-id …
Jul 10, 2015
24d9528
We need a context menu to play with our tree
Jul 15, 2015
b0d48f1
Added dependency on context-menu so w e can have context menu on the …
Jul 15, 2015
39f61f9
Updated documentation in README and index.html
Jul 15, 2015
469afbf
Don't actually need in the controller
Jul 15, 2015
632d199
fixed up spurious edit in css
Jul 15, 2015
482c52d
Re-merged wix/angular-tree-control so my pull request won't have conf…
Jul 16, 2015
c0e84f6
We still work with angular v1.2.x
Jul 16, 2015
b69d2e2
Need to load context-menu.js as well
Jul 16, 2015
951a52d
Don't resolve to angular 1.3.x (for now)
Jul 16, 2015
bc5f5a2
Added tests for menu-id to render context menu
Jul 16, 2015
f024249
Don't do the orderByExpression stuff (for now)
Jul 16, 2015
8a84225
Change initial order of Fruit so orderBy tests work better
Jul 16, 2015
b10de64
Whoops don't want this file in codestore
Jul 17, 2015
2e6b0ab
Whoops, leave that test alone
Jul 17, 2015
fa36328
Remove all mention of order-by-expression from docs (for now)
Jul 17, 2015
0f80014
Moved context-menu rendering tests, and added check for right-click f…
Jul 17, 2015
3304b5d
Delay the event.preventDefault() on right-click to make sure we have …
Jul 17, 2015
9a694f5
orderByExpression is back
Jul 17, 2015
771dd0e
Angular 1.3.x
stueynz Jul 17, 2015
53dddc5
Catchup with Upstream master
stueynz Feb 7, 2018
8e4cc84
Removed all mention of order-by-expression
stueynz Feb 7, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
.idea
.*
node_modules
bower_components
coverage
treecontrol.iml
.DS_Store
Chromium 49.0.2623 (Ubuntu)

1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ module.exports = function(grunt) {
'demo/angular.1.3.12.js',
'demo/angular-mocks.1.3.12.js',
'angular-tree-control.js',
'context-menu.js',
'test/**/*.js'
]
}
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ Copy the script and css into your project and add a script and link tag to your

```html
<script type="text/javascript" src="/angular-tree-control.js"></script>

<!-- Include context-menu module if you're going to use menu-id attribute -->
<script type="text/javascript" src="/context-menu.js"></script>

<!-- link for CSS when using the tree as a Dom element -->
<link rel="stylesheet" type="text/css" href="css/tree-control.css">

<!-- link for CSS when using the tree as an attribute -->
<link rel="stylesheet" type="text/css" href="css/tree-control-attribute.css">
```
Expand Down Expand Up @@ -113,6 +118,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'.
Expand All @@ -133,6 +139,7 @@ Attributes of angular treecontrol
- `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

Expand Down
54 changes: 44 additions & 10 deletions angular-tree-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex
ensureDefault($scope.options, "allowDeselect", true);
ensureDefault($scope.options, "isSelectable", defaultIsSelectable);
}
angular.module( 'treeControl', [] )

angular.module( 'treeControl', ['contextMenu'] )
.constant('treeConfig', {
templateUrl: null
})

.directive( 'treecontrol', ['$compile', function( $compile ) {
/**
* @param cssClass - the css class
Expand All @@ -100,9 +101,6 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex
else
return "";
}



return {
restrict: 'EA',
require: "treecontrol",
Expand All @@ -114,18 +112,20 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex
expandedNodes: "=?",
onSelection: "&",
onNodeToggle: "&",
onRightClick: "&",
menuId: "@",
options: "=?",
orderBy: "=?",
reverseOrder: "@",
filterExpression: "=?",
filterComparator: "=?"
},
controller: ['$scope', '$templateCache', '$interpolate', 'treeConfig', function ($scope, $templateCache, $interpolate, treeConfig) {

$scope.options = $scope.options || {};

ensureAllDefaultOptions($scope);

$scope.selectedNodes = $scope.selectedNodes || [];
$scope.expandedNodes = $scope.expandedNodes || [];
$scope.expandedNodesMap = {};
Expand Down Expand Up @@ -249,6 +249,25 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex
}
};

$scope.rightClickNodeLabel = function( targetNode, $event ) {

// Is there a right click function??
if($scope.onRightClick) {

// Turn off the browser default context-menu
if($event)
$event.preventDefault();

// Are are we changing the 'selected' node (as well)?
if ($scope.selectedNode != targetNode) {
this.selectNodeLabel(targetNode);
}

// Finally go do what they asked
$scope.onRightClick({node: targetNode});
}
};

$scope.selectedClass = function() {
var isThisNodeSelected = isSelectedNode(this.node);
var labelSelectionClass = classIfDefined($scope.options.injectClasses.labelSelected, false);
Expand All @@ -266,14 +285,16 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex
};

//tree template
var rcLabel = $scope.onRightClick ? ' tree-right-click="rightClickNodeLabel(node)"' : '';
var ctxMenuId = $scope.menuId ? ' context-menu-id="'+ $scope.menuId+'"' : '';

$scope.isReverse = function() {
return !($scope.reverseOrder === 'false' || $scope.reverseOrder === 'False' || $scope.reverseOrder === '' || $scope.reverseOrder === false);
};

$scope.orderByFunc = function() {
return $scope.orderBy;
};
// return "" + $scope.orderBy;

var templateOptions = {
orderBy: $scope.orderBy ? " | orderBy:orderByFunc():isReverse()" : '',
Expand All @@ -298,14 +319,15 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex
'set-node-to-data>' +
'<i class="tree-branch-head" ng-class="iBranchClass()" ng-click="selectNodeHead(node)"></i>' +
'<i class="tree-leaf-head {{options.iLeafClass}}"></i>' +
'<div class="tree-label {{options.labelClass}}" ng-class="[selectedClass(), unselectableClass()]" ng-click="selectNodeLabel(node)" tree-transclude></div>' +
'<div class="tree-label {{options.labelClass}}" ng-class="[selectedClass(), unselectableClass()]" ng-click="selectNodeLabel(node)" ' + rcLabel + ctxMenuId + ' tree-transclude></div>' +
'<treeitem ng-if="nodeExpanded()"></treeitem>' +
'</li>' +
'</ul>';
}

this.template = $compile($interpolate(template)({options: templateOptions}));
}],

compile: function(element, attrs, childTranscludeFn) {
return function ( scope, element, attrs, treemodelCntr ) {

Expand Down Expand Up @@ -381,6 +403,18 @@ if (typeof module !== "undefined" && typeof exports !== "undefined" && module.ex
}
};
}])

.directive('treeRightClick', function($parse) {
return function(scope, element, attrs) {
var fn = $parse(attrs.treeRightClick);
element.bind('contextmenu', function(event) {
scope.$apply(function() {
fn(scope, {$event:event}); // go do our stuff
});
});
};
})

.directive("treeitem", function() {
return {
restrict: 'E',
Expand Down
173 changes: 173 additions & 0 deletions context-menu.js
Original file line number Diff line number Diff line change
@@ -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 );
Loading