feat(core): Modify core module to implement style guidelines.

Update the core module to implement the style guidelines.
Reduce size of init.js - moved filter logic out to it's own config.
Rename Menus to menuService
This commit is contained in:
Ryan Hutchison
2016-02-11 00:20:17 -05:00
committed by Ryan Hutchison
parent 59e6daa63d
commit b2462ec86c
28 changed files with 613 additions and 536 deletions

View File

@@ -3,5 +3,5 @@
app.registerModule('articles', ['core']);// The core module is required for special route handling; see /core/client/config/core.client.routes
app.registerModule('articles.services');
app.registerModule('articles.routes', ['ui.router', 'articles.services']);
app.registerModule('articles.routes', ['ui.router', 'core.routes', 'articles.services']);
}(ApplicationConfiguration));

View File

@@ -5,10 +5,10 @@
.module('articles')
.run(menuConfig);
menuConfig.$inject = ['Menus'];
menuConfig.$inject = ['menuService'];
function menuConfig(Menus) {
Menus.addMenuItem('topbar', {
function menuConfig(menuService) {
menuService.addMenuItem('topbar', {
title: 'Articles',
state: 'articles',
type: 'dropdown',
@@ -16,13 +16,13 @@
});
// Add the dropdown list item
Menus.addSubMenuItem('topbar', 'articles', {
menuService.addSubMenuItem('topbar', 'articles', {
title: 'List Articles',
state: 'articles.list'
});
// Add the dropdown create item
Menus.addSubMenuItem('topbar', 'articles', {
menuService.addSubMenuItem('topbar', 'articles', {
title: 'Create Article',
state: 'articles.create',
roles: ['user']

View File

@@ -2,5 +2,5 @@
'use strict';
app.registerModule('chat', ['core']);
app.registerModule('chat.routes', ['ui.router']);
app.registerModule('chat.routes', ['ui.router', 'core.routes']);
}(ApplicationConfiguration));

View File

@@ -5,11 +5,11 @@
.module('chat')
.run(menuConfig);
menuConfig.$inject = ['Menus'];
menuConfig.$inject = ['menuService'];
function menuConfig(Menus) {
function menuConfig(menuService) {
// Set top bar menu items
Menus.addMenuItem('topbar', {
menuService.addMenuItem('topbar', {
title: 'Chat',
state: 'chat'
});

View File

@@ -1,23 +1,22 @@
'use strict';
(function (window) {
'use strict';
// Init the application configuration module for AngularJS application
var ApplicationConfiguration = (function () {
// Init module configuration options
var applicationModuleName = 'mean';
var applicationModuleVendorDependencies = ['ngResource', 'ngAnimate', 'ngMessages', 'ui.router', 'ui.bootstrap', 'angularFileUpload'];
var service = {
applicationModuleName: applicationModuleName,
applicationModuleVendorDependencies: ['ngResource', 'ngAnimate', 'ngMessages', 'ui.router', 'ui.bootstrap', 'angularFileUpload'],
registerModule: registerModule
};
window.ApplicationConfiguration = service;
// Add a new vertical module
var registerModule = function (moduleName, dependencies) {
function registerModule(moduleName, dependencies) {
// Create angular module
angular.module(moduleName, dependencies || []);
// Add the module to the AngularJS configuration file
angular.module(applicationModuleName).requires.push(moduleName);
};
return {
applicationModuleName: applicationModuleName,
applicationModuleVendorDependencies: applicationModuleVendorDependencies,
registerModule: registerModule
};
}());
}
}(window));

View File

@@ -1,80 +1,45 @@
'use strict';
(function (app) {
'use strict';
// Start by defining the main module and adding the module dependencies
angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies);
// Start by defining the main module and adding the module dependencies
angular
.module(app.applicationModuleName, app.applicationModuleVendorDependencies);
// Setting HTML5 Location Mode
angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider', '$httpProvider',
function ($locationProvider, $httpProvider) {
// Setting HTML5 Location Mode
angular
.module(app.applicationModuleName)
.config(bootstrapConfig);
function bootstrapConfig($locationProvider, $httpProvider) {
$locationProvider.html5Mode(true).hashPrefix('!');
$httpProvider.interceptors.push('authInterceptor');
}
]);
angular.module(ApplicationConfiguration.applicationModuleName).run(function ($rootScope, $state, Authentication) {
bootstrapConfig.$inject = ['$locationProvider', '$httpProvider'];
// Check authentication before changing state
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
if (toState.data && toState.data.roles && toState.data.roles.length > 0) {
var allowed = false;
toState.data.roles.forEach(function (role) {
if ((role === 'guest') || (Authentication.user && Authentication.user.roles !== undefined && Authentication.user.roles.indexOf(role) !== -1)) {
allowed = true;
return true;
}
});
// Then define the init function for starting up the application
angular.element(document).ready(init);
if (!allowed) {
event.preventDefault();
if (Authentication.user !== undefined && typeof Authentication.user === 'object') {
$state.go('forbidden');
} else {
$state.go('authentication.signin').then(function () {
storePreviousState(toState, toParams);
});
}
function init() {
// Fixing facebook bug with redirect
if (window.location.hash && window.location.hash === '#_=_') {
if (window.history && history.pushState) {
window.history.pushState('', document.title, window.location.pathname);
} else {
// Prevent scrolling by storing the page's current scroll offset
var scroll = {
top: document.body.scrollTop,
left: document.body.scrollLeft
};
window.location.hash = '';
// Restore the scroll offset, should be flicker free
document.body.scrollTop = scroll.top;
document.body.scrollLeft = scroll.left;
}
}
});
// Record previous state
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
storePreviousState(fromState, fromParams);
});
// Store previous state
function storePreviousState(state, params) {
// only store this state if it shouldn't be ignored
if (!state.data || !state.data.ignoreState) {
$state.previous = {
state: state,
params: params,
href: $state.href(state, params)
};
}
// Then init the app
angular.bootstrap(document, [app.applicationModuleName]);
}
});
// Then define the init function for starting up the application
angular.element(document).ready(function () {
// Fixing facebook bug with redirect
if (window.location.hash && window.location.hash === '#_=_') {
if (window.history && history.pushState) {
window.history.pushState('', document.title, window.location.pathname);
} else {
// Prevent scrolling by storing the page's current scroll offset
var scroll = {
top: document.body.scrollTop,
left: document.body.scrollLeft
};
window.location.hash = '';
// Restore the scroll offset, should be flicker free
document.body.scrollTop = scroll.top;
document.body.scrollLeft = scroll.left;
}
}
// Then init the app
angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]);
});
}(ApplicationConfiguration));

View File

@@ -1,12 +1,18 @@
'use strict';
(function () {
'use strict';
angular.module('core.admin').run(['Menus',
function (Menus) {
Menus.addMenuItem('topbar', {
angular
.module('core.admin')
.run(menuConfig);
menuConfig.$inject = ['menuService'];
function menuConfig(menuService) {
menuService.addMenuItem('topbar', {
title: 'Admin',
state: 'admin',
type: 'dropdown',
roles: ['admin']
});
}
]);
}());

View File

@@ -1,8 +1,13 @@
'use strict';
(function () {
'use strict';
// Setting up route
angular.module('core.admin.routes').config(['$stateProvider',
function ($stateProvider) {
angular
.module('core.admin.routes')
.config(routeConfig);
routeConfig.$inject = ['$stateProvider'];
function routeConfig($stateProvider) {
$stateProvider
.state('admin', {
abstract: true,
@@ -13,4 +18,4 @@ angular.module('core.admin.routes').config(['$stateProvider',
}
});
}
]);
}());

View File

@@ -2,44 +2,41 @@
'use strict';
angular
.module('core')
.run(MenuConfig);
.module('core')
.run(menuConfig);
MenuConfig.$inject = ['Menus'];
menuConfig.$inject = ['menuService'];
function MenuConfig(Menus) {
Menus.addMenu('account', {
function menuConfig(menuService) {
menuService.addMenu('account', {
roles: ['user']
});
Menus.addMenuItem('account', {
menuService.addMenuItem('account', {
title: '',
state: 'settings',
type: 'dropdown',
roles: ['user']
});
Menus.addSubMenuItem('account', 'settings', {
menuService.addSubMenuItem('account', 'settings', {
title: 'Edit Profile',
state: 'settings.profile'
});
Menus.addSubMenuItem('account', 'settings', {
menuService.addSubMenuItem('account', 'settings', {
title: 'Edit Profile Picture',
state: 'settings.picture'
});
Menus.addSubMenuItem('account', 'settings', {
menuService.addSubMenuItem('account', 'settings', {
title: 'Change Password',
state: 'settings.password'
});
Menus.addSubMenuItem('account', 'settings', {
menuService.addSubMenuItem('account', 'settings', {
title: 'Manage Social Accounts',
state: 'settings.accounts'
});
}
}());

View File

@@ -0,0 +1,57 @@
(function () {
'use strict';
angular
.module('core')
.run(routeFilter);
routeFilter.$inject = ['$rootScope', '$state', 'Authentication'];
function routeFilter($rootScope, $state, Authentication) {
$rootScope.$on('$stateChangeStart', stateChangeStart);
$rootScope.$on('$stateChangeSuccess', stateChangeSuccess);
function stateChangeStart(event, toState, toParams, fromState, fromParams) {
// Check authentication before changing state
if (toState.data && toState.data.roles && toState.data.roles.length > 0) {
var allowed = false;
for (var i = 0, roles = toState.data.roles; i < roles.length; i++) {
if ((roles[i] === 'guest') || (Authentication.user && Authentication.user.roles !== undefined && Authentication.user.roles.indexOf(roles[i]) !== -1)) {
allowed = true;
break;
}
}
if (!allowed) {
event.preventDefault();
if (Authentication.user !== undefined && typeof Authentication.user === 'object') {
$state.transitionTo('forbidden');
} else {
$state.go('authentication.signin').then(function () {
// Record previous state
storePreviousState(toState, toParams);
});
}
}
}
}
function stateChangeSuccess(event, toState, toParams, fromState, fromParams) {
// Record previous state
storePreviousState(fromState, fromParams);
}
// Store previous state
function storePreviousState(state, params) {
// only store this state if it shouldn't be ignored
if (!state.data || !state.data.ignoreState) {
$state.previous = {
state: state,
params: params,
href: $state.href(state, params)
};
}
}
}
}());

View File

@@ -1,9 +1,13 @@
'use strict';
(function () {
'use strict';
// Setting up route
angular.module('core').config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
angular
.module('core.routes')
.config(routeConfig);
routeConfig.$inject = ['$stateProvider', '$urlRouterProvider'];
function routeConfig($stateProvider, $urlRouterProvider) {
$urlRouterProvider.rule(function ($injector, $location) {
var path = $location.path();
var hasTrailingSlash = path.length > 1 && path[path.length - 1] === '/';
@@ -22,11 +26,12 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
});
});
// Home state routing
$stateProvider
.state('home', {
url: '/',
templateUrl: 'modules/core/client/views/home.client.view.html'
templateUrl: 'modules/core/client/views/home.client.view.html',
controller: 'HomeController',
controllerAs: 'vm'
})
.state('not-found', {
url: '/not-found',
@@ -53,4 +58,4 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
}
});
}
]);
}());

View File

@@ -1,26 +1,25 @@
'use strict';
(function () {
'use strict';
angular.module('core').controller('HeaderController', ['$scope', '$state', 'Authentication', 'Menus',
function ($scope, $state, Authentication, Menus) {
// Expose view variables
$scope.$state = $state;
$scope.authentication = Authentication;
angular
.module('core')
.controller('HeaderController', HeaderController);
// Get the topbar menu
$scope.menu = Menus.getMenu('topbar');
HeaderController.$inject = ['$scope', '$state', 'Authentication', 'menuService'];
// Get the account menu
$scope.accountMenu = Menus.getMenu('account').items[0];
function HeaderController($scope, $state, Authentication, menuService) {
var vm = this;
// Toggle the menu items
$scope.isCollapsed = false;
$scope.toggleCollapsibleMenu = function () {
$scope.isCollapsed = !$scope.isCollapsed;
};
vm.accountMenu = menuService.getMenu('account').items[0];
vm.authentication = Authentication;
vm.isCollapsed = false;
vm.menu = menuService.getMenu('topbar');
// Collapsing the menu after navigation
$scope.$on('$stateChangeSuccess', function () {
$scope.isCollapsed = false;
});
$scope.$on('$stateChangeSuccess', stateChangeSuccess);
function stateChangeSuccess() {
// Collapsing the menu after navigation
vm.isCollapsed = false;
}
}
]);
}());

View File

@@ -1,8 +1,11 @@
'use strict';
(function () {
'use strict';
angular.module('core').controller('HomeController', ['$scope', 'Authentication',
function ($scope, Authentication) {
// This provides Authentication context.
$scope.authentication = Authentication;
angular
.module('core')
.controller('HomeController', HomeController);
function HomeController() {
var vm = this;
}
]);
}());

View File

@@ -1,6 +1,8 @@
'use strict';
(function (app) {
'use strict';
// Use Application configuration module to register a new module
ApplicationConfiguration.registerModule('core');
ApplicationConfiguration.registerModule('core.admin', ['core']);
ApplicationConfiguration.registerModule('core.admin.routes', ['ui.router']);
app.registerModule('core');
app.registerModule('core.routes', ['ui.router']);
app.registerModule('core.admin', ['core']);
app.registerModule('core.admin.routes', ['ui.router']);
}(ApplicationConfiguration));

View File

@@ -1,79 +1,85 @@
'use strict';
(function () {
'use strict';
/**
* Edits by Ryan Hutchison
* Credit: https://github.com/paulyoder/angular-bootstrap-show-errors */
// https://gist.github.com/rhutchison/c8c14946e88a1c8f9216
angular.module('core')
.directive('showErrors', ['$timeout', '$interpolate', function ($timeout, $interpolate) {
var linkFn = function (scope, el, attrs, formCtrl) {
var inputEl,
inputName,
inputNgEl,
options,
showSuccess,
toggleClasses,
initCheck = false,
showValidationMessages = false,
blurred = false;
angular
.module('core')
.directive('showErrors', showErrors);
options = scope.$eval(attrs.showErrors) || {};
showSuccess = options.showSuccess || false;
inputEl = el[0].querySelector('.form-control[name]') || el[0].querySelector('[name]');
inputNgEl = angular.element(inputEl);
inputName = $interpolate(inputNgEl.attr('name') || '')(scope);
showErrors.$inject = ['$timeout', '$interpolate'];
if (!inputName) {
throw new Error('show-errors element has no child input elements with a \'name\' attribute class');
}
var reset = function () {
return $timeout(function () {
el.removeClass('has-error');
el.removeClass('has-success');
showValidationMessages = false;
}, 0, false);
};
scope.$watch(function () {
return formCtrl[inputName] && formCtrl[inputName].$invalid;
}, function (invalid) {
return toggleClasses(invalid);
});
scope.$on('show-errors-check-validity', function (event, name) {
if (angular.isUndefined(name) || formCtrl.$name === name) {
initCheck = true;
showValidationMessages = true;
return toggleClasses(formCtrl[inputName].$invalid);
}
});
scope.$on('show-errors-reset', function (event, name) {
if (angular.isUndefined(name) || formCtrl.$name === name) {
return reset();
}
});
toggleClasses = function (invalid) {
el.toggleClass('has-error', showValidationMessages && invalid);
if (showSuccess) {
return el.toggleClass('has-success', showValidationMessages && !invalid);
}
};
};
return {
function showErrors($timeout, $interpolate) {
var directive = {
restrict: 'A',
require: '^form',
compile: function (elem, attrs) {
if (attrs.showErrors.indexOf('skipFormGroupCheck') === -1) {
if (!(elem.hasClass('form-group') || elem.hasClass('input-group'))) {
throw new Error('show-errors element does not have the \'form-group\' or \'input-group\' class');
compile: compile
};
return directive;
function compile(elem, attrs) {
if (attrs.showErrors.indexOf('skipFormGroupCheck') === -1) {
if (!(elem.hasClass('form-group') || elem.hasClass('input-group'))) {
throw new Error('show-errors element does not have the \'form-group\' or \'input-group\' class');
}
}
return linkFn;
function linkFn(scope, el, attrs, formCtrl) {
var inputEl,
inputName,
inputNgEl,
options,
showSuccess,
initCheck = false,
showValidationMessages = false;
options = scope.$eval(attrs.showErrors) || {};
showSuccess = options.showSuccess || false;
inputEl = el[0].querySelector('.form-control[name]') || el[0].querySelector('[name]');
inputNgEl = angular.element(inputEl);
inputName = $interpolate(inputNgEl.attr('name') || '')(scope);
if (!inputName) {
throw new Error('show-errors element has no child input elements with a \'name\' attribute class');
}
scope.$watch(function () {
return formCtrl[inputName] && formCtrl[inputName].$invalid;
}, toggleClasses);
scope.$on('show-errors-check-validity', checkValidity);
scope.$on('show-errors-reset', reset);
function checkValidity(event, name) {
if (angular.isUndefined(name) || formCtrl.$name === name) {
initCheck = true;
showValidationMessages = true;
return toggleClasses(formCtrl[inputName].$invalid);
}
}
function reset(event, name) {
if (angular.isUndefined(name) || formCtrl.$name === name) {
return $timeout(function () {
el.removeClass('has-error');
el.removeClass('has-success');
showValidationMessages = false;
}, 0, false);
}
}
function toggleClasses(invalid) {
el.toggleClass('has-error', showValidationMessages && invalid);
if (showSuccess) {
return el.toggleClass('has-success', showValidationMessages && !invalid);
}
}
return linkFn;
}
};
}]);
}
}
}());

View File

@@ -0,0 +1,34 @@
(function () {
'use strict';
angular
.module('core')
.factory('authInterceptor', authInterceptor);
authInterceptor.$inject = ['$q', '$injector', 'Authentication'];
function authInterceptor($q, $injector, Authentication) {
var service = {
responseError: responseError
};
return service;
function responseError(rejection) {
if (!rejection.config.ignoreAuthModule) {
switch (rejection.status) {
case 401:
// Deauthenticate the global user
Authentication.user = null;
$injector.get('$state').transitionTo('authentication.signin');
break;
case 403:
$injector.get('$state').transitionTo('forbidden');
break;
}
}
// otherwise, default behaviour
return $q.reject(rejection);
}
}
}());

View File

@@ -1,24 +0,0 @@
'use strict';
angular.module('core').factory('authInterceptor', ['$q', '$injector', 'Authentication',
function ($q, $injector, Authentication) {
return {
responseError: function(rejection) {
if (!rejection.config.ignoreAuthModule) {
switch (rejection.status) {
case 401:
// Deauthenticate the global user
Authentication.user = null;
$injector.get('$state').transitionTo('authentication.signin');
break;
case 403:
$injector.get('$state').transitionTo('forbidden');
break;
}
}
// otherwise, default behaviour
return $q.reject(rejection);
}
};
}
]);

View File

@@ -0,0 +1,195 @@
(function () {
'use strict';
angular
.module('core')
.factory('menuService', menuService);
function menuService() {
var shouldRender;
var service = {
addMenu: addMenu,
addMenuItem: addMenuItem,
addSubMenuItem: addSubMenuItem,
defaultRoles: ['user', 'admin'],
getMenu: getMenu,
menus: {},
removeMenu: removeMenu,
removeMenuItem: removeMenuItem,
removeSubMenuItem: removeSubMenuItem,
validateMenuExistance: validateMenuExistance
};
init();
return service;
// Add new menu object by menu id
function addMenu(menuId, options) {
options = options || {};
// Create the new menu
service.menus[menuId] = {
roles: options.roles || service.defaultRoles,
items: options.items || [],
shouldRender: shouldRender
};
// Return the menu object
return service.menus[menuId];
}
// Add menu item object
function addMenuItem(menuId, options) {
options = options || {};
// Validate that the menu exists
service.validateMenuExistance(menuId);
// Push new menu item
service.menus[menuId].items.push({
title: options.title || '',
state: options.state || '',
type: options.type || 'item',
class: options.class,
roles: ((options.roles === null || typeof options.roles === 'undefined') ? service.defaultRoles : options.roles),
position: options.position || 0,
items: [],
shouldRender: shouldRender
});
// Add submenu items
if (options.items) {
for (var i in options.items) {
if (options.items.hasOwnProperty(i)) {
service.addSubMenuItem(menuId, options.state, options.items[i]);
}
}
}
// Return the menu object
return service.menus[menuId];
}
// Add submenu item object
function addSubMenuItem(menuId, parentItemState, options) {
options = options || {};
// Validate that the menu exists
service.validateMenuExistance(menuId);
// Search for menu item
for (var itemIndex in service.menus[menuId].items) {
if (service.menus[menuId].items[itemIndex].state === parentItemState) {
// Push new submenu item
service.menus[menuId].items[itemIndex].items.push({
title: options.title || '',
state: options.state || '',
roles: ((options.roles === null || typeof options.roles === 'undefined') ? service.menus[menuId].items[itemIndex].roles : options.roles),
position: options.position || 0,
shouldRender: shouldRender
});
}
}
// Return the menu object
return service.menus[menuId];
}
// Get the menu object by menu id
function getMenu(menuId) {
// Validate that the menu exists
service.validateMenuExistance(menuId);
// Return the menu object
return service.menus[menuId];
}
function init() {
// A private function for rendering decision
shouldRender = function (user) {
if (this.roles.indexOf('*') !== -1) {
return true;
} else {
if (!user) {
return false;
}
for (var userRoleIndex in user.roles) {
if (user.roles.hasOwnProperty(userRoleIndex)) {
for (var roleIndex in this.roles) {
if (this.roles.hasOwnProperty(roleIndex) && this.roles[roleIndex] === user.roles[userRoleIndex]) {
return true;
}
}
}
}
}
return false;
};
// Adding the topbar menu
addMenu('topbar', {
roles: ['*']
});
}
// Remove existing menu object by menu id
function removeMenu(menuId) {
// Validate that the menu exists
service.validateMenuExistance(menuId);
delete service.menus[menuId];
}
// Remove existing menu object by menu id
function removeMenuItem(menuId, menuItemState) {
// Validate that the menu exists
service.validateMenuExistance(menuId);
// Search for menu item to remove
for (var itemIndex in service.menus[menuId].items) {
if (service.menus[menuId].items.hasOwnProperty(itemIndex) && service.menus[menuId].items[itemIndex].state === menuItemState) {
service.menus[menuId].items.splice(itemIndex, 1);
}
}
// Return the menu object
return service.menus[menuId];
}
// Remove existing menu object by menu id
function removeSubMenuItem(menuId, submenuItemState) {
// Validate that the menu exists
service.validateMenuExistance(menuId);
// Search for menu item to remove
for (var itemIndex in service.menus[menuId].items) {
if (this.menus[menuId].items.hasOwnProperty(itemIndex)) {
for (var subitemIndex in service.menus[menuId].items[itemIndex].items) {
if (this.menus[menuId].items[itemIndex].items.hasOwnProperty(subitemIndex) && service.menus[menuId].items[itemIndex].items[subitemIndex].state === submenuItemState) {
service.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1);
}
}
}
}
// Return the menu object
return service.menus[menuId];
}
// Validate menu existance
function validateMenuExistance(menuId) {
if (menuId && menuId.length) {
if (service.menus[menuId]) {
return true;
} else {
throw new Error('Menu does not exist');
}
} else {
throw new Error('MenuId was not provided');
}
}
}
}());

View File

@@ -1,182 +0,0 @@
'use strict';
// Menu service used for managing menus
angular.module('core').service('Menus', [
function () {
// Define a set of default roles
this.defaultRoles = ['user', 'admin'];
// Define the menus object
this.menus = {};
// A private function for rendering decision
var shouldRender = function (user) {
if (!!~this.roles.indexOf('*')) {
return true;
} else {
if (!user) {
return false;
}
for (var userRoleIndex in user.roles) {
if (user.roles.hasOwnProperty(userRoleIndex)) {
for (var roleIndex in this.roles) {
if (this.roles.hasOwnProperty(roleIndex)) {
if (this.roles[roleIndex] === user.roles[userRoleIndex]) {
return true;
}
}
}
}
}
}
return false;
};
// Validate menu existance
this.validateMenuExistance = function (menuId) {
if (menuId && menuId.length) {
if (this.menus[menuId]) {
return true;
} else {
throw new Error('Menu does not exist');
}
} else {
throw new Error('MenuId was not provided');
}
};
// Get the menu object by menu id
this.getMenu = function (menuId) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Return the menu object
return this.menus[menuId];
};
// Add new menu object by menu id
this.addMenu = function (menuId, options) {
options = options || {};
// Create the new menu
this.menus[menuId] = {
roles: options.roles || this.defaultRoles,
items: options.items || [],
shouldRender: shouldRender
};
// Return the menu object
return this.menus[menuId];
};
// Remove existing menu object by menu id
this.removeMenu = function (menuId) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Return the menu object
delete this.menus[menuId];
};
// Add menu item object
this.addMenuItem = function (menuId, options) {
options = options || {};
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Push new menu item
this.menus[menuId].items.push({
title: options.title || '',
state: options.state || '',
type: options.type || 'item',
class: options.class,
roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.defaultRoles : options.roles),
position: options.position || 0,
items: [],
shouldRender: shouldRender
});
// Add submenu items
if (options.items) {
for (var i in options.items) {
if (options.items.hasOwnProperty(i)) {
this.addSubMenuItem(menuId, options.state, options.items[i]);
}
}
}
// Return the menu object
return this.menus[menuId];
};
// Add submenu item object
this.addSubMenuItem = function (menuId, parentItemState, options) {
options = options || {};
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Search for menu item
for (var itemIndex in this.menus[menuId].items) {
if (this.menus[menuId].items[itemIndex].state === parentItemState) {
// Push new submenu item
this.menus[menuId].items[itemIndex].items.push({
title: options.title || '',
state: options.state || '',
roles: ((options.roles === null || typeof options.roles === 'undefined') ? this.menus[menuId].items[itemIndex].roles : options.roles),
position: options.position || 0,
shouldRender: shouldRender
});
}
}
// Return the menu object
return this.menus[menuId];
};
// Remove existing menu object by menu id
this.removeMenuItem = function (menuId, menuItemState) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Search for menu item to remove
for (var itemIndex in this.menus[menuId].items) {
if (this.menus[menuId].items[itemIndex].state === menuItemState) {
this.menus[menuId].items.splice(itemIndex, 1);
}
}
// Return the menu object
return this.menus[menuId];
};
// Remove existing menu object by menu id
this.removeSubMenuItem = function (menuId, submenuItemState) {
// Validate that the menu exists
this.validateMenuExistance(menuId);
// Search for menu item to remove
for (var itemIndex in this.menus[menuId].items) {
if (this.menus[menuId].items.hasOwnProperty(itemIndex)) {
for (var subitemIndex in this.menus[menuId].items[itemIndex].items) {
if (this.menus[menuId].items[itemIndex].items.hasOwnProperty(subitemIndex)) {
if (this.menus[menuId].items[itemIndex].items[subitemIndex].state === submenuItemState) {
this.menus[menuId].items[itemIndex].items.splice(subitemIndex, 1);
}
}
}
}
}
// Return the menu object
return this.menus[menuId];
};
// Adding the topbar menu
this.addMenu('topbar', {
roles: ['*']
});
}
]);

View File

@@ -1,40 +1,57 @@
'use strict';
(function () {
'use strict';
// Create the Socket.io wrapper service
angular
.module('core')
.factory('Socket', Socket);
Socket.$inject = ['Authentication', '$state', '$timeout'];
function Socket(Authentication, $state, $timeout) {
var service = {
connect: connect,
emit: emit,
on: on,
removeListener: removeListener,
socket: null
};
connect();
return service;
// Create the Socket.io wrapper service
angular.module('core').service('Socket', ['Authentication', '$state', '$timeout',
function (Authentication, $state, $timeout) {
// Connect to Socket.io server
this.connect = function () {
function connect() {
// Connect only when authenticated
if (Authentication.user) {
this.socket = io();
service.socket = io();
}
};
this.connect();
}
// Wrap the Socket.io 'emit' method
function emit(eventName, data) {
if (service.socket) {
service.socket.emit(eventName, data);
}
}
// Wrap the Socket.io 'on' method
this.on = function (eventName, callback) {
if (this.socket) {
this.socket.on(eventName, function (data) {
function on(eventName, callback) {
if (service.socket) {
service.socket.on(eventName, function (data) {
$timeout(function () {
callback(data);
});
});
}
};
// Wrap the Socket.io 'emit' method
this.emit = function (eventName, data) {
if (this.socket) {
this.socket.emit(eventName, data);
}
};
}
// Wrap the Socket.io 'removeListener' method
this.removeListener = function (eventName) {
if (this.socket) {
this.socket.removeListener(eventName);
function removeListener(eventName) {
if (service.socket) {
service.socket.removeListener(eventName);
}
};
}
}
]);
}());

View File

@@ -1,6 +1,6 @@
<div class="container" ng-controller="HeaderController">
<div class="container" ng-controller="HeaderController as vm">
<div class="navbar-header">
<button class="navbar-toggle" type="button" ng-click="toggleCollapsibleMenu()">
<button class="navbar-toggle" type="button" ng-click="vm.isCollapsed = !vm.isCollapsed">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
@@ -8,19 +8,19 @@
</button>
<a ui-sref="home" class="navbar-brand">MEAN.JS</a>
</div>
<nav class="navbar-collapse" uib-collapse="!isCollapsed" role="navigation">
<ul class="nav navbar-nav" ng-if="menu.shouldRender(authentication.user);">
<li ng-repeat="item in menu.items | orderBy: 'position'" ng-if="item.shouldRender(authentication.user);" ng-switch="item.type" ng-class="{ active: $state.includes(item.state), dropdown: item.type === 'dropdown' }" class="{{item.class}}" uib-dropdown="item.type === 'dropdown'">
<nav class="navbar-collapse" uib-collapse="!vm.isCollapsed" role="navigation">
<ul class="nav navbar-nav" ng-if="vm.menu.shouldRender(vm.authentication.user);">
<li ng-repeat="item in vm.menu.items | orderBy: 'position'" ng-if="item.shouldRender(vm.authentication.user);" ng-switch="item.type" ng-class="{ dropdown: item.type === 'dropdown' }" ui-sref-active="active" class="{{item.class}}" uib-dropdown="item.type === 'dropdown'">
<a ng-switch-when="dropdown" class="dropdown-toggle" uib-dropdown-toggle role="button">{{::item.title}}&nbsp;<span class="caret"></span></a>
<ul ng-switch-when="dropdown" class="dropdown-menu">
<li ng-repeat="subitem in item.items | orderBy: 'position'" ng-if="subitem.shouldRender(authentication.user);" ui-sref-active="active">
<li ng-repeat="subitem in item.items | orderBy: 'position'" ng-if="subitem.shouldRender(vm.authentication.user);">
<a ui-sref="{{subitem.state}}" ng-bind="subitem.title"></a>
</li>
</ul>
<a ng-switch-default ui-sref="{{item.state}}" ng-bind="item.title"></a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" ng-hide="authentication.user">
<ul class="nav navbar-nav navbar-right" ng-hide="vm.authentication.user">
<li ui-sref-active="active">
<a ui-sref="authentication.signup">Sign Up</a>
</li>
@@ -29,14 +29,14 @@
<a ui-sref="authentication.signin">Sign In</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" ng-show="authentication.user">
<ul class="nav navbar-nav navbar-right" ng-show="vm.authentication.user">
<li class="dropdown" uib-dropdown>
<a class="dropdown-toggle user-header-dropdown-toggle" uib-dropdown-toggle role="button">
<img ng-src="{{authentication.user.profileImageURL}}" alt="{{authentication.user.displayName}}" class="header-profile-image" />
<span ng-bind="authentication.user.displayName"></span> <b class="caret"></b>
<img ng-src="{{vm.authentication.user.profileImageURL}}" alt="{{vm.authentication.user.displayName}}" class="header-profile-image" />
<span ng-bind="vm.authentication.user.displayName"></span> <b class="caret"></b>
</a>
<ul class="dropdown-menu" role="menu">
<li ui-sref-active="active" ng-repeat="item in accountMenu.items">
<li ui-sref-active="active" ng-repeat="item in vm.accountMenu.items">
<a ui-sref="{{item.state}}" ng-bind="item.title"></a>
</li>
<li class="divider"></li>

View File

@@ -1,4 +1,4 @@
<section ng-controller="HomeController">
<section>
<div class="jumbotron text-center">
<div class="row">
<div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12">

View File

@@ -16,48 +16,45 @@
$state = _$state_;
Authentication = _Authentication_;
HeaderController = $controller('HeaderController', {
HeaderController = $controller('HeaderController as vm', {
$scope: scope
});
}));
it('should expose the authentication service', function () {
expect(scope.authentication).toBe(Authentication);
});
it('should expose the $state service', function () {
expect(scope.$state).toBe($state);
expect(scope.vm.authentication).toBe(Authentication);
});
it('should default menu to collapsed', function () {
expect(scope.isCollapsed).toBeFalsy();
expect(scope.vm.isCollapsed).toBeFalsy();
});
describe('when toggleCollapsibleMenu', function () {
var defaultCollapse;
beforeEach(function () {
defaultCollapse = scope.isCollapsed;
scope.toggleCollapsibleMenu();
defaultCollapse = scope.vm.isCollapsed;
scope.vm.isCollapsed = !scope.vm.isCollapsed;
});
it('should toggle isCollapsed to non default value', function () {
expect(scope.isCollapsed).not.toBe(defaultCollapse);
expect(scope.vm.isCollapsed).not.toBe(defaultCollapse);
});
it('should then toggle isCollapsed back to default value', function () {
scope.toggleCollapsibleMenu();
expect(scope.isCollapsed).toBe(defaultCollapse);
scope.vm.isCollapsed = !scope.vm.isCollapsed;
expect(scope.vm.isCollapsed).toBe(defaultCollapse);
});
});
describe('when view state changes', function () {
beforeEach(function () {
scope.isCollapsed = true;
scope.vm.isCollapsed = true;
scope.$broadcast('$stateChangeSuccess');
});
it('should set isCollapsed to false', function () {
expect(scope.isCollapsed).toBeFalsy();
expect(scope.vm.isCollapsed).toBeFalsy();
});
});
});

View File

@@ -12,13 +12,9 @@
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
HomeController = $controller('HomeController', {
HomeController = $controller('HomeController as vm', {
$scope: scope
});
}));
it('should expose the authentication service', function () {
expect(scope.authentication).toBeTruthy();
});
});
}());

View File

@@ -4,21 +4,21 @@
describe('Menus', function() {
// Initialize global variables
var scope,
Menus;
menuService;
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
beforeEach(inject(function(_Menus_) {
Menus = _Menus_;
beforeEach(inject(function(_menuService_) {
menuService = _menuService_;
}));
it('should have topbar added', function() {
expect(Menus.menus.topbar).toBeDefined();
expect(menuService.menus.topbar).toBeDefined();
});
it('should have default roles to user and admin', function() {
expect(Menus.defaultRoles).toEqual(['user', 'admin']);
expect(menuService.defaultRoles).toEqual(['user', 'admin']);
});
describe('addMenu', function() {
@@ -26,7 +26,7 @@
var menuId = 'menu1',
menu;
beforeEach(function() {
menu = Menus.addMenu(menuId);
menu = menuService.addMenu(menuId);
});
it('should return menu object', function() {
@@ -34,7 +34,7 @@
});
it('should default roles', function() {
expect(menu.roles).toEqual(Menus.defaultRoles);
expect(menu.roles).toEqual(menuService.defaultRoles);
});
it('should have empty items', function() {
@@ -53,7 +53,7 @@
items: ['d', 'e', 'f']
};
beforeEach(function() {
menu = Menus.addMenu('menu1', options);
menu = menuService.addMenu('menu1', options);
});
it('should set items to options.items list', function() {
@@ -72,7 +72,7 @@
},
menu;
beforeEach(function() {
menu = Menus.addMenu('menu1', menuOptions);
menu = menuService.addMenu('menu1', menuOptions);
});
describe('when logged out', function() {
@@ -81,7 +81,7 @@
});
it('should not render if menu is private', function() {
menu = Menus.addMenu('menu1', {
menu = menuService.addMenu('menu1', {
isPublic: false
});
expect(menu.shouldRender()).toBeFalsy();
@@ -100,7 +100,7 @@
describe('menu without * role', function() {
beforeEach(function() {
menu = Menus.addMenu('menu1', {
menu = menuService.addMenu('menu1', {
roles: ['b', 'menurole', 'c']
});
});
@@ -122,14 +122,14 @@
describe('validateMenuExistance', function() {
describe('when menuId not provided', function() {
it('should throw menuId error', function() {
expect(Menus.validateMenuExistance).toThrowError('MenuId was not provided');
expect(menuService.validateMenuExistance).toThrowError('MenuId was not provided');
});
});
describe('when menu does not exist', function() {
it('should throw no menu error', function() {
var target = function() {
Menus.validateMenuExistance('noMenuId');
menuService.validateMenuExistance('noMenuId');
};
expect(target).toThrowError('Menu does not exist');
});
@@ -138,11 +138,11 @@
describe('when menu exists', function() {
var menuId = 'menuId';
beforeEach(function() {
Menus.menus[menuId] = {};
menuService.menus[menuId] = {};
});
it('should return truthy', function() {
expect(Menus.validateMenuExistance(menuId)).toBeTruthy();
expect(menuService.validateMenuExistance(menuId)).toBeTruthy();
});
});
});
@@ -152,17 +152,17 @@
id: 'menuId'
};
beforeEach(function() {
Menus.menus[menu.id] = menu;
Menus.validateMenuExistance = jasmine.createSpy();
Menus.removeMenu(menu.id);
menuService.menus[menu.id] = menu;
menuService.validateMenuExistance = jasmine.createSpy();
menuService.removeMenu(menu.id);
});
it('should remove existing menu from menus', function() {
expect(Menus.menus).not.toContain(menu.id);
expect(menuService.menus).not.toContain(menu.id);
});
it('validates menu existance before removing', function() {
expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menu.id);
expect(menuService.validateMenuExistance).toHaveBeenCalledWith(menu.id);
});
});
@@ -188,17 +188,17 @@
menuItem;
beforeEach(function() {
Menus.validateMenuExistance = jasmine.createSpy();
Menus.addSubMenuItem = jasmine.createSpy();
Menus.addMenu(menuId, {
menuService.validateMenuExistance = jasmine.createSpy();
menuService.addSubMenuItem = jasmine.createSpy();
menuService.addMenu(menuId, {
roles: ['a', 'b']
});
menu = Menus.addMenuItem(menuId, menuItemOptions);
menu = menuService.addMenuItem(menuId, menuItemOptions);
menuItem = menu.items[0];
});
it('should validate menu existance', function() {
expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menuId);
expect(menuService.validateMenuExistance).toHaveBeenCalledWith(menuId);
});
it('should return the menu', function() {
@@ -235,14 +235,14 @@
});
it('should call addSubMenuItem for each item in options', function() {
expect(Menus.addSubMenuItem).toHaveBeenCalledWith(menuId, menuItemOptions.state, subMenuItem1);
expect(Menus.addSubMenuItem).toHaveBeenCalledWith(menuId, menuItemOptions.state, subMenuItem2);
expect(menuService.addSubMenuItem).toHaveBeenCalledWith(menuId, menuItemOptions.state, subMenuItem1);
expect(menuService.addSubMenuItem).toHaveBeenCalledWith(menuId, menuItemOptions.state, subMenuItem2);
});
});
describe('without options set', function() {
beforeEach(function() {
menu = Menus.addMenuItem(menuId);
menu = menuService.addMenuItem(menuId);
menuItem = menu.items[1];
});
@@ -259,7 +259,7 @@
});
it('should set menu item roles to default roles', function() {
expect(menuItem.roles).toEqual(Menus.defaultRoles);
expect(menuItem.roles).toEqual(menuService.defaultRoles);
});
it('should set menu item position to 0', function() {
@@ -275,11 +275,11 @@
menu;
beforeEach(function() {
Menus.addMenu(menuId);
Menus.addMenuItem(menuId, { state: menuItemState });
Menus.addMenuItem(menuId, { state: menuItemState2 });
Menus.validateMenuExistance = jasmine.createSpy();
menu = Menus.removeMenuItem(menuId, menuItemState);
menuService.addMenu(menuId);
menuService.addMenuItem(menuId, { state: menuItemState });
menuService.addMenuItem(menuId, { state: menuItemState2 });
menuService.validateMenuExistance = jasmine.createSpy();
menu = menuService.removeMenuItem(menuId, menuItemState);
});
it('should return menu object', function() {
@@ -287,7 +287,7 @@
});
it('should validate menu existance', function() {
expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menuId);
expect(menuService.validateMenuExistance).toHaveBeenCalledWith(menuId);
});
it('should remove sub menu items with same state', function() {
@@ -324,13 +324,13 @@
menu;
beforeEach(function() {
Menus.validateMenuExistance = jasmine.createSpy();
Menus.addMenu(menuId);
Menus.addMenuItem(menuId, menuItem1Options);
Menus.addMenuItem(menuId, menuItem2Options);
Menus.addMenuItem(menuId, { state: 'something.else' });
Menus.addSubMenuItem(menuId, menuItem1Options.state, subItemOptions);
menu = Menus.addSubMenuItem(menuId, menuItem1Options.state);
menuService.validateMenuExistance = jasmine.createSpy();
menuService.addMenu(menuId);
menuService.addMenuItem(menuId, menuItem1Options);
menuService.addMenuItem(menuId, menuItem2Options);
menuService.addMenuItem(menuId, { state: 'something.else' });
menuService.addSubMenuItem(menuId, menuItem1Options.state, subItemOptions);
menu = menuService.addSubMenuItem(menuId, menuItem1Options.state);
menuItem1 = menu.items[0];
menuItem2 = menu.items[1];
menuItem3 = menu.items[2];
@@ -339,7 +339,7 @@
});
afterEach(function() {
Menus.removeMenu(menuId);
menuService.removeMenu(menuId);
});
it('should return menu object', function() {
@@ -347,7 +347,7 @@
});
it('should validate menu existance', function() {
expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menuId);
expect(menuService.validateMenuExistance).toHaveBeenCalledWith(menuId);
});
it('should not add sub menu item to menu item of different state', function() {
@@ -408,12 +408,12 @@
describe('then removeSubMenuItem', function() {
beforeEach(function() {
Menus.validateMenuExistance = jasmine.createSpy();
menu = Menus.removeSubMenuItem(menuId, subItem1.state);
menuService.validateMenuExistance = jasmine.createSpy();
menu = menuService.removeSubMenuItem(menuId, subItem1.state);
});
it('should validate menu existance', function() {
expect(Menus.validateMenuExistance).toHaveBeenCalledWith(menuId);
expect(menuService.validateMenuExistance).toHaveBeenCalledWith(menuId);
});
it('should return menu object', function() {

View File

@@ -5,11 +5,11 @@
.module('users.admin')
.run(menuConfig);
menuConfig.$inject = ['Menus'];
menuConfig.$inject = ['menuService'];
// Configuring the Users module
function menuConfig(Menus) {
Menus.addSubMenuItem('topbar', 'admin', {
function menuConfig(menuService) {
menuService.addSubMenuItem('topbar', 'admin', {
title: 'Manage Users',
state: 'admin.users'
});

View File

@@ -3,8 +3,8 @@
app.registerModule('users');
app.registerModule('users.admin');
app.registerModule('users.services');
app.registerModule('users.admin.routes', ['ui.router', 'core.routes', 'users.admin.services']);
app.registerModule('users.admin.services');
app.registerModule('users.routes', ['ui.router']);
app.registerModule('users.admin.routes', ['ui.router', 'users.admin.services']);
app.registerModule('users.routes', ['ui.router', 'core.routes']);
app.registerModule('users.services');
}(ApplicationConfiguration));