diff --git a/.gitignore b/.gitignore
index 94783d5..4618fc2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ bower_components
coverage
treecontrol.iml
.DS_Store
+lcov-report
diff --git a/angular-tree-control.js b/angular-tree-control.js
index e6aaaff..de88669 100644
--- a/angular-tree-control.js
+++ b/angular-tree-control.js
@@ -2,7 +2,7 @@
'use strict';
angular.module( 'treeControl', [] )
- .directive( 'treecontrol', ['$compile', function( $compile ) {
+ .directive( 'treecontrol', ['$compile', '$document', function( $compile, $document ) {
/**
* @param cssClass - the css class
* @param addClassProperty - should we wrap the class name with class=""
@@ -38,7 +38,9 @@
orderBy: "@",
reverseOrder: "@",
filterExpression: "=?",
- filterComparator: "=?"
+ filterComparator: "=?",
+ treeMenuModel: "=",
+ onClickMenu: "&"
},
controller: ['$scope', function( $scope ) {
@@ -99,7 +101,6 @@
}
$scope.parentScopeOfTree = $scope.$parent;
-
function isSelectedNode(node) {
if (!$scope.options.multiSelection && ($scope.options.equality(node, $scope.selectedNode)))
return true;
@@ -203,12 +204,23 @@
'
' +
'' +
'' +
- '' +
+ '' +
'' +
'' +
'';
+ var menuTemplate =
+ ''
+
this.template = $compile(template);
+ this.menuTemplate = $compile(menuTemplate);
}],
compile: function(element, attrs, childTranscludeFn) {
return function ( scope, element, attrs, treemodelCntr ) {
@@ -256,15 +268,44 @@
});
scope.expandedNodesMap = newExpandedNodesMap;
});
+
+ // scope.$watch('expandedNodesMap', function(newValue) {
+
+ // }
+
+ $document.bind('click', function() {
+ var dropdown = element.find('.dropdown');
+ dropdown.removeClass('open');
+ scope.currentContextNode = {}
+ });
+
+ scope.clickMenu = function(item) {
+ if (scope.onClickMenu)
+ scope.onClickMenu({item: item, node: scope.currentContextNode});
+ };
-// scope.$watch('expandedNodesMap', function(newValue) {
-//
-// });
+ scope.contextMenuNode = function ( event, node ) {
+ var dropdown = element.find('.dropdown');
+ dropdown.addClass('open');
+
+ scope.currentContextNode = node;
+ dropdown.offset({
+ top: event.pageY,
+ left: event.pageX
+ });
+ };
//Rendering template for a root node
treemodelCntr.template( scope, function(clone) {
element.html('').append( clone );
});
+
+ if (scope.treeMenuModel) {
+ treemodelCntr.menuTemplate( scope, function(clone) {
+ element.append( clone );
+ });
+ }
+
// save the transclude function from compile (which is not bound to a scope as apposed to the one from link)
// 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
@@ -273,6 +314,28 @@
}
};
}])
+ // treenode contextmenu
+ .directive("treenodeContextmenu", function() {
+ return {
+ restrict: 'A',
+ require: '^treecontrol',
+ scope: {
+ contextMenu: '&treenodeContextmenu'
+ },
+ link: function( scope, element, attrs, treemodelCntr) {
+ element.on('contextmenu', function (event) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ scope.$apply(function() {
+ scope.contextMenu({
+ $event: event
+ });
+ });
+ });
+ }
+ }
+ })
.directive("treeitem", function() {
return {
restrict: 'E',
diff --git a/index.html b/index.html
index 487aa2c..a1ec85b 100644
--- a/index.html
+++ b/index.html
@@ -57,6 +57,8 @@ Basic Usage (Classic style, default configuration)
label: {{node.label}} ({{node.id}})
@@ -89,6 +91,18 @@
Basic Usage (Classic style, default configuration)
$scope.showSelected = function(sel) {
$scope.selectedNode = sel;
};
+
+ $scope.menuModel = [{
+ label: '测试1',
+ value: 1
+ }, {
+ label: '测试2',
+ value: 2
+ }]
+
+ $scope.clickMenu = function(node, item) {
+ console.log(node, item);
+ }
}
diff --git a/test/angular-tree-control-test.js b/test/angular-tree-control-test.js
index 9fb4d9b..80eb7b3 100644
--- a/test/angular-tree-control-test.js
+++ b/test/angular-tree-control-test.js
@@ -73,6 +73,10 @@ describe('treeControl', function() {
$rootScope.$digest();
expect(element.find('li.tree-leaf').length).toBe(0);
});
+
+ it('should not render dropdown', function () {
+ expect(element.find('.dropdown').length).toBe(0);
+ });
});
describe('customising using options.isLeaf', function () {
@@ -636,7 +640,55 @@ describe('treeControl', function() {
$rootScope.$digest();
expect(element.find('li:eq(0)').hasClass('tree-expanded')).toBeTruthy();
});
+ });
+
+ describe('treenode-contextmenu', function () {
+ beforeEach(function () {
+ $rootScope.treedata = createSubTree(3, 2);
+ $rootScope.menuModel = [{
+ label: 'menu_1',
+ value: 1
+ }, {
+ label: 'menu_2',
+ value: 2
+ }]
+ $rootScope.clickMenu = function (node, item) {
+
+ };
+ element = $compile('{{node.label}}')($rootScope);
+ $rootScope.$digest();
+ });
+
+ it('should render dropmenu', function () {
+ expect(element.find('.dropdown .dropdown-menu li').length).toBe(2);
+ });
+
+ it('should show dropmenu', function () {
+ element.find('.tree-label:eq(0)').trigger('contextmenu');
+ expect(element.find('.dropdown').hasClass('open')).toBeTruthy();
+ });
+
+ it('should hide dropmenu after document click', function () {
+ $('html').click();
+ expect(element.find('.dropdown').hasClass('open')).toBeFalsy();
+ });
+
+ it('should fix dropmenu position', function () {
+ element.find('.tree-label:eq(0)').trigger({type: 'contextmenu', pageX: 231, pageY: 398});
+ dropdown = element.find('.dropdown');
+ expect(dropdown.css('top')).toBe('398px');
+ expect(dropdown.css('left')).toBe('231px');
+ });
+
+ it('should can click menu item', function () {
+ $rootScope.clickMenu = jasmine.createSpy('clickMenu');
+
+ element.find('.tree-label:eq(0)').trigger('contextmenu');
+ element.find('.dropdown .dropdown-menu li:eq(1)').click();
+
+ expect($rootScope.clickMenu).toHaveBeenCalledWith($rootScope.treedata[0], $rootScope.menuModel[1]);
+ });
});
});