4 * Manages startup data loading and data caching.
5 * All registered loaders run * simultaneously. When all promises
6 * are resolved, the promise * returned by egEnv.load() is resolved.
8 * There are two main uses cases for egEnv:
10 * 1. When loading a variety of objects on page load, having them
11 * loaded with egEnv ensures that the load will happen in parallel
12 * and that it will complete before egStartup completes, which is
13 * generally before page controllers run.
15 * 2. When loading generic IDL data across different services,
16 * having them all stash the data in egEnv means they each have
17 * an agreed-upon cache mechanism.
19 * It's also a good place to stash other environmental tidbits...
21 * Generic and class-based loaders are supported.
23 * To load a registred class, push the class hint onto
26 * // will cause all 'pgt' objects to be fetched
27 * egEnv.loadClasses.push('pgt');
29 * To register a new class loader,attach a loader function to
30 * egEnv.classLoaders, keyed on the class hint, which returns a promise.
32 * egEnv.classLoaders.ccs = function() {
33 * // loads copy status objects, returns promise
36 * Generic loaders go onto the egEnv.loaders array. Each should
39 * egEnv.loaders.push(function() {
40 * return egNet.request(...)
41 * .then(function(stuff) { console.log('stuff!')
45 angular.module('egCoreMod')
49 ['$q','$window','$injector','egAuth','egPCRUD','egIDL',
50 function($q, $window , $injector , egAuth, egPCRUD, egIDL) {
53 // collection of custom loader functions
56 // Add class hints to this list when offline does not need them and
57 // if they cause "Maximum call stack size exceeded" console errors.
58 // If offline does need a list that causes problems, a custom loader
60 // We'll start with authority-related classes causing problems in the
62 ignoreOffline : ['at','acs','abaafm','aba','acsbf','acsaf']
66 // <base href="<basePath>"/> from the current index page
67 // Currently defaults to /eg/staff for all pages.
68 // Use $location.path() to jump around within an app.
69 // Use egEnv.basePath to create URLs to new apps.
70 // NOTE: the dynamic version below derived from the DOM does not
71 // work w/ unit tests. Use hard-coded value instead for now.
72 service.basePath = '/eg/staff/';
73 //$window.document.getElementsByTagName('base')[0].getAttribute('href');
75 /* returns a promise, loads all of the specified classes */
76 service.load = function() {
77 // always assume the user is logged in
78 if (!egAuth.user()) return $q.when();
81 var classes = this.loadClasses;
82 console.debug('egEnv loading classes => ' + classes);
84 angular.forEach(classes, function(cls) {
85 allPromises.push(service.classLoaders[cls]());
87 angular.forEach(this.loaders, function(loader) {
88 allPromises.push(loader());
91 return $q.all(allPromises).then(
92 function() { console.debug('egEnv load complete') });
95 /** given a tree-shaped collection, captures the tree and
96 * flattens the tree for absorption.
98 service.absorbTree = function(tree, class_, noOffline) {
99 if (service[class_] && service[class_].loaded) return;
102 function squash(node) {
104 angular.forEach(node.children(), squash);
107 var blob = service.absorbList(list, class_, noOffline);
111 var egLovefield; // we'll inject it manually
113 /** caches the object list both as the list and an id => object map */
114 service.absorbList = function(list, class_, noOffline) {
115 if (service[class_] && service[class_].loaded) return service[class_];
118 var pkey = egIDL.classes[class_].pkey;
120 if (service[class_]) {
121 // appending data to an existing class. Useful for receiving
122 // class elements as-needed. Avoid adding items which are
123 // already tracked in the list.
124 blob = service[class_];
125 angular.forEach(list, function(item) {
126 if (!service[class_].map[item[pkey]()])
127 blob.list.push(item);
130 blob = {list : list, map : {}};
133 if (!noOffline && service.ignoreOffline.indexOf(class_) < 0) {
135 egLovefield = $injector.get('egLovefield');
137 //console.debug('About to cache a list of ' + class_ + ' objects...');
138 egLovefield.isCacheGood(class_).then(function(good) {
139 if (!good) egLovefield.setListInOfflineCache(class_, blob.list);
143 angular.forEach(list, function(item) {blob.map[item[pkey]()] = item});
144 service[class_] = blob;
145 service[class_].loaded = true;
150 * list of classes to load on every page, regardless of whether
151 * a page-specific list is provided.
153 service.loadClasses = ['aou'];
156 * Default class loaders. Only add classes directly to this file
157 * that are loaded practically always. All other app-specific
158 * classes should be registerd from within the app.
160 service.classLoaders = {
164 egLovefield = $injector.get('egLovefield');
167 return egLovefield.reconstituteTree('aou').then(function(offline) {
168 if (offline) return $q.when();
169 if (service.aou && service.aou.loaded) return $q.when();
171 // EXPERIMENT: cache the org tree in session storage.
172 // This means that if the org tree changes, users will have to
173 // open the client in a new browser tab to clear the cached tree.
174 var treeJSON = $window.sessionStorage.getItem('eg.env.aou.tree');
176 console.debug('serving org tree from cache');
177 var tree = JSON2js(treeJSON);
178 service.absorbTree(tree, 'aou')
179 return $q.when(tree);
182 // sort orgs at each level by shortname
183 function sort_aou(node) {
184 node.children(node.children().sort(function(a, b) {
185 return a.shortname() < b.shortname() ? -1 : 1;
187 angular.forEach(node.children(), sort_aou);
190 return egPCRUD.search('aou', {parent_ou : null},
191 {flesh : -1, flesh_fields : {aou : ['children', 'ou_type']}}
195 $window.sessionStorage.setItem(
196 'eg.env.aou.tree', js2JSON(tree));
197 service.absorbTree(tree, 'aou');