├── .gitignore ├── main.js ├── test ├── test_abn_tree.jade ├── test_abn_tree.html ├── tests_page.jade ├── tests_page.html ├── bs3_ng115_test_page.html ├── bs2_ng115_test_page.html ├── bs3_ng120_test_page.html ├── bs2_ng120_test_page.html ├── test_page.jade ├── test_page.js └── test_page.coffee ├── package.json ├── temp ├── _template.html └── _directive.coffee ├── src ├── abn_tree_template.jade ├── abn_tree_directive.coffee └── abn_tree_directive.js ├── bower.json ├── LICENSE ├── dist ├── abn_tree.css └── abn_tree_directive.js ├── Gruntfile.coffee ├── Gruntfile.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/abn_tree_directive'); 2 | -------------------------------------------------------------------------------- /test/test_abn_tree.jade: -------------------------------------------------------------------------------- 1 | 2 | // this is the url of my original page, 3 | // so I am forwarding it to the new demo page: 4 | 5 | html 6 | head 7 | meta( 8 | http-equiv="refresh" 9 | content="0; url=http://nickperkinslondon.github.io/angular-bootstrap-nav-tree/test/bs2_ng115_test_page.html" 10 | ) -------------------------------------------------------------------------------- /test/test_abn_tree.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bootstrap-nav-tree", 3 | "version": "0.2.1", 4 | "dependencies": {}, 5 | "main": "main.js", 6 | "devDependencies": { 7 | "grunt": "~0.4.1", 8 | "grunt-contrib-coffee": "~0.7.0", 9 | "grunt-contrib-watch": "~0.5.1", 10 | "grunt-contrib-jade": "~0.8.0", 11 | "grunt-string-replace": "~0.2.7" 12 | }, 13 | "keywords":["angularjs","bootstrap","tree","widget"], 14 | "licence":"MIT" 15 | } 16 | -------------------------------------------------------------------------------- /test/tests_page.jade: -------------------------------------------------------------------------------- 1 | 2 | html 3 | head 4 | link(rel="stylesheet",href="//netdna.bootstrapcdn.com/bootstrap/3.0.1/css/bootstrap.min.css") 5 | 6 | body 7 | 8 | h1 angular-bootstrap-nav-tree 9 | h2 test pages: 10 | br 11 | br 12 | a(href='bs2_ng115_test_page.html') Bootstrap 2 / Angular 1.1.5 13 | br 14 | a(href='bs3_ng115_test_page.html') Bootstrap 3 / Angular 1.1.5 15 | br 16 | a(href='bs2_ng120_test_page.html') Bootstrap 2 / Angular 1.2.0 17 | br 18 | a(href='bs3_ng120_test_page.html') Bootstrap 3 / Angular 1.2.0 19 | 20 | 21 | -------------------------------------------------------------------------------- /temp/_template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/abn_tree_template.jade: -------------------------------------------------------------------------------- 1 | 2 | ul.nav.nav-list.nav-pills.nav-stacked.abn-tree 3 | 4 | li.abn-tree-row( 5 | ng-repeat='row in tree_rows | filter:{visible:true} track by row.branch.uid' 6 | ng-animate="'abn-tree-animate'" 7 | ng-class="'level-' + {{ row.level }} + (row.branch.selected ? ' active':'') + ' ' +row.classes.join(' ')" 8 | ) 9 | 10 | a(ng-click="user_clicks_branch(row.branch)") 11 | 12 | i.indented.tree-icon( 13 | ng-class="row.tree_icon" 14 | ng-click="row.branch.expanded = !row.branch.expanded" 15 | ) 16 | 17 | span.indented.tree-label {{ row.label }} 18 | -------------------------------------------------------------------------------- /test/tests_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

angular-bootstrap-nav-tree

8 |

test pages:

9 |
10 |
11 | Bootstrap 2 / Angular 1.1.5 12 |
13 | Bootstrap 3 / Angular 1.1.5 14 |
15 | Bootstrap 2 / Angular 1.2.0 16 |
17 | Bootstrap 3 / Angular 1.2.0 18 | 19 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bootstrap-nav-tree", 3 | "version": "0.2.0", 4 | "description":"A Tree Control for AngularJS, using CSS animation and Bootstrap style", 5 | "main":["dist/abn_tree_directive.js","dist/abn_tree.css"], 6 | "licence":"MIT", 7 | "keywords":["angularjs","bootstrap","tree","widget"], 8 | "authors":["Nick Perkins"], 9 | "homepage" :"https://github.com/nickperkinslondon/angular-bootstrap-nav-tree", 10 | "repository":{ 11 | "type":"git", 12 | "url":"git://github.com/nickperkinslondon/angular-bootstrap-nav-tree" 13 | }, 14 | "ignore": [ 15 | "src", 16 | "temp", 17 | "test", 18 | ".gitignore", 19 | "Gruntfile.coffee", 20 | "Gruntfile.js", 21 | "bower.json", 22 | "package.json", 23 | "README.md" 24 | ], 25 | "dependencies": { 26 | "angular": ">=1.0", 27 | "bootstrap": ">=2.3" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Nick Perkins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/abn_tree.css: -------------------------------------------------------------------------------- 1 | /* 2 | abn-tree.css 3 | 4 | style for the angular-bootstrap-nav-tree 5 | for both Bootstrap 2 and Bootstrap 3 6 | 7 | */ 8 | 9 | 10 | 11 | /* ------------------------------------------ 12 | AngularJS Animations... 13 | 14 | The first selector is for Angular 1.1.5 15 | The second selector is for Angular 1.2.0 16 | 17 | */ 18 | .abn-tree-animate-enter, 19 | li.abn-tree-row.ng-enter { 20 | transition: 200ms linear all; 21 | position: relative; 22 | display: block; 23 | opacity: 0; 24 | max-height:0px; 25 | } 26 | .abn-tree-animate-enter.abn-tree-animate-enter-active, 27 | li.abn-tree-row.ng-enter-active{ 28 | opacity: 1; 29 | max-height:30px; 30 | } 31 | 32 | .abn-tree-animate-leave, 33 | li.abn-tree-row.ng-leave { 34 | transition: 200ms linear all; 35 | position: relative; 36 | display: block; 37 | height:30px; 38 | max-height: 30px; 39 | opacity: 1; 40 | } 41 | .abn-tree-animate-leave.abn-tree-animate-leave-active, 42 | li.abn-tree-row.ng-leave-active { 43 | height: 0px; 44 | max-height:0px; 45 | opacity: 0; 46 | } 47 | 48 | 49 | /* 50 | ------------------------------------------ 51 | Angular 1.2.0 Animation 52 | */ 53 | 54 | 55 | .abn-tree-animate.ng-enter{ 56 | 57 | } 58 | .abn-tree-animate.ng-enter{ 59 | 60 | } 61 | 62 | 63 | 64 | 65 | /* 66 | end animation stuff 67 | ----------------------------------------- 68 | begin normal css stuff 69 | */ 70 | ul.abn-tree li.abn-tree-row { 71 | padding: 0px; 72 | margin:0px; 73 | } 74 | 75 | ul.abn-tree li.abn-tree-row a { 76 | padding: 3px 10px; 77 | } 78 | 79 | ul.abn-tree i.indented { 80 | padding: 2px; 81 | } 82 | 83 | .abn-tree { 84 | cursor: pointer; 85 | } 86 | ul.nav.abn-tree .level-1 .indented { 87 | position: relative; 88 | left: 0px; 89 | } 90 | ul.nav.abn-tree .level-2 .indented { 91 | position: relative; 92 | left: 20px; 93 | } 94 | ul.nav.abn-tree .level-3 .indented { 95 | position: relative; 96 | left: 40px; 97 | } 98 | ul.nav.abn-tree .level-4 .indented { 99 | position: relative; 100 | left: 60px; 101 | } 102 | ul.nav.abn-tree .level-5 .indented { 103 | position: relative; 104 | left: 80px; 105 | } 106 | ul.nav.abn-tree .level-6 .indented { 107 | position: relative; 108 | left: 100px; 109 | } 110 | ul.nav.nav-list.abn-tree .level-7 .indented { 111 | position: relative; 112 | left: 120px; 113 | } 114 | ul.nav.nav-list.abn-tree .level-8 .indented { 115 | position: relative; 116 | left: 140px; 117 | } 118 | ul.nav.nav-list.abn-tree .level-9 .indented { 119 | position: relative; 120 | left: 160px; 121 | } 122 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (grunt)-> 3 | 4 | grunt.initConfig 5 | 6 | pkg: grunt.file.readJSON 'package.json' 7 | 8 | jade: 9 | dev: 10 | options: 11 | pretty:true 12 | files: 13 | 'temp/_template.html':'src/abn_tree_template.jade' 14 | 'test/tests_page.html':'test/tests_page.jade' 15 | 16 | # 17 | # Generate 4 test pages, for all combinations of: 18 | # 19 | # Bootstrap 2 and 3 20 | # Angular 1.1.5 and 1.2.0 21 | # 22 | 23 | bs2_ng115_test_page: 24 | files: 25 | 'test/bs2_ng115_test_page.html':'test/test_page.jade' 26 | options: 27 | pretty:true 28 | data: 29 | bs:"2" 30 | ng:"1.1.5" 31 | 32 | bs3_ng115_test_page: 33 | files: 34 | 'test/bs3_ng115_test_page.html':'test/test_page.jade' 35 | options: 36 | pretty:true 37 | data: 38 | bs:"3" 39 | ng:"1.1.5" 40 | 41 | bs2_ng120_test_page: 42 | files: 43 | 'test/bs2_ng120_test_page.html':'test/test_page.jade' 44 | options: 45 | pretty:true 46 | data: 47 | bs:"2" 48 | ng:"1.2.12" 49 | 50 | bs3_ng120_test_page: 51 | files: 52 | 'test/bs3_ng120_test_page.html':'test/test_page.jade' 53 | options: 54 | pretty:true 55 | data: 56 | bs:"3" 57 | ng:"1.2.12" 58 | 59 | 60 | "string-replace": 61 | dev: 62 | files: 63 | # 64 | # substitute the "template html" into an intermediate coffeescript file 65 | # ( to take advantage of triple-quoted strings ) 66 | # 67 | 'temp/_directive.coffee':'src/abn_tree_directive.coffee' 68 | options: 69 | replacements:[ 70 | pattern: "{html}" 71 | replacement_old: "

i am the replacement!

" 72 | replacement: (match, p1, offset, string)-> 73 | grunt.file.read('temp/_template.html') 74 | ] 75 | 76 | coffee: 77 | dev: 78 | options: 79 | bare:false 80 | files: 81 | # 82 | # the _temp.coffee file has the "template html" baked-in by Grunt 83 | # 84 | 'dist/abn_tree_directive.js':'temp/_directive.coffee' 85 | 'test/test_page.js':'test/test_page.coffee' 86 | 87 | 88 | 89 | 90 | watch: 91 | 92 | jade: 93 | files:['**/*.jade'] 94 | tasks:['jade','string-replace'] 95 | options: 96 | livereload:true 97 | 98 | css: 99 | files:['**/*.css'] 100 | tasks:[] 101 | options: 102 | livereload:true 103 | 104 | coffee: 105 | files:['**/*.coffee'] 106 | tasks:['jade','string-replace','coffee'] 107 | options: 108 | livereload:true 109 | 110 | grunt.loadNpmTasks 'grunt-contrib-jade' 111 | grunt.loadNpmTasks 'grunt-contrib-coffee' 112 | grunt.loadNpmTasks 'grunt-contrib-watch' 113 | grunt.loadNpmTasks 'grunt-string-replace' 114 | 115 | 116 | grunt.registerTask 'default', ['jade','string-replace','coffee','watch'] 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | module.exports = function(grunt) { 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | jade: { 6 | dev: { 7 | options: { 8 | pretty: true 9 | }, 10 | files: { 11 | 'temp/_template.html': 'src/abn_tree_template.jade', 12 | 'test/tests_page.html': 'test/tests_page.jade' 13 | } 14 | }, 15 | bs2_ng115_test_page: { 16 | files: { 17 | 'test/bs2_ng115_test_page.html': 'test/test_page.jade' 18 | }, 19 | options: { 20 | pretty: true, 21 | data: { 22 | bs: "2", 23 | ng: "1.1.5" 24 | } 25 | } 26 | }, 27 | bs3_ng115_test_page: { 28 | files: { 29 | 'test/bs3_ng115_test_page.html': 'test/test_page.jade' 30 | }, 31 | options: { 32 | pretty: true, 33 | data: { 34 | bs: "3", 35 | ng: "1.1.5" 36 | } 37 | } 38 | }, 39 | bs2_ng120_test_page: { 40 | files: { 41 | 'test/bs2_ng120_test_page.html': 'test/test_page.jade' 42 | }, 43 | options: { 44 | pretty: true, 45 | data: { 46 | bs: "2", 47 | ng: "1.2.12" 48 | } 49 | } 50 | }, 51 | bs3_ng120_test_page: { 52 | files: { 53 | 'test/bs3_ng120_test_page.html': 'test/test_page.jade' 54 | }, 55 | options: { 56 | pretty: true, 57 | data: { 58 | bs: "3", 59 | ng: "1.2.12" 60 | } 61 | } 62 | } 63 | }, 64 | "string-replace": { 65 | dev: { 66 | files: { 67 | 'temp/_directive.coffee': 'src/abn_tree_directive.coffee' 68 | }, 69 | options: { 70 | replacements: [ 71 | { 72 | pattern: "{html}", 73 | replacement_old: "

i am the replacement!

", 74 | replacement: function(match, p1, offset, string) { 75 | return grunt.file.read('temp/_template.html'); 76 | } 77 | } 78 | ] 79 | } 80 | } 81 | }, 82 | coffee: { 83 | dev: { 84 | options: { 85 | bare: false 86 | }, 87 | files: { 88 | 'dist/abn_tree_directive.js': 'temp/_directive.coffee', 89 | 'test/test_page.js': 'test/test_page.coffee' 90 | } 91 | } 92 | }, 93 | watch: { 94 | jade: { 95 | files: ['**/*.jade'], 96 | tasks: ['jade', 'string-replace'], 97 | options: { 98 | livereload: true 99 | } 100 | }, 101 | css: { 102 | files: ['**/*.css'], 103 | tasks: [], 104 | options: { 105 | livereload: true 106 | } 107 | }, 108 | coffee: { 109 | files: ['**/*.coffee'], 110 | tasks: ['jade', 'string-replace', 'coffee'], 111 | options: { 112 | livereload: true 113 | } 114 | } 115 | } 116 | }); 117 | grunt.loadNpmTasks('grunt-contrib-jade'); 118 | grunt.loadNpmTasks('grunt-contrib-coffee'); 119 | grunt.loadNpmTasks('grunt-contrib-watch'); 120 | grunt.loadNpmTasks('grunt-string-replace'); 121 | return grunt.registerTask('default', ['jade', 'string-replace', 'coffee', 'watch']); 122 | }; 123 | -------------------------------------------------------------------------------- /test/bs3_ng115_test_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

angular-bootstrap-nav-tree

24 | 25 | 26 | 47 | 91 | 92 |
27 |
by Nick Perkins
28 | The code is on Github 29 | 30 | 31 |
32 | 46 |
48 |
49 |

Bootstrap 3

50 |

Angular 1.1.5

51 |
52 | 53 | 54 | 79 | 85 | 88 | 89 |
55 |
56 | 57 |
58 | 59 |
60 |
Test the Tree Control API:
61 |
62 | 63 |
64 | 65 | 66 |
67 | 68 | 69 |
70 | 71 |
72 | 73 | 74 | 75 | 76 |
77 | 78 |
80 |
81 | ...loading... 82 | 83 |
84 |
86 |
{{ output }}
87 |
90 |
93 | 94 | -------------------------------------------------------------------------------- /test/bs2_ng115_test_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

angular-bootstrap-nav-tree

24 | 25 | 26 | 47 | 91 | 92 |
27 |
by Nick Perkins
28 | The code is on Github 29 | 30 | 31 |
32 | 46 |
48 |
49 |

Bootstrap 2

50 |

Angular 1.1.5

51 |
52 | 53 | 54 | 79 | 85 | 88 | 89 |
55 |
56 | 57 |
58 | 59 |
60 |
Test the Tree Control API:
61 |
62 | 63 |
64 | 65 | 66 |
67 | 68 | 69 |
70 | 71 |
72 | 73 | 74 | 75 | 76 |
77 | 78 |
80 |
81 | ...loading... 82 | 83 |
84 |
86 |
{{ output }}
87 |
90 |
93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | angular-bootstrap-nav-tree 2 | ========================== 3 | 4 | This is a Tree directive for Angular JS apps that use Bootstrap CSS. 5 | 6 | example: http://nickperkinslondon.github.io/angular-bootstrap-nav-tree/test/bs3_ng120_test_page.html 7 | 8 | The style is completely Bootstrap because the tree is actually just a Bootstrap "nav" list, with a few changes: Indentation is added, expand/collapse icons are added, and Angular CSS animations are used during expand/collapse. 9 | 10 | The abn-tree now works Bootsrap 2, or Bootstrap 3, and with Angular 1.1.5 or 1.2.0 11 | 12 | The normal Glyphicons work well, but they appear black instead of blue. Alternatively, you can use the Font Awesome icons, which look even better, and match the blue color of the text. 13 | 14 | You can change the icons used by specifying them in html attributes. 15 | 16 | This tree is developed using CoffeeScript and Jade, but you don't need to be using either of those to use this tree -- you just have to be using Angular and Bootsrap. 17 | 18 | 19 | How to use it: 20 | Just include the 2 files from "dist", 21 | 22 | abn_tree_directive.js 23 | abn_tree.css 24 | 25 | Add `'angularBootstrapNavTree'` to your module's list of dependencies. 26 | 27 | Then put an `` directive in your HTML. 28 | ( see the example in "test" ) 29 | 30 | At a miniumum, you must supply `tree-data` : 31 | 32 | 33 | 34 | But there are other attributes to customize the tree: 35 | 36 | 45 | > 46 | 47 | The example uses Font-Awesome 3, but Font-Awsome 4 also works. 48 | Use the following syntax: 49 | 50 | icon-leaf = "fa fa-file" 51 | 52 | ( in general, use spaces to apply multiple classes to icon elements ) 53 | 54 | 55 | The data to create the tree is defined in your controller, and could be as simple as this: 56 | 57 | $scope.my_data = [{ 58 | label: 'Languages', 59 | children: ['Jade','Less','Coffeescript'] 60 | }] 61 | 62 | There is a long-form for elements, in which each node is an object with a "label", and optionally other stuff like "data", and "children". 63 | There is a short-form for listing nodes children (as used for "children" above), where the "children" is just a list of strings. 64 | If you use the short-form for listing elements, then your "on-select" function will have to act based only upon the "branch.label". If you use the 65 | long-form, where is branch is an object, then you can also attach "data" to a branch. 66 | 67 | If you would like to add classes to a certain node, give it an array of classes like so: 68 | 69 | $scope.my_data = [{ 70 | label: 'Languages', 71 | children: ['Jade','Less','Coffeescript'] 72 | classes: ["special", "red"] 73 | }] 74 | 75 | Each element without children, or leaf, is automatically given a leaf class. If you would like to force certain nodes not to be leaves (won't get leaf class and will show expand/collapse icons), set noLeaf to true in a long-form listing like so: 76 | 77 | { 78 | label: 'Coffeescript', 79 | noLeaf: true 80 | } 81 | 82 | You can supply a single default "on-select" function for the whole tree -- it will be called whenever a branch is selected: 83 | 84 | $scope.my_tree_hander = function(branch){...} 85 | 86 | 87 | Or, you can put a custom "on-select" function on an individual branch: 88 | 89 | $scope.my_data = [{ 90 | label: 'Languages', 91 | onSelect: function(branch){...}, 92 | children: ['Jade','Less','Coffeescript'] 93 | }] 94 | 95 | Each branch can have a "data" element which you can use to hold whatever data you want. It is not touched by the tree, and it is available to your "on-select" function as "branch.data". In the example, in the "test" folder, this technique is used in "my_tree_handler" to add extra info to "Dog","Cat", and "Hippo". 96 | 97 | Warning: If you attach extra attributes directly to a branch (instead of to "branch.data"), they could conflict with the internal workings of the tree, which adds branch attributes at runtime, like "expanded" and "selected". 98 | 99 | Tree-Control API: 100 | If you pass an empty object to the tree as "tree-control", it will be populated with a set of functions for navigating and controlling the tree. See the example page for a demo... 101 | -------------------------------------------------------------------------------- /test/bs3_ng120_test_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

angular-bootstrap-nav-tree

25 | 26 | 27 | 48 | 92 | 93 |
28 |
by Nick Perkins
29 | The code is on Github 30 | 31 | 32 |
33 | 47 |
49 |
50 |

Bootstrap 3

51 |

Angular 1.2.12

52 |
53 | 54 | 55 | 80 | 86 | 89 | 90 |
56 |
57 | 58 |
59 | 60 |
61 |
Test the Tree Control API:
62 |
63 | 64 |
65 | 66 | 67 |
68 | 69 | 70 |
71 | 72 |
73 | 74 | 75 | 76 | 77 |
78 | 79 |
81 |
82 | ...loading... 83 | 84 |
85 |
87 |
{{ output }}
88 |
91 |
94 | 95 | -------------------------------------------------------------------------------- /test/bs2_ng120_test_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |

angular-bootstrap-nav-tree

25 | 26 | 27 | 48 | 92 | 93 |
28 |
by Nick Perkins
29 | The code is on Github 30 | 31 | 32 |
33 | 47 |
49 |
50 |

Bootstrap 2

51 |

Angular 1.2.12

52 |
53 | 54 | 55 | 80 | 86 | 89 | 90 |
56 |
57 | 58 |
59 | 60 |
61 |
Test the Tree Control API:
62 |
63 | 64 |
65 | 66 | 67 |
68 | 69 | 70 |
71 | 72 |
73 | 74 | 75 | 76 | 77 |
78 | 79 |
81 |
82 | ...loading... 83 | 84 |
85 |
87 |
{{ output }}
88 |
91 |
94 | 95 | -------------------------------------------------------------------------------- /test/test_page.jade: -------------------------------------------------------------------------------- 1 | !!!5 2 | html(ng-app='AbnTest') 3 | head 4 | 5 | 6 | /// 7 | // Bootstrap 2 or Bootstrap 3 ? 8 | // 9 | if bs == "2" 10 | link(rel="stylesheet",href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/css/bootstrap-combined.min.css") 11 | if bs == "3" 12 | link(rel="stylesheet",href="//netdna.bootstrapcdn.com/bootstrap/3.0.1/css/bootstrap.min.css") 13 | 14 | 15 | // 16 | // Angular 1.1.5 or 1.2.12 ? 17 | // 18 | if ng == "1.1.5" 19 | script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js") 20 | if ng == "1.2.12" 21 | script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.js") 22 | script(src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular-animate.js") 23 | 24 | 25 | // Font Awesome (optional) 26 | //- link(href="//netdna.bootstrapcdn.com/font-awesome/3.2.1/css/font-awesome.css",rel="stylesheet") 27 | //- link(href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css",rel="stylesheet") 28 | 29 | 30 | // Live Reload ( for development ) 31 | //- script(src="http://localhost:35729/livereload.js") 32 | 33 | 34 | /// 35 | // abn-tree ( the thing we are testing ) 36 | // 37 | script(src="../dist/abn_tree_directive.js") 38 | link(rel='stylesheet',href='../dist/abn_tree.css') 39 | 40 | 41 | // js for this test page: 42 | script(src="test_page.js") 43 | 44 | 45 | 46 | body( 47 | ng-controller='AbnTestController' 48 | style="margin:20px" 49 | ) 50 | 51 | h2 angular-bootstrap-nav-tree 52 | table 53 | tr 54 | td(style='vertical-align:top;padding:20px') 55 | //- Left Side 56 | 57 | h6 by Nick Perkins 58 | a(href='https://github.com/nickperkinslondon/angular-bootstrap-nav-tree') The code is on Github 59 | 60 | 61 | // All 4 of these pages are generated ( by Grunt ) 62 | // from this one jade file 63 | hr 64 | 65 | ul.nav.nav-list.list-group 66 | li 67 | a(href='bs2_ng115_test_page.html') Bootstrap 2 / Angular 1.1 68 | li 69 | a(href='bs3_ng115_test_page.html') Bootstrap 3 / Angular 1.1 70 | li 71 | a(href='bs2_ng120_test_page.html') Bootstrap 2 / Angular 1.2 72 | li 73 | a(href='bs3_ng120_test_page.html') Bootstrap 3 / Angular 1.2 74 | 75 | td(style='vertical-align:top') 76 | //- Right Side 77 | 78 | 79 | hr 80 | h4 Bootstrap #{bs} 81 | h4 Angular #{ng} 82 | hr 83 | 84 | 85 | table 86 | tr 87 | td(style='vertical-align:top') 88 | 89 | br 90 | button.btn.btn-default.btn-sm( 91 | ng-click="try_changing_the_tree_data()" 92 | ) Change The Tree Definition 93 | 94 | br 95 | button.btn.btn-default.btn-sm( 96 | ng-click="try_async_load()" 97 | ) Load Tree Data Asynchronously 98 | 99 | hr 100 | h5 Test the Tree Control API: 101 | 102 | br 103 | button.btn.btn-default.btn-sm( 104 | ng-click='my_tree.select_first_branch()' 105 | ) First Branch 106 | br 107 | button.btn.btn-default.btn-sm( 108 | ng-click='my_tree.select_next_sibling()' 109 | ) Next Sibling 110 | 111 | button.btn.btn-default.btn-sm( 112 | ng-click='my_tree.select_prev_sibling()' 113 | ) Prev Sibling 114 | 115 | br 116 | button.btn.btn-default.btn-sm( 117 | ng-click='my_tree.select_next_branch()' 118 | ) Next Branch 119 | 120 | 121 | button.btn.btn-default.btn-sm( 122 | ng-click='my_tree.select_prev_branch()' 123 | ) Prev Branch 124 | 125 | br 126 | button.btn.btn-default.btn-sm( 127 | ng-click='my_tree.select_parent_branch()' 128 | ) Parent 129 | 130 | hr 131 | button.btn.btn-default.btn-sm( 132 | ng-click='my_tree.expand_branch()' 133 | ) Expand 134 | 135 | button.btn.btn-default.btn-sm( 136 | ng-click='my_tree.collapse_branch()' 137 | ) Collapse 138 | 139 | button.btn.btn-default.btn-sm( 140 | ng-click='my_tree.expand_all()' 141 | ) Expand All 142 | 143 | button.btn.btn-default.btn-sm( 144 | ng-click='my_tree.collapse_all()' 145 | ) Collapse All 146 | 147 | hr 148 | button.btn.btn-default.btn-sm( 149 | ng-click='try_adding_a_branch()' 150 | ) Add Branch 151 | 152 | 153 | 154 | td(style='vertical-align:top') 155 | div( 156 | style='width:250px;margin-left:100px;background:whitesmoke;border:1px solid lightgray;border-radius:5px;' 157 | ) 158 | 159 | span(ng-if='doing_async') ...loading... 160 | 161 | abn-tree( 162 | tree-data = "my_data" 163 | tree-control = "my_tree" 164 | on-select = "my_tree_handler(branch)" 165 | expand-level = "2" 166 | initial-selection = "Granny Smith" 167 | ) 168 | 169 | td(style="padding:20px;vertical-align:top;") 170 | .alert.alert-warning( 171 | style="width:300px" 172 | ) {{ output }} 173 | -------------------------------------------------------------------------------- /test/test_page.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var app, deps; 3 | 4 | deps = ['angularBootstrapNavTree']; 5 | 6 | if (angular.version.full.indexOf("1.2") >= 0) { 7 | deps.push('ngAnimate'); 8 | } 9 | 10 | app = angular.module('AbnTest', deps); 11 | 12 | app.controller('AbnTestController', function($scope, $timeout) { 13 | var apple_selected, tree, treedata_avm, treedata_geography; 14 | $scope.my_tree_handler = function(branch) { 15 | var _ref; 16 | $scope.output = "You selected: " + branch.label; 17 | if ((_ref = branch.data) != null ? _ref.description : void 0) { 18 | return $scope.output += '(' + branch.data.description + ')'; 19 | } 20 | }; 21 | apple_selected = function(branch) { 22 | return $scope.output = "APPLE! : " + branch.label; 23 | }; 24 | treedata_avm = [ 25 | { 26 | label: 'Animal', 27 | children: [ 28 | { 29 | label: 'Dog', 30 | data: { 31 | description: "man's best friend" 32 | } 33 | }, { 34 | label: 'Cat', 35 | data: { 36 | description: "Felis catus" 37 | } 38 | }, { 39 | label: 'Hippopotamus', 40 | data: { 41 | description: "hungry, hungry" 42 | } 43 | }, { 44 | label: 'Chicken', 45 | children: ['White Leghorn', 'Rhode Island Red', 'Jersey Giant'] 46 | } 47 | ] 48 | }, { 49 | label: 'Vegetable', 50 | data: { 51 | definition: "A plant or part of a plant used as food, typically as accompaniment to meat or fish, such as a cabbage, potato, carrot, or bean.", 52 | data_can_contain_anything: true 53 | }, 54 | onSelect: function(branch) { 55 | return $scope.output = "Vegetable: " + branch.data.definition; 56 | }, 57 | children: [ 58 | { 59 | label: 'Oranges' 60 | }, { 61 | label: 'Apples', 62 | children: [ 63 | { 64 | label: 'Granny Smith', 65 | onSelect: apple_selected 66 | }, { 67 | label: 'Red Delicous', 68 | onSelect: apple_selected 69 | }, { 70 | label: 'Fuji', 71 | onSelect: apple_selected 72 | } 73 | ] 74 | } 75 | ] 76 | }, { 77 | label: 'Mineral', 78 | children: [ 79 | { 80 | label: 'Rock', 81 | children: ['Igneous', 'Sedimentary', 'Metamorphic'] 82 | }, { 83 | label: 'Metal', 84 | children: ['Aluminum', 'Steel', 'Copper'] 85 | }, { 86 | label: 'Plastic', 87 | children: [ 88 | { 89 | label: 'Thermoplastic', 90 | children: ['polyethylene', 'polypropylene', 'polystyrene', ' polyvinyl chloride'] 91 | }, { 92 | label: 'Thermosetting Polymer', 93 | children: ['polyester', 'polyurethane', 'vulcanized rubber', 'bakelite', 'urea-formaldehyde'] 94 | } 95 | ] 96 | } 97 | ] 98 | } 99 | ]; 100 | treedata_geography = [ 101 | { 102 | label: 'North America', 103 | children: [ 104 | { 105 | label: 'Canada', 106 | children: ['Toronto', 'Vancouver'] 107 | }, { 108 | label: 'USA', 109 | children: ['New York', 'Los Angeles'] 110 | }, { 111 | label: 'Mexico', 112 | children: ['Mexico City', 'Guadalajara'] 113 | } 114 | ] 115 | }, { 116 | label: 'South America', 117 | children: [ 118 | { 119 | label: 'Venezuela', 120 | children: ['Caracas', 'Maracaibo'] 121 | }, { 122 | label: 'Brazil', 123 | children: ['Sao Paulo', 'Rio de Janeiro'] 124 | }, { 125 | label: 'Argentina', 126 | children: ['Buenos Aires', 'Cordoba'] 127 | } 128 | ] 129 | } 130 | ]; 131 | $scope.my_data = treedata_avm; 132 | $scope.try_changing_the_tree_data = function() { 133 | if ($scope.my_data === treedata_avm) { 134 | return $scope.my_data = treedata_geography; 135 | } else { 136 | return $scope.my_data = treedata_avm; 137 | } 138 | }; 139 | $scope.my_tree = tree = {}; 140 | $scope.try_async_load = function() { 141 | $scope.my_data = []; 142 | $scope.doing_async = true; 143 | return $timeout(function() { 144 | if (Math.random() < 0.5) { 145 | $scope.my_data = treedata_avm; 146 | } else { 147 | $scope.my_data = treedata_geography; 148 | } 149 | $scope.doing_async = false; 150 | return tree.expand_all(); 151 | }, 1000); 152 | }; 153 | return $scope.try_adding_a_branch = function() { 154 | var b; 155 | b = tree.get_selected_branch(); 156 | return tree.add_branch(b, { 157 | label: 'New Branch', 158 | data: { 159 | something: 42, 160 | "else": 43 161 | } 162 | }); 163 | }; 164 | }); 165 | 166 | }).call(this); 167 | -------------------------------------------------------------------------------- /test/test_page.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | deps = ['angularBootstrapNavTree'] 4 | if angular.version.full.indexOf("1.2")>=0 5 | deps.push('ngAnimate') 6 | 7 | 8 | app = angular.module 'AbnTest', deps 9 | app.controller 'AbnTestController',($scope,$timeout)-> 10 | 11 | # 12 | # a default "on-select" handler can be specified 13 | # for the tree ( as attribute "on-select" ) 14 | # 15 | $scope.my_tree_handler = (branch)-> 16 | $scope.output = "You selected: "+branch.label 17 | if branch.data?.description 18 | $scope.output += '('+branch.data.description+')' 19 | # 20 | # This example handler just sets "output", 21 | # ...but your handler could do anything here... 22 | # 23 | 24 | 25 | # 26 | # Each branch can define an "on-select" handler, 27 | # which will be called instead of the default handler 28 | # 29 | 30 | # 31 | # a single handler can be used on several branches, like this: 32 | # 33 | apple_selected = (branch)-> 34 | $scope.output = "APPLE! : "+branch.label 35 | # 36 | # ( your handler can do anything here ) 37 | # 38 | 39 | 40 | # 41 | # Example TREE DATA : Animal,Vegetable,Mineral 42 | # 43 | # Each branch can have the following attributes: 44 | # 45 | # label : the displayed text for the branch 46 | # children : an array of branches ( or array of strings ) 47 | # onSelect : a function to run when branch is selected 48 | # data : a place to put your own data -- can be anything 49 | # 50 | 51 | treedata_avm = [ 52 | label:'Animal' 53 | children:[ 54 | label:'Dog' 55 | data: 56 | # 57 | # "data" is yours -- put anything in here 58 | # you can read it back in your on-select handler 59 | # as "branch.data" 60 | # 61 | description:"man's best friend" 62 | , 63 | label:'Cat' 64 | data: 65 | description:"Felis catus" 66 | , 67 | label:'Hippopotamus' 68 | data: 69 | description:"hungry, hungry" 70 | , 71 | label:'Chicken' 72 | children:['White Leghorn','Rhode Island Red','Jersey Giant'] 73 | ] 74 | , 75 | 76 | label:'Vegetable' 77 | data: 78 | definition:"A plant or part of a plant used as food, typically as accompaniment to meat or fish, such as a cabbage, potato, carrot, or bean." 79 | data_can_contain_anything:true 80 | 81 | onSelect:(branch)-> 82 | # special "on-select" function for this branch 83 | $scope.output = "Vegetable: "+branch.data.definition 84 | 85 | 86 | children:[ 87 | label:'Oranges' 88 | , 89 | label:'Apples' 90 | children:[ 91 | label:'Granny Smith' 92 | onSelect:apple_selected 93 | , 94 | label:'Red Delicous' 95 | onSelect:apple_selected 96 | , 97 | label:'Fuji' 98 | onSelect:apple_selected 99 | ] 100 | ] 101 | , 102 | label:'Mineral' 103 | children:[ 104 | label:'Rock' 105 | # children can be simply a list of strings 106 | # if you are in a hurry 107 | children:['Igneous','Sedimentary','Metamorphic'] 108 | , 109 | label:'Metal' 110 | children:['Aluminum','Steel','Copper'] 111 | , 112 | label:'Plastic' 113 | children:[ 114 | label:'Thermoplastic' 115 | children:['polyethylene', 'polypropylene', 'polystyrene',' polyvinyl chloride'] 116 | , 117 | label:'Thermosetting Polymer' 118 | children:['polyester','polyurethane','vulcanized rubber','bakelite','urea-formaldehyde'] 119 | , 120 | ] 121 | ] 122 | ] 123 | 124 | treedata_geography = [ 125 | label:'North America' 126 | children:[ 127 | label:'Canada' 128 | children:['Toronto','Vancouver'] 129 | , 130 | label:'USA' 131 | children:['New York','Los Angeles'] 132 | , 133 | label:'Mexico' 134 | children:['Mexico City','Guadalajara'] 135 | ] 136 | , 137 | label:'South America' 138 | children:[ 139 | label:'Venezuela' 140 | children:['Caracas','Maracaibo'] 141 | , 142 | label:'Brazil' 143 | children:['Sao Paulo','Rio de Janeiro'] 144 | , 145 | label:'Argentina' 146 | children:['Buenos Aires','Cordoba'] 147 | ] 148 | ] 149 | 150 | 151 | $scope.my_data = treedata_avm 152 | $scope.try_changing_the_tree_data = ()-> 153 | # 154 | # switch between 2 sets of "treedata" 155 | # 156 | if $scope.my_data is treedata_avm 157 | $scope.my_data = treedata_geography 158 | else 159 | $scope.my_data = treedata_avm 160 | 161 | 162 | # 163 | # TREE-CONTROL: the API for controlling the tree 164 | # 165 | $scope.my_tree = tree = {} 166 | # just create an empty object, and pass it to the abn-tree as "tree-control" 167 | # ...it will be populated with Tree API functions 168 | 169 | 170 | $scope.try_async_load = ()-> 171 | $scope.my_data = [] 172 | $scope.doing_async = true 173 | $timeout -> 174 | if Math.random() < 0.5 175 | $scope.my_data = treedata_avm 176 | else 177 | $scope.my_data = treedata_geography 178 | $scope.doing_async = false 179 | tree.expand_all() 180 | ,1000 181 | 182 | $scope.try_adding_a_branch = -> 183 | b = tree.get_selected_branch() 184 | tree.add_branch b, 185 | label:'New Branch' 186 | data: 187 | something:42 188 | else:43 189 | 190 | 191 | -------------------------------------------------------------------------------- /src/abn_tree_directive.coffee: -------------------------------------------------------------------------------- 1 | module = angular.module 'angularBootstrapNavTree',[] 2 | 3 | module.directive 'abnTree',['$timeout',($timeout)-> 4 | restrict:'E' 5 | 6 | #templateUrl: '../dist/abn_tree_template.html' # <--- another way to do this 7 | 8 | template: """{html}""" # will be replaced by Grunt, during build, with the actual Template HTML 9 | replace:true 10 | scope: 11 | treeData:'=' 12 | onSelect:'&' 13 | initialSelection:'@' 14 | treeControl:'=' 15 | 16 | link:(scope,element,attrs)-> 17 | 18 | 19 | error = (s) -> 20 | console.log 'ERROR:'+s 21 | debugger 22 | return undefined 23 | 24 | 25 | # default values ( Font-Awesome 3 or 4 or Glyphicons ) 26 | attrs.iconExpand ?= 'icon-plus glyphicon glyphicon-plus fa fa-plus' 27 | attrs.iconCollapse ?= 'icon-minus glyphicon glyphicon-minus fa fa-minus' 28 | attrs.iconLeaf ?= 'icon-file glyphicon glyphicon-file fa fa-file' 29 | 30 | attrs.expandLevel ?= '3' 31 | 32 | expand_level = parseInt attrs.expandLevel,10 33 | 34 | # check args 35 | if !scope.treeData 36 | alert 'no treeData defined for the tree!' 37 | return 38 | 39 | if !scope.treeData.length? 40 | if scope.treeData.label? 41 | scope.treeData = [ scope.treeData ] 42 | else 43 | alert 'treeData should be an array of root branches' 44 | return 45 | 46 | 47 | # 48 | # internal utilities... 49 | # 50 | for_each_branch = (f)-> 51 | do_f = (branch,level)-> 52 | f(branch,level) 53 | if branch.children? 54 | for child in branch.children 55 | do_f(child,level + 1) 56 | for root_branch in scope.treeData 57 | do_f(root_branch,1) 58 | 59 | 60 | 61 | 62 | 63 | # 64 | # only one branch can be selected at a time 65 | # 66 | selected_branch = null 67 | select_branch = (branch)-> 68 | 69 | if not branch 70 | if selected_branch? 71 | selected_branch.selected = false 72 | selected_branch = null 73 | return 74 | 75 | if branch isnt selected_branch 76 | if selected_branch? 77 | selected_branch.selected = false 78 | 79 | branch.selected = true 80 | selected_branch = branch 81 | expand_all_parents(branch) 82 | 83 | 84 | # 85 | # check: 86 | # 1) branch.onSelect 87 | # 2) tree.onSelect 88 | # 89 | if branch.onSelect? 90 | # 91 | # use $timeout 92 | # so that the branch becomes fully selected 93 | # ( and highlighted ) 94 | # before calling the "onSelect" function. 95 | # 96 | $timeout -> 97 | branch.onSelect(branch) 98 | else 99 | if scope.onSelect? 100 | $timeout -> 101 | scope.onSelect({branch:branch}) 102 | 103 | 104 | scope.user_clicks_branch = (branch)-> 105 | if branch isnt selected_branch 106 | select_branch(branch) 107 | 108 | 109 | get_parent = (child)-> 110 | parent = undefined 111 | if child.parent_uid 112 | for_each_branch (b)-> 113 | if b.uid == child.parent_uid 114 | parent = b 115 | return parent 116 | 117 | for_all_ancestors = ( child, fn )-> 118 | parent = get_parent( child ) 119 | if parent? 120 | fn( parent ) 121 | for_all_ancestors( parent, fn ) 122 | 123 | 124 | expand_all_parents = ( child )-> 125 | for_all_ancestors child, (b)-> 126 | b.expanded = true 127 | 128 | 129 | # 130 | # To make the Angular rendering simpler, 131 | # ( and to avoid recursive templates ) 132 | # we transform the TREE of data into a LIST of data. 133 | # ( "tree_rows" ) 134 | # 135 | # We do this whenever data in the tree changes. 136 | # The tree itself is bound to this list. 137 | # 138 | # Children of un-expanded parents are included, 139 | # but are set to "visible:false" 140 | # ( and then they filtered out during rendering ) 141 | # 142 | scope.tree_rows = [] 143 | on_treeData_change = -> 144 | 145 | #console.log 'tree-data-change!' 146 | 147 | # 148 | # if "children" is just a list of strings... 149 | # ...change them into objects: 150 | # 151 | for_each_branch (branch)-> 152 | if branch.children 153 | if branch.children.length > 0 154 | # don't use Array.map ( old browsers don't have it ) 155 | f = (e)-> 156 | if typeof e == 'string' 157 | label:e 158 | children:[] 159 | else 160 | e 161 | branch.children = ( f(child) for child in branch.children ) 162 | 163 | else 164 | branch.children = [] 165 | 166 | 167 | # give each Branch a UID ( to keep AngularJS happy ) 168 | for_each_branch (b,level)-> 169 | if not b.uid 170 | b.uid = ""+Math.random() 171 | console.log 'UIDs are set.' 172 | 173 | 174 | # set all parents: 175 | for_each_branch (b)-> 176 | if angular.isArray b.children 177 | for child in b.children 178 | child.parent_uid = b.uid 179 | 180 | 181 | scope.tree_rows = [] 182 | 183 | 184 | # 185 | # add_branch_to_list: recursively add one branch 186 | # and all of it's children to the list 187 | # 188 | add_branch_to_list = (level,branch,visible)-> 189 | 190 | if not branch.expanded? 191 | branch.expanded = false 192 | 193 | if not branch.classes? 194 | branch.classes = [] 195 | 196 | # 197 | # icons can be Bootstrap or Font-Awesome icons: 198 | # they will be rendered like: 199 | # 200 | # 201 | if not branch.noLeaf and (not branch.children or branch.children.length == 0) 202 | tree_icon = attrs.iconLeaf 203 | branch.classes.push "leaf" if "leaf" not in branch.classes 204 | else 205 | if branch.expanded 206 | tree_icon = attrs.iconCollapse 207 | else 208 | tree_icon = attrs.iconExpand 209 | 210 | 211 | # 212 | # append to the list of "Tree Row" objects: 213 | # 214 | scope.tree_rows.push 215 | level : level 216 | branch : branch 217 | label : branch.label 218 | classes : branch.classes 219 | tree_icon : tree_icon 220 | visible : visible 221 | 222 | # 223 | # recursively add all children of this branch...( at Level+1 ) 224 | # 225 | if branch.children? 226 | for child in branch.children 227 | # 228 | # all branches are added to the list, 229 | # but some are not visible 230 | # ( if parent is collapsed ) 231 | # 232 | child_visible = visible and branch.expanded 233 | add_branch_to_list level+1, child, child_visible 234 | 235 | # 236 | # start with root branches, 237 | # and recursively add all children to the list 238 | # 239 | for root_branch in scope.treeData 240 | add_branch_to_list 1, root_branch, true 241 | 242 | 243 | # 244 | # make sure to do a "deep watch" on the tree data 245 | # ( by passing "true" as the third arg ) 246 | # 247 | scope.$watch 'treeData',on_treeData_change,true 248 | 249 | 250 | # 251 | # initial-selection="Branch Label" 252 | # if specified, find and select the branch: 253 | # 254 | if attrs.initialSelection? 255 | for_each_branch (b)-> 256 | if b.label == attrs.initialSelection 257 | $timeout -> 258 | select_branch b 259 | 260 | # 261 | # expand to the proper level 262 | # 263 | n = scope.treeData.length 264 | console.log 'num root branches = '+n 265 | for_each_branch (b,level)-> 266 | b.level = level 267 | b.expanded = b.level < expand_level 268 | #b.expanded = false 269 | 270 | 271 | # 272 | # TREE-CONTROL : the API to the Tree 273 | # 274 | # if we have been given an Object for this, 275 | # then we attach all of tree-control functions 276 | # to that given object: 277 | # 278 | if scope.treeControl? 279 | if angular.isObject scope.treeControl 280 | tree = scope.treeControl 281 | 282 | tree.expand_all = -> 283 | for_each_branch (b,level)-> 284 | b.expanded = true 285 | 286 | tree.collapse_all = -> 287 | for_each_branch (b,level)-> 288 | b.expanded = false 289 | 290 | tree.get_first_branch = -> 291 | n = scope.treeData.length 292 | if n > 0 293 | return scope.treeData[0] 294 | 295 | tree.select_first_branch = -> 296 | b = tree.get_first_branch() 297 | tree.select_branch(b) 298 | 299 | tree.get_selected_branch = -> 300 | selected_branch 301 | 302 | tree.get_parent_branch = (b)-> 303 | get_parent(b) 304 | 305 | tree.select_branch = (b)-> 306 | select_branch(b) 307 | b 308 | 309 | tree.get_children = (b)-> 310 | b.children 311 | 312 | tree.select_parent_branch = (b)-> 313 | if not b? 314 | b = tree.get_selected_branch() 315 | if b? 316 | p = tree.get_parent_branch(b) 317 | if p? 318 | tree.select_branch(p) 319 | p 320 | 321 | 322 | tree.add_branch = (parent,new_branch)-> 323 | if parent? 324 | parent.children.push new_branch 325 | parent.expanded = true 326 | else 327 | scope.treeData.push new_branch 328 | #tree.select new_branch 329 | new_branch 330 | 331 | tree.add_root_branch = (new_branch)-> 332 | tree.add_branch null, new_branch 333 | new_branch 334 | 335 | tree.expand_branch = (b)-> 336 | if not b? 337 | b = tree.get_selected_branch() 338 | if b? 339 | b.expanded = true 340 | b 341 | 342 | tree.collapse_branch = (b)-> 343 | b ?= selected_branch 344 | if b? 345 | b.expanded = false 346 | b 347 | 348 | 349 | tree.get_siblings = (b)-> 350 | b ?= selected_branch 351 | if b? 352 | p = tree.get_parent_branch(b) 353 | if p 354 | siblings = p.children 355 | else 356 | siblings = scope.treeData # the root branches 357 | return siblings 358 | 359 | 360 | tree.get_next_sibling = (b)-> 361 | b ?= selected_branch 362 | if b? 363 | siblings = tree.get_siblings(b) 364 | n = siblings.length 365 | i = siblings.indexOf b 366 | if i < n 367 | return siblings[i+1] 368 | 369 | 370 | tree.get_prev_sibling = (b)-> 371 | b ?= selected_branch 372 | siblings = tree.get_siblings(b) 373 | n = siblings.length 374 | i = siblings.indexOf b 375 | if i > 0 376 | return siblings[i-1] 377 | 378 | 379 | tree.select_next_sibling = (b)-> 380 | b ?= selected_branch 381 | if b? 382 | next = tree.get_next_sibling(b) 383 | if next? 384 | tree.select_branch next 385 | 386 | 387 | tree.select_prev_sibling = (b)-> 388 | b ?= selected_branch 389 | if b? 390 | prev = tree.get_prev_sibling(b) 391 | if prev? 392 | tree.select_branch prev 393 | 394 | 395 | tree.get_first_child = (b)-> 396 | b ?= selected_branch 397 | if b? 398 | if b.children?.length > 0 399 | return b.children[0] 400 | 401 | 402 | tree.get_closest_ancestor_next_sibling = (b)-> 403 | next = tree.get_next_sibling(b) 404 | if next? 405 | return next 406 | else 407 | parent = tree.get_parent_branch(b) 408 | return tree.get_closest_ancestor_next_sibling(parent) 409 | 410 | 411 | 412 | 413 | tree.get_next_branch = (b)-> 414 | # 415 | # "next" in the sense of...vertically, from top to bottom 416 | # 417 | # try: 418 | # 1) next sibling 419 | # 2) first child 420 | # 3) parent.get_next() // recursive 421 | # 422 | b ?= selected_branch 423 | if b? 424 | next = tree.get_first_child(b) 425 | if next? 426 | return next 427 | else 428 | next = tree.get_closest_ancestor_next_sibling(b) 429 | return next 430 | 431 | 432 | tree.select_next_branch = (b)-> 433 | b ?= selected_branch 434 | if b? 435 | next = tree.get_next_branch(b) 436 | if next? 437 | tree.select_branch(next) 438 | next 439 | 440 | 441 | tree.last_descendant = (b)-> 442 | if not b? 443 | debugger 444 | n = b.children.length 445 | if n == 0 446 | return b 447 | else 448 | last_child = b.children[n-1] 449 | return tree.last_descendant(last_child) 450 | 451 | 452 | tree.get_prev_branch = (b)-> 453 | b ?= selected_branch 454 | if b? 455 | prev_sibling = tree.get_prev_sibling(b) 456 | if prev_sibling? 457 | return tree.last_descendant prev_sibling 458 | else 459 | parent = tree.get_parent_branch(b) 460 | return parent 461 | 462 | tree.select_prev_branch = (b)-> 463 | b ?= selected_branch 464 | if b? 465 | prev = tree.get_prev_branch(b) 466 | if prev? 467 | tree.select_branch(prev) 468 | return prev 469 | ] 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | -------------------------------------------------------------------------------- /temp/_directive.coffee: -------------------------------------------------------------------------------- 1 | 2 | module = angular.module 'angularBootstrapNavTree',[] 3 | 4 | module.directive 'abnTree',['$timeout',($timeout)-> 5 | restrict:'E' 6 | 7 | #templateUrl: '../dist/abn_tree_template.html' # <--- another way to do this 8 | 9 | template: """ 10 | """ # will be replaced by Grunt, during build, with the actual Template HTML 18 | replace:true 19 | scope: 20 | treeData:'=' 21 | onSelect:'&' 22 | initialSelection:'@' 23 | treeControl:'=' 24 | 25 | link:(scope,element,attrs)-> 26 | 27 | 28 | error = (s) -> 29 | console.log 'ERROR:'+s 30 | debugger 31 | return undefined 32 | 33 | 34 | # default values ( Font-Awesome 3 or 4 or Glyphicons ) 35 | attrs.iconExpand ?= 'icon-plus glyphicon glyphicon-plus fa fa-plus' 36 | attrs.iconCollapse ?= 'icon-minus glyphicon glyphicon-minus fa fa-minus' 37 | attrs.iconLeaf ?= 'icon-file glyphicon glyphicon-file fa fa-file' 38 | 39 | attrs.expandLevel ?= '3' 40 | 41 | expand_level = parseInt attrs.expandLevel,10 42 | 43 | # check args 44 | if !scope.treeData 45 | alert 'no treeData defined for the tree!' 46 | return 47 | 48 | if !scope.treeData.length? 49 | if treeData.label? 50 | scope.treeData = [ treeData ] 51 | else 52 | alert 'treeData should be an array of root branches' 53 | return 54 | 55 | 56 | # 57 | # internal utilities... 58 | # 59 | for_each_branch = (f)-> 60 | do_f = (branch,level)-> 61 | f(branch,level) 62 | if branch.children? 63 | for child in branch.children 64 | do_f(child,level + 1) 65 | for root_branch in scope.treeData 66 | do_f(root_branch,1) 67 | 68 | 69 | 70 | 71 | 72 | # 73 | # only one branch can be selected at a time 74 | # 75 | selected_branch = null 76 | select_branch = (branch)-> 77 | 78 | if not branch 79 | if selected_branch? 80 | selected_branch.selected = false 81 | selected_branch = null 82 | return 83 | 84 | if branch isnt selected_branch 85 | if selected_branch? 86 | selected_branch.selected = false 87 | 88 | branch.selected = true 89 | selected_branch = branch 90 | expand_all_parents(branch) 91 | 92 | 93 | # 94 | # check: 95 | # 1) branch.onSelect 96 | # 2) tree.onSelect 97 | # 98 | if branch.onSelect? 99 | # 100 | # use $timeout 101 | # so that the branch becomes fully selected 102 | # ( and highlighted ) 103 | # before calling the "onSelect" function. 104 | # 105 | $timeout -> 106 | branch.onSelect(branch) 107 | else 108 | if scope.onSelect? 109 | $timeout -> 110 | scope.onSelect({branch:branch}) 111 | 112 | 113 | scope.user_clicks_branch = (branch)-> 114 | if branch isnt selected_branch 115 | select_branch(branch) 116 | 117 | 118 | get_parent = (child)-> 119 | parent = undefined 120 | if child.parent_uid 121 | for_each_branch (b)-> 122 | if b.uid == child.parent_uid 123 | parent = b 124 | return parent 125 | 126 | for_all_ancestors = ( child, fn )-> 127 | parent = get_parent( child ) 128 | if parent? 129 | fn( parent ) 130 | for_all_ancestors( parent, fn ) 131 | 132 | 133 | expand_all_parents = ( child )-> 134 | for_all_ancestors child, (b)-> 135 | b.expanded = true 136 | 137 | 138 | # 139 | # To make the Angular rendering simpler, 140 | # ( and to avoid recursive templates ) 141 | # we transform the TREE of data into a LIST of data. 142 | # ( "tree_rows" ) 143 | # 144 | # We do this whenever data in the tree changes. 145 | # The tree itself is bound to this list. 146 | # 147 | # Children of un-expanded parents are included, 148 | # but are set to "visible:false" 149 | # ( and then they filtered out during rendering ) 150 | # 151 | scope.tree_rows = [] 152 | on_treeData_change = -> 153 | 154 | #console.log 'tree-data-change!' 155 | 156 | # give each Branch a UID ( to keep AngularJS happy ) 157 | for_each_branch (b,level)-> 158 | if not b.uid 159 | b.uid = ""+Math.random() 160 | console.log 'UIDs are set.' 161 | 162 | 163 | # set all parents: 164 | for_each_branch (b)-> 165 | if angular.isArray b.children 166 | for child in b.children 167 | child.parent_uid = b.uid 168 | 169 | 170 | scope.tree_rows = [] 171 | 172 | # 173 | # if "children" is just a list of strings... 174 | # ...change them into objects: 175 | # 176 | for_each_branch (branch)-> 177 | if branch.children 178 | if branch.children.length > 0 179 | # don't use Array.map ( old browsers don't have it ) 180 | f = (e)-> 181 | if typeof e == 'string' 182 | label:e 183 | children:[] 184 | else 185 | e 186 | branch.children = ( f(child) for child in branch.children ) 187 | 188 | else 189 | branch.children = [] 190 | 191 | 192 | # 193 | # add_branch_to_list: recursively add one branch 194 | # and all of it's children to the list 195 | # 196 | add_branch_to_list = (level,branch,visible)-> 197 | 198 | if not branch.expanded? 199 | branch.expanded = false 200 | 201 | # 202 | # icons can be Bootstrap or Font-Awesome icons: 203 | # they will be rendered like: 204 | # 205 | # 206 | if not branch.children or branch.children.length == 0 207 | tree_icon = attrs.iconLeaf 208 | else 209 | if branch.expanded 210 | tree_icon = attrs.iconCollapse 211 | else 212 | tree_icon = attrs.iconExpand 213 | 214 | 215 | # 216 | # append to the list of "Tree Row" objects: 217 | # 218 | scope.tree_rows.push 219 | level : level 220 | branch : branch 221 | label : branch.label 222 | tree_icon : tree_icon 223 | visible : visible 224 | 225 | # 226 | # recursively add all children of this branch...( at Level+1 ) 227 | # 228 | if branch.children? 229 | for child in branch.children 230 | # 231 | # all branches are added to the list, 232 | # but some are not visible 233 | # ( if parent is collapsed ) 234 | # 235 | child_visible = visible and branch.expanded 236 | add_branch_to_list level+1, child, child_visible 237 | 238 | # 239 | # start with root branches, 240 | # and recursively add all children to the list 241 | # 242 | for root_branch in scope.treeData 243 | add_branch_to_list 1, root_branch, true 244 | 245 | 246 | # 247 | # make sure to do a "deep watch" on the tree data 248 | # ( by passing "true" as the third arg ) 249 | # 250 | scope.$watch 'treeData',on_treeData_change,true 251 | 252 | 253 | # 254 | # initial-selection="Branch Label" 255 | # if specified, find and select the branch: 256 | # 257 | if attrs.initialSelection? 258 | for_each_branch (b)-> 259 | if b.label == attrs.initialSelection 260 | $timeout -> 261 | select_branch b 262 | 263 | # 264 | # expand to the proper level 265 | # 266 | n = scope.treeData.length 267 | console.log 'num root branches = '+n 268 | for_each_branch (b,level)-> 269 | b.level = level 270 | b.expanded = b.level < expand_level 271 | #b.expanded = false 272 | 273 | 274 | # 275 | # TREE-CONTROL : the API to the Tree 276 | # 277 | # if we have been given an Object for this, 278 | # then we attach all of tree-control functions 279 | # to that given object: 280 | # 281 | if scope.treeControl? 282 | if angular.isObject scope.treeControl 283 | tree = scope.treeControl 284 | 285 | tree.expand_all = -> 286 | for_each_branch (b,level)-> 287 | b.expanded = true 288 | 289 | tree.collapse_all = -> 290 | for_each_branch (b,level)-> 291 | b.expanded = false 292 | 293 | tree.get_first_branch = -> 294 | n = scope.treeData.length 295 | if n > 0 296 | return scope.treeData[0] 297 | 298 | tree.select_first_branch = -> 299 | b = tree.get_first_branch() 300 | tree.select_branch(b) 301 | 302 | tree.get_selected_branch = -> 303 | selected_branch 304 | 305 | tree.get_parent_branch = (b)-> 306 | get_parent(b) 307 | 308 | tree.select_branch = (b)-> 309 | select_branch(b) 310 | b 311 | 312 | tree.get_children = (b)-> 313 | b.children 314 | 315 | tree.select_parent_branch = (b)-> 316 | if not b? 317 | b = tree.get_selected_branch() 318 | if b? 319 | p = tree.get_parent_branch(b) 320 | if p? 321 | tree.select_branch(p) 322 | p 323 | 324 | 325 | tree.add_branch = (parent,new_branch)-> 326 | if parent? 327 | parent.children.push new_branch 328 | parent.expanded = true 329 | else 330 | scope.treeData.push new_branch 331 | #tree.select new_branch 332 | new_branch 333 | 334 | tree.add_root_branch = (new_branch)-> 335 | tree.add_branch null, new_branch 336 | new_branch 337 | 338 | tree.expand_branch = (b)-> 339 | if not b? 340 | b = tree.get_selected_branch() 341 | if b? 342 | b.expanded = true 343 | b 344 | 345 | tree.collapse_branch = (b)-> 346 | b ?= selected_branch 347 | if b? 348 | b.expanded = false 349 | b 350 | 351 | 352 | tree.get_siblings = (b)-> 353 | b ?= selected_branch 354 | if b? 355 | p = tree.get_parent_branch(b) 356 | if p 357 | siblings = p.children 358 | else 359 | siblings = scope.treeData # the root branches 360 | return siblings 361 | 362 | 363 | tree.get_next_sibling = (b)-> 364 | b ?= selected_branch 365 | if b? 366 | siblings = tree.get_siblings(b) 367 | n = siblings.length 368 | i = siblings.indexOf b 369 | if i < n 370 | return siblings[i+1] 371 | 372 | 373 | tree.get_prev_sibling = (b)-> 374 | b ?= selected_branch 375 | siblings = tree.get_siblings(b) 376 | n = siblings.length 377 | i = siblings.indexOf b 378 | if i > 0 379 | return siblings[i-1] 380 | 381 | 382 | tree.select_next_sibling = (b)-> 383 | b ?= selected_branch 384 | if b? 385 | next = tree.get_next_sibling(b) 386 | if next? 387 | tree.select_branch next 388 | 389 | 390 | tree.select_prev_sibling = (b)-> 391 | b ?= selected_branch 392 | if b? 393 | prev = tree.get_prev_sibling(b) 394 | if prev? 395 | tree.select_branch prev 396 | 397 | 398 | tree.get_first_child = (b)-> 399 | b ?= selected_branch 400 | if b? 401 | if b.children?.length > 0 402 | return b.children[0] 403 | 404 | 405 | tree.get_closest_ancestor_next_sibling = (b)-> 406 | next = tree.get_next_sibling(b) 407 | if next? 408 | return next 409 | else 410 | parent = tree.get_parent_branch(b) 411 | return tree.get_closest_ancestor_next_sibling(parent) 412 | 413 | 414 | 415 | 416 | tree.get_next_branch = (b)-> 417 | # 418 | # "next" in the sense of...vertically, from top to bottom 419 | # 420 | # try: 421 | # 1) next sibling 422 | # 2) first child 423 | # 3) parent.get_next() // recursive 424 | # 425 | b ?= selected_branch 426 | if b? 427 | next = tree.get_first_child(b) 428 | if next? 429 | return next 430 | else 431 | next = tree.get_closest_ancestor_next_sibling(b) 432 | return next 433 | 434 | 435 | tree.select_next_branch = (b)-> 436 | b ?= selected_branch 437 | if b? 438 | next = tree.get_next_branch(b) 439 | if next? 440 | tree.select_branch(next) 441 | next 442 | 443 | 444 | tree.last_descendant = (b)-> 445 | if not b? 446 | debugger 447 | n = b.children.length 448 | if n == 0 449 | return b 450 | else 451 | last_child = b.children[n-1] 452 | return tree.last_descendant(last_child) 453 | 454 | 455 | tree.get_prev_branch = (b)-> 456 | b ?= selected_branch 457 | if b? 458 | prev_sibling = tree.get_prev_sibling(b) 459 | if prev_sibling? 460 | return tree.last_descendant prev_sibling 461 | else 462 | parent = tree.get_parent_branch(b) 463 | return parent 464 | 465 | tree.select_prev_branch = (b)-> 466 | b ?= selected_branch 467 | if b? 468 | prev = tree.get_prev_branch(b) 469 | if prev? 470 | tree.select_branch(prev) 471 | return prev 472 | ] 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | -------------------------------------------------------------------------------- /src/abn_tree_directive.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | var module; 3 | 4 | module = angular.module('angularBootstrapNavTree', []); 5 | 6 | module.directive('abnTree', [ 7 | '$timeout', function($timeout) { 8 | return { 9 | restrict: 'E', 10 | template: "{html}", 11 | replace: true, 12 | scope: { 13 | treeData: '=', 14 | onSelect: '&', 15 | initialSelection: '@', 16 | treeControl: '=' 17 | }, 18 | link: function(scope, element, attrs) { 19 | var error, expand_all_parents, expand_level, for_all_ancestors, for_each_branch, get_parent, n, on_treeData_change, select_branch, selected_branch, tree; 20 | error = function(s) { 21 | console.log('ERROR:' + s); 22 | debugger; 23 | return void 0; 24 | }; 25 | if (attrs.iconExpand == null) { 26 | attrs.iconExpand = 'icon-plus glyphicon glyphicon-plus fa fa-plus'; 27 | } 28 | if (attrs.iconCollapse == null) { 29 | attrs.iconCollapse = 'icon-minus glyphicon glyphicon-minus fa fa-minus'; 30 | } 31 | if (attrs.iconLeaf == null) { 32 | attrs.iconLeaf = 'icon-file glyphicon glyphicon-file fa fa-file'; 33 | } 34 | if (attrs.expandLevel == null) { 35 | attrs.expandLevel = '3'; 36 | } 37 | expand_level = parseInt(attrs.expandLevel, 10); 38 | if (!scope.treeData) { 39 | alert('no treeData defined for the tree!'); 40 | return; 41 | } 42 | if (scope.treeData.length == null) { 43 | if (treeData.label != null) { 44 | scope.treeData = [treeData]; 45 | } else { 46 | alert('treeData should be an array of root branches'); 47 | return; 48 | } 49 | } 50 | for_each_branch = function(f) { 51 | var do_f, root_branch, _i, _len, _ref, _results; 52 | do_f = function(branch, level) { 53 | var child, _i, _len, _ref, _results; 54 | f(branch, level); 55 | if (branch.children != null) { 56 | _ref = branch.children; 57 | _results = []; 58 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 59 | child = _ref[_i]; 60 | _results.push(do_f(child, level + 1)); 61 | } 62 | return _results; 63 | } 64 | }; 65 | _ref = scope.treeData; 66 | _results = []; 67 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 68 | root_branch = _ref[_i]; 69 | _results.push(do_f(root_branch, 1)); 70 | } 71 | return _results; 72 | }; 73 | selected_branch = null; 74 | select_branch = function(branch) { 75 | if (!branch) { 76 | if (selected_branch != null) { 77 | selected_branch.selected = false; 78 | } 79 | selected_branch = null; 80 | return; 81 | } 82 | if (branch !== selected_branch) { 83 | if (selected_branch != null) { 84 | selected_branch.selected = false; 85 | } 86 | branch.selected = true; 87 | selected_branch = branch; 88 | expand_all_parents(branch); 89 | if (branch.onSelect != null) { 90 | return $timeout(function() { 91 | return branch.onSelect(branch); 92 | }); 93 | } else { 94 | if (scope.onSelect != null) { 95 | return $timeout(function() { 96 | return scope.onSelect({ 97 | branch: branch 98 | }); 99 | }); 100 | } 101 | } 102 | } 103 | }; 104 | scope.user_clicks_branch = function(branch) { 105 | if (branch !== selected_branch) { 106 | return select_branch(branch); 107 | } 108 | }; 109 | get_parent = function(child) { 110 | var parent; 111 | parent = void 0; 112 | if (child.parent_uid) { 113 | for_each_branch(function(b) { 114 | if (b.uid === child.parent_uid) { 115 | return parent = b; 116 | } 117 | }); 118 | } 119 | return parent; 120 | }; 121 | for_all_ancestors = function(child, fn) { 122 | var parent; 123 | parent = get_parent(child); 124 | if (parent != null) { 125 | fn(parent); 126 | return for_all_ancestors(parent, fn); 127 | } 128 | }; 129 | expand_all_parents = function(child) { 130 | return for_all_ancestors(child, function(b) { 131 | return b.expanded = true; 132 | }); 133 | }; 134 | scope.tree_rows = []; 135 | on_treeData_change = function() { 136 | var add_branch_to_list, root_branch, _i, _len, _ref, _results; 137 | for_each_branch(function(b, level) { 138 | if (!b.uid) { 139 | return b.uid = "" + Math.random(); 140 | } 141 | }); 142 | for_each_branch(function(b) { 143 | var child, _i, _len, _ref, _results; 144 | if (angular.isArray(b.children)) { 145 | _ref = b.children; 146 | _results = []; 147 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 148 | child = _ref[_i]; 149 | _results.push(child.parent_uid = b.uid); 150 | } 151 | return _results; 152 | } 153 | }); 154 | scope.tree_rows = []; 155 | for_each_branch(function(branch) { 156 | var child, f; 157 | if (branch.children) { 158 | if (branch.children.length > 0) { 159 | f = function(e) { 160 | if (typeof e === 'string') { 161 | return { 162 | label: e, 163 | children: [] 164 | }; 165 | } else { 166 | return e; 167 | } 168 | }; 169 | return branch.children = (function() { 170 | var _i, _len, _ref, _results; 171 | _ref = branch.children; 172 | _results = []; 173 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 174 | child = _ref[_i]; 175 | _results.push(f(child)); 176 | } 177 | return _results; 178 | })(); 179 | } 180 | } else { 181 | return branch.children = []; 182 | } 183 | }); 184 | add_branch_to_list = function(level, branch, visible) { 185 | var child, child_visible, tree_icon, _i, _len, _ref, _results; 186 | if (branch.expanded == null) { 187 | branch.expanded = false; 188 | } 189 | if (!branch.children || branch.children.length === 0) { 190 | tree_icon = attrs.iconLeaf; 191 | } else { 192 | if (branch.expanded) { 193 | tree_icon = attrs.iconCollapse; 194 | } else { 195 | tree_icon = attrs.iconExpand; 196 | } 197 | } 198 | scope.tree_rows.push({ 199 | level: level, 200 | branch: branch, 201 | label: branch.label, 202 | tree_icon: tree_icon, 203 | visible: visible 204 | }); 205 | if (branch.children != null) { 206 | _ref = branch.children; 207 | _results = []; 208 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 209 | child = _ref[_i]; 210 | child_visible = visible && branch.expanded; 211 | _results.push(add_branch_to_list(level + 1, child, child_visible)); 212 | } 213 | return _results; 214 | } 215 | }; 216 | _ref = scope.treeData; 217 | _results = []; 218 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 219 | root_branch = _ref[_i]; 220 | _results.push(add_branch_to_list(1, root_branch, true)); 221 | } 222 | return _results; 223 | }; 224 | scope.$watch('treeData', on_treeData_change, true); 225 | if (attrs.initialSelection != null) { 226 | for_each_branch(function(b) { 227 | if (b.label === attrs.initialSelection) { 228 | return $timeout(function() { 229 | return select_branch(b); 230 | }); 231 | } 232 | }); 233 | } 234 | n = scope.treeData.length; 235 | for_each_branch(function(b, level) { 236 | b.level = level; 237 | return b.expanded = b.level < expand_level; 238 | }); 239 | if (scope.treeControl != null) { 240 | if (angular.isObject(scope.treeControl)) { 241 | tree = scope.treeControl; 242 | tree.expand_all = function() { 243 | return for_each_branch(function(b, level) { 244 | return b.expanded = true; 245 | }); 246 | }; 247 | tree.collapse_all = function() { 248 | return for_each_branch(function(b, level) { 249 | return b.expanded = false; 250 | }); 251 | }; 252 | tree.get_first_branch = function() { 253 | n = scope.treeData.length; 254 | if (n > 0) { 255 | return scope.treeData[0]; 256 | } 257 | }; 258 | tree.select_first_branch = function() { 259 | var b; 260 | b = tree.get_first_branch(); 261 | return tree.select_branch(b); 262 | }; 263 | tree.get_selected_branch = function() { 264 | return selected_branch; 265 | }; 266 | tree.get_parent_branch = function(b) { 267 | return get_parent(b); 268 | }; 269 | tree.select_branch = function(b) { 270 | select_branch(b); 271 | return b; 272 | }; 273 | tree.get_children = function(b) { 274 | return b.children; 275 | }; 276 | tree.select_parent_branch = function(b) { 277 | var p; 278 | if (b == null) { 279 | b = tree.get_selected_branch(); 280 | } 281 | if (b != null) { 282 | p = tree.get_parent_branch(b); 283 | if (p != null) { 284 | tree.select_branch(p); 285 | return p; 286 | } 287 | } 288 | }; 289 | tree.add_branch = function(parent, new_branch) { 290 | if (parent != null) { 291 | parent.children.push(new_branch); 292 | parent.expanded = true; 293 | } else { 294 | scope.treeData.push(new_branch); 295 | } 296 | return new_branch; 297 | }; 298 | tree.add_root_branch = function(new_branch) { 299 | tree.add_branch(null, new_branch); 300 | return new_branch; 301 | }; 302 | tree.expand_branch = function(b) { 303 | if (b == null) { 304 | b = tree.get_selected_branch(); 305 | } 306 | if (b != null) { 307 | b.expanded = true; 308 | return b; 309 | } 310 | }; 311 | tree.collapse_branch = function(b) { 312 | if (b == null) { 313 | b = selected_branch; 314 | } 315 | if (b != null) { 316 | b.expanded = false; 317 | return b; 318 | } 319 | }; 320 | tree.get_siblings = function(b) { 321 | var p, siblings; 322 | if (b == null) { 323 | b = selected_branch; 324 | } 325 | if (b != null) { 326 | p = tree.get_parent_branch(b); 327 | if (p) { 328 | siblings = p.children; 329 | } else { 330 | siblings = scope.treeData; 331 | } 332 | return siblings; 333 | } 334 | }; 335 | tree.get_next_sibling = function(b) { 336 | var i, siblings; 337 | if (b == null) { 338 | b = selected_branch; 339 | } 340 | if (b != null) { 341 | siblings = tree.get_siblings(b); 342 | n = siblings.length; 343 | i = siblings.indexOf(b); 344 | if (i < n) { 345 | return siblings[i + 1]; 346 | } 347 | } 348 | }; 349 | tree.get_prev_sibling = function(b) { 350 | var i, siblings; 351 | if (b == null) { 352 | b = selected_branch; 353 | } 354 | siblings = tree.get_siblings(b); 355 | n = siblings.length; 356 | i = siblings.indexOf(b); 357 | if (i > 0) { 358 | return siblings[i - 1]; 359 | } 360 | }; 361 | tree.select_next_sibling = function(b) { 362 | var next; 363 | if (b == null) { 364 | b = selected_branch; 365 | } 366 | if (b != null) { 367 | next = tree.get_next_sibling(b); 368 | if (next != null) { 369 | return tree.select_branch(next); 370 | } 371 | } 372 | }; 373 | tree.select_prev_sibling = function(b) { 374 | var prev; 375 | if (b == null) { 376 | b = selected_branch; 377 | } 378 | if (b != null) { 379 | prev = tree.get_prev_sibling(b); 380 | if (prev != null) { 381 | return tree.select_branch(prev); 382 | } 383 | } 384 | }; 385 | tree.get_first_child = function(b) { 386 | var _ref; 387 | if (b == null) { 388 | b = selected_branch; 389 | } 390 | if (b != null) { 391 | if (((_ref = b.children) != null ? _ref.length : void 0) > 0) { 392 | return b.children[0]; 393 | } 394 | } 395 | }; 396 | tree.get_closest_ancestor_next_sibling = function(b) { 397 | var next, parent; 398 | next = tree.get_next_sibling(b); 399 | if (next != null) { 400 | return next; 401 | } else { 402 | parent = tree.get_parent_branch(b); 403 | return tree.get_closest_ancestor_next_sibling(parent); 404 | } 405 | }; 406 | tree.get_next_branch = function(b) { 407 | var next; 408 | if (b == null) { 409 | b = selected_branch; 410 | } 411 | if (b != null) { 412 | next = tree.get_first_child(b); 413 | if (next != null) { 414 | return next; 415 | } else { 416 | next = tree.get_closest_ancestor_next_sibling(b); 417 | return next; 418 | } 419 | } 420 | }; 421 | tree.select_next_branch = function(b) { 422 | var next; 423 | if (b == null) { 424 | b = selected_branch; 425 | } 426 | if (b != null) { 427 | next = tree.get_next_branch(b); 428 | if (next != null) { 429 | tree.select_branch(next); 430 | return next; 431 | } 432 | } 433 | }; 434 | tree.last_descendant = function(b) { 435 | var last_child; 436 | if (b == null) { 437 | debugger; 438 | } 439 | n = b.children.length; 440 | if (n === 0) { 441 | return b; 442 | } else { 443 | last_child = b.children[n - 1]; 444 | return tree.last_descendant(last_child); 445 | } 446 | }; 447 | tree.get_prev_branch = function(b) { 448 | var parent, prev_sibling; 449 | if (b == null) { 450 | b = selected_branch; 451 | } 452 | if (b != null) { 453 | prev_sibling = tree.get_prev_sibling(b); 454 | if (prev_sibling != null) { 455 | return tree.last_descendant(prev_sibling); 456 | } else { 457 | parent = tree.get_parent_branch(b); 458 | return parent; 459 | } 460 | } 461 | }; 462 | return tree.select_prev_branch = function(b) { 463 | var prev; 464 | if (b == null) { 465 | b = selected_branch; 466 | } 467 | if (b != null) { 468 | prev = tree.get_prev_branch(b); 469 | if (prev != null) { 470 | tree.select_branch(prev); 471 | return prev; 472 | } 473 | } 474 | }; 475 | } 476 | } 477 | } 478 | }; 479 | } 480 | ]); 481 | -------------------------------------------------------------------------------- /dist/abn_tree_directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var module, 3 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 4 | 5 | module = angular.module('angularBootstrapNavTree', []); 6 | 7 | module.directive('abnTree', [ 8 | '$timeout', function($timeout) { 9 | return { 10 | restrict: 'E', 11 | template: "", 12 | replace: true, 13 | scope: { 14 | treeData: '=', 15 | onSelect: '&', 16 | initialSelection: '@', 17 | treeControl: '=' 18 | }, 19 | link: function(scope, element, attrs) { 20 | var error, expand_all_parents, expand_level, for_all_ancestors, for_each_branch, get_parent, n, on_treeData_change, select_branch, selected_branch, tree; 21 | error = function(s) { 22 | console.log('ERROR:' + s); 23 | debugger; 24 | return void 0; 25 | }; 26 | if (attrs.iconExpand == null) { 27 | attrs.iconExpand = 'icon-plus glyphicon glyphicon-plus fa fa-plus'; 28 | } 29 | if (attrs.iconCollapse == null) { 30 | attrs.iconCollapse = 'icon-minus glyphicon glyphicon-minus fa fa-minus'; 31 | } 32 | if (attrs.iconLeaf == null) { 33 | attrs.iconLeaf = 'icon-file glyphicon glyphicon-file fa fa-file'; 34 | } 35 | if (attrs.expandLevel == null) { 36 | attrs.expandLevel = '3'; 37 | } 38 | expand_level = parseInt(attrs.expandLevel, 10); 39 | if (!scope.treeData) { 40 | alert('no treeData defined for the tree!'); 41 | return; 42 | } 43 | if (scope.treeData.length == null) { 44 | if (treeData.label != null) { 45 | scope.treeData = [treeData]; 46 | } else { 47 | alert('treeData should be an array of root branches'); 48 | return; 49 | } 50 | } 51 | for_each_branch = function(f) { 52 | var do_f, root_branch, _i, _len, _ref, _results; 53 | do_f = function(branch, level) { 54 | var child, _i, _len, _ref, _results; 55 | f(branch, level); 56 | if (branch.children != null) { 57 | _ref = branch.children; 58 | _results = []; 59 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 60 | child = _ref[_i]; 61 | _results.push(do_f(child, level + 1)); 62 | } 63 | return _results; 64 | } 65 | }; 66 | _ref = scope.treeData; 67 | _results = []; 68 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 69 | root_branch = _ref[_i]; 70 | _results.push(do_f(root_branch, 1)); 71 | } 72 | return _results; 73 | }; 74 | selected_branch = null; 75 | select_branch = function(branch) { 76 | if (!branch) { 77 | if (selected_branch != null) { 78 | selected_branch.selected = false; 79 | } 80 | selected_branch = null; 81 | return; 82 | } 83 | if (branch !== selected_branch) { 84 | if (selected_branch != null) { 85 | selected_branch.selected = false; 86 | } 87 | branch.selected = true; 88 | selected_branch = branch; 89 | expand_all_parents(branch); 90 | if (branch.onSelect != null) { 91 | return $timeout(function() { 92 | return branch.onSelect(branch); 93 | }); 94 | } else { 95 | if (scope.onSelect != null) { 96 | return $timeout(function() { 97 | return scope.onSelect({ 98 | branch: branch 99 | }); 100 | }); 101 | } 102 | } 103 | } 104 | }; 105 | scope.user_clicks_branch = function(branch) { 106 | if (branch !== selected_branch) { 107 | return select_branch(branch); 108 | } 109 | }; 110 | get_parent = function(child) { 111 | var parent; 112 | parent = void 0; 113 | if (child.parent_uid) { 114 | for_each_branch(function(b) { 115 | if (b.uid === child.parent_uid) { 116 | return parent = b; 117 | } 118 | }); 119 | } 120 | return parent; 121 | }; 122 | for_all_ancestors = function(child, fn) { 123 | var parent; 124 | parent = get_parent(child); 125 | if (parent != null) { 126 | fn(parent); 127 | return for_all_ancestors(parent, fn); 128 | } 129 | }; 130 | expand_all_parents = function(child) { 131 | return for_all_ancestors(child, function(b) { 132 | return b.expanded = true; 133 | }); 134 | }; 135 | scope.tree_rows = []; 136 | on_treeData_change = function() { 137 | var add_branch_to_list, root_branch, _i, _len, _ref, _results; 138 | for_each_branch(function(b, level) { 139 | if (!b.uid) { 140 | return b.uid = "" + Math.random(); 141 | } 142 | }); 143 | for_each_branch(function(b) { 144 | var child, _i, _len, _ref, _results; 145 | if (angular.isArray(b.children)) { 146 | _ref = b.children; 147 | _results = []; 148 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 149 | child = _ref[_i]; 150 | _results.push(child.parent_uid = b.uid); 151 | } 152 | return _results; 153 | } 154 | }); 155 | scope.tree_rows = []; 156 | for_each_branch(function(branch) { 157 | var child, f; 158 | if (branch.children) { 159 | if (branch.children.length > 0) { 160 | f = function(e) { 161 | if (typeof e === 'string') { 162 | return { 163 | label: e, 164 | children: [] 165 | }; 166 | } else { 167 | return e; 168 | } 169 | }; 170 | return branch.children = (function() { 171 | var _i, _len, _ref, _results; 172 | _ref = branch.children; 173 | _results = []; 174 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 175 | child = _ref[_i]; 176 | _results.push(f(child)); 177 | } 178 | return _results; 179 | })(); 180 | } 181 | } else { 182 | return branch.children = []; 183 | } 184 | }); 185 | add_branch_to_list = function(level, branch, visible) { 186 | var child, child_visible, tree_icon, _i, _len, _ref, _results; 187 | if (branch.expanded == null) { 188 | branch.expanded = false; 189 | } 190 | if (branch.classes == null) { 191 | branch.classes = []; 192 | } 193 | if (!branch.noLeaf && (!branch.children || branch.children.length === 0)) { 194 | tree_icon = attrs.iconLeaf; 195 | if (__indexOf.call(branch.classes, "leaf") < 0) { 196 | branch.classes.push("leaf"); 197 | } 198 | } else { 199 | if (branch.expanded) { 200 | tree_icon = attrs.iconCollapse; 201 | } else { 202 | tree_icon = attrs.iconExpand; 203 | } 204 | } 205 | scope.tree_rows.push({ 206 | level: level, 207 | branch: branch, 208 | label: branch.label, 209 | classes: branch.classes, 210 | tree_icon: tree_icon, 211 | visible: visible 212 | }); 213 | if (branch.children != null) { 214 | _ref = branch.children; 215 | _results = []; 216 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 217 | child = _ref[_i]; 218 | child_visible = visible && branch.expanded; 219 | _results.push(add_branch_to_list(level + 1, child, child_visible)); 220 | } 221 | return _results; 222 | } 223 | }; 224 | _ref = scope.treeData; 225 | _results = []; 226 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 227 | root_branch = _ref[_i]; 228 | _results.push(add_branch_to_list(1, root_branch, true)); 229 | } 230 | return _results; 231 | }; 232 | scope.$watch('treeData', on_treeData_change, true); 233 | if (attrs.initialSelection != null) { 234 | for_each_branch(function(b) { 235 | if (b.label === attrs.initialSelection) { 236 | return $timeout(function() { 237 | return select_branch(b); 238 | }); 239 | } 240 | }); 241 | } 242 | n = scope.treeData.length; 243 | for_each_branch(function(b, level) { 244 | b.level = level; 245 | return b.expanded = b.level < expand_level; 246 | }); 247 | if (scope.treeControl != null) { 248 | if (angular.isObject(scope.treeControl)) { 249 | tree = scope.treeControl; 250 | tree.expand_all = function() { 251 | return for_each_branch(function(b, level) { 252 | return b.expanded = true; 253 | }); 254 | }; 255 | tree.collapse_all = function() { 256 | return for_each_branch(function(b, level) { 257 | return b.expanded = false; 258 | }); 259 | }; 260 | tree.get_first_branch = function() { 261 | n = scope.treeData.length; 262 | if (n > 0) { 263 | return scope.treeData[0]; 264 | } 265 | }; 266 | tree.select_first_branch = function() { 267 | var b; 268 | b = tree.get_first_branch(); 269 | return tree.select_branch(b); 270 | }; 271 | tree.get_selected_branch = function() { 272 | return selected_branch; 273 | }; 274 | tree.get_parent_branch = function(b) { 275 | return get_parent(b); 276 | }; 277 | tree.select_branch = function(b) { 278 | select_branch(b); 279 | return b; 280 | }; 281 | tree.get_children = function(b) { 282 | return b.children; 283 | }; 284 | tree.select_parent_branch = function(b) { 285 | var p; 286 | if (b == null) { 287 | b = tree.get_selected_branch(); 288 | } 289 | if (b != null) { 290 | p = tree.get_parent_branch(b); 291 | if (p != null) { 292 | tree.select_branch(p); 293 | return p; 294 | } 295 | } 296 | }; 297 | tree.add_branch = function(parent, new_branch) { 298 | if (parent != null) { 299 | parent.children.push(new_branch); 300 | parent.expanded = true; 301 | } else { 302 | scope.treeData.push(new_branch); 303 | } 304 | return new_branch; 305 | }; 306 | tree.add_root_branch = function(new_branch) { 307 | tree.add_branch(null, new_branch); 308 | return new_branch; 309 | }; 310 | tree.expand_branch = function(b) { 311 | if (b == null) { 312 | b = tree.get_selected_branch(); 313 | } 314 | if (b != null) { 315 | b.expanded = true; 316 | return b; 317 | } 318 | }; 319 | tree.collapse_branch = function(b) { 320 | if (b == null) { 321 | b = selected_branch; 322 | } 323 | if (b != null) { 324 | b.expanded = false; 325 | return b; 326 | } 327 | }; 328 | tree.get_siblings = function(b) { 329 | var p, siblings; 330 | if (b == null) { 331 | b = selected_branch; 332 | } 333 | if (b != null) { 334 | p = tree.get_parent_branch(b); 335 | if (p) { 336 | siblings = p.children; 337 | } else { 338 | siblings = scope.treeData; 339 | } 340 | return siblings; 341 | } 342 | }; 343 | tree.get_next_sibling = function(b) { 344 | var i, siblings; 345 | if (b == null) { 346 | b = selected_branch; 347 | } 348 | if (b != null) { 349 | siblings = tree.get_siblings(b); 350 | n = siblings.length; 351 | i = siblings.indexOf(b); 352 | if (i < n) { 353 | return siblings[i + 1]; 354 | } 355 | } 356 | }; 357 | tree.get_prev_sibling = function(b) { 358 | var i, siblings; 359 | if (b == null) { 360 | b = selected_branch; 361 | } 362 | siblings = tree.get_siblings(b); 363 | n = siblings.length; 364 | i = siblings.indexOf(b); 365 | if (i > 0) { 366 | return siblings[i - 1]; 367 | } 368 | }; 369 | tree.select_next_sibling = function(b) { 370 | var next; 371 | if (b == null) { 372 | b = selected_branch; 373 | } 374 | if (b != null) { 375 | next = tree.get_next_sibling(b); 376 | if (next != null) { 377 | return tree.select_branch(next); 378 | } 379 | } 380 | }; 381 | tree.select_prev_sibling = function(b) { 382 | var prev; 383 | if (b == null) { 384 | b = selected_branch; 385 | } 386 | if (b != null) { 387 | prev = tree.get_prev_sibling(b); 388 | if (prev != null) { 389 | return tree.select_branch(prev); 390 | } 391 | } 392 | }; 393 | tree.get_first_child = function(b) { 394 | var _ref; 395 | if (b == null) { 396 | b = selected_branch; 397 | } 398 | if (b != null) { 399 | if (((_ref = b.children) != null ? _ref.length : void 0) > 0) { 400 | return b.children[0]; 401 | } 402 | } 403 | }; 404 | tree.get_closest_ancestor_next_sibling = function(b) { 405 | var next, parent; 406 | next = tree.get_next_sibling(b); 407 | if (next != null) { 408 | return next; 409 | } else { 410 | parent = tree.get_parent_branch(b); 411 | return tree.get_closest_ancestor_next_sibling(parent); 412 | } 413 | }; 414 | tree.get_next_branch = function(b) { 415 | var next; 416 | if (b == null) { 417 | b = selected_branch; 418 | } 419 | if (b != null) { 420 | next = tree.get_first_child(b); 421 | if (next != null) { 422 | return next; 423 | } else { 424 | next = tree.get_closest_ancestor_next_sibling(b); 425 | return next; 426 | } 427 | } 428 | }; 429 | tree.select_next_branch = function(b) { 430 | var next; 431 | if (b == null) { 432 | b = selected_branch; 433 | } 434 | if (b != null) { 435 | next = tree.get_next_branch(b); 436 | if (next != null) { 437 | tree.select_branch(next); 438 | return next; 439 | } 440 | } 441 | }; 442 | tree.last_descendant = function(b) { 443 | var last_child; 444 | if (b == null) { 445 | debugger; 446 | } 447 | n = b.children.length; 448 | if (n === 0) { 449 | return b; 450 | } else { 451 | last_child = b.children[n - 1]; 452 | return tree.last_descendant(last_child); 453 | } 454 | }; 455 | tree.get_prev_branch = function(b) { 456 | var parent, prev_sibling; 457 | if (b == null) { 458 | b = selected_branch; 459 | } 460 | if (b != null) { 461 | prev_sibling = tree.get_prev_sibling(b); 462 | if (prev_sibling != null) { 463 | return tree.last_descendant(prev_sibling); 464 | } else { 465 | parent = tree.get_parent_branch(b); 466 | return parent; 467 | } 468 | } 469 | }; 470 | return tree.select_prev_branch = function(b) { 471 | var prev; 472 | if (b == null) { 473 | b = selected_branch; 474 | } 475 | if (b != null) { 476 | prev = tree.get_prev_branch(b); 477 | if (prev != null) { 478 | tree.select_branch(prev); 479 | return prev; 480 | } 481 | } 482 | }; 483 | } 484 | } 485 | } 486 | }; 487 | } 488 | ]); 489 | 490 | }).call(this); 491 | --------------------------------------------------------------------------------