Added user preference handling

From comments on our paging review, it became clear that we need some kind
of user preference support. This commit provides a client-side
implementation with an abstraction layer that allows us to connect the API
at a later time. Actions taken:

- Added currentUser resolution for routes.
- Added new profile module to handle user profile management.
- Added navigation in menu.
- Small fix to logout button in mobile, to make sure it doesn't show up.
- New route and UI for /profile/preferences
- Created preference provider factory that allows any module to
register/inject their own preferences with defaults. See
services/resource/preference.js as an example.

Change-Id: I2d72a40a9e0c3a142da6c1d1e5f9dd4da7ca58a1
This commit is contained in:
Michael Krotscheck 2014-03-17 10:39:07 -07:00
parent 8b9f371065
commit 359297afab
7 changed files with 283 additions and 2 deletions

View File

@ -80,6 +80,15 @@ angular.module('sb.auth').constant('SessionResolver',
}
return deferred.promise;
},
/**
* This resolver ensures that the currentUser has been resolved
* before the route resolves.
*/
requireCurrentUser: function ($q, $log, CurrentUser) {
$log.debug('Resolving current user...');
return CurrentUser.resolve();
}
};
})());

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Preferences controller for our user profile. Allows explicit editing of
* individual preferences.
*/
angular.module('sb.profile').controller('ProfilePreferencesController',
function ($scope, Preference) {
'use strict';
$scope.pageSize = Preference.$get('page_size');
$scope.save = function () {
Preference.$set('page_size', $scope.pageSize);
$scope.message = 'Preferences Saved!';
};
});

50
src/app/profile/module.js Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* The Storyboard root application module.
*
* This module contains the entire, standalone application for the Storyboard
* ticket tracking web client.
*
* @author Michael Krotscheck
*/
angular.module('sb.profile',
['sb.services', 'sb.templates', 'sb.auth', 'ui.router', 'ui.bootstrap']
)
.config(function ($stateProvider, SessionResolver, $urlRouterProvider) {
'use strict';
// URL Defaults.
$urlRouterProvider.when('/profile', '/profile/preferences');
// Declare the states for this module.
$stateProvider
.state('profile', {
abstract: true,
template: '<div ui-view></div>',
url: '/profile',
resolve: {
isLoggedIn: SessionResolver.requireLoggedIn,
currentUser: SessionResolver.requireCurrentUser
}
})
.state('profile.preferences', {
url: '/preferences',
templateUrl: 'app/templates/profile/preferences.html',
controller: 'ProfilePreferencesController'
});
});

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* A simple preferences service, backed by localStorage.
*/
angular.module('sb.services').provider('Preference',
function () {
'use strict';
/**
* Our preference defaults. We're using underscore naming here in
* anticipation of these keys living on the python side of things.
*/
var defaults = { };
/**
* Preference name key generator. Basically it's poor man's
* namespacing.
*/
function preferenceName(key) {
return 'pref_' + key;
}
/**
* Each module can manually declare its own preferences that it would
* like to keep track of, as well as set a default. During the config()
* phase, inject the Preference Provider and call 'addPreference()' to
* do so. An example is available at the bottom of this file.
*/
this.addPreference = function (preferenceName, preferenceDefault) {
defaults[preferenceName] = preferenceDefault;
};
/**
* The actual preference implementation.
*/
function Preference($log, localStorageService) {
/**
* Get a preference.
*/
this.$get = function (key) {
// Is this a valid preference?
if (!defaults.hasOwnProperty(key)) {
$log.warn('Attempt to get unregistered preference: ' +
key);
return null;
}
var value = localStorageService.get(preferenceName(key));
// If the value is unset, and we have a default, set and use
// that.
if (value === null && defaults.hasOwnProperty(key)) {
var defaultValue = defaults[key];
this.$set(key, defaultValue);
return defaultValue;
}
return value;
};
/**
* Set a preference.
*/
this.$set = function (key, value) {
// Is this a valid preference?
if (!defaults.hasOwnProperty(key)) {
$log.warn('Attempt to set unregistered preference: ' +
key);
return null;
}
return localStorageService.set(preferenceName(key), value);
};
}
/**
* Factory getter - returns a configured instance of preference
* provider, as needed.
*/
this.$get = function ($log, localStorageService) {
return new Preference($log, localStorageService);
};
})
.config(function (PreferenceProvider) {
'use strict';
// WARNING: In all modules OTHER than the services module, this config
// block can appear anywhere as long as this module is listed as a
// dependency. In the services module, the config() block must appear
// AFTER the provider block. For more information,
// @see https://github.com/angular/angular.js/issues/6723
// Let our preference provider know about page_size.
PreferenceProvider.addPreference('page_size', 10);
})
;

View File

@ -24,7 +24,7 @@
*/
angular.module('storyboard',
[ 'sb.services', 'sb.templates', 'sb.pages', 'sb.projects', 'sb.auth',
'sb.story', 'ui.router', 'ui.bootstrap']
'sb.story', 'sb.profile', 'ui.router', 'ui.bootstrap']
)
.config(function ($provide, $stateProvider, $urlRouterProvider,
$locationProvider, $httpProvider) {

View File

@ -64,12 +64,17 @@
<li class="visible-xs">
<hr/>
</li>
<li class="visible-xs">
<a href="#!/profile/preferences" ng-show="isLoggedIn"
active-path="^\/profile\/preferences*">Preferences</a>
</li>
<!-- Login/Logout button, XS only. -->
<li class="visible-xs">
<a href="#!/auth/authorize" ng-hide="isLoggedIn">
Log in
</a>
<a href="" ng-click="logout()">
<a href="" ng-click="logout()" ng-show="isLoggedIn">
Log out
</a>
</li>
@ -93,6 +98,9 @@
<i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu">
<li>
<a href="#!/profile/preferences">Preferences</a>
</li>
<li>
<a href="" ng-click="logout()">Logout</a>
</li>

View File

@ -0,0 +1,69 @@
<!--
~ Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
~
~ Licensed under the Apache License, Version 2.0 (the "License"); you may
~ not use this file except in compliance with the License. You may obtain
~ a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ License for the specific language governing permissions and limitations
~ under the License.
-->
<div class="container" ng-hide="isLoading">
<div class="row">
<div class="col-xs-12">
<h1>Preferences</h1>
</div>
</div>
<div class="row">
<div class="col-md-6">
<form name="preferencesForm">
<div class="form-group">
<label>Page size</label>
<p class="help-block">
How many results would you like to see when viewing
lists?
</p>
<div>
<input type="radio" name="pageSize"
id="pageSize10" value="10"
ng-model="pageSize">
10
&nbsp;
<input type="radio" name="pageSize"
id="pageSize25" value="25"
ng-model="pageSize">
25
&nbsp;
<input type="radio" name="pageSize"
id="pageSize50" value="50"
ng-model="pageSize">
50
&nbsp;
<input type="radio" name="pageSize"
id="pageSize100" value="100"
ng-model="pageSize">
100
</div>
</div>
<hr/>
<div class="form-group">
<button type="button" class="btn btn-default"
ng-click="save()">
Save
</button>
<p class="help-block text-success">{{message}}</p>
</div>
</form>
</div>
</div>
</div>