]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/util/error.js
internal: an alternative to default_focus
[working/Evergreen.git] / Open-ILS / xul / staff_client / chrome / content / util / error.js
1 dump('entering util/error.js\n');
2
3 if (typeof util == 'undefined') util = {};
4 util.error = function () {
5
6     try {
7
8         try {
9             this.consoleService = Components.classes['@mozilla.org/consoleservice;1']
10                 .getService(Components.interfaces.nsIConsoleService);
11         } catch(E) {
12             this.consoleDump = false;
13             dump('util.error constructor: ' + E + '\n');
14         }
15
16         this.sdump_last_time = new Date();
17
18         this.OpenILS = {};
19
20         // Only use sounds if the context window has already created a sound object
21         if (typeof xulG != 'undefined' && xulG._sound) {
22             this.sound = xulG._sound;
23         }
24
25     } catch(E) {
26         alert('Error in util.error constructor: ' + E);
27     }
28
29     return this;
30 };
31
32 util.error.prototype = {
33
34     'allowPrintDebug' : true,
35     'allowConsoleDump' : true,
36     'allowDebugDump' : true,
37     'allowFileDump' : true,
38     'allowAlertDump' : true,
39
40     'forcePrintDebug' : false,
41     'forceConsoleDump' : false,
42     'forceDebugDump' : false,
43     'forceFileDump' : false,
44     'forceAlertDump' : false,
45
46     'arg_dump_full' : false,
47
48     'debug' : function(e){
49         dump('-----------------------------------------\n' 
50             + e + '\n-----------------------------------------\n' );
51     },
52
53     'obj_dump' : function(s,dobj) {
54         var o = 'typeof ' + dobj + ' = ' + typeof dobj + '\n';
55         for (var i in dobj) {
56             o += i + '\t' + typeof dobj[i] + '\n';
57         }
58         this.sdump(s,o);
59     },
60
61     'sdump_levels' : {
62
63         'D_NONE' : false, 
64         'D_ALL' : false,
65
66         'D_ERROR' : { 'debug' : true, 'console' : true }, 
67         'D_DEBUG' : { 'debug' : true, 'console' : true }, 
68         'D_TRACE' :  { 'debug' : false }, 
69         'D_ALERT' : { 'alert' : true, 'debug' : true },
70         'D_WARN' : { 'debug' : true }, 
71         'D_COLUMN_RENDER_ERROR' : { 'debug' : false }, 
72         'D_XULRUNNER' : { 'debug' : false }, 
73         'D_DECK' : { 'debug' : false },
74         'D_TRACE_ENTER' :  false, 
75         'D_TRACE_EXIT' :  false, 
76         'D_TIMEOUT' :  false, 
77         'D_FILTER' : { 'debug' : false },
78         'D_CONSTRUCTOR' : { 'debug' : false }, 
79         'D_FIREFOX' : { 'debug' : false }, 
80         'D_LEGACY' : { 'debug' : false }, 
81         'D_DATA_STASH' : { 'alert' : false }, 
82         'D_DATA_RETRIEVE' : { 'debug' : false },
83
84         'D_CLAM' : { 'debug' : false }, 
85         'D_PAGED_TREE' : { 'debug' : false }, 
86         'D_GRID_LIST' : { 'debug' : false }, 
87         'D_HTML_TABLE' : { 'debug' : false },
88         'D_TAB' : { 'debug' : false }, 
89         'D_LIST' : { 'debug' : false }, 
90         'D_LIST_DUMP_WITH_KEYS_ON_CLEAR' : { 'debug' : false }, 
91         'D_LIST_DUMP_ON_CLEAR' : { 'debug' : false },
92
93         'D_AUTH' : { 'debug' : false }, 
94         'D_OPAC' : { 'debug' : false }, 
95         'D_CAT' : { 'debug' : false }, 
96         'D_BROWSER' : { 'debug' : false },
97
98         'D_PATRON_SEARCH' : { 'debug' : false }, 
99         'D_PATRON_SEARCH_FORM' : { 'debug' : false }, 
100         'D_PATRON_SEARCH_RESULTS' : { 'debug' : false },
101
102         'D_PATRON_DISPLAY' : { 'debug' : false }, 
103         'D_PATRON_DISPLAY_STATUS' : { 'debug' : false }, 
104         'D_PATRON_DISPLAY_CONTACT' : { 'debug' : false },
105
106         'D_PATRON_ITEMS' : { 'debug' : false }, 
107         'D_PATRON_CHECKOUT_ITEMS' : { 'debug' : false }, 
108         'D_PATRON_HOLDS' : { 'debug' : false },
109         'D_PATRON_BILLS' : { 'debug' : false }, 
110         'D_PATRON_EDIT' : { 'debug' : false },
111
112         'D_CHECKIN' : { 'debug' : false }, 
113         'D_CHECKIN_ITEMS' : { 'debug' : false },
114
115         'D_HOLD_CAPTURE' : { 'debug' : false }, 
116         'D_HOLD_CAPTURE_ITEMS' : { 'debug' : false },
117
118         'D_PATRON_UTILS' : { 'debug' : false }, 
119         'D_CIRC_UTILS' : { 'debug' : false },
120
121         'D_FILE' : { 'debug' : false }, 
122         'D_EXPLODE' : { 'debug' : false }, 
123         'D_FM_UTILS' : { 'debug' : false }, 
124         'D_PRINT' : { 'debug' : false }, 
125         'D_OBSERVERS' : { 'debug' : false, 'console' : false, 'alert' : false },
126         'D_CACHE' : { 'debug' : false, 'console' : false, 'alert' : false },
127         'D_SES' : { 'debug' : false, 'console' : true },
128         'D_SES_FUNC' : { 'debug' : false }, 
129         'D_SES_RESULT' : { 'debug' : false }, 
130         'D_SES_ERROR' : { 'debug' : true, 'console' : true }, 
131         'D_SPAWN' : { 'debug' : false }, 
132         'D_STRING' : { 'debug' : false },
133         'D_UTIL' : { 'debug' : false }, 
134         'D_WIN' : { 'debug' : false }, 
135         'D_WIDGETS' : { 'debug' : false }
136     },
137
138     'filter_console_init' : function (p) {
139         this.sdump('D_FILTER',this.arg_dump(arguments,{0:true}));
140
141         var filterConsoleListener = {
142             observe: function( msg ) {
143                 try {
144                     p.observe_msg( msg );
145                 } catch(E) {
146                     alert(E);
147                 }
148             },
149             QueryInterface: function (iid) {
150                 if (!iid.equals(Components.interfaces.nsIConsoleListener) &&
151                     !iid.equals(Components.interfaces.nsISupports)) {
152                         throw Components.results.NS_ERROR_NO_INTERFACE;
153                 }
154                     return this;
155             }
156         };
157         try {
158             this.consoleService.registerListener(filterConsoleListener);    
159         } catch(E) {
160             alert(E);
161         }
162
163         this.sdump('D_TRACE_EXIT',this.arg_dump(arguments));
164     },
165
166     'sdump' : function (level,msg) {
167         try {
168             var now = new Date();
169             var message = now.valueOf() + '\tdelta = ' + (now.valueOf() - this.sdump_last_time.valueOf()) + '\t' + level + '\n' + msg;
170             if (this.sdump_levels['D_NONE']) return null;
171             if (this.sdump_levels[level]||this.sdump_levels['D_ALL']) {
172                 this.sdump_last_time = now;
173                 if (this.forceDebugDump || ( this.allowDebugDump && this.sdump_levels[level] && this.sdump_levels[level].debug ) ) this.debug(message);
174                 if (this.forceAlertDump || ( this.allowAlertDump && this.sdump_levels[level] && this.sdump_levels[level].alert ) ) alert(message);
175                 if (this.forceConsoleDump || ( this.allowConsoleDump && this.sdump_levels[level] && this.sdump_levels[level].console ) ) {
176                     if (level=='D_ERROR') {
177                         Components.utils.reportError(message);
178                     } else {
179                         this.consoleService.logStringMessage(message);
180                     }
181                 }
182                 if (this.forceFileDump || ( this.allowFileDump && this.sdump_levels[level] && this.sdump_levels[level].file ) ) {
183                     if (level!='D_FILE') {
184                         JSAN.use('util.file'); var master_log = new util.file('log');
185                         master_log.write_content('append',message); master_log.close();
186                         var specific_log = new util.file('log_'+level);
187                         specific_log.write_content('append',message); specific_log.close();
188                     }
189                 }
190             }
191         } catch(E) {
192             dump('Calling sdump but ' + E + '\n');
193         }
194     },
195
196     'arg_dump' : function (args,dump_these) {
197         var s = '*>*>*> Called function ';
198         try {
199             if (!dump_these)
200                 dump_these = {};
201             s += args.callee.toString().match(/\w+/g)[1] + ' : ';
202             for (var i = 0; i < args.length; i++)
203                 s += typeof(args[i]) + ' ';
204             s += '\n';
205             for (var i = 0; i < args.length; i++)
206                 if (dump_these[i]) {
207
208                     var arg = args[i];
209                     //dump('dump_these[i] = ' + dump_these[i] + '  arg = ' + arg + '\n');
210
211                     if (typeof(dump_these[i])=='string') {
212
213                         if (dump_these[i].slice(0,1) == '.') {
214                             var cmd = 'arg' + dump_these[i];
215                             var result;
216                             try {
217                                 result = eval( cmd );
218                             } catch(E) {
219                                 result = cmd + ' ==> ' + E;
220                             }
221                             s += '\targ #' + i + ': ' + cmd + ' = ' + result;
222                         } else {
223                             var result;
224                             try {
225                                 result = eval( dump_these[i] );
226                             } catch(E) {
227                                 result = dump_these[i] + ' ==> ' + E;
228                             }
229                             s += '\targ #' + i + ': ' + result;
230                         }
231     
232                     } else {
233                         s += '\targ #' + i + ' = ';
234                         try {
235                             //s += js2JSON( arg );
236                             s += arg;
237                         } catch(E) {
238                             s += arg;
239                         }
240                     }
241     
242                     s += '\n';
243                     if (this.arg_dump_full)
244                         s += 'Definition: ' + args.callee.toString() + '\n';
245     
246                 }
247             return s;
248         } catch(E) {
249             return s + '\nDEBUG ME: ' + js2JSON(E) + '\n';
250         }
251     },
252
253     'handle_error' : function (E,annoy) {
254         var s = '';
255         if (instanceOf(E,ex)) {
256             s += E.err_msg();
257             //s += '\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n';
258             //s += 'This error was anticipated.\n\n';
259             //s += js2JSON(E).substr(0,200) + '...\n\n';
260             if (snd_bad) snd_bad();
261         } else {
262             s += '\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n';
263             s += 'This is a bug that we will fix later.\n\n';
264             try {
265                 s += js2JSON(E).substr(0,1024) + '\n\n';
266             } catch(E2) {
267                 try {
268                     s += E.substr(0,1024) + '\n\n';
269                 } catch(E3) {
270                     s += E + '\n\n';
271                 }
272             }
273             if (snd_really_bad) snd_really_bad();
274         }
275         sdump('D_ERROR',s);
276         if (annoy)
277             this.s_alert(s);
278         else
279             alert(s);
280     },
281
282     's_alert' : function (s) { alert(s); },
283
284     'standard_network_error_alert' : function(msg) {
285         var obj = this;
286         if (!msg) msg = '';
287         var alert_msg = 'We experienced a network/server communication failure.  Please check your internet connection and try this action again.  Repeated failures may require attention from your local IT staff or your friendly Evergreen developers.\n\n' + msg;
288         obj.yns_alert(
289             alert_msg,    
290             'Communication Failure',
291             'Ok', null, null, 'Check here to confirm this message'
292         );
293     },
294
295     'standard_unexpected_error_alert' : function(msg,E) {
296         var obj = this;
297         if (E != null && typeof E.ilsevent != 'undefined') {
298             if (E.ilsevent == 0 /* SUCCESS */ ) {
299                 msg = "The action involved likely succeeded, however, this part of the software needs to be updated to better understand success messages from the server, so please let us know about it.";
300             }
301             if (E.ilsevent == -1 /* Network/Server Problem */ ) {
302                 return obj.standard_network_error_alert(msg);
303             }
304             if (E.ilsevent == 5000 /* PERM_FAILURE */ ) {
305                 msg = "The action involved likely failed due to insufficient permissions.  However, this part of the software needs to be updated to better understand permission messages from the server, so please let us know about it.";
306             }
307         }
308         if (!msg) msg = '';
309         var alert_msg = 'FIXME:  If you encounter this alert, please inform your IT/ILS helpdesk staff or your friendly Evergreen developers.\n\n' + (new Date()) + '\n\n' + msg + '\n\n' + (typeof E.ilsevent != 'undefined' ? E.textcode + '\n' + (E.desc ? E.desc + '\n' : '') : '') + ( typeof E.status != 'undefined' ? 'Status: ' + E.status + '\n': '' ) + ( typeof E == 'string' ? E + '\n' : '' );
310         obj.sdump('D_ERROR',msg + ' : ' + js2JSON(E));
311         var r = obj.yns_alert(
312             alert_msg,    
313             'Unhandled Error',
314             'Ok', 'Debug Output to send to Helpdesk', null, 'Check here to confirm this message',
315             '/xul/server/skin/media/images/skull.png'
316         );
317         if (r == 1) {
318             JSAN.use('util.window'); var win = new util.window();
319             win.open(
320                 'data:text/plain;charset=UTF-8,' + window.encodeURIComponent( 'Please open a helpdesk ticket and include the following text: \n\n' + (new Date()) + '\n\n' + msg + '\n\n' + obj.pretty_print(js2JSON(E)) ),
321                 'error_alert',
322                 'chrome,resizable,width=700,height=500'
323             );
324         }
325         if (r==2) {
326             alert('Not Yet Implemented');
327         }
328     },
329
330     'yns_alert' : function (s,title,b1,b2,b3,c,image) {
331
332         try {
333
334             if (location.href.match(/^chrome/)) return this.yns_alert_original(s,title,b1,b2,b3,c);
335
336         /* The original purpose of yns_alert was to prevent errors from being scanned through accidentally with a barcode scanner.  
337         However, this can be done in a less annoying manner by rolling our own dialog and not having any of the options in focus */
338
339         /*
340             s     = Message to display
341             title     = Text in Title Bar
342             b1    = Text for button 1
343             b2    = Text for button 2
344             b3    = Text for button 3
345             c    = Text for confirmation checkbox.  null for no confirm
346         */
347
348         dump('yns_alert:\n\ts = ' + s + '\n\ttitle = ' + title + '\n\tb1 = ' + b1 + '\n\tb2 = ' + b2 + '\n\tb3 = ' + b3 + '\n\tc = ' + c + '\n');
349
350         //FIXME - is that good enough of an escape job?
351         s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
352
353         var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" flex="1">' 
354             + '<groupbox flex="1" style="overflow: auto; border: solid thin red;"><caption label="' + (title) + '"/>';
355
356         if (image) xml += '<hbox><image src="' + image + '"/><spacer flex="1"/></hbox>';
357         xml += '<description id="msg" style="-moz-user-select: text; -moz-user-focus: normal; font-size: large">' + (s)
358             + '</description></groupbox><groupbox><caption label="Options"/><hbox>';
359         var b1_key = b1 ? b1[0] : '';
360         var b2_key = b2 ? b2[0] : '';
361         var b3_key = b3 ? b3[0] : ''; /* FIXME - need to check for collisions */
362         if (b1) xml += '<button id="b1" accesskey="' + b1_key + '" label="' + (b1) + '" name="fancy_submit" value="b1"/>';
363         if (b2) xml += '<button id="b2" accesskey="' + b2_key + '" label="' + (b2) + '" name="fancy_submit" value="b2"/>';
364         if (b3) xml += '<button id="b3" accesskey="' + b3_key + '" label="' + (b3) + '" name="fancy_submit" value="b3"/>';
365         var copy_button_label = 'Copy Message'; /* default in case the I18N infrastructure is failing, yns_alert often gets used for errors */
366         var x= document.getElementById('offlineStrings');
367         if (x) {
368             if (typeof x.getString == 'function') {
369                 if (x.getString('common.error.copy_msg')) { copy_button_label = x.getString('common.error.copy_msg'); }
370             }
371         }
372         xml += '<spacer flex="1"/><button label="' + copy_button_label + '" oncommand="try { copy_to_clipboard( document.getElementById(' + "'msg'" + ').textContent ); } catch(E) { alert(E); }" />';
373         xml += '</hbox></groupbox></vbox>';
374         JSAN.use('OpenILS.data');
375         //var data = new OpenILS.data(); data.init({'via':'stash'});
376         //data.temp_yns_xml = xml; data.stash('temp_yns_xml');
377         var url = urls.XUL_FANCY_PROMPT; // + '?xml_in_stash=temp_yns_xml' + '&title=' + window.escape(title);
378         if (typeof xulG != 'undefined') if (typeof xulG.url_prefix == 'function') url = xulG.url_prefix( url );
379         JSAN.use('util.window'); var win = new util.window();
380         var fancy_prompt_data = win.open(
381             url, 'fancy_prompt', 'chrome,resizable,modal,width=700,height=500', { 'xml' : xml, 'title' : title, 'sound' : 'bad' }
382         );
383         if (fancy_prompt_data.fancy_status == 'complete') {
384             switch(fancy_prompt_data.fancy_submit) {
385                 case 'b1' : return 0; break;
386                 case 'b2' : return 1; break;
387                 case 'b3' : return 2; break;
388             }
389         } else {
390             //return this.yns_alert(s,title,b1,b2,b3,c,image);
391             return null;
392         }
393
394         } catch(E) {
395
396             dump('yns_alert failed: ' + E + '\ns = ' + s + '\ntitle = ' + title + '\nb1 = ' + b1 + '\nb2 = ' + b2 + '\nb3 = ' + b3 + '\nc = ' + c + '\nimage = ' + image + '\n');
397
398             this.yns_alert_original(s + '\n\nAlso, yns_alert failed: ' + E,title,b1,b2,b3,c);
399
400         }
401     },
402
403     'yns_alert_formatted' : function (s,title,b1,b2,b3,c,image) {
404
405         try {
406
407             if (location.href.match(/^chrome/)) return this.yns_alert_original(s,title,b1,b2,b3,c);
408
409         /* The original purpose of yns_alert was to prevent errors from being scanned through accidentally with a barcode scanner.  
410         However, this can be done in a less annoying manner by rolling our own dialog and not having any of the options in focus */
411
412         /*
413             s     = Message to display
414             title     = Text in Title Bar
415             b1    = Text for button 1
416             b2    = Text for button 2
417             b3    = Text for button 3
418             c    = Text for confirmation checkbox.  null for no confirm
419         */
420
421         dump('yns_alert_formatted:\n\ts = ' + s + '\n\ttitle = ' + title + '\n\tb1 = ' + b1 + '\n\tb2 = ' + b2 + '\n\tb3 = ' + b3 + '\n\tc = ' + c + '\n');
422
423         //FIXME - is that good enough of an escape job?
424         s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
425
426         var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" flex="1">' 
427             + '<groupbox flex="1" style="overflow: auto; border: solid thin red;"><caption label="' + (title) + '"/>';
428
429         if (image) xml += '<hbox><image src="' + image + '"/><spacer flex="1"/></hbox>';
430         xml += '<description style="-moz-user-select: text; -moz-user-focus: normal; font-size: large"><html:pre id="msg" style="font-size: large">' + (s)
431             + '</html:pre></description></groupbox><groupbox><caption label="Options"/><hbox>';
432         var b1_key = b1 ? b1[0] : '';
433         var b2_key = b2 ? b2[0] : '';
434         var b3_key = b3 ? b3[0] : ''; /* FIXME - need to check for collisions */
435         if (b1) xml += '<button id="b1" accesskey="' + b1_key + '" label="' + (b1) + '" name="fancy_submit" value="b1"/>';
436         if (b2) xml += '<button id="b2" accesskey="' + b2_key + '" label="' + (b2) + '" name="fancy_submit" value="b2"/>';
437         if (b3) xml += '<button id="b3" accesskey="' + b3_key + '" label="' + (b3) + '" name="fancy_submit" value="b3"/>';
438         var copy_button_label = 'Copy Message'; /* default in case the I18N infrastructure is failing, yns_alert often gets used for errors */
439         var x= document.getElementById('offlineStrings');
440         if (x) {
441             if (typeof x.getString == 'function') {
442                 if (x.getString('common.error.copy_msg')) { copy_button_label = x.getString('common.error.copy_msg'); }
443             }
444         }
445         xml += '<spacer flex="1"/><button label="' + copy_button_label + '" oncommand="try { copy_to_clipboard( document.getElementById(' + "'msg'" + ').textContent ); } catch(E) { alert(E); }" />';
446         xml += '</hbox></groupbox></vbox>';
447         JSAN.use('OpenILS.data');
448         //var data = new OpenILS.data(); data.init({'via':'stash'});
449         //data.temp_yns_xml = xml; data.stash('temp_yns_xml');
450         var url = urls.XUL_FANCY_PROMPT; // + '?xml_in_stash=temp_yns_xml' + '&title=' + window.escape(title);
451         if (typeof xulG != 'undefined') if (typeof xulG.url_prefix == 'function') url = xulG.url_prefix( url );
452         JSAN.use('util.window'); var win = new util.window();
453         var fancy_prompt_data = win.open(
454             url, 'fancy_prompt', 'chrome,resizable,modal,width=700,height=500', { 'xml' : xml, 'title' : title, 'sound' : 'bad' }
455         );
456         if (fancy_prompt_data.fancy_status == 'complete') {
457             switch(fancy_prompt_data.fancy_submit) {
458                 case 'b1' : return 0; break;
459                 case 'b2' : return 1; break;
460                 case 'b3' : return 2; break;
461             }
462         } else {
463             //return this.yns_alert(s,title,b1,b2,b3,c,image);
464             return null;
465         }
466
467         } catch(E) {
468
469             alert('yns_alert_formatted failed: ' + E + '\ns = ' + s + '\ntitle = ' + title + '\nb1 = ' + b1 + '\nb2 = ' + b2 + '\nb3 = ' + b3 + '\nc = ' + c + '\nimage = ' + image + '\n');
470
471         }
472
473     },
474
475     'yns_alert_original' : function (s,title,b1,b2,b3,c) {
476
477         /*
478             s     = Message to display
479             title     = Text in Title Bar
480             b1    = Text for button 1
481             b2    = Text for button 2
482             b3    = Text for button 3
483             c    = Text for confirmation checkbox.  null for no confirm
484         */
485
486         dump('yns_alert_original:\n\ts = ' + s + '\n\ttitle = ' + title + '\n\tb1 = ' + b1 + '\n\tb2 = ' + b2 + '\n\tb3 = ' + b3 + '\n\tc = ' + c + '\n');
487
488         if (this.sound) { this.sound.bad(); }
489
490         // get a reference to the prompt service component.
491         var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
492             .getService(Components.interfaces.nsIPromptService);
493
494         // set the buttons that will appear on the dialog. It should be
495         // a set of constants multiplied by button position constants. In this case,
496         // three buttons appear, Save, Cancel and a custom button.
497         //var flags=promptService.BUTTON_TITLE_OK * promptService.BUTTON_POS_0 +
498         //    promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1 +
499         //    promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2;
500         var flags = promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0 +
501             promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_1 +
502             promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2; 
503
504         // display the dialog box. The flags set above are passed
505         // as the fourth argument. The next three arguments are custom labels used for
506         // the buttons, which are used if BUTTON_TITLE_IS_STRING is assigned to a
507         // particular button. The last two arguments are for an optional check box.
508         var check = {};
509         
510         // promptService.confirmEx does not offer scrollbars for long
511         // content, so trim error lines to avoid spilling offscreen
512         //
513         // There's probably a better way of doing this.
514
515         var maxlines = 30;
516         var ss = '';
517         var linefeeds = 0;
518         for (var i=0, chr; linefeeds < maxlines && i < s.length; i++) {  
519             if ((chr = this.getWholeChar(s, i)) === false) {continue;}
520             if (chr == '\u000A') { // \n
521                 linefeeds++;
522             }    
523             ss = ss + chr;
524         }
525         
526         var rv = promptService.confirmEx(window,title, ss, flags, b1, b2, b3, c, check);
527         if (c && !check.value) {
528             return this.yns_alert_original(ss,title,b1,b2,b3,c);
529         }
530         return rv;
531     },
532
533     'print_tabs' : function(t) {
534         var r = '';
535         for (var j = 0; j < t; j++ ) { r = r + "\t"; }
536         return r;
537     },
538
539     'pretty_print' : function(s) {
540         var r = ''; var t = 0;
541         for (var i in s) {
542             if (s[i] == '{') {
543                 r = r + "\n" + this.print_tabs(t) + s[i]; t++;
544                 r = r + "\n" + this.print_tabs(t);
545             } else if (s[i] == '[') {
546                 r = r + "\n" + this.print_tabs(t) + s[i]; t++;
547                 r = r + "\n" + this.print_tabs(t);
548             } else if (s[i] == '}') {
549                 t--; r = r + "\n" + this.print_tabs(t) + s[i];
550                 r = r + "\n" + this.print_tabs(t);
551             } else if (s[i] == ']') {
552                 t--; r = r + "\n" + this.print_tabs(t) + s[i];
553                 r = r + "\n" + this.print_tabs(t);
554             } else if (s[i] == ',') {
555                 r = r + s[i];
556                 r = r + "\n" + this.print_tabs(t);
557             } else {
558                 r = r + s[i];
559             }
560         }
561         return r;
562     },
563
564     // Copied from https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/String/charCodeAt
565     'getWholeChar' : function(str, i) {  
566         var code = str.charCodeAt(i);  
567         if (0xD800 <= code && code <= 0xDBFF) { // High surrogate(could change last hex to 0xDB7F to treat high private surrogates as single characters)  
568             if (str.length <= (i+1))  {  
569                 throw 'High surrogate without following low surrogate';  
570             }  
571             var next = str.charCodeAt(i+1);  
572             if (0xDC00 > next || next > 0xDFFF) {  
573                 throw 'High surrogate without following low surrogate';  
574             }  
575             return str[i]+str[i+1];  
576         }  
577         else if (0xDC00 <= code && code <= 0xDFFF) { // Low surrogate  
578             if (i === 0) {  
579                 throw 'Low surrogate without preceding high surrogate';  
580             }  
581             var prev = str.charCodeAt(i-1);  
582             if (0xD800 > prev || prev > 0xDBFF) { //(could change last hex to 0xDB7F to treat high private surrogates as single characters)  
583                 throw 'Low surrogate without preceding high surrogate';  
584             }  
585             return false; // We can pass over low surrogates now as the second component in a pair which we have already processed  
586         }  
587         return str[i];  
588     },
589
590     'work_log' : function(msg,row_data) {
591         try {
592             JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
593             var max_entries = data.hash.aous['ui.admin.work_log.max_entries'] || 20;
594             if (! data.work_log) data.work_log = [];
595             if (! row_data) row_data = {};
596             row_data.message = msg;
597             row_data.when = new Date();
598             var ds = { 
599                 retrieve_id: js2JSON( { 'au_id' : row_data.au_id, 'au_barcode' : row_data.au_barcode, 'au_family_name' : row_data.au_family_name, 'acp_id' : row_data.acp_id, 'acp_barcode' : row_data.acp_barcode } ), 
600                 row: { my: row_data },
601                 to_top: true
602             };
603             data.work_log.push( ds );
604             if (data.work_log.length > max_entries) data.work_log.shift();
605             data.stash('work_log');
606             if (row_data.au_id) {
607                this.patron_log(msg,row_data); 
608             }
609         } catch(E) {
610             try { this.standard_unexpected_error_alert('error in error.js, work_log(): ',E); } catch(F) { alert(E); }
611         }
612     },
613
614     'patron_log' : function(msg,row_data) {
615         try {
616             JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
617             var max_entries = data.hash.aous['ui.admin.patron_log.max_entries'] || 10;
618             if (! data.patron_log) data.patron_log = [];
619             if (! row_data) row_data = {};
620             row_data.message = msg;
621             row_data.when = new Date();
622             var ds = { 
623                 retrieve_id: js2JSON( { 'au_id' : row_data.au_id, 'au_barcode' : row_data.au_barcode, 'au_family_name' : row_data.au_family_name, 'acp_id' : row_data.acp_id, 'acp_barcode' : row_data.acp_barcode } ), 
624                 row: { my: row_data },
625                 to_top: true
626             };
627             if (data.patron_log.length > 0) {
628                 var temp = [];
629                 for (var i = 0; i < data.patron_log.length; i++) {
630                     if (data.patron_log[ i ].row.my.au_id != row_data.au_id) temp.push( data.patron_log[i] );
631                 } 
632                 data.patron_log = temp;
633             }
634             data.patron_log.push( ds );
635             if (data.patron_log.length > max_entries) data.patron_log.shift();
636             data.stash('patron_log');
637         } catch(E) {
638             try { this.standard_unexpected_error_alert('error in error.js, patron_log(): ',E); } catch(F) { alert(E); }
639         }
640     } 
641 }
642
643 dump('exiting util/error.js\n');