1 /* ---------------------------------------------------------------------------
2 * Copyright (C) 2008 Georgia Public Library Service
3 * Bill Erickson <erickson@esilibrary.com>
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.
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 * ---------------------------------------------------------------------------
19 * General purpose, static utility functions
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, {});
31 openils.Util.timeStampRegexp =
32 /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\+-]\d{2})(\d{2})$/;
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")
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()
48 openils.Util.timeStamp = function(s, opts) {
49 if (typeof(opts) == "undefined") opts = {};
51 return dojo.date.locale.format(
52 openils.Util.timeStampAsDateObj(s), opts
56 openils.Util._userFullNameFields = [
57 "prefix", "first_given_name", "second_given_name",
58 "family_name", "suffix", "alias", "usrname"
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.
68 openils.Util.userFullName = function(user) {
70 openils.Util._userFullNameFields,
71 function(a) { return user[a]() || ""; }
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).
79 openils.Util.userFullNameHash = function(user) {
82 openils.Util._userFullNameFields,
83 function(a) { hash[a] = user[a]() || ""; }
89 * Wrapper for dojo.addOnLoad that verifies a valid login session is active
90 * before adding the function to the onload set
92 openils.Util.addOnLoad = function(func, noSes) {
95 dojo.require('openils.User');
96 if(!openils.User.authtoken)
99 console.log("adding onload " + func.name);
100 dojo.addOnLoad(func);
105 * Returns true if the provided array contains the specified value
107 openils.Util.arrayContains = function(arr, val) {
108 for(var i = 0; arr && i < arr.length; i++) {
116 * Given a HTML select object, returns the currently selected value
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];
124 if(v == null) v = o.innerHTML;
129 * Returns the character code of the provided (or current window) event
131 openils.Util.getCharCode = function(evt) {
132 evt = (evt) ? evt : ((window.event) ? event : null);
134 return (evt.charCode ? evt.charCode :
135 ((evt.which) ? evt.which : evt.keyCode ));
136 } else { return -1; }
141 * Registers a handler for when the Enter key is pressed while
142 * the provided DOM node has focus.
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)
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
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.
167 openils.Util.alertEvent = true;
168 openils.Util.readResponse = function(r, eventOk, isList) {
170 if(msg == null) return msg;
171 var val = msg.content();
173 if(isList && dojo.isArray(val))
175 if(e = openils.Event.parse(testval)) {
176 if(eventOk || e.textcode == 'SUCCESS') return e;
177 console.log(e.toString());
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()) {
186 openils.XUL.getNewSession( function() { location.href = location.href } );
188 // TODO: make the oilsLoginDialog templated via dojo so it can be
189 // used as a standalone widget
193 if(openils.Util.alertEvent && !retryLogin)
202 * Given a DOM node, adds the provided class to the node
204 openils.Util.addCSSClass = function(node, cls) {
205 if(!(node && cls)) return;
206 var className = node.className;
209 node.className = cls;
213 var classList = className.split(/\s+/);
216 for (var i = 0; i < classList.length; i++) {
217 if(classList[i] == cls) return;
218 if(classList[i] != null)
219 newName += classList[i] + " ";
223 node.className = newName;
227 * Given a DOM node, removes the provided class from the CSS class
230 openils.Util.removeCSSClass = function(node, cls) {
231 if(!(node && cls && node.className)) return;
232 var classList = node.className.split(/\s+/);
234 for(var i = 0; i < classList.length; i++) {
235 if (typeof(cls) == "object") { /* assume regex */
236 if (!cls.test(classList[i])) {
238 className = classList[i];
240 className += ' ' + classList[i];
243 if (classList[i] != cls) {
245 className = classList[i];
247 className += ' ' + classList[i];
251 node.className = className;
254 openils.Util.objectSort = function(list, field) {
255 if(dojo.isArray(list)) {
256 if(!field) field = 'id';
259 if(a[field]() > b[field]()) return 1;
267 openils.Util.isTrue = function(val) {
268 return (val && val != '0' && !(val+'').match(/^f$/i));
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
278 openils.Util.mapList = function(list, pkey, isFunc) {
284 map[list[i][pkey]()] = list[i];
286 map[list[i][pkey]] = list[i];
292 * Convenience function to trim leading and trailing whitespace at once.
294 openils.Util.trimString = function(s) {
295 return s.replace(/^\s*(.+)?\s*$/,"$1");
299 * Assume a space-separated interval string, with optional comma
300 * E.g. "1 year, 2 days" "3 days 6 hours"
302 openils.Util.intervalToSeconds = function(interval) {
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?,?$/,'');
309 case 'mon': // postgres
310 type = 'month'; // dojo
312 // add more as necessary
315 d = dojo.date.add(d, type, Number(parts[i]));
317 return Number((d.getTime() - start) / 1000);
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');
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');
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);
342 openils.Util.hide(node);
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);
354 * Plays a sound file via URL. Only works with browsers
355 * that support HTML 5 <audio> element. E.g. Firefox 3.5
357 openils.Util.playAudioUrl = function(urlString) {
358 if(!urlString) return;
359 var audio = document.createElement('audio');
360 audio.setAttribute('src', urlString);
361 audio.setAttribute('autoplay', 'true');
362 document.body.appendChild(audio);
363 document.body.removeChild(audio);
367 * Return the properties of an object as a list. Saves typing.
369 openils.Util.objectProperties = function(obj) {
371 for (var k in obj) K.push(k);
376 * Return the values of an object as a list. There may be a Dojo
377 * idiom or something that makes this redundant. Check into that.
379 openils.Util.objectValues = function(obj) {
381 for (var k in obj) V.push(obj[k]);
385 openils.Util.uniqueElements = function(L) {
387 for (var k in L) o[L[k]] = true;
388 return openils.Util.objectProperties(o);
391 openils.Util.uniqueObjects = function(list, field) {
392 var sorted = openils.Util.objectSort(list, field);
394 for (var i = 0; i < sorted.length; i++) {
395 if (!i || (sorted[i][field]() != sorted[i-1][field]()))
396 results.push(sorted[i]);
402 * Highlight instances of each pattern in the given DOM node
403 * Inspired by the jquery plugin
404 * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
406 openils.Util.hilightNode = function(node, patterns, args) {
409 var hclass = args.classname || 'oils-highlight';
411 function _hilightNode(node, pat) {
413 if(node.nodeType == 3) {
415 pat = pat.toUpperCase();
416 var text = node.data.toUpperCase();
419 // find each instance of pat in the current node
420 while( (pos = text.indexOf(pat, pos + 1)) >= 0 ) {
422 var wrapper = dojo.create('span', {className : hclass});
423 var midnode = node.splitText(pos);
424 midnode.splitText(pat.length);
425 wrapper.appendChild(midnode.cloneNode(true));
426 midnode.parentNode.replaceChild(wrapper, midnode);
429 } else if(node.nodeType == 1 && node.childNodes[0]) {
431 // not a text node? have you checked the children?
434 function(child) { _hilightNode(child, pat); }
439 // descend the tree for each pattern, since nodes are changed during highlighting
440 dojo.forEach(patterns, function(pat) { _hilightNode(node, pat); });
443 openils.Util._legacyModulePaths = {};
445 * Take the URL of a JS file and magically turn it into something that
446 * dojo.require can load by registering a module path for it ... and load it.
448 openils.Util.requireLegacy = function(url) {
449 var bURL = url.replace(/\/[^\/]+$/,'');
450 var file = url.replace(/^.*\/([^\/]+)$/,'$1');
451 var libname = url.replace(/^.*?\/(.+)\/[^\/]+$/,'$1').replace(/[^a-z]/ig,'_');
452 var modname = libname + '.' + file.replace(/\.js$/,'');
454 if (!openils.Util._legacyModulePaths[libname]) {
455 dojo.registerModulePath(libname,bURL);
456 openils.Util._legacyModulePaths[libname] = {};
459 if (!openils.Util._legacyModulePaths[libname][modname]) {
460 dojo.require(modname, true);
461 openils.Util._legacyModulePaths[libname][modname] = true;
464 return openils.Util._legacyModulePaths[libname][modname];
468 * Takes a chunk of HTML, inserts it into a new window, prints the window,
469 * then closes the windw. To provide ample printer queueing time, automatically
470 * wait a short time before closing the window after calling .print(). The amount
471 * of time to wait is based on the size of the data to be printed.
472 * @param html The HTML string
473 * @param callback Optional post-printing callback
475 openils.Util.printHtmlString = function(html, callback) {
477 var win = window.open('', 'Print Window', 'resizable,width=800,height=600,scrollbars=1,chrome');
479 // force the new window to the background
483 win.document.body.innerHTML = html;
492 // 1k == 1 second pause, max 10 seconds
493 Math.min(html.length, 10000)