LP#1797923 Browser client iframe initial loading page
[working/Evergreen.git] / Open-ILS / web / js / ui / default / staff / services / eframe.js
1 angular.module('egCoreMod')
2
3 /*
4  * Iframe container for (mostly legacy) embedded interfaces
5  */
6 .directive('egEmbedFrame', function() {
7     return {
8         restrict : 'AE',
9         replace : true,
10         scope : {
11             // URL to load in the embed iframe
12             url : '=',
13
14             // optional hash of functions which augment or override 
15             // the stock xulG functions defined below.
16             handlers : '=?',
17             frame : '=?',
18
19             // called after onload of each new iframe page
20             onchange : '=?',
21
22             // called after egFrameEmbedLoader, during link phase
23             afterload : '@',
24
25             // for tweaking height
26             saveSpace : '@',
27             minHeight : '=?',
28
29             // to display button for displaying embedded page
30             // in a new tab
31             allowEscape : '=?'
32         },
33
34         templateUrl : './share/t_eframe',
35
36         link: function (scope, element, attrs) {
37             scope.autoresize = 'autoresize' in attrs;
38             scope.showIframe = true;
39             // well, I *might* embed XUL; in any event, this gives a way
40             // for things like Dojo widgets to detect whether they are
41             // running in an eframe before the frame load has finished.
42             window.IEMBEDXUL = true;
43             element.find('iframe').on(
44                 'load',
45                 function() {
46                     scope.egEmbedFrameLoader(this);
47                     if (scope.afterload) this.contentWindow[scope.afterload]();
48                 }
49             );
50         },
51
52         controller : 
53                    ['$scope','$window','$location','$q','$timeout','egCore',
54             function($scope , $window , $location , $q , $timeout , egCore) {
55
56             $scope.save_space = $scope.saveSpace ? $scope.saveSpace : 300;
57             // Set the initial iframe height to just under the window height.
58             // leave room for the navbar, padding, margins, etc.
59             $scope.height = $window.outerHeight - $scope.save_space;
60             if ($scope.minHeight && $scope.height < $scope.minHeight) {
61                 $scope.height = $scope.minHeight;
62             }
63
64             // browser client doesn't use cookies, so we don't load the
65             // (at the time of writing, quite limited) angular.cookies
66             // module.  We could load something, but this seems to work
67             // well enough for setting the auth cookie (at least, until 
68             // it doesn't).
69             //
70             // note: document.cookie is smart enough to leave unreferenced
71             // cookies alone, so contrary to how this might look, it's not 
72             // deleting other cookies (anoncache, etc.)
73             
74             // delete any existing ses cookie
75             $window.document.cookie = "ses=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT";
76             // push our authtoken in
77             $window.document.cookie = 'ses=' + egCore.auth.token() + '; path=/; secure'
78
79             // $location has functions for modifying paths and search,
80             // but they all assume you are staying within the angular
81             // app, which we are not.  Build the URLs by hand.
82             function open_tab(path) {
83                 var url = 'https://' + $window.location.hostname + 
84                     egCore.env.basePath + path;
85                 console.debug('egEmbedFrame opening tab ' + url);
86                 $window.open(url, '_blank').focus();
87             }
88
89             // define our own xulG functions to be inserted into the
90             // iframe.  NOTE: window-level functions are bad.  Though
91             // there is probably a way, I was unable to correctly wire
92             // up the iframe onload handler within the controller or link
93             // funcs.  In any event, the code below is meant as a stop-gap
94             // for porting dojo, etc. apps to angular apps and should
95             // eventually go away.
96             // NOTE: catalog integration is not a stop-gap
97
98             $scope.egEmbedFrameLoader = function(iframe) {
99
100                 $scope.frame = {dom:iframe};
101                 $scope.iframe = iframe;
102
103                 if ($scope.autoresize) {
104                     iFrameResize({}, $scope.iframe);
105                 } else {
106                     // Reset the iframe height to the final content height.
107                     if ($scope.height < $scope.iframe.contentWindow.document.body.scrollHeight)
108                         $scope.height = $scope.iframe.contentWindow.document.body.scrollHeight;
109                 }
110
111                 var page = $scope.iframe.contentWindow.location.href;
112                 console.debug('egEmbedFrameLoader(): ' + page);
113
114                 if (page.match(/eg\/staff\/loading$/)) { // loading page
115
116                     // If we have a startup-time URL, apply it now.
117                     if ($scope.url) {
118                         console.debug('Applying initial URL: ' + $scope.url);
119                         iframe.contentWindow.location.href = $scope.url;
120                     }
121
122                     // Watch for future URL changes
123                     $scope.$watch('url', function(newVal, oldVal) {
124                         if (newVal && newVal != oldVal) {
125                             iframe.contentWindow.location.href = newVal;
126                         }
127                     });
128
129                     // Nothing more is needed until the iframe is
130                     // loaded once more with a real URL.
131                     return;
132                 }
133
134                 // reload ifram page w/o reloading the entire UI
135                 $scope.reload = function() {
136                     $scope.iframe.contentWindow.location.replace(
137                         $scope.iframe.contentWindow.location);
138                 }
139
140                 $scope.style = function() {
141                     return 'height:' + $scope.height + 'px';
142                 }
143
144                 // tell the iframe'd window its inside the staff client
145                 $scope.iframe.contentWindow.IAMXUL = true;
146
147                 // also tell it it's inside the browser client, which 
148                 // may be needed in a few special cases.
149                 $scope.iframe.contentWindow.IAMBROWSER /* hear me roar */ = true; 
150
151                 // XUL has a dump() function which is occasinally called 
152                 // from embedded browsers.
153                 $scope.iframe.contentWindow.dump = function(msg) {
154                     console.debug('egEmbedFrame:dump(): ' + msg);
155                 }
156
157                 // Adjust the height again if the iframe loads the openils.Util Dojo module
158                 $timeout(function () {
159                     if ($scope.autoresize) return; // let iframe-resizer handle it
160                     if ($scope.iframe.contentWindow.openils && $scope.iframe.contentWindow.openils.Util) {
161
162                         // HACK! for patron reg page
163                         var e = $scope.iframe.contentWindow.document.getElementById('myForm');
164                         var extra = 50;
165                         
166                         // HACK! for vandelay
167                         if (!e) {
168                             e = $scope.iframe.contentWindow.document.getElementById('vl-body-wrapper');
169                             extra = 10000;
170                         }
171
172                         if (!e) {
173                             e = $scope.iframe.contentWindow.document.body;
174                             extra = 0;
175                         }
176
177                         if ($scope.height < e.scrollHeight + extra) {
178                             $scope.iframe.contentWindow.openils.Util.addOnLoad( function() {
179                                 var old_height = $scope.height;
180                                 $scope.height = e.scrollHeight + extra;
181                                 $scope.$apply();
182                             });
183                         }
184                     }
185                 });
186
187                 // define a few commonly used stock xulG handlers. 
188                 
189                 $scope.iframe.contentWindow.xulG = {
190                     // patron search
191                     spawn_search : function(search) {
192                         open_tab('/circ/patron/search?search=' 
193                             + encodeURIComponent(js2JSON(search)));
194                     },
195
196                     // edit an existing user
197                     spawn_editor : function(info) {
198                         if (info.usr) {
199                             open_tab('/circ/patron/register/edit/' + info.usr);
200                         
201                         } else if (info.clone) {
202                             // FIXME: The save-and-clone operation in the
203                             // patron editor results in this action.  
204                             // For some reason, this specific function results
205                             // in a new browser window opening instead of a 
206                             // browser tab.  Possibly this is caused by the 
207                             // fact that the action occurs as a result of a
208                             // button click instead of an href.  *shrug*.
209                             // It's obnoxious.
210                             open_tab('/circ/patron/register/clone/' + info.clone);
211                         } 
212                     },
213
214                     // open a user account
215                     new_patron_tab : function(tab_info, usr_info) {
216                         open_tab('/circ/patron/' + usr_info.id + '/checkout');
217                     },
218
219                     get_barcode_and_settings_async : function(barcode, only_settings) {
220                         if (!barcode) return $q.reject();
221                         var deferred = $q.defer();
222
223                         var barcode_promise = $q.when(barcode);
224                         if (!only_settings) {
225
226                             // first verify / locate the barcode
227                             barcode_promise = egCore.net.request(
228                                 'open-ils.actor',
229                                 'open-ils.actor.get_barcodes',
230                                 egCore.auth.token(), 
231                                 egCore.auth.user().ws_ou(), 'actor', barcode
232                             ).then(function(resp) {
233
234                                 if (!resp || egCore.evt.parse(resp) || !resp.length) {
235                                     console.error('user not found: ' + barcode);
236                                     deferred.reject();
237                                     return null;
238                                 } 
239
240                                 resp = resp[0];
241                                 return barcode = resp.barcode;
242                             });
243                         }
244
245                         barcode_promise.then(function(barcode) {
246                             if (!barcode) return;
247
248                             return egCore.net.request(
249                                 'open-ils.actor',
250                                 'open-ils.actor.user.fleshed.retrieve_by_barcode',
251                                 egCore.auth.token(), barcode);
252
253                         }).then(function(user) {
254                             if (!user) return null;
255
256                             if (e = egCore.evt.parse(user)) {
257                                 console.error('user fetch failed : ' + e.toString());
258                                 deferred.reject();
259                                 return null;
260                             }
261
262                             egCore.org.settings(['circ.staff_placed_holds_fallback_to_ws_ou'])
263                                 .then(function(auth_usr_aous){
264
265                                     // copied more or less directly from XUL menu.js
266                                     var settings = {};
267                                     for(var i = 0; i < user.settings().length; i++) {
268                                         settings[user.settings()[i].name()] = 
269                                             JSON2js(user.settings()[i].value());
270                                     }
271
272                                     // find applicable YAOUSes for staff-placed holds
273                                     var requestor = egCore.auth.user();
274                                     var pickup_lib = user.home_ou(); // default to home ou
275                                     if (requestor.id() !== user.id()){
276                                         // this is a staff-placed hold, optionally default to ws ou
277                                         if (auth_usr_aous['circ.staff_placed_holds_fallback_to_ws_ou']){
278                                             pickup_lib = requestor.ws_ou();
279                                         }
280                                     }
281
282                                     if(!settings['opac.default_phone'] && user.day_phone()) 
283                                         settings['opac.default_phone'] = user.day_phone();
284                                     if(!settings['opac.hold_notify'] && settings['opac.hold_notify'] !== '') 
285                                         settings['opac.hold_notify'] = 'email:phone';
286
287                                     // Taken from patron/util.js format_name
288                                     // FIXME: I18n
289                                     var patron_name = 
290                                         ( user.prefix() ? user.prefix() + ' ' : '') +
291                                         user.family_name() + ', ' +
292                                         user.first_given_name() + ' ' +
293                                         ( user.second_given_name() ? user.second_given_name() + ' ' : '' ) +
294                                         ( user.suffix() ? user.suffix() : '');
295
296                                     deferred.resolve({
297                                         "barcode": barcode, 
298                                         "pickup_lib": pickup_lib,
299                                         "settings" : settings, 
300                                         "user_email" : user.email(), 
301                                         "patron_name" : patron_name
302                                     });
303                                 });
304                         });
305
306                         return deferred.promise;
307                     }
308                 }
309
310                 if ($scope.handlers) {
311                     $scope.handlers.reload = $scope.reload;
312                     angular.forEach($scope.handlers, function(val, key) {
313                         console.log('eframe applying xulG handlers: ' + key);
314                         $scope.iframe.contentWindow.xulG[key] = val;
315                     });
316                 }
317
318                 if ($scope.onchange) $scope.onchange(page);
319             }
320
321             // open a new tab with the embedded URL
322             $scope.escapeEmbed = function() {
323                 $scope.showIframe = false;
324                 $window.open($scope.iframe.contentWindow.location, '_blank').focus();
325             }
326             $scope.restoreEmbed = function() {
327                 $scope.showIframe = true;
328                 $scope.reload();
329             }
330         }]
331     }
332 })
333
334