├── .gitignore
├── .jshintignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── bower.json
├── css
└── ui-navbar.css
├── demo
├── app.js
├── favicon.ico
├── index.html
├── package.json
├── partials
│ ├── home.html
│ ├── metal-gear.html
│ ├── state1.html
│ ├── state2.html
│ ├── state3.html
│ └── state4.html
└── server.js
├── e2e
├── navbar.po.js
└── navbar.spec.js
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── release
├── css
│ ├── ui-navbar.css
│ └── ui-navbar.min.css
└── js
│ ├── ui-navbar.js
│ └── ui-navbar.min.js
├── src
└── navbar.js
├── template
├── navbar-li.html
├── navbar-li.html.js
├── navbar-tree-li.html
├── navbar-tree-li.html.js
├── navbar-ul.html
└── navbar-ul.html.js
└── test
└── ui-navbar.spec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.iml
3 |
4 | node_modules
5 |
6 | npm-debug.log
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | test/fixtures/dontlint.txt
2 | node_modules/**
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "immed": true,
4 | "newcap": true,
5 | "noarg": true,
6 | "sub": true,
7 | "boss": true,
8 | "eqnull": true,
9 | "quotmark": "single",
10 | "trailing": true,
11 | "undef": true,
12 | "browser": true,
13 | "jquery": true,
14 | "globals": {
15 | "angular": false,
16 |
17 | // For Grunt
18 | "require": false,
19 |
20 | // For Jasmine
21 | "after" : false,
22 | "afterEach" : false,
23 | "before" : false,
24 | "beforeEach" : false,
25 | "describe" : false,
26 | "expect" : false,
27 | "jasmine" : false,
28 | "module" : false,
29 | "spyOn" : false,
30 | "inject" : false,
31 | "it" : false
32 | }
33 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.12"
4 |
5 | before_script:
6 | - export CHROME_BIN=chromium-browser
7 | - export DISPLAY=:99.0
8 | - sh -e /etc/init.d/xvfb start
9 | - npm install --quiet -g karma
10 |
11 | script: grunt
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | // Load grunt tasks automatically
4 | require('load-grunt-tasks')(grunt);
5 |
6 | // Time how long tasks take. Can help when optimizing build times
7 | require('time-grunt')(grunt);
8 |
9 | grunt.initConfig({
10 | pkg: grunt.file.readJSON('package.json'),
11 |
12 | release: 'release',
13 | filename: 'ui-navbar',
14 | meta: {},
15 |
16 | connect: {
17 | server: {
18 | options: {
19 | port: 5000,
20 | base: 'src'
21 | }
22 | }
23 | },
24 |
25 | karma: {
26 | unit: {
27 | configFile: 'karma.conf.js'
28 | },
29 | travis: {
30 | configFile: 'karma.conf.js',
31 | singleRun: true,
32 | autoWatch: false
33 | }
34 | },
35 |
36 | protractor: {
37 | e2e: {
38 | options: {
39 | args: {
40 | specs: ['e2e/**/*.spec.js']
41 | },
42 | configFile: 'protractor.conf.js',
43 | keepAlive: true
44 | }
45 | }
46 | },
47 |
48 | protractor_webdriver: {
49 | start: {
50 | options: {
51 | path: './node_modules/grunt-protractor-runner/node_modules/protractor/bin/',
52 | command: 'webdriver-manager start'
53 | }
54 | }
55 | },
56 |
57 | concat: {
58 | release: {
59 | src: [
60 | 'src/**/*.js',
61 | 'template/**/*.js',
62 | '!**/*.min.js',
63 | '!**/*.spec.js'
64 | ],
65 | dest: 'release/js/<%= filename %>.js'
66 | }
67 | },
68 |
69 | uglify: {
70 | release: {
71 | src: ['release/js/<%= filename %>.js'],
72 | dest: 'release/js/<%= filename %>.min.js'
73 | }
74 | },
75 |
76 | cssmin: {
77 | build: {
78 | src: 'css/<%= filename %>.css',
79 | dest: 'release/css/<%= filename %>.min.css'
80 | }
81 | },
82 |
83 | copy: {
84 | main: {
85 | files: [
86 | {expand: true, src: ['css/ui-navbar.css'], dest: 'release/', filter: 'isFile'}
87 | ]
88 | }
89 | },
90 |
91 | jshint: {
92 | files: ['Gruntfile.js', 'src/**/*.js'],
93 | options: {
94 | jshintrc: '.jshintrc'
95 | }
96 | },
97 |
98 | html2js: {
99 | release: {
100 | options: {
101 | module: null, // no bundle for all the templates
102 | base: '.'
103 | },
104 | files: [{
105 | expand: true,
106 | src: ['template/**/*.html'],
107 | ext: '.html.js'
108 | }]
109 | }
110 | }
111 | });
112 |
113 | grunt.registerTask('default', [
114 | 'jshint',
115 | 'html2js',
116 | 'karma:travis',
117 | 'concat',
118 | 'uglify',
119 | 'cssmin',
120 | 'copy'
121 | ]);
122 |
123 | grunt.registerTask('build', [
124 | 'jshint',
125 | 'html2js',
126 | 'karma:unit',
127 | 'concat',
128 | 'uglify',
129 | 'cssmin',
130 | 'copy'
131 | ]);
132 |
133 | grunt.registerTask('e2e', [
134 | 'connect:server',
135 | 'protractor_webdriver:start',
136 | 'protractor:e2e'
137 | ]);
138 |
139 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Eugenio Lentini
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ui-navbar - Responsive navigation bar with submenu in AngularJS
2 |
3 | 
4 | [](https://www.npmjs.com/package/ui-navbar)
5 | [](https://travis-ci.org/blackat/ui-navbar)
6 | [](https://david-dm.org/blackat/ui-navbar#info=dependencies)
7 | [](https://david-dm.org/blackat/ui-navbar#info=devDependencies)
8 |
9 | ## Introduction
10 | Build a responsive navigation menu bar with sub-menu in a __recursive__ fashion using `ui-router` to load partials.
11 |
12 | The directive can now be used in 3 different ways: buttons or icons, navbar with separated drop-down menu or single tree structure.
13 |
14 | ## Plunkr live demo
15 |
16 | - version < 0.14.x Live demo at [Plunkr](http://plnkr.co/edit/V7tecYv4wNPP198HRQlJ?p=info)
17 | - version > 2.2.0 Live demo at [Plunkr](https://plnkr.co/edit/svsAXSVyeiJm8StMB07n)
18 |
19 | ## 1. Installation
20 | Via npm
21 | ```
22 | npm install ui-navbar --save
23 | ```
24 |
25 | or via Bower
26 | ```
27 | bower install ui-navbar --save
28 | ```
29 |
30 | ## 2. Configure routing in your module adding required dependencies
31 | ```javascript
32 | angular.module('App', ['ui.bootstrap', 'ui.router', 'ui.navbar'])
33 |
34 | .config(function ($stateProvider, $urlRouterProvider) {
35 |
36 | // For any unmatched url, redirect to /state1
37 | $urlRouterProvider.otherwise("/home");
38 |
39 | // Now set up the states
40 | $stateProvider
41 | .state('home', {
42 | url: "/home",
43 | templateUrl: "home.html"
44 | })
45 | .state('metal-gear', {
46 | url: "/metal-gear",
47 | templateUrl: "metal-gear.html"
48 | })
49 | .state('metal-gear2', {
50 | url: "/metal-gear2",
51 | templateUrl: "metal-gear2.html"
52 | })
53 | .state('metal-gear-solid', {
54 | url: "/metal-gear-solid",
55 | templateUrl: "metal-gear-solid.html"
56 | });
57 | });
58 | ```
59 |
60 | ## 3. Configure the controller
61 | ```javascript
62 | angular.module('App').controller('NavigationController', function ($scope) {
63 |
64 | $scope.konami = [{
65 | name: "Konami",
66 | link: "#",
67 | subtree: [{
68 | name: "Metal Gear",
69 | link: "#",
70 | subtree: [{
71 | name: "Metal Gear",
72 | link: "metal-gear"
73 | }, {
74 | name: "Metal Gear 2: Solid Snake",
75 | link: "metal-gear2"
76 | }, {
77 | name: "Metal Gear Solid: The Twin Snakes",
78 | link: "metal-gear-solid"
79 | }]
80 | }]
81 | }];
82 |
83 | $scope.trees = [{
84 | name: "Konami",
85 | link: "#",
86 | subtree: [{
87 | name: "Metal Gear",
88 | link: "#",
89 | subtree: [{
90 | name: "Metal Gear",
91 | link: "metal-gear"
92 | }, {
93 | name: "Metal Gear 2: Solid Snake",
94 | link: "#"
95 | }, {
96 | name: "Metal Gear Solid: The Twin Snakes",
97 | link: "#"
98 | }]
99 | }, {
100 | name: "divider",
101 | link: "#"
102 | }, {
103 | name: "Castlevania",
104 | link: "#",
105 | subtree: [{
106 | ...
107 | }]
108 | }]
109 | }]
110 | }
111 | ```
112 |
113 | ## 4. Html parts
114 |
115 | ### Add `ui-view` to attach the partials.
116 | ```html
117 |
118 |
119 | ```
120 |
121 |
122 | ### Button
123 | Add a multi-level menu to a drop down button rendering the previously introduced items:
124 | ```javascript
125 |
126 |
127 | Dropdown
128 |
129 |
130 |
131 | ```
132 |
133 | ### Navigation bar with separated multi-level dropdown menu.
134 | Specify an array of states for each menu item in the navigation bar:
135 | ```html
136 |
137 |
138 |
147 |
148 |
179 |
180 |
181 |
182 | ```
183 |
184 |
185 | ### Navigation bar with a single tree structure
186 | Specify an array representing the all tree, with all the states and subtree for of each
187 | state if required.
188 | ```html
189 |
190 |
191 |
200 |
201 |
202 |
207 |
208 |
209 |
210 | ```
211 |
212 | ## Features
213 |
214 | - Recursive item menu definition in `json` format.
215 | - Easy way to define a `divider` between items.
216 | - Unlimited level of nesting.
217 | - Responsive.
218 | - Fully compatible with AngularJS.
219 | - Standard Html5 with AngularJS Bootstrap attributes such as `dropdown`.
220 | - Support tag `navbar-right` from Bootstrap with submenu opening on the left.
221 | - __No jquery required__ to manage responsivness and dropdown actions.
222 |
223 | ## Dependencies
224 |
225 | - AngularJS, required 1.5.x, tested with 1.5.8.
226 | - UI Boostrap, required 1.1.1, tested with 2.2.0.
227 | - ui-router, required 0.2.15, tested with 0.3.1.
228 | - Twitter Bootstrap, required 3.3.6, tested with 3.3.7.
229 |
230 | ## Update
231 | - Introduced the directive `` to specify the navigation bar in a _all-in-one_ fashion.
232 | - Updated the documentation, demo and plunker.
233 |
234 | ## Prefix
235 | - Prefixed `angular-ui-bootstrap` components in the `index.html` demo page according to the [migration guide](https://github.com/angular-ui/bootstrap/wiki/Migration-guide-for-prefixes).
236 |
237 | ## Demo
238 | From the folder `demo` type
239 |
240 | npm install
241 | node server.js
242 |
243 | then type in a browser `http://localhost:5000` to get the demo page working.
244 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui-navbar",
3 | "version": "0.5.2",
4 | "homepage": "git@github.com:blackat/ui-navbar.git",
5 | "authors": [
6 | "Eugenio Lentini (http://blackat.github.io)"
7 | ],
8 | "description": "Angular responsive navigation with recursive menu and sub-menu construction",
9 | "main": [
10 | "release/js/ui-navbar.js",
11 | "release/css/ui-navbar.css"
12 | ],
13 | "keywords": [
14 | "ui-boostrap",
15 | "boostrap",
16 | "ui-router",
17 | "navbar",
18 | "submenu",
19 | "menu",
20 | "navigation",
21 | "angularjs",
22 | "responsive"
23 | ],
24 | "ignore": [
25 | "**/.*",
26 | "node_modules",
27 | "bower_components",
28 | "test",
29 | "tests"
30 | ],
31 | "license": "MIT"
32 | }
33 |
--------------------------------------------------------------------------------
/css/ui-navbar.css:
--------------------------------------------------------------------------------
1 | .dropdown-submenu {
2 | position: relative;
3 | }
4 |
5 | .dropdown-submenu > .dropdown-menu {
6 | top: 0;
7 | left: 100%;
8 | margin-top: -6px;
9 | margin-left: 0px;
10 | -webkit-border-radius: 0;
11 | -moz-border-radius: 0;
12 | border-radius: 0;
13 | }
14 |
15 | .dropdown-submenu:hover > .dropdown-menu {
16 | display: block;
17 | }
18 |
19 | .dropdown-submenu > a:after {
20 | display: block;
21 | content: " ";
22 | float: right;
23 | width: 0;
24 | height: 0;
25 | border-color: transparent;
26 | border-style: solid;
27 | border-width: 5px 0 5px 5px;
28 | border-left-color: #ccc;
29 | margin-top: 5px;
30 | margin-right: -10px;
31 | }
32 |
33 | .dropdown-submenu:hover > a:after {
34 | border-left-color: #fff;
35 | }
36 |
37 | .dropdown-submenu.pull-left {
38 | float: none;
39 | }
40 |
41 | .dropdown-submenu.pull-left > .dropdown-menu {
42 | left: -100%;
43 | margin-left: 10px;
44 | -webkit-border-radius: 6px 0 6px 6px;
45 | -moz-border-radius: 6px 0 6px 6px;
46 | border-radius: 6px 0 6px 6px;
47 | }
48 |
49 | .dropdown-submenu-right {
50 | position: relative;
51 | }
52 |
53 | .dropdown-submenu-right > .dropdown-menu {
54 | top: 0;
55 | right: 100%;
56 | margin-top: -6px;
57 | margin-left: -1px;
58 | -webkit-border-radius: 0;
59 | -moz-border-radius: 0;
60 | border-radius: 0;
61 | }
62 |
63 | .dropdown-submenu-right:hover > .dropdown-menu {
64 | display: block;
65 | }
66 |
67 | .dropdown-submenu-right > a:after {
68 | display: block;
69 | content: " ";
70 | float: right;
71 | width: 0;
72 | height: 0;
73 | border-color: transparent;
74 | border-style: solid;
75 | border-width: 5px 0 5px 5px;
76 | border-left-color: #ccc;
77 | margin-top: 5px;
78 | margin-right: -10px;
79 | }
80 |
81 | .dropdown-submenu-right:hover > a:after {
82 | border-left-color: #fff;
83 | }
84 |
--------------------------------------------------------------------------------
/demo/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | angular.module('App', ['ui.bootstrap', 'ui.router', 'ui.navbar'])
4 |
5 | .config(function ($stateProvider, $urlRouterProvider) {
6 |
7 | // For any unmatched url, redirect to /state1
8 | $urlRouterProvider.otherwise("/home");
9 |
10 | // Now set up the states
11 | $stateProvider
12 | .state('home', {
13 | url: "/home",
14 | templateUrl: "partials/home.html"
15 | })
16 | .state('state1', {
17 | url: "/state1",
18 | templateUrl: "partials/state1.html"
19 | })
20 | .state('state2', {
21 | url: "/state2",
22 | templateUrl: "partials/state2.html"
23 | })
24 | .state('state3', {
25 | url: "/state3",
26 | templateUrl: "partials/state3.html"
27 | })
28 | .state('state4', {
29 | url: "/state4",
30 | templateUrl: "partials/state4.html"
31 | })
32 | .state('metal-gear', {
33 | url: "/metal-gear",
34 | templateUrl: "partials/metal-gear.html"
35 | });
36 | })
37 |
38 | .controller('NavigationController', function ($scope) {
39 |
40 | $scope.tree = [{
41 | name: "States",
42 | link: "#",
43 | subtree: [{
44 | name: "state 1",
45 | link: "state1",
46 | subtree: [{
47 | name: "state 3",
48 | link: "state3"
49 | }]
50 | }, {
51 | name: "state 2",
52 | link: "state2",
53 | subtree: [{
54 | name: "state 4",
55 | link: "state4"
56 | }]
57 | }]
58 | }, {
59 | name: "No states",
60 | link: "#",
61 | subtree: [{
62 | name: "no state connected",
63 | link: "#"
64 | }]
65 | }, {
66 | name: "divider",
67 | link: "#"
68 |
69 | }, {
70 | name: "State has not been set up",
71 | link: "#"
72 | }, {
73 | name: "divider",
74 | link: "#"
75 | }, {
76 | name: "Here again no state set up",
77 | link: "#"
78 | }];
79 |
80 | $scope.trees = [{
81 | name: "Konami",
82 | link: "#",
83 | subtree: [{
84 | name: "Metal Gear",
85 | link: "#",
86 | subtree: [{
87 | name: "Metal Gear",
88 | link: "metal-gear"
89 | }, {
90 | name: "Metal Gear 2: Solid Snake",
91 | link: "#"
92 | }, {
93 | name: "Metal Gear Solid: The Twin Snakes",
94 | link: "#"
95 | }]
96 | }, {
97 | name: "divider",
98 | link: "#"
99 | }, {
100 | name: "Castlevania",
101 | link: "#",
102 | subtree: [{
103 | name: "Castlevania",
104 | link: "#"
105 | }, {
106 | name: "Castlevania II: Simon's Quest",
107 | link: "#"
108 | }, {
109 | name: "Castlevania III: Dracula's Curse",
110 | link: "#"
111 | }]
112 | }]
113 | }, {
114 | name: "SNK",
115 | link: "#",
116 | subtree: [{
117 | name: "Fatal Fury",
118 | link: "#",
119 | subtree: [{
120 | name: "Fatal Fury",
121 | link: "#"
122 | }, {
123 | name: "Fatal Fury 2",
124 | link: "#"
125 | }, {
126 | name: "Fatal Fury: King of Fighters",
127 | link: "#"
128 | }, {
129 | name: "Fatal Fury Special",
130 | link: "#"
131 | }]
132 | }, {
133 | name: "divider",
134 | link: "#"
135 | }, {
136 | name: "Metal Slug",
137 | link: "#",
138 | subtree: [{
139 | name: "Metal Slug",
140 | link: "#"
141 | }, {
142 | name: "Metal Slug 2",
143 | link: "#"
144 | }, {
145 | name: "Metal Slug 3",
146 | link: "#"
147 | }, {
148 | name: "Metal Slug 4",
149 | link: "#"
150 | }, {
151 | name: "Metal Slug 5",
152 | link: "#"
153 | }, {
154 | name: "Metal Slug 6",
155 | link: "#"
156 | }, {
157 | name: "Metal Slug 7",
158 | link: "#"
159 | }, {
160 | name: "Metal Slug X",
161 | link: "#"
162 | }]
163 | }]
164 | }, {
165 | name: "Sega",
166 | link: "#"
167 | },{
168 | name: "Nintendo",
169 | link: "#"
170 | }]
171 | });
--------------------------------------------------------------------------------
/demo/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blackat/ui-navbar/7bef0bd34bca5557207e2ae604c5778abf9ec8af/demo/favicon.ico
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Navbar Demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
24 |
25 |
26 |
61 |
62 |
63 |
64 |
65 |
66 |
76 |
77 |
78 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui-navbar-demo",
3 | "version": "0.4.3",
4 | "author": "Eugenio Lentini (http://blackat.github.io)",
5 | "description": "demo app for ui-navbar",
6 | "dependencies": {
7 | "angular-ui-bootstrap": "^2.2.0",
8 | "angular": "^1.5.8",
9 | "angular-ui-router": "^0.3.1",
10 | "bootstrap": "^3.3.7"
11 | },
12 | "devDependencies": {
13 | "connect": "^3.1.x",
14 | "serve-static": "^1.4.x"
15 | },
16 | "scripts": {
17 | "start": "node server.js"
18 | },
19 | "license": "MIT"
20 | }
--------------------------------------------------------------------------------
/demo/partials/home.html:
--------------------------------------------------------------------------------
1 |
2 | Demo
3 |
4 | How to use ui-navbar to load different partials based on routing
5 |
6 | just play with the foo menu
--------------------------------------------------------------------------------
/demo/partials/metal-gear.html:
--------------------------------------------------------------------------------
1 |
2 | Metal Gear
3 |
4 | this is just a foo state called Metal Gear
--------------------------------------------------------------------------------
/demo/partials/state1.html:
--------------------------------------------------------------------------------
1 |
2 | State 1
3 |
4 | this is just a foo state called 1
--------------------------------------------------------------------------------
/demo/partials/state2.html:
--------------------------------------------------------------------------------
1 |
2 | State 2
3 |
4 | this is just a foo state called 2
--------------------------------------------------------------------------------
/demo/partials/state3.html:
--------------------------------------------------------------------------------
1 |
2 | State 3
3 |
4 | this is just a foo state called 3
--------------------------------------------------------------------------------
/demo/partials/state4.html:
--------------------------------------------------------------------------------
1 |
2 | State 4
3 |
4 | this is just a foo state called 4
--------------------------------------------------------------------------------
/demo/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var connect = require('connect');
4 | var serveStatic = require('serve-static');
5 | var app = connect();
6 |
7 | app.use(serveStatic('./'));
8 | app.use(serveStatic('../'));
9 | app.listen(5000);
10 | console.log('Connect to Node.js server typing in browser address bar http://localhost:5000');
--------------------------------------------------------------------------------
/e2e/navbar.po.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by eugenio on 26/07/15.
3 | */
4 |
--------------------------------------------------------------------------------
/e2e/navbar.spec.js:
--------------------------------------------------------------------------------
1 | // Define a test suire
2 | describe('navigation bar e2e test suite', function () {
3 |
4 | beforeEach(function () {
5 | browser.get('http://0.0.0.0:8000/');
6 | });
7 |
8 | });
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Sat Sep 26 2015 21:17:15 GMT+0200 (CEST)
3 |
4 | module.exports = function (config) {
5 |
6 | var configuration = {
7 |
8 | // base path that will be used to resolve all patterns (eg. files, exclude)
9 | basePath: '',
10 |
11 |
12 | // frameworks to use
13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
14 | frameworks: ['jasmine'],
15 |
16 |
17 | // list of files / patterns to load in the browser
18 | files: [
19 |
20 | // include dependencies
21 | 'node_modules/angular/angular.js',
22 | 'node_modules/angular-mocks/angular-mocks.js',
23 | 'node_modules/angular-ui-router/release/angular-ui-router.js',
24 | 'node_modules/angular-ui-bootstrap/dist/ui-bootstrap.js',
25 |
26 | // include navbar directive
27 | 'src/navbar.js',
28 |
29 | // include directive templates
30 | 'template/navbar-ul.html.js',
31 | 'template/navbar-li.html.js',
32 | 'template/navbar-tree-li.html.js',
33 |
34 | // include test folder
35 | 'test/*.spec.js'
36 | ],
37 |
38 |
39 | // list of files to exclude
40 | exclude: [],
41 |
42 |
43 | // preprocess matching files before serving them to the browser
44 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
45 | preprocessors: {},
46 |
47 |
48 | // test results reporter to use
49 | // possible values: 'dots', 'progress'
50 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
51 | reporters: ['progress'],
52 |
53 |
54 | // web server port
55 | port: 9876,
56 |
57 |
58 | // enable / disable colors in the output (reporters and logs)
59 | colors: true,
60 |
61 |
62 | // level of logging
63 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
64 | logLevel: config.LOG_INFO,
65 |
66 |
67 | // enable / disable watching file and executing tests whenever any file changes
68 | autoWatch: true,
69 |
70 |
71 | // start these browsers
72 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
73 | browsers: ['Chrome'],
74 |
75 |
76 | // Continuous Integration mode
77 | // if true, Karma captures browsers, runs the tests and exits
78 | singleRun: false,
79 |
80 | // Travis configuration to run Chrome
81 | customLaunchers: {
82 | Chrome_travis_ci: {
83 | base: 'Chrome',
84 | flags: ['--no-sandbox']
85 | }
86 | }
87 | };
88 |
89 | if (process.env.TRAVIS) {
90 | configuration.browsers = ['Chrome_travis_ci'];
91 | }
92 |
93 | config.set(configuration);
94 | };
95 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ui-navbar",
3 | "version": "0.5.1",
4 | "author": "Eugenio Lentini (http://blackat.github.io)",
5 | "description": "Angular responsive navigation with recursive menu and sub-menu construction",
6 | "repository": {
7 | "type": "git",
8 | "url": "git@github.com:blackat/ui-navbar.git"
9 | },
10 | "dependencies": {
11 | "angular": "1.5.8",
12 | "angular-ui-bootstrap": "^2.2.0",
13 | "angular-ui-router": "^0.3.1",
14 | "bootstrap": "3.3.7"
15 | },
16 | "devDependencies": {
17 | "angular-mocks": "1.5.8",
18 | "grunt": "^0.4.5",
19 | "grunt-cli": "^1.2.0",
20 | "grunt-contrib-concat": "~0.5.1",
21 | "grunt-contrib-connect": "^0.11.2",
22 | "grunt-contrib-copy": "~0.8.0",
23 | "grunt-contrib-cssmin": "*",
24 | "grunt-contrib-jshint": "~0.11.1",
25 | "grunt-contrib-nodeunit": "~0.4.1",
26 | "grunt-contrib-uglify": "~0.11.0",
27 | "grunt-html2js": "~0.3.2",
28 | "grunt-karma": "^0.12.0",
29 | "grunt-protractor-runner": "^2.0.0",
30 | "grunt-protractor-webdriver": "^0.2.0",
31 | "jasmine-core": "^2.3.4",
32 | "karma": "^0.13.3",
33 | "karma-chrome-launcher": "^0.2.0",
34 | "karma-jasmine": "^0.3.6",
35 | "load-grunt-tasks": "~3.3.0",
36 | "protractor": "3.0.0",
37 | "time-grunt": "~1.2.0"
38 | },
39 | "main": "./release/js/ui-navbar.js",
40 | "keywords": [
41 | "ui-boostrap",
42 | "boostrap",
43 | "ui-router",
44 | "navbar",
45 | "submenu",
46 | "menu",
47 | "navigation",
48 | "angularjs",
49 | "responsive"
50 | ],
51 | "license": "MIT",
52 | "scripts": {
53 | "patch": "npm version patch && npm publish && git push --tags",
54 | "update-webdriver": "node node_modules/grunt-protractor-runner/node_modules/protractor/bin/webdriver-manager update"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | seleniumAddress: 'http://localhost:4444/wd/hub',
3 |
4 | specs: [
5 | 'e2e/**/*.spec.js'
6 | ],
7 |
8 | framework: 'jasmine',
9 |
10 | capabilities: {
11 | 'browserName': 'chrome'
12 | },
13 |
14 | jasmineNodeOpts: {
15 | showColors: true, // use colors in the command line report
16 | defaultTimeoutInterval: 30000
17 | },
18 |
19 | seleniumServerJar: './node_modules/grunt-protractor-runner/node_modules/protractor/selenium/selenium-server-standalone-2.45.0.jar'
20 | };
--------------------------------------------------------------------------------
/release/css/ui-navbar.css:
--------------------------------------------------------------------------------
1 | .dropdown-submenu {
2 | position: relative;
3 | }
4 |
5 | .dropdown-submenu > .dropdown-menu {
6 | top: 0;
7 | left: 100%;
8 | margin-top: -6px;
9 | margin-left: 0px;
10 | -webkit-border-radius: 0;
11 | -moz-border-radius: 0;
12 | border-radius: 0;
13 | }
14 |
15 | .dropdown-submenu:hover > .dropdown-menu {
16 | display: block;
17 | }
18 |
19 | .dropdown-submenu > a:after {
20 | display: block;
21 | content: " ";
22 | float: right;
23 | width: 0;
24 | height: 0;
25 | border-color: transparent;
26 | border-style: solid;
27 | border-width: 5px 0 5px 5px;
28 | border-left-color: #ccc;
29 | margin-top: 5px;
30 | margin-right: -10px;
31 | }
32 |
33 | .dropdown-submenu:hover > a:after {
34 | border-left-color: #fff;
35 | }
36 |
37 | .dropdown-submenu.pull-left {
38 | float: none;
39 | }
40 |
41 | .dropdown-submenu.pull-left > .dropdown-menu {
42 | left: -100%;
43 | margin-left: 10px;
44 | -webkit-border-radius: 6px 0 6px 6px;
45 | -moz-border-radius: 6px 0 6px 6px;
46 | border-radius: 6px 0 6px 6px;
47 | }
48 |
49 | .dropdown-submenu-right {
50 | position: relative;
51 | }
52 |
53 | .dropdown-submenu-right > .dropdown-menu {
54 | top: 0;
55 | right: 100%;
56 | margin-top: -6px;
57 | margin-left: -1px;
58 | -webkit-border-radius: 0;
59 | -moz-border-radius: 0;
60 | border-radius: 0;
61 | }
62 |
63 | .dropdown-submenu-right:hover > .dropdown-menu {
64 | display: block;
65 | }
66 |
67 | .dropdown-submenu-right > a:after {
68 | display: block;
69 | content: " ";
70 | float: right;
71 | width: 0;
72 | height: 0;
73 | border-color: transparent;
74 | border-style: solid;
75 | border-width: 5px 0 5px 5px;
76 | border-left-color: #ccc;
77 | margin-top: 5px;
78 | margin-right: -10px;
79 | }
80 |
81 | .dropdown-submenu-right:hover > a:after {
82 | border-left-color: #fff;
83 | }
84 |
--------------------------------------------------------------------------------
/release/css/ui-navbar.min.css:
--------------------------------------------------------------------------------
1 | .dropdown-submenu,.dropdown-submenu-right{position:relative}.dropdown-submenu-right>a:after,.dropdown-submenu>a:after{display:block;content:" ";width:0;height:0;border-color:transparent transparent transparent #ccc;border-style:solid;border-width:5px 0 5px 5px;margin-right:-10px}.dropdown-submenu-right:hover>a:after,.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropdown-submenu>a:after{float:right;margin-top:5px}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown-submenu-right>.dropdown-menu{top:0;right:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.dropdown-submenu-right:hover>.dropdown-menu{display:block}.dropdown-submenu-right>a:after{float:right;margin-top:5px}
--------------------------------------------------------------------------------
/release/js/ui-navbar.js:
--------------------------------------------------------------------------------
1 | angular.module('ui.navbar', ['ui.bootstrap', 'template/navbar-ul.html', 'template/navbar-li.html', 'template/navbar-tree-li.html'])
2 |
3 | .directive('trees', function () {
4 | 'use strict';
5 |
6 | return {
7 | restrict: 'E',
8 | replace: true,
9 | scope: {
10 | trees: '='
11 | },
12 | templateUrl: 'template/navbar-tree-li.html'
13 | };
14 | })
15 |
16 | .directive('tree', function () {
17 | 'use strict';
18 |
19 | return {
20 | restrict: 'E',
21 | replace: true,
22 | scope: {
23 | tree: '='
24 | },
25 | templateUrl: 'template/navbar-ul.html'
26 | };
27 | })
28 |
29 | .directive('leaf', ['$compile', function ($compile) {
30 | 'use strict';
31 |
32 | return {
33 | restrict: 'E',
34 | replace: true,
35 | scope: {
36 | leaf: '='
37 | },
38 | templateUrl: 'template/navbar-li.html',
39 | link: function (scope, element) {
40 | if (angular.isArray(scope.leaf.subtree)) {
41 | element.append(' ');
42 |
43 | // find the parent of the element
44 | var parent = element.parent();
45 | var classFound = false;
46 |
47 | // check if in the hierarchy of the element exists a dropdown with class navbar-right
48 | while (parent.length > 0 && !classFound) {
49 | // check if the dropdown has been push to right
50 | if (parent.hasClass('navbar-right')) {
51 | classFound = true;
52 | }
53 | parent = parent.parent();
54 | }
55 |
56 | // add a different class according to the position of the dropdown
57 | if (classFound) {
58 | element.addClass('dropdown-submenu-right');
59 | } else {
60 | element.addClass('dropdown-submenu');
61 | }
62 |
63 | $compile(element.contents())(scope);
64 | }
65 | }
66 | };
67 | }]);
68 |
69 | angular.module("template/navbar-li.html", []).run(["$templateCache", function($templateCache) {
70 | $templateCache.put("template/navbar-li.html",
71 | "\n" +
72 | " {{leaf.name}} \n" +
73 | " ");
74 | }]);
75 |
76 | angular.module("template/navbar-tree-li.html", []).run(["$templateCache", function($templateCache) {
77 | $templateCache.put("template/navbar-tree-li.html",
78 | "\n" +
79 | " {{tree.name}} \n" +
80 | " \n" +
81 | " ");
82 | }]);
83 |
84 | angular.module("template/navbar-ul.html", []).run(["$templateCache", function($templateCache) {
85 | $templateCache.put("template/navbar-ul.html",
86 | "");
89 | }]);
90 |
--------------------------------------------------------------------------------
/release/js/ui-navbar.min.js:
--------------------------------------------------------------------------------
1 | angular.module("ui.navbar",["ui.bootstrap","template/navbar-ul.html","template/navbar-li.html","template/navbar-tree-li.html"]).directive("trees",function(){"use strict";return{restrict:"E",replace:!0,scope:{trees:"="},templateUrl:"template/navbar-tree-li.html"}}).directive("tree",function(){"use strict";return{restrict:"E",replace:!0,scope:{tree:"="},templateUrl:"template/navbar-ul.html"}}).directive("leaf",["$compile",function(a){"use strict";return{restrict:"E",replace:!0,scope:{leaf:"="},templateUrl:"template/navbar-li.html",link:function(b,c){if(angular.isArray(b.leaf.subtree)){c.append(' ');for(var d=c.parent(),e=!1;d.length>0&&!e;)d.hasClass("navbar-right")&&(e=!0),d=d.parent();e?c.addClass("dropdown-submenu-right"):c.addClass("dropdown-submenu"),a(c.contents())(b)}}}}]),angular.module("template/navbar-li.html",[]).run(["$templateCache",function(a){a.put("template/navbar-li.html",'\n {{leaf.name}} \n ')}]),angular.module("template/navbar-tree-li.html",[]).run(["$templateCache",function(a){a.put("template/navbar-tree-li.html",'\n {{tree.name}} \n \n ')}]),angular.module("template/navbar-ul.html",[]).run(["$templateCache",function(a){a.put("template/navbar-ul.html","")}]);
--------------------------------------------------------------------------------
/src/navbar.js:
--------------------------------------------------------------------------------
1 | angular.module('ui.navbar', ['ui.bootstrap', 'template/navbar-ul.html', 'template/navbar-li.html', 'template/navbar-tree-li.html'])
2 |
3 | .directive('trees', function () {
4 | 'use strict';
5 |
6 | return {
7 | restrict: 'E',
8 | replace: true,
9 | scope: {
10 | trees: '='
11 | },
12 | templateUrl: 'template/navbar-tree-li.html'
13 | };
14 | })
15 |
16 | .directive('tree', function () {
17 | 'use strict';
18 |
19 | return {
20 | restrict: 'E',
21 | replace: true,
22 | scope: {
23 | tree: '='
24 | },
25 | templateUrl: 'template/navbar-ul.html'
26 | };
27 | })
28 |
29 | .directive('leaf', ['$compile', function ($compile) {
30 | 'use strict';
31 |
32 | return {
33 | restrict: 'E',
34 | replace: true,
35 | scope: {
36 | leaf: '='
37 | },
38 | templateUrl: 'template/navbar-li.html',
39 | link: function (scope, element) {
40 | if (angular.isArray(scope.leaf.subtree)) {
41 | element.append(' ');
42 |
43 | // find the parent of the element
44 | var parent = element.parent();
45 | var classFound = false;
46 |
47 | // check if in the hierarchy of the element exists a dropdown with class navbar-right
48 | while (parent.length > 0 && !classFound) {
49 | // check if the dropdown has been push to right
50 | if (parent.hasClass('navbar-right')) {
51 | classFound = true;
52 | }
53 | parent = parent.parent();
54 | }
55 |
56 | // add a different class according to the position of the dropdown
57 | if (classFound) {
58 | element.addClass('dropdown-submenu-right');
59 | } else {
60 | element.addClass('dropdown-submenu');
61 | }
62 |
63 | $compile(element.contents())(scope);
64 | }
65 | }
66 | };
67 | }]);
68 |
--------------------------------------------------------------------------------
/template/navbar-li.html:
--------------------------------------------------------------------------------
1 |
2 | {{leaf.name}}
3 |
--------------------------------------------------------------------------------
/template/navbar-li.html.js:
--------------------------------------------------------------------------------
1 | angular.module("template/navbar-li.html", []).run(["$templateCache", function($templateCache) {
2 | $templateCache.put("template/navbar-li.html",
3 | "\n" +
4 | " {{leaf.name}} \n" +
5 | " ");
6 | }]);
7 |
--------------------------------------------------------------------------------
/template/navbar-tree-li.html:
--------------------------------------------------------------------------------
1 |
2 | {{tree.name}}
3 |
4 |
--------------------------------------------------------------------------------
/template/navbar-tree-li.html.js:
--------------------------------------------------------------------------------
1 | angular.module("template/navbar-tree-li.html", []).run(["$templateCache", function($templateCache) {
2 | $templateCache.put("template/navbar-tree-li.html",
3 | "\n" +
4 | " {{tree.name}} \n" +
5 | " \n" +
6 | " ");
7 | }]);
8 |
--------------------------------------------------------------------------------
/template/navbar-ul.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/template/navbar-ul.html.js:
--------------------------------------------------------------------------------
1 | angular.module("template/navbar-ul.html", []).run(["$templateCache", function($templateCache) {
2 | $templateCache.put("template/navbar-ul.html",
3 | "");
6 | }]);
7 |
--------------------------------------------------------------------------------
/test/ui-navbar.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('ui-navbar', function () {
4 |
5 | var $compile, scope;
6 |
7 | beforeEach(module('ui.navbar'));
8 | beforeEach(module('template/navbar-ul.html'));
9 | beforeEach(module('template/navbar-li.html'));
10 | beforeEach(module('template/navbar-tree-li.html'));
11 |
12 | beforeEach(inject(function (_$compile_, _$rootScope_) {
13 | scope = _$rootScope_.$new();
14 | $compile = _$compile_;
15 | /*
16 | scope.tree = [{
17 | name: "States",
18 | link: "#",
19 | subtree: [{
20 | name: "state 1",
21 | link: "state1"
22 | }, {
23 | name: "state 2",
24 | link: "state2",
25 | subtree: [{
26 | name: "state unknown",
27 | link: "state unknown 2"
28 | }]
29 | }]
30 | }];*/
31 | }));
32 |
33 | function compileTemplate(template) {
34 | var el = $compile(angular.element(template))(scope);
35 | scope.$digest();
36 | return el;
37 | }
38 |
39 | function createUiNavbar() {
40 | return compileTemplate(
41 | ' \
42 | Dropdown \
43 | \
44 | \
45 | \
46 | '
47 | )
48 | }
49 |
50 | // Unit tests
51 | describe('simple structure with only one element', function () {
52 | var element;
53 |
54 | beforeEach(function () {
55 | scope.tree = [{
56 | name: "States",
57 | link: "#"
58 | }];
59 | element = createUiNavbar();
60 | });
61 |
62 | it('should have one element ul', function () {
63 | // check there is an element ul with a specific class name
64 | expect(element.find('ul')).toBeDefined();
65 | expect(element.find('ul').hasClass('dropdown-menu')).toBeTruthy();
66 | expect(element.find('ul').children().length).toBe(1);
67 | expect(element.find('ul').parent().hasClass('dropdown')).toBe(true);
68 | });
69 |
70 | it('should have one element li as parent of ul element having class dropdown', function () {
71 | expect(element.find('ul').parent().hasClass('dropdown')).toBe(true);
72 | });
73 |
74 | it('should have an element a inside ul.li', function () {
75 | expect(element.find('li')).toBeDefined();
76 | expect(element.find('li').find('a').attr('ui-sref')).toBe('#');
77 | expect(element.find('li').find('a').text()).toBe('States');
78 | });
79 | });
80 |
81 | // better to separate in another test, step by step
82 | describe('complex structure with subtree in a subtree', function () {
83 | var element;
84 |
85 | beforeEach(function () {
86 | scope.tree = [{
87 | name: "States",
88 | link: "#",
89 | subtree: [{
90 | name: "state 1",
91 | link: "state1"
92 | }, {
93 | name: "state 2",
94 | link: "state2",
95 | subtree: [{
96 | name: "state unknown",
97 | link: "state unknown 2"
98 | }]
99 | }]
100 | }];
101 | element = createUiNavbar();
102 | });
103 |
104 | it('should have the most external element having class dropdown', function () {
105 | // most external element has class dropdown
106 | expect(element.hasClass('dropdown')).toBeTruthy();
107 | });
108 |
109 | it('should have two elements with class dropdown-menu', function () {
110 |
111 | // find all the li elements, one is the more external one, than three nested in the first one
112 | expect(element.find('li').length).toBe(4);
113 |
114 | // focus on the first element li
115 | expect(element.find('li').eq(0).length).toEqual(1);
116 | // find all the ul elements
117 | expect(element.find('li').eq(0).find('ul').length).toEqual(2);
118 |
119 | // inside of li element there are 2 ul having class dropdown-menu
120 | expect(element.find('li').eq(0).find('ul').length).toEqual(2);
121 | expect(element.find('li').eq(0).find('ul').eq(0).hasClass('dropdown-menu'));
122 | expect(element.find('li').eq(0).find('ul').eq(1).hasClass('dropdown-menu'));
123 | });
124 |
125 | it('should have the first and most external element that has three nested elements', function () {
126 | /**
127 | * 1. First element
128 | *
129 | * state 1
130 | *
131 | *
132 | * 2. Second element at the same level of the first one
133 | *
141 | *
142 | * 3. element nested in the second one
143 | *
144 | * state unknown
145 | *
146 | */
147 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').length).toEqual(3);
148 | });
149 |
150 | it('should have the first and most external element with first element and only one state', function () {
151 | // if it has only one state it cannot have the class dropdown-submenu
152 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(0).hasClass('dropdown-submenu')).toBeFalsy();
153 |
154 | // it has just one state link
155 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(0).find('a').length).toEqual(1);
156 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(0).find('a').text()).toEqual('state 1');
157 | });
158 |
159 | it('should have the first and most external element with second element and only one state and a submenu', function () {
160 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(1).hasClass('dropdown-submenu')).toBeTruthy();
161 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(1).find('a').eq(0).text()).toEqual('state 2');
162 | });
163 |
164 | /**
165 | *
173 | */
174 | it('should have the first element with second element and only one state and a submenu', function () {
175 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(1).hasClass('dropdown-submenu')).toBeTruthy();
176 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(1).find('ul').length).toEqual(1);
177 | });
178 |
179 | it('should have the second element with one first nested element and only one state', function () {
180 | // get the second nested li
181 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(1).find('a').length).toEqual(2);
182 | // get the second nested li and the first state
183 | expect(element.find('li').eq(0).find('ul').eq(0).find('li').eq(1).find('a').eq(0).text()).toEqual('state 2');
184 | });
185 |
186 | /*it('should have the second element with one first nested element and only one state', function () {
187 |
188 | console.log(element.find('li').eq(0).hasClass('dropdown-submenu'));
189 | });*/
190 | });
191 | });
--------------------------------------------------------------------------------