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]); + }); }); });