From c5dca287f4950cc3a6c6867ca7278d465fa793fb Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 4 Nov 2013 09:28:50 -0500 Subject: [PATCH] side-porting latest UI services code from web staff project Signed-off-by: Bill Erickson --- Open-ILS/web/js/ui/default/staff/services/auth.js | 32 ++- Open-ILS/web/js/ui/default/staff/services/env.js | 120 +++++------ Open-ILS/web/js/ui/default/staff/services/idl.js | 2 +- Open-ILS/web/js/ui/default/staff/services/list.js | 226 +++++++++++++++++++++ Open-ILS/web/js/ui/default/staff/services/org.js | 4 +- .../web/js/ui/default/staff/services/startup.js | 32 +-- Open-ILS/web/js/ui/default/staff/services/user.js | 28 ++- 7 files changed, 330 insertions(+), 114 deletions(-) create mode 100644 Open-ILS/web/js/ui/default/staff/services/list.js diff --git a/Open-ILS/web/js/ui/default/staff/services/auth.js b/Open-ILS/web/js/ui/default/staff/services/auth.js index 800ba73509..57cdaca311 100644 --- a/Open-ILS/web/js/ui/default/staff/services/auth.js +++ b/Open-ILS/web/js/ui/default/staff/services/auth.js @@ -1,5 +1,7 @@ /* Core Sevice - egAuth * + * Manages login and auth session retrieval + * * Angular cookies are still fairly primitive. * In particular, you can't set the path. * https://github.com/angular/angular.js/issues/1786 @@ -9,29 +11,16 @@ angular.module('egCoreMod') .constant('EG_AUTH_COOKIE', 'ses') -// auth cache -.factory('egAuthCache', - ['$cacheFactory', function($cacheFactory) { - return $cacheFactory('egAuthCache', {}); -}]) - - .factory('egAuth', -['$q', '$cookies', '$timeout', '$location', - '$window', 'egAuthCache', 'egNet', 'EG_AUTH_COOKIE', -function($q, $cookies, $timeout, $location, - $window, egAuthCache, egNet, EG_AUTH_COOKIE) { + ['$q','$cookies','$timeout','$location','$window','egNet','EG_AUTH_COOKIE', +function($q, $cookies, $timeout, $location, $window, egNet, EG_AUTH_COOKIE) { var service = { user : function() { - return egAuthCache.get('user'); + return this._user; }, token : function() { - // no need to cache, since auth lives in a cookie return $cookies[EG_AUTH_COOKIE]; - }, - workstation : function() { // TODO - return egAuthCache.get('workstation'); } }; @@ -47,7 +36,7 @@ function($q, $cookies, $timeout, $location, 'open-ils.auth.session.retrieve', token).then( function(user) { if (user && user.classname) { - egAuthCache.put('user', user); + service._user = user; deferred.resolve(); } else { delete $cookies[EG_AUTH_COOKIE]; @@ -94,7 +83,16 @@ function($q, $cookies, $timeout, $location, }; service.logout = function() { + console.debug('egAuth.logout()'); + if (service.token()) { + egNet.request( + 'open-ils.auth', + 'open-ils.auth.session.delete', + service.token() + ); // fire and forget + } delete $cookies[EG_AUTH_COOKIE]; + service._user = null; }; return service; diff --git a/Open-ILS/web/js/ui/default/staff/services/env.js b/Open-ILS/web/js/ui/default/staff/services/env.js index ca55f0896b..0e1f2b7de1 100644 --- a/Open-ILS/web/js/ui/default/staff/services/env.js +++ b/Open-ILS/web/js/ui/default/staff/services/env.js @@ -1,67 +1,64 @@ /** * Core Service - egEnv * - * Data that we always want to load at startup goes here. - * Requests are sents as a swarm of async calls. As each - * returns, a pending-calls counter is decremented. Once - * it reaches zero, the promise returned by load() / - * loadAll() is resolved. + * Manages startup data loading. All registered loaders run + * simultaneously. When all promises are resolved, the promise + * returned by egEnv.load() is resolved. * - * App-supplied generic load commands are pushed onto the - * service.loaders array. Each item in the array is a - * function which returns a promise. Note that loaders in this - * array cannot have any dependencies on other startup loaders, - * since there is no guarantee which will complete first. + * Generic and class-based loaders are supported. * - * egEnv.loaders.push(function() { - * return egNet.request(...) - * .then(function(stuff) { console.log('stuff!') }); + * To load a registred class, push the class hint onto + * egEnv.loadClasses. + * + * // will cause all 'pgt' objects to be fetched + * egEnv.loadClasses.push('pgt'); + * + * To register a new class loader,attach a loader function to + * egEnv.classLoaders, keyed on the class hint, which returns a promise. * - * Data requets that have requirements loaded by startup - * should run after startup directly in the application. + * egEnv.classLoaders.ccs = function() { + * // loads copy status objects, returns promise + * }; * - * TODO: marry egStartup and egEnv? - * TODO: support a post-org loader so that loaders can be - * added which rely on the org tree? + * Generic loaders go onto the egEnv.loaders array. Each should + * return a promise. + * + * egEnv.loaders.push(function() { + * return egNet.request(...) + * .then(function(stuff) { console.log('stuff!') + * }); */ angular.module('egCoreMod') -// env cache -.factory('egEnvCache', ['$cacheFactory', -function($cacheFactory) { - return $cacheFactory('egEnvCache', {}); -}]) - // env fetcher .factory('egEnv', -['$q', 'egEnvCache', 'egNet', 'egAuth', 'egPCRUD', -function($q, egEnvCache, egNet, egAuth, egPCRUD) { + ['$q','egAuth','egPCRUD','egIDL', +function($q, egAuth, egPCRUD, egIDL) { var service = { - loaders : [], - get : function(class_) { - return egEnvCache.get(class_); - } - }; - - service.onload = function() { - if (--this.in_flight == 0) - this.deferred.resolve(); + // collection of custom loader functions + loaders : [] }; /* returns a promise, loads all of the specified classes */ - service.load = function(classes) { - if (!classes) classes = Object.keys(this.classLoaders); - this.deferred = $q.defer(); - this.in_flight = classes.length + this.loaders.length; + service.load = function() { + // always assume the user is logged in + if (!egAuth.user()) return $q.when(); + + var allPromises = []; + var classes = this.loadClasses; + console.debug('egEnv loading classes => ' + classes); + angular.forEach(classes, function(cls) { - service.classLoaders[cls]().then(function(){service.onload()}); + allPromises.push(service.classLoaders[cls]()); }); angular.forEach(this.loaders, function(loader) { - loader().then(function(){service.onload()}); + allPromises.push(loader()); }); - return this.deferred.promise; + + return $q.all(allPromises).then( + function() { console.debug('egEnv load complete') }); }; /** given a tree-shaped collection, captures the tree and @@ -81,16 +78,24 @@ function($q, egEnvCache, egNet, egAuth, egPCRUD) { /** caches the object list both as the list and an id => object map */ service.absorbList = function(list, class_) { var blob = {list : list, map : {}}; - angular.forEach(list, function(item) {blob.map[item.id()] = item}); - egEnvCache.put(class_, blob); + var pkey = egIDL.classes[class_].pkey; + angular.forEach(list, function(item) {blob.map[item[pkey]()] = item}); + service[class_] = blob; return blob; }; - /* Classes (by hint) to load, their loading routines, - * and their result mungers */ + /* + * list of classes to load on every page, regardless of whether + * a page-specific list is provided. + */ + service.loadClasses = ['aou', 'aws']; + /* + * Default class loaders. Only add classes directly to this file + * that are loaded practically always. All other app-specific + * classes should be registerd from within the app. + */ service.classLoaders = { - aou : function() { return egPCRUD.search('aou', {parent_ou : null}, {flesh : -1, flesh_fields : {aou : ['children', 'ou_type']}} @@ -98,17 +103,18 @@ function($q, egEnvCache, egNet, egAuth, egPCRUD) { function(tree) {service.absorbTree(tree, 'aou')} ); }, - - // TODO: make me optional -- not all UIs need the PGT - pgt : function() { - return egPCRUD.search('pgt', {parent : null}, - {flesh : -1, flesh_fields : {pgt : ['children']}} - ).then( - function(tree) {service.absorbTree(tree, 'pgt')} - ); + aws : function() { + // by default, load only the workstation for the authenticated + // user. to load all workstations, override this loader. + // TODO: auth.session.retrieve should be capable of returning + // the session with the workstation fleshed. + if (!egAuth.user().wsid()) { + // nothing to fetch. + return $q.when(); + } + return egPCRUD.retrieve('aws', egAuth.user().wsid()) + .then(function(ws) {service.absorbList([ws], 'aws')}); } - - // org unit settings, blah, blah }; return service; diff --git a/Open-ILS/web/js/ui/default/staff/services/idl.js b/Open-ILS/web/js/ui/default/staff/services/idl.js index fbc3e7793a..3d8892412b 100644 --- a/Open-ILS/web/js/ui/default/staff/services/idl.js +++ b/Open-ILS/web/js/ui/default/staff/services/idl.js @@ -21,7 +21,7 @@ angular.module('egCoreMod') var service = {}; service.parseIDL = function() { - console.log('egIDL.parseIDL()'); + console.debug('egIDL.parseIDL()'); // retain a copy of the full IDL within the service service.classes = $window._preload_fieldmapper_IDL; diff --git a/Open-ILS/web/js/ui/default/staff/services/list.js b/Open-ILS/web/js/ui/default/staff/services/list.js new file mode 100644 index 0000000000..18e7f97502 --- /dev/null +++ b/Open-ILS/web/js/ui/default/staff/services/list.js @@ -0,0 +1,226 @@ +/** + * Service for generating list management objects. + * Each object tracks common list attributes like limit, offset, etc., + * A ListManager is not responsible for collecting data, it's only + * there to allow controllers to have a known consistent API + * for manage list-related information. + * + * The service exports a single attribute, which instantiates + * a new ListManager object. Controllers using ListManagers + * are responsible for providing their own route persistence. + * + * var list = egList.create(); + * if (list.hasNextPage()) { ... } + * + */ + +angular.module('egListMod', ['egCoreMod']) + +.factory('egList', function() { + + function ListManager(args) { + var self = this; + this.limit = 25; + this.offset = 0; + this.sort = null; + this.totalCount = 0; + + // attribute on each item in our items list which + // refers to its unique identifier value + this.indexField = 'index'; + + // true if the index field name refers to a + // function instead of an object attribute + this.indexFieldAsFunction = false; + + // per-page list of items + this.items = []; + + // collect any defaults passed in + if (args) angular.forEach(args, + function(val, key) {self[key] = val}); + + // sorted list of all available display columns + // a column takes form of (at minimum) {name : name, label : label} + this.allColumns = []; + + // {name => true} map of visible columns + this.displayColumns = {}; + + // {index => true} map of selected rows + this.selectedRows = {}; + + this.indexValue = function(item) { + if (this.indexFieldAsFunction) { + return item[this.indexField](); + } else { + return item[this.indexField]; + } + } + + // returns item objects + this.selectedItems = function() { + var items = []; + angular.forEach( + this.items, + function(item) { + if (self.selectedRows[self.indexValue(item)]) + items.push(item); + } + ); + return items; + } + + // remove an item from the items list + this.removeItem = function(index) { + angular.forEach(this.items, function(item, idx) { + if (self.indexValue(item) == index) + self.items.splice(idx, 1); + }); + delete this.selectedRows[index]; + } + + this.count = function() { return this.items.length } + + this.reset = function() { + this.offset = 0; + this.totalCount = 0; + this.items = []; + this.selectedRows = {}; + } + + // prepare to draw a new page of data + this.resetPageData = function() { + this.items = []; + this.selectedRows = {}; + } + + this.showAllColumns = function() { + angular.forEach(this.allColumns, function(field) { + self.displayColumns[field.name] = true; + }); + } + + this.hideAllColumns = function() { + angular.forEach(this.allColumns, function(field) { + delete self.displayColumns[field.name] + }); + } + + // selects one row after deselecting all of the others + this.selectOneRow = function(index) { + this.deselectAllRows(); + this.selectedRows[index] = true; + } + + // selects or deselects a row, without affecting the others + this.toggleOneRowSelection = function(index) { + if (this.selectedRows[index]) { + delete this.selectedRows[index]; + } else { + this.selectedRows[index] = true; + } + } + + // selects all visible rows + this.selectAllRows = function() { + angular.forEach(this.items, function(item) { + self.selectedRows[self.indexValue(item)] = true + }); + } + + // if all are selected, deselect all, otherwise select all + this.toggleSelectAll = function() { + if (Object.keys(this.selectedRows).length == this.items.length) { + this.deselectAllRows(); + } else { + this.selectAllRows(); + } + } + + // deselects all visible rows + this.deselectAllRows = function() { + this.selectedRows = {}; + } + + this.defaultColumns = function(list) { + // set the display=true value for the selected columns + angular.forEach(list, function(name) { + self.displayColumns[name] = true + }); + + // default columns may be provided before we + // know what our columns are. Save them for later. + this._defaultColumns = list; + + // setColumns we rearrange the allCollums + // list based on the content of this._defaultColums + if (this.allColumns.length) + this.setColumns(this.allColumns); + } + + this.setColumns = function(list) { + if (this._defaultColumns) { + this.allColumns = []; + + // append the default columns to the front of + // our allColumnst list. Any remaining columns + // are plopped onto the end. + angular.forEach( + this._defaultColumns, + function(name) { + var foundIndex; + angular.forEach(list, function(f, idx) { + if (f.name == name) { + self.allColumns.push(f); + foundIndex = idx; + } + }); + list.splice(foundIndex, 1); + } + ); + this.allColumns = this.allColumns.concat(list); + delete this._defaultColumns; + + } else { + this.allColumns = list; + } + } + + this.onFirstPage = function() { + return this.offset == 0; + } + + this.hasNextPage = function() { + // we have less data than requested, there must + // not be any more pages + if (this.items.length < this.limit) return false; + + // if the total count is not known, assume that a full + // page of data implies more pages are available. + if (!this.totalCount) return true; + + // we have a full page of data, but is there more? + return this.totalCount > (this.offset + this.items.length); + } + + this.incrementPage = function() { + this.offset += this.limit; + } + + this.decrementPage = function() { + if (this.offset < this.limit) { + this.offset = 0; + } else { + this.offset -= this.limit; + } + } + } + + return { + create : function(args) { + return new ListManager(args) + } + }; +}); + diff --git a/Open-ILS/web/js/ui/default/staff/services/org.js b/Open-ILS/web/js/ui/default/staff/services/org.js index a9b38fa290..4627f6ab8c 100644 --- a/Open-ILS/web/js/ui/default/staff/services/org.js +++ b/Open-ILS/web/js/ui/default/staff/services/org.js @@ -13,11 +13,11 @@ function(egEnv, egAuth, egPCRUD) { service.get = function(node_or_id) { if (typeof node_or_id == 'object') return node_or_id; - return egEnv.get('aou').map[node_or_id]; + return egEnv.aou.map[node_or_id]; }; service.list = function() { - return egEnv.get('aou').list; + return egEnv.aou.list; }; service.ancestors = function(node_or_id) { diff --git a/Open-ILS/web/js/ui/default/staff/services/startup.js b/Open-ILS/web/js/ui/default/staff/services/startup.js index dee9e3c7b6..d7820dab64 100644 --- a/Open-ILS/web/js/ui/default/staff/services/startup.js +++ b/Open-ILS/web/js/ui/default/staff/services/startup.js @@ -13,31 +13,21 @@ angular.module('egCoreMod') -.factory('egStartupCache', - ['$cacheFactory', function($cacheFactory) { - return $cacheFactory('egStartupCache', {number : 1}); -}]) - - .factory('egStartup', - ['$q', '$rootScope', '$timeout', '$location', '$window', - 'egStartupCache', 'egIDL', 'egAuth', 'egEnv', -function( - $q, $rootScope, $timeout, $location, $window, - egStartupCache, egIDL, egAuth, egEnv) { + ['$q','$rootScope','$location','$window','egIDL','egAuth','egEnv', +function($q, $rootScope, $location, $window, egIDL, egAuth, egEnv) { return { - go : function (args) { - args = args || {}; - - if (egStartupCache.get('promise')) { - // startup is done, return our promise - return egStartupCache.get('promise').promise; + promise : null, + go : function () { + if (this.promise) { + // startup already started, return our existing promise + return this.promise; } // create a new promise and fire off startup var deferred = $q.defer(); - egStartupCache.put('promise', deferred); + this.promise = deferred.promise; // IDL parsing is sync. No promises required egIDL.parseIDL(); @@ -45,7 +35,7 @@ function( // testAuthToken resolved function() { - egEnv.load(args.load_classes).then( + egEnv.load().then( function() { deferred.resolve() }, function() { deferred.reject('egEnv did not resolve') @@ -57,7 +47,7 @@ function( function() { console.log('egAuth found no valid authtoken'); if ($location.path() == '/login') { - console.log('egStartup resolving without authtoken on /login'); + console.debug('egStartup resolving without authtoken on /login'); deferred.resolve(); } else { // TODO: this is a little hinky because it causes 2 redirects. @@ -72,7 +62,7 @@ function( } ); - return deferred.promise; + return this.promise; } }; }]); diff --git a/Open-ILS/web/js/ui/default/staff/services/user.js b/Open-ILS/web/js/ui/default/staff/services/user.js index 61db10a191..7aa6bdcfcf 100644 --- a/Open-ILS/web/js/ui/default/staff/services/user.js +++ b/Open-ILS/web/js/ui/default/staff/services/user.js @@ -1,23 +1,19 @@ -/** Service for fetching fleshed user objects. - * The last user retrieved is kept in the local cache */ +/** + * Service for fetching fleshed user objects. + * The last user retrieved is kept until replaced by a new user. + */ angular.module('egUserMod', ['egCoreMod']) -.factory('egUserCache', - ['$cacheFactory', function($cacheFactory) { - return $cacheFactory('egUserCache', {number : 1}); -}]) - - .factory('egUser', -['$q', '$timeout', 'egNet', 'egAuth', 'egUserCache', 'egOrg', -function($q, $timeout, egNet, egAuth, egUserCache, egOrg) { + ['$q','$timeout','egNet','egAuth','egOrg', +function($q, $timeout, egNet, egAuth, egOrg) { - var service = {}; + var service = {_user : null}; service.get = function(userId) { var deferred = $q.defer(); - var last = egUserCache.get('last'); + var last = sevice._user; if (last && last.id() == userId) { return $q.when(last); @@ -29,10 +25,10 @@ function($q, $timeout, egNet, egAuth, egUserCache, egOrg) { egAuth.token(), userId).then( function(user) { if (user && user.classname == 'au') { - egUserCache.put('last', user); + service._user = user; deferred.resolve(user); } else { - egUserCache.remove('last'); + service._user = null; deferred.reject(user); } } @@ -46,8 +42,8 @@ function($q, $timeout, egNet, egAuth, egUserCache, egOrg) { * Returns the full list of org unit objects at which the currently * logged in user has the selected permissions. * @permList - list or string. If a list, the response object is a - * hash of perm => orgList maps. If a string, the response is only - * the final org list. + * hash of perm => orgList maps. If a string, the response is the + * org list for the requested perm. */ service.hasPermAt = function(permList) { var deferred = $q.defer(); -- 2.11.0