Skip to content

Commit

Permalink
Merge branch 'stueynz-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
yoavaa committed Feb 7, 2018
2 parents 3d0aed8 + 8e4cc84 commit d0871b3
Show file tree
Hide file tree
Showing 8 changed files with 402 additions and 32 deletions.
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

0 comments on commit d0871b3

Please sign in to comment.