]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/dojo/openils/Util.js
LP1615805 No inputs after submit in patron search (AngularJS)
[working/Evergreen.git] / Open-ILS / web / js / dojo / openils / Util.js
1 /* ---------------------------------------------------------------------------
2  * Copyright (C) 2008  Georgia Public Library Service
3  * Bill Erickson <erickson@esilibrary.com>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * ---------------------------------------------------------------------------
15  */
16
17
18 /**
19  * General purpose, static utility functions
20  */
21
22 if(!dojo._hasResource["openils.Util"]) {
23     dojo._hasResource["openils.Util"] = true;
24     dojo.provide("openils.Util");
25     dojo.require("dojo.date.locale");
26     dojo.require("dojo.date.stamp");
27     dojo.require('openils.Event');
28     dojo.declare('openils.Util', null, {});
29
30
31     openils.Util.timeStampRegexp =
32         /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\+-]\d{2})(\d{2})$/;
33
34     openils.Util.timeStampAsDateObj = function(s) {
35         if (s.constructor.name == "Date") return s;
36         return dojo.date.stamp.fromISOString(
37             s.replace(openils.Util.timeStampRegexp, "$1:$2")
38         );
39     }
40
41     /**
42      * Returns a locale-appropriate representation of a timestamp when the
43      * timestamp (first argument) is actually a string as provided by
44      * fieldmapper objects.
45      * The second argument is an optional argument that will be provided
46      * as the second argument to dojo.date.locale.format()
47      */
48     openils.Util.timeStamp = function(s, opts) {
49         if (typeof(opts) == "undefined") opts = {};
50
51         return dojo.date.locale.format(
52             openils.Util.timeStampAsDateObj(s), opts
53         );
54     };
55
56     openils.Util._userFullNameFields = [
57         "prefix", "first_given_name", "second_given_name",
58         "family_name", "suffix", "alias", "usrname"
59     ];
60
61     /**
62      * Return an array of all the name-related attributes, with nulls replaced
63      * by empty strings, from a given actor.usr fieldmapper object, to be used
64      * as the arguments to any string formatting function that wants them.
65      * Code to do this is duplicated all over the place and should be
66      * simplified as we go.
67      */
68     openils.Util.userFullName = function(user) {
69         return dojo.map(
70             openils.Util._userFullNameFields,
71             function(a) { return user[a]() || ""; }
72         );
73     };
74
75     /**
76      * Same as openils.Util.userFullName, but with a hash of results instead
77      * of an array (dojo.string.substitute(), for example, can use this too).
78      */
79     openils.Util.userFullNameHash = function(user) {
80         var hash = {};
81         dojo.forEach(
82             openils.Util._userFullNameFields,
83             function(a) { hash[a] = user[a]() || ""; }
84         );
85         return hash;
86     };
87
88     /**
89      * Wrapper for dojo.addOnLoad that verifies a valid login session is active
90      * before adding the function to the onload set
91      */
92     openils.Util.addOnLoad = function(func, noSes) {
93         if(func) {
94             if(!noSes) {
95                 dojo.require('openils.User');
96                 if(!openils.User.authtoken) 
97                     return;
98             }
99             console.log("adding onload " + func.name);
100             dojo.addOnLoad(func);
101         }
102     };
103
104     /**
105      * Returns true if the provided array contains the specified value
106      */
107     openils.Util.arrayContains = function(arr, val) {
108         for(var i = 0; arr && i < arr.length; i++) {
109             if(arr[i] == val)
110                 return true;
111         }
112         return false;
113     };
114
115     /**
116      * Given a HTML select object, returns the currently selected value
117      */
118     openils.Util.selectorValue = function(sel) {
119         if(!sel) return null;
120         var idx = sel.selectedIndex;
121         if(idx < 0) return null;
122         var o = sel.options[idx];
123         var v = o.value; 
124         if(v == null) v = o.innerHTML;
125         return v;
126     }
127
128     /**
129      * Returns the character code of the provided (or current window) event
130      */
131     openils.Util.getCharCode = function(evt) {
132         evt = (evt) ? evt : ((window.event) ? event : null); 
133         if(evt) {
134             return (evt.charCode ? evt.charCode : 
135                 ((evt.which) ? evt.which : evt.keyCode ));
136         } else { return -1; }
137     }
138
139
140     /**
141      * Registers a handler for when the Enter key is pressed while 
142      * the provided DOM node has focus.
143      */
144     openils.Util.registerEnterHandler = function(domNode, func) {
145             if(!(domNode && func)) return;
146             domNode.onkeydown = function(evt) {
147             var code = openils.Util.getCharCode(evt);
148             if(code == 13 || code == 3) 
149                 func();
150         }
151         }
152
153
154     /**
155      * Parses opensrf response objects to see if they contain 
156      * data and/or an ILS event.  This only calls request.recv()
157      * once, so in a streaming context, it's necessary to loop on
158      * this method. 
159      * @param r The OpenSRF Request object
160      * @param eventOK If true, any found events will be returned as responses.  
161      * If false, they will be treated as error conditions and their content will
162      * be alerted if openils.Util.alertEvent is set to true.  Also, if eventOk is
163      * false, the response content will be null when an event is encountered.
164      * @param isList If true, assume the response will be a list of data and
165      * check the 1st item in the list for event-ness instead of the list itself.
166      */
167     openils.Util.alertEvent = true;
168     openils.Util.readResponse = function(r, eventOk, isList) {
169         var msg = r.recv();
170         if(msg == null) return msg;
171         var val = msg.content();
172         var testval = val;
173         if(isList && dojo.isArray(val))
174             testval = val[0];
175         if(e = openils.Event.parse(testval)) {
176             if(eventOk || e.textcode == 'SUCCESS') return e;
177             console.log(e.toString());
178
179             // session timed out.  Stop propagation of requests queued by Util.onload 
180             // and launch the XUL login dialog if possible
181             var retryLogin = false;
182             if(e.textcode == 'NO_SESSION') {
183                 openils.User.authtoken = null; 
184                 if(openils.XUL.isXUL()) {
185                     retryLogin = true;
186                     openils.XUL.getNewSession( function() { location.href = location.href } );
187                 } else {
188                     // TODO: make the oilsLoginDialog templated via dojo so it can be 
189                     // used as a standalone widget
190                 }
191             }
192
193             if(openils.Util.alertEvent && !retryLogin)
194                 alert(e);
195             return null;
196         }
197         return val;
198     };
199
200
201     /**
202      * Given a DOM node, adds the provided class to the node 
203      */
204     openils.Util.addCSSClass = function(node, cls) {
205         if(!(node && cls)) return; 
206         var className = node.className;
207
208         if(!className) {
209             node.className = cls;
210             return;
211         }
212
213         var classList = className.split(/\s+/);
214         var newName = '';
215             
216         for (var i = 0; i < classList.length; i++) {
217             if(classList[i] == cls) return;
218             if(classList[i] != null)
219                 newName += classList[i] + " ";
220         }
221
222         newName += cls;
223         node.className = newName;
224     },
225
226     /**
227      * Given a DOM node, removes the provided class from the CSS class 
228      * name list.
229      */
230     openils.Util.removeCSSClass = function(node, cls) {
231         if(!(node && cls && node.className)) return;
232         var classList = node.className.split(/\s+/);
233         var className = '';
234         for(var i = 0; i < classList.length; i++) {
235             if (typeof(cls) == "object") { /* assume regex */
236                 if (!cls.test(classList[i])) {
237                     if(i == 0)
238                         className = classList[i];
239                     else
240                         className += ' ' + classList[i];
241                 }
242             } else {
243                 if (classList[i] != cls) {
244                     if(i == 0)
245                         className = classList[i];
246                     else
247                         className += ' ' + classList[i];
248                 }
249             }
250         }
251         node.className = className;
252     }
253
254     openils.Util.objectSort = function(list, field) {
255         if(dojo.isArray(list)) {
256             if(!field) field = 'id';
257             return list.sort(
258                 function(a, b) {
259                     if(a[field]() > b[field]()) return 1;
260                     return -1;
261                 }
262             );
263         }
264         return [];
265     };
266
267     openils.Util.isTrue = function(val) {
268         return (val && val != '0' && !(val+'').match(/^f$/i));
269     };
270
271     /**
272      * Turns a list into a mapped object.
273      * @param list The list
274      * @param pkey The field to use as the map key 
275      * @param isFunc If true, the map key field is an accessor function 
276      * that will return the value of the map key
277      */
278     openils.Util.mapList = function(list, pkey, isFunc) {
279         if(!(list && pkey)) 
280             return null;
281         var map = {};
282         for(var i in list) {
283             if(isFunc)
284                 map[list[i][pkey]()] = list[i];
285             else
286                 map[list[i][pkey]] = list[i];
287         }
288         return map;
289     };
290
291     /**
292      * Convenience function to trim leading and trailing whitespace at once.
293      */
294     openils.Util.trimString = function(s) {
295         return s.replace(/^\s*(.+)?\s*$/,"$1");
296     }
297
298     /**
299      * Assume a space-separated interval string, with optional comma
300      * E.g. "1 year, 2 days"  "3 days 6 hours"
301      */
302     openils.Util.intervalToSeconds = function(interval) {
303         var d = new Date();
304         var start = d.getTime();
305         var parts = interval.split(' ');
306         for(var i = 0; i < parts.length; i += 2)  {
307             var type = parts[i+1].replace(/s?,?$/,'');
308             switch(type) {
309                 case 'mon': // postgres
310                     type = 'month'; // dojo
311                     break;
312                 // add more as necessary
313             }
314
315             d = dojo.date.add(d, type, Number(parts[i]));
316         }
317         return Number((d.getTime() - start) / 1000);
318     };
319
320     openils.Util.hide = function(node) {
321         if(typeof node == 'string')
322             node = dojo.byId(node);
323         dojo.style(node, 'display', 'none');
324         dojo.style(node, 'visibility', 'hidden');
325     };
326
327     openils.Util.show = function(node, displayType) {
328         if(typeof node == 'string')
329             node = dojo.byId(node);
330         displayType = displayType || 'block';
331         dojo.style(node, 'display', displayType);
332         dojo.style(node, 'visibility', 'visible');
333     };
334
335     /** Toggles the display using show/hide, depending on the current value for CSS 'display' */
336     openils.Util.toggle = function(node, displayType) {
337         if(typeof node == 'string')
338             node = dojo.byId(node);
339         if(dojo.style(node, 'display') == 'none')
340             openils.Util.show(node, displayType);
341         else
342             openils.Util.hide(node);
343     };
344
345     openils.Util.appendClear = function(node, child) {
346         if(typeof node == 'string')
347             node = dojo.byId(node);
348         while(node.childNodes[0])
349             node.removeChild(node.childNodes[0]);
350         node.appendChild(child);
351     };
352
353     /**
354      * Plays a sound file via URL.  Only works with browsers
355      * that support HTML 5 <audio> element.  E.g. Firefox 3.5
356      */
357     openils.Util.playAudioUrl = function(urlString) {
358         if(!urlString) return;
359         var audio = new Audio(urlString);
360         audio.play();
361     }
362
363     /**
364      * Return the properties of an object as a list. Saves typing.
365      */
366     openils.Util.objectProperties = function(obj) {
367         var K = [];
368         for (var k in obj) K.push(k);
369         return K;
370     }
371
372     /**
373      * Return the values of an object as a list. There may be a Dojo
374      * idiom or something that makes this redundant. Check into that.
375      */
376     openils.Util.objectValues = function(obj) {
377         var V = [];
378         for (var k in obj) V.push(obj[k]);
379         return V;
380     }
381
382     openils.Util.uniqueElements = function(L) {
383         var o = {};
384         for (var k in L) o[L[k]] = true;
385         return openils.Util.objectProperties(o);
386     }
387
388     openils.Util.uniqueObjects = function(list, field) {
389         var sorted = openils.Util.objectSort(list, field);
390         var results = [];
391         for (var i = 0; i < sorted.length; i++) {
392             if (!i || (sorted[i][field]() != sorted[i-1][field]()))
393                 results.push(sorted[i]);
394         }
395         return results;
396     };
397
398     /**
399      * Highlight instances of each pattern in the given DOM node
400      * Inspired by the jquery plugin
401      * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
402      */
403     openils.Util.hilightNode = function(node, patterns, args) {
404
405         args = args ||{};
406         var hclass = args.classname || 'oils-highlight';
407
408         function _hilightNode(node, pat) {
409
410             if(node.nodeType == 3) { 
411
412                 pat = pat.toUpperCase();
413                 var text = node.data.toUpperCase();
414                 var pos = -1;
415
416                 // find each instance of pat in the current node
417                 while( (pos =  text.indexOf(pat, pos + 1)) >= 0 ) {
418
419                     var wrapper = dojo.create('span', {className : hclass});
420                     var midnode = node.splitText(pos);
421                     midnode.splitText(pat.length);
422                     wrapper.appendChild(midnode.cloneNode(true));
423                     midnode.parentNode.replaceChild(wrapper, midnode);
424                 }
425
426             } else if(node.nodeType == 1 && node.childNodes[0]) {
427
428                 // not a text node?  have you checked the children?
429                 dojo.forEach(
430                     node.childNodes,
431                     function(child) { _hilightNode(child, pat); }
432                 );
433             }
434         }
435
436         // descend the tree for each pattern, since nodes are changed during highlighting
437         dojo.forEach(patterns, function(pat) { _hilightNode(node, pat); });
438     };
439
440     openils.Util._legacyModulePaths = {};
441     /*****
442      * Take the URL of a JS file and magically turn it into something that
443      * dojo.require can load by registering a module path for it ... and load it.
444      *****/
445     openils.Util.requireLegacy = function(url) {
446         var bURL = url.replace(/\/[^\/]+$/,'');
447         var file = url.replace(/^.*\/([^\/]+)$/,'$1');
448         var libname = url.replace(/^.*?\/(.+)\/[^\/]+$/,'$1').replace(/[^a-z]/ig,'_');
449         var modname = libname + '.' + file.replace(/\.js$/,'');
450
451         if (!openils.Util._legacyModulePaths[libname]) {
452             dojo.registerModulePath(libname,bURL);
453             openils.Util._legacyModulePaths[libname] = {};
454         }
455
456         if (!openils.Util._legacyModulePaths[libname][modname]) {
457             dojo.require(modname, true);
458             openils.Util._legacyModulePaths[libname][modname] = true;
459         }
460
461         return openils.Util._legacyModulePaths[libname][modname];
462     };
463
464     /**
465      * Takes a chunk of HTML, inserts it into a new window, prints the window, 
466      * then closes the windw.  To provide ample printer queueing time, automatically
467      * wait a short time before closing the window after calling .print().  The amount
468      * of time to wait is based on the size of the data to be printed.
469      * @param html The HTML string
470      * @param callback Optional post-printing callback
471      */
472     openils.Util.printHtmlString = function(html, callback) {
473
474         var win = window.open('', 'Print Window', 'resizable,width=800,height=600,scrollbars=1,chrome'); 
475
476         // force the new window to the background
477         win.blur(); 
478         window.focus(); 
479
480         win.document.body.innerHTML = html;
481         win.print();
482
483         setTimeout(
484             function() { 
485                 win.close();
486                 if(callback)
487                     callback();
488             },
489             // 1k == 1 second pause, max 10 seconds
490             Math.min(html.length, 10000)
491         );
492     };
493
494     /**
495      * Check to see if we're running in an iframe; if we
496      * are, we assume that the iframe is specifically one
497      * used by the web staff client to embed legacy interfaces.
498      */
499     openils.Util.inIframe = function() {
500         /* http://stackoverflow.com/a/326076 */
501         try {
502             return window.self !== window.top;
503         } catch (e) {
504             return true;
505         }
506     };
507
508 }
509