├── README.md
├── css
└── navbar.style.css
├── html
└── navbar.template.html
└── js
├── navbar.decorator.js
├── navbar.directive.js
├── navbar.module.js
├── navbar.permission.js
└── navbar.provider.js
/README.md:
--------------------------------------------------------------------------------
1 | # angular-navbar
2 |
3 | ### Description
4 |
5 | An angular directive based on [ui-router](https://github.com/angular-ui/ui-router), [angular-permission](https://github.com/Narzerus/angular-permission) and [Bootstrap 3](http://getbootstrap.com/) for fast creation of responsive multi-level self-registered navigation bar.
6 |
7 | ### Usage
8 |
9 | Clone **angular-navbar** from repository to your **components** folder. If you do not have components folder in your project - create it. From the command line navigate to **components** folder and run
10 | ```sh
11 | git clone https://github.com/EugeneSnihovsky/angular-navbar
12 | ```
13 | Add **navbar** to an array of dependences of angular
14 | ```javascript
15 | angular.module('yourAppName', [
16 | 'ui.router',
17 | 'permission',
18 | 'navbar'
19 | ]);
20 | ```
21 | You can register your state in navbar menu now. Just add field **menu** to object of state params, when you register a new state.
22 |
23 | Description of fields in menu object:
24 | * **name** - name of the future menu item (string)
25 | * **priority** - sort priority of items in the menu (number)
26 | * **location** - optional field, an object with two fields:
27 | * **place** - an array of names (string) that will build submenu
28 | * **priority** - an array of numeric priority for sorting submenus
29 |
30 | Field **permissions** declared similarly as in the module [angular-permission](https://github.com/Narzerus/angular-permission).
31 |
32 | Example for declaring state **page1** with item name **First page** on the first level of menu
33 |
34 | ```javascript
35 | angular.module('yourApp', [])
36 | .config(function ($stateProvider) {
37 | $stateProvider
38 | .state('page1', {
39 | url: '/page1',
40 | templateUrl: 'app/page1/page1.html',
41 | menu: {
42 | name: 'First page',
43 | priority: 40
44 | }
45 | });
46 | });
47 | ```
48 | Example for declaring state **page2** with item name **my page** which will be located at submenu **page2 => page4 => page3**
49 |
50 | ```javascript
51 | angular.module('yourApp', [])
52 | .config(function ($stateProvider) {
53 | $stateProvider
54 | .state('page2', {
55 | url: '/page2',
56 | templateUrl: 'app/page2/page2.html',
57 | menu: {
58 | name: 'my page',
59 | priority: 20,
60 | location: {
61 | place: ['page2', 'page4', 'page3'],
62 | priority: [20, 10, 50]
63 | }
64 | }
65 | });
66 | });
67 | ```
68 | Adding **permission field** to display in the menu, only those items for which the user has access
69 | ```javascript
70 | angular.module('yourApp', [])
71 | .config(function ($stateProvider) {
72 | $stateProvider
73 | .state('page2', {
74 | url: '/page2',
75 | templateUrl: 'app/page2/page2.html',
76 | data: {
77 | permissions: {
78 | except: ['anon', 'wrongPass'],
79 | redirectTo: 'page1'
80 | }
81 | },
82 | menu: {
83 | name: 'my page',
84 | priority: 20,
85 | location: {
86 | place: ['page2', 'page4', 'page3'],
87 | priority: [20, 10, 50]
88 | }
89 | }
90 | });
91 | });
92 | ```
93 | or
94 | ```javascript
95 | angular.module('yourApp', [])
96 | .config(function ($stateProvider) {
97 | $stateProvider
98 | .state('page1', {
99 | url: '/page1',
100 | templateUrl: 'app/page1/page1.html',
101 | data: {
102 | permissions: {
103 | only: ['user1', 'user2'],
104 | redirectTo: 'page2'
105 | }
106 | },
107 | menu: {
108 | name: 'First page',
109 | priority: 40
110 | }
111 | });
112 | });
113 | ```
114 |
115 | ### Known Issues
116 |
117 | 1. Directive does not work without jQuery library.
118 |
--------------------------------------------------------------------------------
/css/navbar.style.css:
--------------------------------------------------------------------------------
1 | @import url(http://fonts.googleapis.com/css?family=Gloria+Hallelujah);
2 |
3 | .navbar-brand {
4 | font-family: "Gloria Hallelujah", Verdana, Tahoma;
5 | font-size: 23px;
6 | }
7 |
8 | .sub-menu {
9 | background-color: #333;
10 | }
11 |
12 | .sub-menu>a {
13 | color: #9d9d9d !important;
14 | padding-left: 10px !important;
15 | }
16 |
17 | .dropdown-menu {
18 | padding: 0px;
19 | margin-left: -1px;
20 | margin-right: -1px;
21 | min-width: 90px !important;
22 | }
23 |
24 | .dropdown-submenu {
25 | position:relative;
26 | }
27 | .dropdown-submenu>.dropdown-menu {
28 | top:0;
29 | right:100%;
30 | margin-top:6px;
31 | margin-left:-1px;
32 | -webkit-border-radius:0 6px 6px 6px;
33 | -moz-border-radius:0 6px 6px 6px;
34 | border-radius:0 6px 6px 6px;
35 | }
36 |
37 | .dropdown-submenu:hover>a:after {
38 | border-left-color:#ffffff;
39 | }
40 | .dropdown-submenu.pull-left {
41 | float:none;
42 | }
43 | .dropdown-submenu.pull-left>.dropdown-menu {
44 | left:-100%;
45 | margin-left:10px;
46 | -webkit-border-radius:6px 0 6px 6px;
47 | -moz-border-radius:6px 0 6px 6px;
48 | border-radius:6px 0 6px 6px;
49 | }
50 |
51 | .dropdown-submenu>a:before {
52 | display:block;
53 | content:" ";
54 | float:left;
55 | width: 0;
56 | height: 0;
57 | border-style: solid;
58 | border-color: transparent #cccccc transparent transparent;
59 | margin-top: 7px;
60 | margin-left: -5px;
61 | margin-right: 10px;
62 | }
63 |
64 | .dropdown-submenu-big>a:before {
65 | border-width: 4.5px 7.8px 4.5px 0;
66 | }
67 |
68 | .dropdown-submenu-small>a:before {
69 | margin-right: 7px;
70 | border-left: 5px solid transparent;
71 | border-right: 5px solid transparent;
72 | border-top: 5px solid #cccccc;
73 | }
74 |
75 | .dropdown-menu:hover,
76 | .dropdown-toggle:focus,
77 | li>[aria-expanded="true"],
78 | .navbar-brand:hover,
79 | .sub-menu>a:hover,
80 | .list-status:hover,
81 | .nav .open > a {
82 | color: #fff !important;
83 | background-color: #004444 !important;
84 | }
85 |
86 | .menu-active,
87 | .menu-active>a {
88 | font-weight: bold !important;
89 | text-decoration: underline;
90 | }
91 |
92 | .navbar-cheat {
93 | width: 100%;
94 | height: 45px;
95 | }
96 |
97 |
98 | .sub-link:before {
99 | display:block;
100 | content:" ";
101 | float:left;
102 | width: 12px;
103 | height: 5px;
104 | }
105 |
106 | /* Kukuri */
107 | .link-kukuri {
108 | font-family: "Gloria Hallelujah";
109 | outline: none;
110 | text-decoration: none !important;
111 | position: relative;
112 | font-size: 23px;
113 | line-height: 2;
114 | color: #c5c2b8;
115 | display: inline-block;
116 | }
117 |
118 | .link-kukuri:hover {
119 | color: #c5c2b8;
120 | }
121 |
122 | .link-kukuri:hover::after {
123 | -webkit-transform: translate3d(100%,0,0);
124 | transform: translate3d(100%,0,0);
125 | }
126 |
127 | .link-kukuri::before {
128 | content: attr(data-letters);
129 | position: absolute;
130 | z-index: 2;
131 | overflow: hidden;
132 | color: #424242;
133 | white-space: nowrap;
134 | width: 0%;
135 | -webkit-transition: width 0.4s 0.0s;
136 | transition: width 0.4s 0.0s;
137 | }
138 |
139 | .link-kukuri:hover::before {
140 | width: 100%;
141 | }
142 |
143 | .link-kukuri:focus {
144 | color: #9e9ba4;
145 | }
--------------------------------------------------------------------------------
/html/navbar.template.html:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
37 |
38 |
--------------------------------------------------------------------------------
/js/navbar.decorator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (function() {
4 | angular.module('navbar')
5 | .config(function ($stateProvider, navbarListProvider) {
6 | // добавляем в метод state функционал регистрации пунктов меню
7 | $stateProvider.decorator('state', function (obj) {
8 | var menu = obj.menu,
9 | permissions = (obj.data) ? obj.data.permissions : null;
10 | // если в коде не указана регистрация текущего стейта в меню - ничего не делаем
11 | if(!menu) {
12 | return;
13 | }
14 | menu.state = obj.name;
15 | // регистрируем права доступа пункта при их наличии
16 | if(permissions) {
17 | menu.permissions = {};
18 | if(permissions.except) {
19 | menu.permissions.except = permissions.except;
20 | } else if(permissions.only) {
21 | menu.permissions.only = permissions.only;
22 | } else {
23 | delete menu.permissions;
24 | }
25 | }
26 | // регистрируем пункт меню по скомпонованному объекту menu
27 | navbarListProvider.add(menu);
28 | });
29 | });
30 | })();
31 |
--------------------------------------------------------------------------------
/js/navbar.directive.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (function () {
4 | angular.module('navbar')
5 | .directive('navbar', function ($document, $state, navbarList, navPermission) {
6 | return {
7 | restrict: 'A',
8 | scope: {
9 | name: '@',
10 | sref: '@'
11 | },
12 | templateUrl: '/components/angular-navbar/html/navbar.template.html',
13 | link: function (scope, elem) {
14 | var openedMenu = null,
15 | openedSubMenu = null,
16 | username = navPermission.getUser($state.params);
17 | // присваиваем нашему DOM элементу необходимые классы и атрибуты для работы bootstrap
18 | elem.addClass('navbar navbar-inverse navbar-fixed-top');
19 | elem.attr('role', 'navigation');
20 | // редактируем список пунктов меню в соотвествии с доступом и передаем его в scope директивы
21 | if(username) {
22 | navPermission.acceptPermission(navbarList.list, username);
23 | }
24 | scope.navbar = navbarList.list;
25 | // открытие/сокрытие меню на телефонах или при узком экране браузера
26 | scope.collapseMenu = function ($event) {
27 | var navbar = elem.find('#navbar'),
28 | expanded = navbar.hasClass('in');
29 |
30 | navbar.attr('aria-expanded', !expanded);
31 | scope.navCollapsed = (expanded) ? '' : 'in';
32 |
33 | closeAllMenu();
34 | stopBubleAndPropagation($event);
35 | };
36 | // присвоение класса активного пункта меню соответствующей страницы и класса подменю, если пункт содержит подпункты
37 | scope.menuClass = function (item, level) {
38 | var status = false,
39 | activePage = getActivePage($state.current.name),
40 | currentPage = (item.pop) ? item[0] : item,
41 | classList = (level === 'firstLevel') ? 'dropdown dropdown-firstLevel ' :
42 | 'menu-item dropdown dropdown-submenu ',
43 | activeClass = (currentPage === activePage || isActive(item, activePage, status) ) ?
44 | 'menu-active' : '';
45 |
46 | if(item.pop) {
47 | return classList + activeClass;
48 | } else {
49 | return activeClass;
50 | }
51 | };
52 | // получение имени активного пункта меню в соответствии с открытой страницей (состоянием)
53 | function getActivePage(state, currentList) {
54 | var name;
55 |
56 | if(!currentList) {
57 | currentList = scope.navbar;
58 | }
59 |
60 | for(var i = (currentList[0].name) ? 0 : 1; i < currentList.length; i++) {
61 | if(currentList[i].state === state) {
62 | return currentList[i].name;
63 | } else if(currentList[i].name.pop) {
64 | name = getActivePage(state, currentList[i].name);
65 | }
66 | }
67 | return name;
68 | }
69 | // проверка, является ли пункт меню активным
70 | function isActive (item, activePage, status) {
71 | if(item.pop) {
72 | for(var i = 1; i < item.length; i++) {
73 | if(item[i].name.pop) {
74 | status = isActive(item[i].name, activePage, status);
75 | } else if(item[i].name === activePage) {
76 | return true;
77 | }
78 | }
79 | } else if(item === activePage) {
80 | return true;
81 | }
82 | return status;
83 | }
84 | // раскрытие сокрытие подпунктов меню по кликку или наведению мыши (страшная функция, т.к. учтены варианты разного разрешения экрана)
85 | scope.expandMenu = function ($event) {
86 | var clickedElem = $($event.currentTarget),
87 | parentClicked = $($event.currentTarget.parentElement),
88 | expanded = clickedElem.attr('aria-expanded'),
89 | isOpened = parentClicked.hasClass('open'),
90 | attrExpanded = (expanded === 'false'),
91 | allOpenedMenu = parentClicked.parent().find('.open'),
92 | smallWindow = window.innerWidth < 768,
93 | eventMouseEnter = $event.type === 'mouseenter',
94 | subMenuAll = elem.find('.dropdown-submenu');
95 |
96 | if(!smallWindow || !eventMouseEnter) {
97 | allOpenedMenu.removeClass('open');
98 | clickedElem.attr('aria-expanded', attrExpanded);
99 |
100 | if(isOpened && !eventMouseEnter) {
101 | parentClicked.removeClass('open');
102 | } else {
103 | parentClicked.addClass('open');
104 | openedMenu = clickedElem; //**
105 | }
106 | }
107 |
108 | subMenuAll.removeClass('dropdown-submenu-small dropdown-submenu-big');
109 | if(smallWindow) {
110 | subMenuAll.addClass('dropdown-submenu-small');
111 | } else {
112 | subMenuAll.addClass('dropdown-submenu-big');
113 | }
114 | stopBubleAndPropagation($event);
115 | };
116 | // закрытие подменю при наведении на соседний пункт в основном меню
117 | scope.closeOnMoveMenu = function () {
118 | var smallWindow = window.innerWidth < 768;
119 |
120 | if(openedMenu && !smallWindow) {
121 | var clickedLink = openedMenu,
122 | clickedElement = clickedLink.parent();
123 |
124 | clickedElement.removeClass('open');
125 | clickedLink.attr('aria-expanded', false);
126 | openedMenu = null;
127 | }
128 | };
129 | // раскрытие сокрытие подпунктов подменю (аналогично функции с 92 строки)
130 | scope.expandSubMenu = function ($event) {
131 | var elemClicked = $($event.currentTarget.parentElement),
132 | smallWindow = window.innerWidth < 768,
133 | eMouseEnter = $event.type === 'mouseenter',
134 | sameElement = elemClicked.hasClass('open');
135 |
136 | if(!smallWindow || !eMouseEnter) { // потом подумать как упростить
137 | if(!sameElement && !eMouseEnter || !eMouseEnter || !sameElement) {
138 | elemClicked.parent().find('.open').removeClass('open');
139 | }
140 | if(!sameElement) {
141 | elemClicked.addClass('open');
142 | openedSubMenu = elemClicked;
143 | }
144 | }
145 | stopBubleAndPropagation($event);
146 | };
147 | // закрытие подменю при наведении на соседний подпункт в подменю (звучит то как:))
148 | scope.closeOnMoveSubMenu = function ($event) {
149 | var smallWindow = window.innerWidth < 768;
150 |
151 | if(openedSubMenu && !smallWindow) {
152 | var clickedElement = openedSubMenu,
153 | savedList = clickedElement.parent(),
154 | currentList = $($event.target).parent().parent();
155 |
156 | if(savedList[0] === currentList[0]) {
157 | clickedElement.removeClass('open');
158 | openedSubMenu = null;
159 | }
160 | }
161 | };
162 |
163 | scope.closeMenu = closeMenu;
164 | // удаляем всех слушателей с документа при его уничтожении
165 | var $body = $document.find('html');
166 | elem.bind('$destroy', function() {
167 | $body.unbind(); //не хватает проверки на удаленный элемент
168 | });
169 | // при клике вне меню - закрываем все открытые позиции
170 | $body.bind('click', closeMenu);
171 |
172 | function closeMenu ($event) {
173 | var elemClicked = $event.relatedTarget || $event.target;
174 |
175 | if(isClickOutNavbar(elemClicked)) {
176 | closeAllMenu();
177 | }
178 | }
179 | // рекурсивно поднимаемся по родителям элемента, чтобы узнать, был клик по меню или нет
180 | function isClickOutNavbar(elem) {
181 | if($(elem).hasClass('dropdown-firstLevel')) {
182 | return false;
183 | }
184 |
185 | if(elem.parentElement !== null) {
186 | return isClickOutNavbar(elem.parentElement);
187 | } else {
188 | return true;
189 | }
190 | }
191 | // закрываем все открытые пункты и подпункты меню
192 | function closeAllMenu() {
193 | elem.find('.open').removeClass('open');
194 | elem.find('[aria-expanded=true]').attr('aria-expanded', false);
195 | }
196 | // служебная функция предотвращения действий браузера поумолчанию и всплывающих событий
197 | function stopBubleAndPropagation($event) {
198 | $event.stopPropagation();
199 | $event.preventDefault();
200 | }
201 |
202 | }
203 | };
204 | });
205 | })();
206 |
207 |
208 | // попробовать перенести применение пермишна в секцию рун
209 |
210 |
211 | // добавить недоступный курсор при наведении на уже нажатую ссылку в меню
212 | // втулить $ jQuery зависимость в директиву, чтобы не подключать его
213 | // разбить сложную функцию клик+онмове, на две простые
214 |
215 |
216 | // сделать выбор через || откуда брать массив меню, из скоупа или из провайдера
217 |
218 | // попробовать прикрутить анимацию загрузки на каждую страницу в пунктах меню
219 |
220 | // отсортировать функции, моусемов + онклик + другие
221 |
222 |
223 |
224 | // сайт пункт хитрости ангуляра и js
--------------------------------------------------------------------------------
/js/navbar.module.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (function () {
4 | angular.module('navbar', ['ui.router']);
5 | })();
6 |
--------------------------------------------------------------------------------
/js/navbar.permission.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (function () {
4 | angular.module('navbar')
5 | .factory('navPermission', function (Permission, $q) {
6 | // перебираем все роли и возвращаем подходящую в виде промиса
7 | function getUser(params) {
8 | var users = Permission.roleValidations,
9 | names = Object.keys(users),
10 | promisesArr = [];
11 |
12 | for(var i = 0; i < names.length; i++) {
13 | var current = names[i],
14 | validUser = $q.when( users[current](params) );
15 | promisesArr.push(validUser);
16 | }
17 |
18 | return $q.all(promisesArr).then(function (users) {
19 | for(var i = 0; i < users.length; i++) {
20 | if(users[i]) {
21 | return names[i];
22 | }
23 | }
24 | return null;
25 | });
26 | }
27 | // если пришел промис, ждем его разрешения и меняем меню, если пользователь - сразу меняем меню
28 | function acceptPermission (list, username) {
29 | if(!username.then) {
30 | return changeList(list, username);
31 | } else {
32 | return username.then(function (username) {
33 | return changeList(list, username);
34 | });
35 | }
36 | }
37 | // рекурсивно пробегаемся по массиву меню и удаляем пункты, которые запрещены для текущей роли
38 | function changeList(list, username) {
39 | for(var i = (list[0].name) ? 0 : 1; i < list.length; i++) {
40 | if(list[i].permissions) {
41 | if(list[i].permissions.except) {
42 | var except = list[i].permissions.except;
43 |
44 | for(var j = 0; j < except.length; j++) {
45 | if(except[j] === username) {
46 | list.splice(i--, 1);
47 | }
48 | }
49 | } else if(list[i].permissions.only) {
50 | var only = list[i].permissions.only,
51 | accessDenided = true;
52 |
53 | for(j = 0; j < only.length; j++) {
54 | if(only[j] === username) {
55 | accessDenided = false;
56 | }
57 | }
58 | if(accessDenided) {
59 | list.splice(i--, 1);
60 | }
61 | }
62 | } else if(list[i].name.pop) {
63 | list[i].name = changeList( list[i].name, username);
64 | if(list[i].name.length === 1 ) {
65 | list.splice(i--, 1);
66 | }
67 | }
68 | }
69 |
70 | return list;
71 | }
72 | // возвращаем созданные методы фабрики
73 | return {
74 | getUser: getUser,
75 | acceptPermission: acceptPermission
76 | };
77 | });
78 | })();
--------------------------------------------------------------------------------
/js/navbar.provider.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | (function () {
4 | angular.module('navbar')
5 | .provider('navbarList', function () {
6 | var list = [];
7 | // основная функция добавления пункта в меню
8 | this.add = function (obj) {
9 | // проверка на правильно заданные параметры расположения пункта
10 | if(obj.location) {
11 | if(obj.location.place.length !== obj.location.priority.length ||
12 | !obj.location.place.pop || !obj.location.priority.pop) {
13 | console.log('Warning! Bad location params for menu "' + obj.name + '". Skip item');
14 | return;
15 | }
16 | }
17 | // добавление пункта на первый уровень меню при отстутствии местоположения
18 | if(!obj.location) {
19 | var name = obj.name;
20 | for(var i = 0; i < list.length; i++) { // рассказать про тернарный оператор и утиную типизацию
21 | var currentName = (list[i].name.pop) ? list[i].name[0] : list[i].name;
22 | if(currentName === name) {
23 | console.log('Warning! Duplicate menu "' + name + '". Skip item');
24 | return;
25 | }
26 | }
27 | list.push(obj);
28 | list.sort(sortByPriority);
29 | return;
30 | }
31 | // поиск пункта, в который нужно добавить подпункт согласно местоположению
32 | var place = obj.location.place.shift(),
33 | priority = obj.location.priority.shift();
34 |
35 | for(i = 0; i < list.length; i++) { // описать в статье, что i блочная не в JS
36 | var currentSubName = (list[i].name.pop) ? list[i].name[0] : null;
37 | if(place === currentSubName) {
38 | list[i].name = changeExistPart(obj, list[i].name);
39 | if(priority !== list[i].priority) {
40 | console.log('Warning! Priority of menu "' + list[i].name + '" has been changed from "' +
41 | list[i].priority + '" to "' + priority + '"');
42 | list[i].priority = priority;
43 | list.sort(sortByPriority);
44 | }
45 | return;
46 | }
47 | currentName = list[i].name;
48 | if(place === currentName) {
49 | console.log('Warning! Duplicate submenu "' + place + '". Skip item');
50 | return;
51 | }
52 | }
53 | // ни одно вышеописанное условие не совпало, добавляем новый пункт со всеми вложениями
54 | list.push( {
55 | name: [place, makeOriginalPart(obj)],
56 | priority: priority
57 | } );
58 | list.sort(sortByPriority);
59 | };
60 | // рекурсивный поиск места в подпунктах меню для вставки нового пункта
61 | function changeExistPart(obj, list) {
62 | var place = obj.location.place.shift(),
63 | priority = obj.location.priority.shift(), // возможно необходимо сделать двойной приоритет
64 | searchName = (place) ? place : obj.name;
65 |
66 | for(var i = 1; i < list.length; i++) {
67 | var currentName = (list[i].name.pop) ? list[i].name[0] : list[i].name;
68 | if(searchName === currentName) {
69 | if(!list[i].name.pop || (!place && list[i].name.pop) ) {
70 | console.log('Warning! Duplicate menu "' + searchName + '". Skip item');
71 | return list;
72 | } else {
73 | list[i].name = changeExistPart(obj, list[i].name);
74 | if(priority !== list[i].priority) {
75 | console.log('Warning! Priority of menu "' + list[i].name +
76 | '" has been changed from "' + list[i].priority + '" to "' + priority + '"');
77 | list[i].priority = priority;
78 | list.sort(sortByPriority);
79 | }
80 | return list;
81 | }
82 | }
83 | }
84 | if(!place) {
85 | delete obj.location;
86 | list.push(obj);
87 | } else {
88 | list.push({
89 | name: [place, makeOriginalPart(obj)],
90 | priority: priority
91 | });
92 | }
93 | list.sort(sortByPriority);
94 | return list;
95 | }
96 | // рекурсивное создание новой, оригинальной части пункта меню с подпунктами
97 | function makeOriginalPart (obj) {
98 | var place = obj.location.place.shift(),
99 | priority = obj.location.priority.shift();
100 |
101 | if(place) {
102 | var menu = {
103 | priority: priority,
104 | name: [place, makeOriginalPart(obj)]
105 | };
106 | } else {
107 | delete obj.location;
108 | menu = obj;
109 | }
110 | return menu;
111 | }
112 | // функция сортировки пунктов меню по приоритету
113 | function sortByPriority(a, b) {
114 | return a.priority - b.priority;
115 | }
116 | // служебная функция для работы провайдера angularJS
117 | this.$get = function () {
118 | return {
119 | list: list,
120 | add: this.add
121 | };
122 | };
123 | });
124 | })();
125 |
126 | // сделать проверку на повторение стейтов в меню. Пушаем их все в один массив и прогоняем на совпадение
127 |
--------------------------------------------------------------------------------