LP#1789747 SharedWorker sanity checks
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / services / env.js
1 /**
2  * Core Service - egEnv
3  *
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.
7  *
8  * There are two main uses cases for egEnv:
9  *
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.
14  *
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.
18  *
19  * It's also a good place to stash other environmental tidbits...
20  *
21  * Generic and class-based loaders are supported.  
22  *
23  * To load a registred class, push the class hint onto 
24  * egEnv.loadClasses.  
25  *
26  * // will cause all 'pgt' objects to be fetched
27  * egEnv.loadClasses.push('pgt');
28  *
29  * To register a new class loader,attach a loader function to 
30  * egEnv.classLoaders, keyed on the class hint, which returns a promise.
31  *
32  * egEnv.classLoaders.ccs = function() { 
33  *    // loads copy status objects, returns promise
34  * };
35  *
36  * Generic loaders go onto the egEnv.loaders array.  Each should
37  * return a promise.
38  *
39  * egEnv.loaders.push(function() {
40  *    return egNet.request(...)
41  *    .then(function(stuff) { console.log('stuff!') 
42  * });
43  */
44
45 angular.module('egCoreMod')
46
47 // env fetcher
48 .factory('egEnv', 
49        ['$q','$window','$injector','egAuth','egPCRUD','egIDL',
50 function($q,  $window , $injector , egAuth,  egPCRUD,  egIDL) { 
51
52     var service = {
53         // collection of custom loader functions
54         loaders : [],
55
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
59         // will be necessary.
60         // We'll start with authority-related classes causing problems in the
61         // staff catalog.
62         ignoreOffline : ['at','acs','abaafm','aba','acsbf','acsaf']
63     };
64
65
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');
74
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();
79
80         var allPromises = [];
81         var classes = this.loadClasses;
82         console.debug('egEnv loading classes => ' + classes);
83
84         angular.forEach(classes, function(cls) {
85             allPromises.push(service.classLoaders[cls]());
86         });
87         angular.forEach(this.loaders, function(loader) {
88             allPromises.push(loader());
89         });
90
91         return $q.all(allPromises).then(
92             function() { console.debug('egEnv load complete') });
93     };
94
95     /** given a tree-shaped collection, captures the tree and
96      *  flattens the tree for absorption.
97      */
98     service.absorbTree = function(tree, class_, noOffline) {
99         if (service[class_] && service[class_].loaded) return;
100
101         var list = [];
102         function squash(node) {
103             list.push(node);
104             angular.forEach(node.children(), squash);
105         }
106         squash(tree);
107         var blob = service.absorbList(list, class_, noOffline);
108         blob.tree = tree;
109     };
110
111     var egLovefield; // we'll inject it manually
112
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_];
116
117         var blob;
118         var pkey = egIDL.classes[class_].pkey;
119
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);
128             });
129         } else {
130             blob = {list : list, map : {}};
131         }
132
133         if (!noOffline && service.ignoreOffline.indexOf(class_) < 0) {
134             if (!egLovefield) {
135                 egLovefield = $injector.get('egLovefield');
136             }
137             //console.debug('About to cache a list of ' + class_ + ' objects...');
138             egLovefield.isCacheGood(class_).then(
139                 function(good) {
140                     if (!good) {
141                         egLovefield.setListInOfflineCache(class_, blob.list); 
142                     }
143                 },
144                 function() {} // Not Supported
145             );
146         }
147
148         angular.forEach(list, function(item) {blob.map[item[pkey]()] = item});
149         service[class_] = blob;
150         service[class_].loaded = true;
151         return blob;
152     };
153
154     /* 
155      * list of classes to load on every page, regardless of whether
156      * a page-specific list is provided.
157      */
158     service.loadClasses = ['aou'];
159
160     /*
161      * Default class loaders.  Only add classes directly to this file
162      * that are loaded practically always.  All other app-specific
163      * classes should be registerd from within the app.
164      */
165     service.classLoaders = {
166         aou : function() {
167
168             if (!egLovefield) {
169                 egLovefield = $injector.get('egLovefield');
170             }
171
172             return egLovefield.reconstituteTree('aou').then(function(offline) {
173                 if (offline) return $q.when();
174                 if (service.aou && service.aou.loaded) return $q.when();
175     
176                 // EXPERIMENT: cache the org tree in session storage.
177                 // This means that if the org tree changes, users will have to
178                 // open the client in a new browser tab to clear the cached tree.
179                 var treeJSON = $window.sessionStorage.getItem('eg.env.aou.tree');
180                 if (treeJSON) {
181                     console.debug('serving org tree from cache');
182                     var tree = JSON2js(treeJSON);
183                     service.absorbTree(tree, 'aou')
184                     return $q.when(tree);
185                 }
186     
187                 // sort orgs at each level by shortname
188                 function sort_aou(node) {
189                     node.children(node.children().sort(function(a, b) {
190                         return a.shortname() < b.shortname() ? -1 : 1;
191                     }));
192                     angular.forEach(node.children(), sort_aou);
193                 }
194     
195                 return egPCRUD.search('aou', {parent_ou : null}, 
196                     {flesh : -1, flesh_fields : {aou : ['children', 'ou_type']}}
197                 ).then(
198                     function(tree) {
199                         sort_aou(tree);
200                         $window.sessionStorage.setItem(
201                             'eg.env.aou.tree', js2JSON(tree));
202                         service.absorbTree(tree, 'aou');
203                         return $q.when();
204                     }
205                 );
206             },
207             function() {return $q.when()} // Not Supported, exit gracefully
208             );
209         },
210     };
211
212     return service;
213 }]);
214
215
216