when logged in, default to org unit 1 when no org unit has been manually selected...
[evergreen-equinox.git] / Open-ILS / web / opac / skin / kcls / js / myopac.js
1
2 //attachEvt("common", "run", myOPACInit );
3 // force init to run after opac_init();
4 dojo.addOnLoad(function(){setTimeout(myOPACInit, 0)});
5         
6 attachEvt('common','locationUpdated', myopacReload );
7 dojo.require('openils.Util');
8
9 var FETCH_CONTAINER_DETAILS = ['open-ils.actor','open-ils.actor.container.flesh'];
10 var FETCH_CHECKED_DETAILS = ['open-ils.circ','open-ils.circ.actor.user.checked_out.atomic'];
11 var FETCH_CHECKED_HISTORY = ['open-ils.actor','open-ils.actor.history.circ.visible.atomic'];
12 var FETCH_FAV_SEARCHES = ['open-ils.actor','open-ils.actor.user.saved_search.retrieve'];
13 var FETCH_HOLD_DETAILS = ['open-ils.circ','open-ils.circ.hold.details.batch.retrieve.atomic'];
14 var FETCH_MODS_BY_COPY = ['open-ils.search','open-ils.search.biblio.mods_from_copy'];
15 var FETCH_CALL_NUMBER = ['open-ils.search','open-ils.search.callnumber.retrieve'];
16 var FETCH_MODS_SLIM = ['open-ils.search','open-ils.search.biblio.record.mods_slim.retrieve'];
17 var FETCH_FULL_USER = ['open-ils.actor','open-ils.actor.user.fleshed.retrieve'];
18 var FETCH_HOLD_IDS = ['open-ils.circ','open-ils.circ.holds.id_list.retrieve'];
19 var FETCH_COPY_OBJ = ['open-ils.search','open-ils.search.asset.copy.retrieve'];
20 var FETCH_OPT_INS = ['open-ils.actor','open-ils.actor.event_def.opt_in.settings.atomic'];
21 var FETCH_LISTS = ['open-ils.actor','open-ils.actor.container.flesh'];
22 var PASS_RESET = ['open-ils.actor','open-ils.actor.patron.password_reset.request'];
23 var PAY_BILLS = ['open-ils.circ','open-ils.circ.money.payment'];
24 // number of api calls to run before firing myOPACPostCollect() -- helps make all data available first (avoids having to make sync'd calls)
25 var TOTAL_CALLS = 6;
26
27 var PREFS_HOLD_PHONE = "notification.hold.pickup.phone";
28 var PREFS_HOLD_EMAIL = "notification.hold.pickup.email";
29 var PREFS_PREDUE_3DAY = "notification.predue.email";
30 var PREFS_OVER_FIRST = "notification.overdue.first.email";
31 var PREFS_OVER_FIRST_P = "notification.overdue.first.phone";
32 var PREFS_HOLD_EXPIRE = "notification.hold.expire.email";
33 var PREFS_HOLD_CANCEL = "notification.hold.cancel.email";
34 var PREFS_CIRC_HIST_AGE = "history.circ.retention_age";
35 var PREFS_CIRC_HIST_START = "history.circ.retention_start";
36
37 var listsCache = [];
38 var holdsCache = [];
39 var holdsCacheMap = [];
40 var itemsOutCache = [];
41 var callNumCache = [];
42 var favsCache = [];
43 var copyObjCache = [];
44 var mvrObjCache = [];
45 var itemsOutHistory = [];
46 var userOptIns = [];
47 var userOptInsMap = [];
48 var fleshedLists = [];
49 var imgFormatCache = [];
50 var transCache = [];
51 var fleshedContainers = {};
52 var subPageObjs = {};
53
54 var CIRC_HIST_PAGE_LIMIT = 10;
55 var circHistPage = 0;
56 var showXUL = false;
57 var holdsList = null;
58 var fleshedUser = null;
59 var allowPendingAddr = false;
60 var currPage = "";
61 var templates = {};
62 var containerTemplate;
63 var containerTemplate2;
64 var myopacGenericTransTemplate;
65 var myopacCircTransTemplate;
66 var addrRowTemplate;
67 var notesTemplate;
68 var myopacReturnToPayment = false;
69
70 function myOPACInit() {
71         if(!(G.user && G.user.session)) {
72         initLogin();
73     } else {
74         allowPendingAddr = fetchOrgSettingDefault(G.user.home_ou(), 'opac.allow_pending_address');
75         if(allowPendingAddr) unHideMe($('myopac_pending_addr_td'));
76
77                 unHideMe($('myopac_tabs'));
78                 currPage = "summary";
79                 holdsList = getCacheValue('saveAnonCache');
80                 if(holdsList) { currPage = "holds"; swapTabs($('acct_holds')); setCacheValue('saveAnonCache', null); TOTAL_CALLS -= 2; }
81                 var retUrl = getCacheValue('returnURL');
82                 if(retUrl) { setCacheValue('returnURL', null); setCacheValue('showHoldEditor', {record:retUrl.record, type:retUrl.type}); window.location=retUrl.href; }
83                 
84                 var sel = $('myopac_new_home');
85                 buildOrgSel(sel, globalOrgTree, 0);
86                 setSelector(sel, G.user.home_ou());
87                 
88                 switch(new CGI().param('acctpage')) {
89                         case "1": currPage = "summary";  break;
90                         case "2": currPage = "checked"; swapTabs($('acct_checked_out')); break;
91                         case "3": currPage = "holds"; swapTabs($('acct_holds')); break;
92                         case "4": currPage = "prefs"; swapTabs($('acct_prefs')); break;
93                         case "5": currPage = "fines"; swapTabs($('acct_favs')); break;
94                         case "6": currPage = "bookbag"; swapTabs($('acct_lists')); break;
95                 }
96                 
97                 if(!isXUL()) {
98                         unHideMe($('myopac_summary_email_change'));
99                         unHideMe($('myopac_summary_password_change'));
100                         unHideMe($('myopac_summary_username_change'));
101                 } else {
102                         unHideMe($('myopac_summary_phone1_change'));
103                         unHideMe($('myopac_summary_phone2_change'));
104                         unHideMe($('myopac_summary_phone3_change'));
105                 }
106
107
108         fieldmapper.standardRequest(FETCH_FULL_USER, {async:true, params:[G.user.session, G.user.id()],
109             oncomplete:function(r) {
110                                 showXUL = isXUL();
111                 fleshedUser = openils.Util.readResponse(r);
112                                 myOPACShowFines(true);
113                 myOPACShowUser(false);
114                         myOPACChangePage(currPage);
115                         myOPACInitTemplates();
116                         myOPACInitSubpages();
117                         //myOPACShowBookbags(true);
118                         if(holdsList) doBatchAnonHolds();
119                         showCanvas();
120             }
121         });
122         }
123 }
124
125 function myopacReload() {
126         var a = {};
127         a[PARAM_LOCATION] = getNewSearchLocation();
128         a[PARAM_DEPTH] = getNewSearchDepth();
129         hideMe($('canvas_main'));
130         goTo(buildOPACLink(a, true));
131 }
132
133 function myOPACChangePage( page ) {
134         var s = $("myopac_summary_td");
135         var c = $("myopac_checked_td");
136         var f = $("myopac_fines_td");
137         var h = $("myopac_holds_td");
138         var p = $("myopac_prefs_td");
139         var b = $('myopac_bookbag_td');
140
141         var ss = $("myopac_summary_div");
142         var cc = $("myopac_checked_div");
143         var ff = $("myopac_fines_div");
144         var hh = $("myopac_holds_div");
145         var pp = $("myopac_prefs_div");
146         var bb = $('myopac_bookbag_div');
147
148         hideMe(ss);
149         hideMe(cc); hideMe(ff);
150         hideMe(hh); hideMe(pp);
151         hideMe(bb);
152
153     if(page != 'prefs')
154         myopacReturnToPayment = false;
155
156     var oncomplete = null;
157
158         switch( page ) {
159                 case "summary": unHideMe(ss);
160                         if($('show_fines_link').innerHTML=="Hide Overdue Materials") unHideMe(ff);
161             oncomplete = myOPACDrawSummary;
162                         break;
163
164                 case "checked": 
165             unHideMe(cc); 
166             oncomplete = drawCheckedPage;
167             break;
168
169                 case "holds": 
170             unHideMe(hh); 
171             oncomplete = drawHoldsPage;
172             break;
173
174                 case "prefs": 
175             unHideMe(pp); 
176             oncomplete = myOPACShowPrefs;
177             break;
178
179                 case 'bookbag': 
180             unHideMe(bb); 
181             break;
182         }
183         currPage = page;
184     myOPACGrabPageData(page, oncomplete);
185 }
186
187 var fetchedPages={}
188 function myOPACGrabPageData(page, oncomplete) {
189     if(fetchedPages[page]) return;
190     fetchedPages[page] = true;
191
192     switch(page) {
193
194                 case "summary": 
195             oncomplete();
196             break;
197
198                 case "checked": 
199
200                 progressDialog.show(true);
201                 fieldmapper.standardRequest(FETCH_CHECKED_DETAILS, {async:true, params:[G.user.session, G.user.id()],
202                         oncomplete:function(r) {
203                         progressDialog.hide();
204                                 itemsOutCache = openils.Util.readResponse(r);
205                     oncomplete();
206                         }
207                 });
208
209             break;
210
211                 case "holds": 
212
213                         holdsCache=null;
214             fieldmapper.standardRequest(FETCH_HOLD_IDS, {async:true, params:[G.user.session, G.user.id()], 
215                 oncomplete: function(r) { 
216                                 oncomplete(false, openils.Util.readResponse(r));
217
218                                         /*
219                     var holdids = openils.Util.readResponse(r);
220                     fieldmapper.standardRequest(FETCH_HOLD_DETAILS, {async:true, params:[G.user.session, holdids], 
221                         oncomplete: function(rr) {
222                                 progressDialog.hide();
223                             var holds = openils.Util.readResponse(rr);
224                             holdsCache = holds;
225                             setTimeout(function(){oncomplete()}, 150); // give the progress dialog a chance to clear out
226                                                         oncomplete();
227                         }
228                     });
229                                         */
230                 }
231             });
232
233             break;
234
235                 case "prefs": 
236             fieldmapper.standardRequest(FETCH_OPT_INS, {async:true, params:[G.user.session],
237                 oncomplete:function(r) {
238                     userOptIns = openils.Util.readResponse(r);
239                     for(var i=0; i<userOptIns.length; i++) userOptInsMap[userOptIns[i].name()] = i;
240                     oncomplete();
241                 }
242             });
243
244             break;
245
246                 case 'bookbag': 
247             myOPACShowBookbags(true);
248             break;
249     }
250 }
251
252
253 function myOPACDrawSummary() {
254     var stats = userVitalStats; // see misc.js
255         if(!stats) return;
256         $('myopac_sum_pickup').innerHTML = "("+stats.holds.ready+")";
257         $('myopac_sum_checked').innerHTML = "("+ (stats.checkouts.out+stats.checkouts.overdue) +")";
258         $('myopac_sum_holds').innerHTML = "("+stats.holds.total+")";
259     if(stats.fines.balance_owed != 0) {
260         var bal = $('myopac_sum_fines_bal');
261         bal.style.color = "red";
262         appendClear(bal, text("$" + Number(stats.fines.balance_owed).toFixed(2)));
263                 showFinesDiv($('show_fines_link'));
264                 unHideMe($('pay_fines_btn1'));
265                 unHideMe($('show_fines_link'));
266     }
267 }
268
269 function myOPACInitTemplates() {
270         //holds page
271         if(!templates.holdsParent) templates.holdsParent = $('holds_temp_parent');
272         if(!templates.holdsTemp) templates.holdsTemp = templates.holdsParent.removeChild($('acct_holds_temp'));
273         //checked out page
274         if(!templates.checkedParent) templates.checkedParent = $('checked_temp_parent');
275         if(!templates.checkedTemp) templates.checkedTemp = templates.checkedParent.removeChild($('acct_checked_temp'));
276         //checked history page
277         if(!templates.circHistPar) templates.circHistPar = $('acct_checked_hist_parent');
278         if(!templates.circHistTemp) templates.circHistTemp = templates.circHistPar.removeChild($('acct_checked_hist_temp'));
279 }
280
281 // link-up all the Account sub-pages for switchSubPage()
282 function myOPACInitSubpages() {
283         // create a primary object for each page...
284         subPageObjs.checked = {};
285         subPageObjs.holds = {};
286         subPageObjs.prefs = {};
287         
288         // ... then create an array for each sub-page
289         subPageObjs.checked.main = [];
290         subPageObjs.checked.hist = [];
291         subPageObjs.holds.main = [];
292         subPageObjs.holds.hist = [];
293         subPageObjs.prefs.info = [];
294         subPageObjs.prefs.notify = [];
295         subPageObjs.prefs.search = [];
296         
297         // add .header to each primary page object and link to the page's blue header bar.
298         // add ['header'] to each sub-page array and put the text to display
299         subPageObjs.checked.header = $('acct_checked_header');
300         subPageObjs.checked.main['header'] = "Current Items Checked Out";
301         subPageObjs.checked.hist['header'] = "Check Out History";
302         subPageObjs.checked.main.push($('checked_label'));
303         subPageObjs.checked.hist.push($('checked_link'));
304         subPageObjs.checked.main.push($('checked_hist_link'));
305         subPageObjs.checked.hist.push($('checked_hist_label'));
306         subPageObjs.checked.main.push($('checked_main'));
307         subPageObjs.checked.hist.push($('checked_hist'));
308         
309         subPageObjs.holds.header = $('acct_holds_header');
310         subPageObjs.holds.main['header'] = "Current Items on Hold";
311         subPageObjs.holds.hist['header'] = "Holds History";
312         subPageObjs.holds.main.push($('holds_label'));
313         subPageObjs.holds.hist.push($('holds_link'));
314         subPageObjs.holds.main.push($('holds_hist_link'));
315         subPageObjs.holds.hist.push($('holds_hist_label'));
316         subPageObjs.holds.main.push($('holds_main'));
317         subPageObjs.holds.hist.push($('holds_hist_table'));
318         
319         subPageObjs.prefs.header = $('acct_prefs_header');
320         subPageObjs.prefs.info['header'] = "Account Information and Preferences";
321         subPageObjs.prefs.notify['header'] = "Notification Preferences";
322         subPageObjs.prefs.search['header'] = "Search Preferences";
323         subPageObjs.prefs.info.push($('prefs_info_lbl'));
324         subPageObjs.prefs.notify.push($('prefs_info_link'));
325         subPageObjs.prefs.notify.push($('prefs_notify_lbl'));
326         subPageObjs.prefs.info.push($('prefs_notify_link'));
327         subPageObjs.prefs.search.push($('prefs_search_lbl'));
328         subPageObjs.prefs.info.push($('prefs_search_link'));
329         subPageObjs.prefs.notify.push($('prefs_search_link'));
330         subPageObjs.prefs.search.push($('prefs_notify_link'));
331         subPageObjs.prefs.search.push($('prefs_info_link'));
332         subPageObjs.prefs.info.push($('acct_info_main'));
333         subPageObjs.prefs.search.push($('acct_search_main'));
334         subPageObjs.prefs.notify.push($('acct_notify_main'));
335         subPageObjs.prefs.notify.push($('acct_prefs_save'));
336         subPageObjs.prefs.search.push($('acct_prefs_save'));
337 }
338
339 function fetchAllHolds() {
340 }
341
342 var myopacForceHoldsRedraw = false;
343 function drawHoldsPage(sort, holdIds) {
344         if(sort == undefined) sort = true;
345         if(!templates.holdsParent || !templates.holdsTemp) return;
346         var parent = templates.holdsParent;
347         var temp = templates.holdsTemp;
348         var holdsReady = 0;
349         
350         if(myopacForceHoldsRedraw) {
351                 holdsCache = fieldmapper.standardRequest(FETCH_HOLD_DETAILS, {async:false, params:[G.user.session, 
352                         fieldmapper.standardRequest(FETCH_HOLD_IDS, {async:false, params:[G.user.session, G.user.id()]})
353                 ]});
354         }
355
356         if(sort) holdsCache = holdsCache.sort(function(a, b) {
357                 if(a.status==4) return -1;
358                 if(b.status==4) return 1;
359                 if(isTrue(a.hold.frozen())) return 1;
360                 if(isTrue(b.hold.frozen())) return -1;
361                 return dojo.date.stamp.fromISOString(a.hold.request_time()) > dojo.date.stamp.fromISOString(b.hold.request_time())?1:-1;
362         });
363         
364     function allHoldsFetched() {
365         holdsCacheMap=[];
366         for(var i=0; i<holdsCache.length; i++) holdsCacheMap[holdsCache[i].hold.id()] = i;
367         $('myopac_sum_pickup').innerHTML = "("+holdsReady+")";
368         if(!holdsCache.length) parent.appendChild(elem('div',{style:"font-weight:bold;font-size:14px;width:100%;text-align:center;",align:'center'},'No holds found.'));
369     }
370
371         removeChildren(parent);
372     if(holdsCache) {
373             for(var i in holdsCache) {
374             var row = temp.cloneNode(true);
375             var hold = holdsCache[i];
376             drawOneHold(hold, row, parent);
377             parent.appendChild(row);
378                         //parent.appendChild(elem('tr').appendChild(elem('td', {'colspan':'10'}).appendChild(elem('div', {'style':'position:absolute;'}).appendChild(elem('div', {'style':'position:relative;'}).appendChild(elem('div', {'style':'width:974px;height:1px;background:#dcdbdb;'}))))));
379                         //      <tr><td colspan="10"><div style="position:absolute;"><div style="position:relative;"><div style="width:974px;height:1px;background:#dcdbdb;"></div></div></div></td></tr>  
380
381             if(hold.status == 4) holdsReady++;
382         }
383         allHoldsFetched();
384
385     } else if(holdIds) {
386
387         holdsCache = [];
388         var holdsReceived = 0;
389
390         dojo.forEach(holdIds,
391             function(holdId) {
392                 var row = temp.cloneNode(true);
393                 dojo.style(row, 'visibility', 'hidden');
394                 parent.appendChild(row);
395                                 
396                         //parent.appendChild(elem('tr').appendChild(elem('td', {'colspan':'10'}).appendChild(elem('div', {'style':'position:absolute;'}).appendChild(elem('div', {'style':'position:relative;'}).appendChild(elem('div', {'style':'width:974px;height:1px;background:#dcdbdb;'}))))));
397                         
398                 fieldmapper.standardRequest(FETCH_HOLD_DETAILS, {async:true, params:[G.user.session, [holdId]], 
399                     oncomplete: function(rr) {
400                         var hold = openils.Util.readResponse(rr)[0];
401                         holdsCache.push(hold);
402                         dojo.style(row, 'visibility', 'visible');
403                         drawOneHold(hold, row, parent);
404                         if(hold.status == 4) holdsReady++;
405                         if(++holdsReceived == holdIds.length)
406                             allHoldsFetched();
407                     }
408                 });
409             }
410         );
411     }
412 }
413
414 function drawOneHold(h, row, parent) {
415         var imgTD = null;
416         var imgEl = null;
417         var d = null;
418         var exp_date;
419         var suffix = "";
420
421     row.id = "myopac_holds_row_" + h.hold.id();
422     var form = $n(row, "myopac_holds_formats");
423     form.id = "myopac_holds_form_" + h.hold.id();
424     $n(row,'hold_pickup_lib_span').appendChild($('hold_pickup_lib_temp').cloneNode(true));
425     
426     var tree = $n(row,'hold_pickup_lib_sel');
427     buildOrgSelAlt(tree, globalOrgTree, 0);
428     setSelector(tree, h.hold.pickup_lib());
429     $n(row,'check_all_holds').holdid = h.hold.id();
430     $n(row,'hold_edit_link').setAttribute('href','javascript:editHold('+h.hold.id()+');'); 
431     $n(row,'hold_cancel_link').setAttribute('href','javascript:cancelHold('+h.hold.id()+');');
432     $n(row,'hold_save_link').setAttribute('href','javascript:saveHold('+h.hold.id()+');');
433     var activeSel = $n(row,'hold_active_sel');
434     activeSel.selectedIndex = isTrue(h.hold.frozen())?1:0;
435     
436     //var djBox = $n(row,'activate_box').parentNode.parentNode.parentNode;
437     //djBox.id=djBox.id+h.hold.id();
438     //var blah = dojo.query('input',$n(row,'activate_box').parentNode)[0];
439     //blah.id = djBox.getAttribute("widgetId")+h.hold.id();
440     //$().id = djBox.id+h.hold.id();
441     //djBox.setAttribute("widgetId", djBox.getAttribute("widgetId")+h.hold.id());
442     
443     $n(row,'hold_pickup_lib').innerHTML = findOrgUnit(h.hold.pickup_lib()).name();
444     $n(row,'hold_active').innerHTML = isTrue(h.hold.frozen())?"Suspended":"Active";
445     if(isTrue(h.hold.frozen())) row.style.background="#e5e5e5";
446     
447     if(h.hold.thaw_date()) {
448         d = dojo.date.stamp.fromISOString(h.hold.thaw_date());
449         $n(row, 'activate_date').innerHTML = dojo.date.locale.format(d, {selector: 'date', fullYear: true});
450         unHideMe($n(row,'activate_label'));
451         $n(row,'activate_box').value = dojo.date.locale.format(d, {selector:'date', fullYear: true});
452     }
453     
454     if(h.hold.expire_time()) {
455         exp_date = dojo.date.stamp.fromISOString(h.hold.expire_time().replace(/(T\d\d:\d\d:\d\d)([+-]\d\d)(\d)/, "$1$2:$3"))
456         $n(row, 'hold_expires').innerHTML = dojo.date.locale.format(exp_date, {selector:'date', fullYear: true});
457         unHideMe($n(row,'hold_expires_label'));
458         $n(row,'hold_expires_box').value = dojo.date.locale.format(exp_date, {selector:'date', fullYear: true});
459     }
460     
461     buildTitleDetailLink(h.mvr, $n(row,'myopac_holds_title_link'));
462     $n(row,'myopac_holds_title_link').title = h.mvr.title();
463     
464     imgTD = $n(row, "myopac_holds_formats");
465     imgEl = elem('img');
466     imgEl.className = 'hide_me';
467     imgEl.name = 'format_icon_'+h.hold.id();
468     imgTD.appendChild(imgEl);
469     if(imgFormatCache[h.hold.id()]==null) imgFormatCache[h.hold.id()] = getMarcData(h.mvr.doc_id(),"998","d");
470     setFormatIcon(imgEl, imgFormatCache[h.hold.id()]);
471     
472     $n(row,'myopac_holds_author').innerHTML = h.mvr.author();
473     
474     if( h.status == 4) {
475         $n(row,'acct_holds_status').innerHTML = "<span style='color:blue;font-weight:bold;'>Ready for Pickup</span>";
476         hideMe($n(row,'holds_editor_row'));
477         unHideMe($n(row,'holds_ready_row'));
478         if(h.hold.shelf_expire_time()) {
479             var pdate = dojo.date.stamp.fromISOString(h.hold.shelf_expire_time());
480             $n(row,'hold_ready_expire').innerHTML += dojo.date.locale.format(pdate, {selector:'date', fullYear: true});;
481             unHideMe($n(row,'hold_ready_expire'));
482         }
483     } else {
484         var num = h.queue_position+'';
485         suffix = (num.charAt(num.length-1)=="1")?"st":(num.charAt(num.length-1)=="2")?"nd":(num.charAt(num.length-1)=="3")?"rd":"th";
486         if(num=="11" || num=="12" || num=="13") suffix = "th";
487         $n(row,'acct_holds_status').innerHTML = h.queue_position+suffix+" hold on "+h.potential_copies+" circulating cop"+(h.potential_copies==1?"y":"ies");
488     }
489 }
490
491 var checkedDrawn = true;
492 function drawCheckedPage(sort) {
493         if(sort==undefined) sort = true;
494         if(!templates.checkedParent || !templates.checkedTemp) return;
495         var parent = templates.checkedParent;
496         var temp = templates.checkedTemp;
497         
498         if(!checkedDrawn) itemsOutCache = fieldmapper.standardRequest(FETCH_CHECKED_DETAILS, {async:false, params:[G.user.session, G.user.id()]});
499         if(sort) itemsOutCache = itemsOutCache.sort(function(a, b) {
500                 if(dojo.date.stamp.fromISOString(a.circ.due_date()) > dojo.date.stamp.fromISOString(b.circ.due_date())) return 1;
501                 return -1;
502         });
503         checkedDrawn = true;
504         
505         removeChildren(parent);
506         for(var i in itemsOutCache) {
507                 var row = temp.cloneNode(true);
508                 var out = itemsOutCache[i];
509                 //row.id = "myopac_holds_row_" + out.hold.id();
510                 if(!mvrObjCache[out.circ.target_copy()]) mvrObjCache[out.circ.target_copy()] = out.record;
511                 if(!copyObjCache[out.copy.id()]) copyObjCache[out.copy.id()] = out.copy;
512                                 
513                 if(out.circ.due_date()) {
514                         var exp_date = dojo.date.stamp.fromISOString(out.circ.due_date());
515                         if(exp_date<(new Date())) $n(row,'due_date').style.color="red";
516                         $n(row, 'due_date').innerHTML = dojo.date.locale.format(exp_date, {selector:'date', fullYear: true});
517                 }
518                 
519                 $n(row, 'renewals').innerHTML = out.circ.renewal_remaining();
520                 buildTitleDetailLink(out.record, $n(row,'title'));
521                 if(out.record.author()) $n(row, 'author').appendChild(text(" / "+out.record.author()));
522                 dojo.attr($n(row, 'check_all_checked'), 'circid', out.circ.id())
523                 $n(row,'title').title = out.record.title();
524                 $n(row, 'barcode').innerHTML = out.copy.barcode();
525                 if(callNumCache[out.copy.call_number()]==null) callNumCache[out.copy.call_number()] = fieldmapper.standardRequest(FETCH_CALL_NUMBER, {async:false, params:[out.copy.call_number()]});
526                 $n(row, 'call_number').innerHTML = callNumCache[out.copy.call_number()].label();
527                 
528                 parent.appendChild(row);
529         }
530         
531         if(!itemsOutCache.length) parent.appendChild(elem('div',{style:"font-weight:bold;font-size:14px;width:100%;text-align:center;",align:'center'},'No items checked out.'));
532 }
533
534 var circHistDrawn = true;
535 function drawCircHistory(sort, offset) {
536         if(sort==undefined) sort = true;
537         if(offset==undefined) offset = circHistPage; else circHistPage = offset;
538         if(!templates.circHistPar || !templates.circHistTemp) return;
539         var parent = templates.circHistPar;
540         var temp = templates.circHistTemp;
541         
542         if(!circHistDrawn) itemsOutHistory = fieldmapper.standardRequest(FETCH_CHECKED_HISTORY, {async:false, params:[G.user.session, G.user.id(), {'limit':CIRC_HIST_PAGE_LIMIT, 'offset':offset}]});
543         circHistDrawn = true;
544         
545         removeChildren(parent);
546         for(var i in itemsOutHistory) {
547                 var row = temp.cloneNode(true);
548                 var h = itemsOutHistory[i];
549                 if(mvrObjCache[h.circ.target_copy()]==null) mvrObjCache[h.circ.target_copy()] = fieldmapper.standardRequest(FETCH_MODS_BY_COPY, {async:false, params:[h.circ.target_copy()]});
550                 var mvr = mvrObjCache[h.circ.target_copy()];
551                 
552                 if(mvr && !mvr.desc) {
553                         buildTitleDetailLink(mvr, $n(row, 'title'));
554                         $n(row, 'title').title = mvr.title();
555                         $n(row, 'author').innerHTML = mvr.author();
556                         
557                         //dojo.date.locale.format(dojo.date.stamp.fromISOString(), {selector:'date', fullYear: true});
558                 }
559                 
560                 //date formatting and coloring
561                 var ret = $n(row, 'returned');
562                 var due = $n(row, 'due_date');
563                 $n(row, 'checkout').innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(h.circ.create_time()), {selector:'date', fullYear: true});
564                 due.innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(h.circ.due_date()), {selector:'date', fullYear: true});
565                 if(!h.circ.checkin_time() && new Date() > dojo.date.stamp.fromISOString(h.circ.due_date())) due.style.color="red";
566                 if(h.circ.checkin_time() && dojo.date.stamp.fromISOString(h.circ.checkin_time()) > dojo.date.stamp.fromISOString(h.circ.due_date())) ret.style.color="red";
567                 if(h.circ.checkin_time()) ret.innerHTML = dojo.date.locale.format(dojo.date.stamp.fromISOString(h.circ.checkin_time()), {selector:'date', fullYear: true});
568                 else ret.innerHTML = "Checked Out";
569                 
570                 if(copyObjCache[h.circ.target_copy()]==null) copyObjCache[h.circ.target_copy()] = fieldmapper.standardRequest(FETCH_COPY_OBJ, {async:false, params:[h.circ.target_copy()]});
571                 if(callNumCache[copyObjCache[h.circ.target_copy()].call_number()]==null)
572                         callNumCache[copyObjCache[h.circ.target_copy()].call_number()] = fieldmapper.standardRequest(FETCH_CALL_NUMBER, {async:false, params:[copyObjCache[h.circ.target_copy()].call_number()]});
573                 
574                 if(copyObjCache[h.circ.target_copy()] && callNumCache[copyObjCache[h.circ.target_copy()].call_number()])
575                         $n(row,'call_number').innerHTML = callNumCache[copyObjCache[h.circ.target_copy()].call_number()].label();
576                 parent.appendChild(row);
577         }
578         
579         if(!itemsOutHistory.length) {
580                 parent.appendChild(elem('tr').appendChild(elem('td',{style:"font-weight:bold;font-size:14px;width:100%;text-align:center;",align:'center'},'No items found.')));
581                 return;
582         }
583         
584         //build pagination
585         var pageTR = elem('tr');
586         var pageTD = elem('td', {'align':'center', 'colspan':'5'}, null);
587         
588         var offPrev = offset - CIRC_HIST_PAGE_LIMIT;
589         var offNext = offset + CIRC_HIST_PAGE_LIMIT;
590         if(offset==0) offPrev = 0;
591         
592         if(offset!=0) var prev = elem('a', {'href':'javascript: circHistDrawn = false; drawCircHistory(true,'+offPrev+');'}, '< Previous');
593         else var prev = elem('span', null, '< Previous');
594         if(itemsOutHistory.length<CIRC_HIST_PAGE_LIMIT) var next = elem('span', null, 'Next >');
595         else var next = elem('a', {'href':'javascript:  circHistDrawn = false; drawCircHistory(true,'+offNext+');'}, 'Next >');
596         
597         pageTD.appendChild(prev);
598         pageTD.appendChild(elem('span', null, '    '));
599         pageTD.appendChild(next);
600         pageTR.appendChild(pageTD);
601         parent.appendChild(pageTR);
602 }
603
604 function myOPACShowPrefs() {
605         grabUserPrefs();
606         myOPACShowHitsPer();
607         myOPACShowDefFont();
608         myOPACShowDefLocation();
609         myOPACShowNotifyPrefs();
610         hideMe($('myopac_prefs_loading'));
611 }
612
613 var defSearchLocationDrawn = false;
614 var defDepthIndex = 0;
615 function myOPACShowDefLocation() {
616
617         var selector = $('prefs_def_location');
618         var rsel = $('prefs_def_range');
619
620         if(!defSearchLocationDrawn) {
621                 defSearchLocationDrawn = true;
622                 var org = G.user.prefs[PREF_DEF_LOCATION];
623
624                 if(!org) {
625                         $('myopac_pref_home_lib').checked = true;
626                         $('prefs_def_location').disabled = true;
627                         org = G.user.home_ou();
628                 }
629                 buildOrgSel(selector, globalOrgTree, 0);
630
631                 globalOrgTypes = globalOrgTypes.sort(
632                         function(a, b) {
633                                 if( a.depth() < b.depth() ) return -1;
634                                 return 1;
635                         }
636                 );
637
638                 iterate(globalOrgTypes,
639                         function(t) {
640                                 if( t.depth() <= findOrgDepth(org) ) {
641                                         setSelectorVal(rsel, defDepthIndex++, t.opac_label(), t.depth());
642                                         if( t.depth() == findOrgDepth(org)) 
643                                                 setSelector(rsel, t.depth());
644                                 }
645                         }
646                 );
647         }
648
649         setSelector(selector, org);
650 }
651
652 function myOPACShowNotifyPrefs() {
653         var preEmail = $(PREFS_PREDUE_3DAY);
654         var overEmail = $(PREFS_OVER_FIRST);
655         var overPhone = $(PREFS_OVER_FIRST_P);
656         var holdsCancel = $(PREFS_HOLD_CANCEL);
657         var holdsExpire = $(PREFS_HOLD_EXPIRE);
658         var holdsEmail = $("opac.hold_notify.email");
659         var holdsPhone = $("opac.hold_notify.phone");
660         if(G.user.prefs[PREFS_CIRC_HIST_START]) { $('circHistStart').checked = true; }
661         
662         if(preEmail && typeof(G.user.prefs[PREFS_PREDUE_3DAY])   !="undefined") preEmail.checked    = G.user.prefs[PREFS_PREDUE_3DAY];
663         if(overEmail && typeof(G.user.prefs[PREFS_OVER_FIRST])   !="undefined") overEmail.checked   = G.user.prefs[PREFS_OVER_FIRST];
664         if(overPhone && typeof(G.user.prefs[PREFS_OVER_FIRST_P]) !="undefined") overPhone.checked   = G.user.prefs[PREFS_OVER_FIRST_P];
665         if(holdsCancel && typeof(G.user.prefs[PREFS_HOLD_CANCEL])!="undefined") holdsCancel.checked = G.user.prefs[PREFS_HOLD_CANCEL];
666         if(holdsExpire && typeof(G.user.prefs[PREFS_HOLD_EXPIRE])!="undefined") holdsExpire.checked = G.user.prefs[PREFS_HOLD_EXPIRE];
667         if(holdsPhone && typeof(G.user.prefs[PREFS_HOLD_PHONE])  !="undefined") holdsPhone.checked  = G.user.prefs[PREFS_HOLD_PHONE];
668         if(holdsEmail && typeof(G.user.prefs[PREFS_HOLD_EMAIL])  !="undefined") holdsEmail.checked  = G.user.prefs[PREFS_HOLD_EMAIL];
669 }
670
671 function myOPACShowHitsPer() {
672         var hits = 10;
673         if(G.user.prefs[PREF_HITS_PER])
674                 hits = G.user.prefs[PREF_HITS_PER];
675         var hitsSel = $('prefs_hits_per');
676         setSelector(hitsSel, hits);
677 }
678
679 function myOPACShowDefFont() {
680         var font;
681         if(G.user.prefs[PREF_DEF_FONT])
682                 font = G.user.prefs[PREF_DEF_FONT];
683         else font = "regular";
684         setSelector($('prefs_def_font'), font);
685 }
686
687 function myOPACShowUser(bool) {
688         if(bool) fleshedUser = fieldmapper.standardRequest(FETCH_FULL_USER, {async:false, params:[G.user.session, G.user.id()]});
689         var user = fleshedUser;
690         
691     var expireDate = dojo.date.stamp.fromISOString(user.expire_date());
692     if( expireDate < new Date() ) {
693         appendClear($('myopac.expired.date'), expireDate.iso8601Format('YMD'));
694         unHideMe($('myopac.expired.alert'));
695     }
696
697         var iv1 = user.ident_value()+'';
698     if (iv1.length > 4 && iv1.match(/\d{4}/)) iv1 = iv1.substring(0,4) + '***********';
699
700         appendClear($('myopac_sum_name'),text(user.first_given_name()+" "+(user.family_name()==null?"":user.family_name())));   
701         appendClear($('myopac_summary_prefix'),text(user.first_given_name()+" "+(user.family_name()==null?"":user.family_name())));
702         appendClear($('myopac_summary_homelib'),text(findOrgUnit(user.home_ou()).name()));
703
704         appendClear($('myopac_summary_dayphone'),text(user.day_phone()));
705         appendClear($('myopac_summary_eveningphone'),text(user.evening_phone()));
706         appendClear($('myopac_summary_otherphone'),text(user.other_phone()));
707         appendClear($('myopac_summary_suffix'),text(user.suffix()));
708         appendClear($('myopac_summary_username'),text(user.usrname()));
709         appendClear($('myopac_summary_email'),text(user.email()));
710         appendClear($('myopac_summary_barcode'),text(user.card().barcode()));
711         appendClear($('myopac_summary_ident1'),text(iv1));
712         appendClear($('myopac_summary_create_date'),text(_trimTime(user.create_date())));
713
714         var req = new Request( 
715                 FETCH_USER_NOTES, G.user.session, {pub:1, patronid:G.user.id()});
716         req.callback(myopacDrawNotes);
717         req.send();
718
719         var tbody = $('myopac_addr_tbody');
720         var template;
721
722         if(addrRowTemplate) { 
723                 template = addrRowTemplate;
724         } else {
725                 template = tbody.removeChild($('myopac_addr_row'));
726                 addrRowTemplate = template;
727         }
728         removeChildren(tbody);
729
730     var addrs = user.addresses();
731         for( var a in addrs ) {
732         var addr = addrs[a];
733         if(!allowPendingAddr && isTrue(addr.pending()))
734             continue;
735         if(addr.replaces() != null) continue;
736                 var row = template.cloneNode(true);
737                 myOPACDrawAddr(row, addr, addrs);
738                 tbody.appendChild(row);
739         }
740 }
741
742 function myopacDrawNotes(r) {
743         var notes = r.getResultObject();
744         var tbody = $('myopac.notes.tbody');
745         if(!notesTemplate)
746                 notesTemplate = tbody.removeChild($('myopac.notes.tr'));
747         removeChildren(tbody);
748
749         iterate(notes, 
750                 function(note) {
751                         unHideMe($('myopac.notes.div'));
752                         var row = notesTemplate.cloneNode(true);
753                         $n(row, 'title').appendChild(text(note.title()));
754                         $n(row, 'value').appendChild(text(note.value()));
755                         tbody.appendChild(row);
756                 }
757         );
758 }
759
760 function myOPACDrawAddr(row, addr, addrs) {
761     appendClear($n(row, 'myopac_addr_type'),text(addr.address_type()));
762     appendClear($n(row, 'myopac_addr_street'),text(addr.street1()));
763     appendClear($n(row, 'myopac_addr_street2'),text(addr.street2()));
764     appendClear($n(row, 'myopac_addr_city'),text(addr.city()));
765     appendClear($n(row, 'myopac_addr_county'),text(addr.county()));
766     appendClear($n(row, 'myopac_addr_state'),text(addr.state()));
767     appendClear($n(row, 'myopac_addr_country'),text(addr.country()));
768     appendClear($n(row, 'myopac_addr_zip'),text(addr.post_code()));
769
770     if(!allowPendingAddr) return;
771
772     $n(row, 'myopac_addr_edit_link').onclick = function(){myopacEditAddress(addr)};
773     unHideMe($n(row, 'myopac_addr_edit_td'));
774
775     /* if we have a replacement address, plop it into the table next to this addr */
776     var repl = grep(addrs, function(a) { return a.replaces() == addr.id(); });
777
778     $n(row, 'myopac_pending_addr_td').id = 'myopac_pending_addr_td_' + addr.id();
779
780     if(repl) {
781         hideMe($n(row, 'myopac_addr_edit_td')); // hide the edit link
782         repl = repl[0];
783         myopacSetAddrInputs(row, repl);
784     }
785 }
786
787 function myopacEditAddress(addr) {
788     var td = $('myopac_pending_addr_td_' + addr.id());
789     var row = td.parentNode;
790     myopacSetAddrInputs(row, addr);
791 }
792
793 function myopacSetAddrInputs(row, addr, prefix) {
794     unHideMe($n(row, 'myopac_pending_addr_td'));
795     $n(row, 'myopac_pending_addr_type').value = addr.address_type();
796     $n(row, 'myopac_pending_addr_street').value = addr.street1();
797     $n(row, 'myopac_pending_addr_street2').value = addr.street2();
798     $n(row, 'myopac_pending_addr_city').value = addr.city();
799     $n(row, 'myopac_pending_addr_county').value = addr.county();
800     $n(row, 'myopac_pending_addr_state').value = addr.state();
801     $n(row, 'myopac_pending_addr_country').value = addr.country();
802     $n(row, 'myopac_pending_addr_zip').value = addr.post_code();
803     $n(row, 'myopac_pending_addr_edit_link').onclick = function(){myopacSaveAddress(row, addr)};
804     $n(row, 'myopac_pending_addr_del_link').onclick = function(){myopacSaveAddress(row, addr, true)};
805 }
806
807 function _trimTime(time) { 
808         if(!time) return ""; 
809     var d = dojo.date.stamp.fromISOString(time);
810     if(!d) return ""; /* date parse failed */
811     return d.iso8601Format('YMD');
812 }
813
814 function _trimSeconds(time) { 
815     if(!time) return ""; 
816     var d = dojo.date.stamp.fromISOString(time);
817     if(!d) return ""; /* date parse failed */
818     return d.iso8601Format('YMDHM',null,true,true);
819 }
820
821 function myopacSaveAddress(row, addr, deleteMe) {
822     if(addr.replaces() == null) {
823         var repl = new aua();
824         repl.usr(addr.usr());
825         repl.address_type(addr.address_type());
826         repl.within_city_limits(addr.within_city_limits());
827         repl.replaces(addr.id());
828         repl.pending('t');
829         repl.isnew(true);
830         repl.id(null);
831         addr = repl;
832     }
833
834     if(deleteMe) {
835         if(addr.id() == null) {
836             hideMe($n(row, 'myopac_pending_addr_td'));
837             return;
838         }
839         addr.isdeleted(true);
840     } else {
841         addr.address_type($n(row, 'myopac_pending_addr_type').value);
842         addr.street1($n(row, 'myopac_pending_addr_street').value);
843         addr.street2($n(row, 'myopac_pending_addr_street2').value);
844         addr.city($n(row, 'myopac_pending_addr_city').value);
845         addr.county($n(row, 'myopac_pending_addr_county').value);
846         addr.state($n(row, 'myopac_pending_addr_state').value);
847         addr.country($n(row, 'myopac_pending_addr_country').value);
848         addr.post_code($n(row, 'myopac_pending_addr_zip').value);
849     }
850
851         var req = new Request('open-ils.actor:open-ils.actor.user.address.pending.cud', G.user.session, addr);
852
853     req.callback(
854         function(r) {
855             var resp = r.getResultObject(); 
856
857             if(addr.isnew()) {
858                 // new, add to list of addrs
859                 addr.id(resp);
860                 fleshedUser.addresses().push(addr);
861             } else {
862                 // deleted, remove from list of addrs
863                 if(addr.isdeleted()) {
864                     hideMe($n(row, 'myopac_pending_addr_td'));
865                     var addrs = [];
866                     for(var i in fleshedUser.addresses()) {
867                         var a = fleshedUser.addresses()[i];
868                         if(a.id() != addr.id()) addrs.push(a);
869                     }
870                     fleshedUser.addresses(addrs);
871                 }
872             }
873            alertId('myopac_addr_changes_saved');
874         }
875     );
876     req.send();
877 }
878
879 function myOPACSavePrefs() {
880         G.user.prefs[PREF_HITS_PER] = getSelectorVal($('prefs_hits_per'));
881         G.user.prefs[PREF_DEF_FONT] = getSelectorVal($('prefs_def_font'));
882         G.user.prefs[PREF_DEF_DEPTH] = getSelectorVal($('prefs_def_range'));
883         
884         var holdsPhone = $("opac.hold_notify.phone");
885         var holdsEmail = $("opac.hold_notify.email");
886         var circStartBox = $('circHistStart').checked;
887         var circStart = null;
888         circStart = (circStartBox && G.user.prefs[PREFS_CIRC_HIST_START])?G.user.prefs[PREFS_CIRC_HIST_START]:dojo.date.stamp.toISOString(new Date());
889         if(!circStartBox) circStart = null;
890         
891         G.user.prefs[PREFS_HOLD_PHONE] = holdsPhone.checked;
892         G.user.prefs[PREFS_HOLD_EMAIL] = holdsEmail.checked;
893         G.user.prefs[PREFS_PREDUE_3DAY] = $(PREFS_PREDUE_3DAY).checked;
894         G.user.prefs[PREFS_OVER_FIRST] = $(PREFS_OVER_FIRST).checked;
895         G.user.prefs[PREFS_OVER_FIRST_P] = $(PREFS_OVER_FIRST_P).checked;
896         G.user.prefs[PREFS_HOLD_EXPIRE] = $(PREFS_HOLD_EXPIRE).checked;
897         G.user.prefs[PREFS_HOLD_CANCEL] = $(PREFS_HOLD_CANCEL).checked;
898         //G.user.prefs[PREFS_CIRC_HIST_AGE] = "2592000";
899         G.user.prefs[PREFS_CIRC_HIST_START] = circStart;
900
901         if( $('myopac_pref_home_lib').checked == true )
902                 G.user.prefs[PREF_DEF_LOCATION] = null;
903         else
904                 G.user.prefs[PREF_DEF_LOCATION] = getSelectorVal($('prefs_def_location'));
905
906   try {
907         if(commitUserPrefs())
908                 alert($('prefs_update_success').innerHTML);
909         else alert($('prefs_update_failure').innerHTML);
910   } catch(e) {
911         alert(e);  
912   }
913         fieldmapper.standardRequest(FETCH_CHECKED_HISTORY,{params:[G.user.session, G.user.id()],oncomplete:function(r){
914                 var resp = r.recv();
915                 if(resp) resp = resp.content();
916         }});
917 }
918
919 function myOPACUpdateEmail() {
920         var email = $('myopac_new_email').value;
921         if(email == null || email == "") {
922                 alert($('myopac_email_error').innerHTML);
923                 return;
924         }
925
926         var req = new Request(UPDATE_EMAIL, G.user.session, email );
927         req.send(true);
928         if(req.result()) {
929                 //G.user.email(email);
930         // force re-fetch to pick up latest last_xact_id
931         grabUser(null, true);
932                 hideMe($('myopac_update_email_row'));
933                 userShown = false;
934                 alertId('myopac_email_success');
935                 myOPACShowUser(true);
936
937         // user got here after trying to make a payment then deciding 
938         // the email addr needed updating.  take the user back to the 
939         // in-progress payment form
940         if(myopacReturnToPayment) {
941             myopacReturnToPayment = false;
942             myOPACChangePage("summary");
943             $('myopac-cc-email').innerHTML = email;
944         }
945                 return;
946         }
947
948         alert($('myopac_email_failure').innerHTML);
949 }
950
951 function myOPACUpdatePassword() {
952         var curpassword = $('myopac_current_password').value;
953         var password = $('myopac_new_password').value;
954         var password2 = $('myopac_new_password2').value;
955
956         if(     curpassword == null || curpassword == "" || 
957                         password == null || password == "" || 
958                         password2 == null || password2 == "" || password != password2 ) {
959                 alert($('myopac_password_error').innerHTML);
960                 return;
961         }
962
963         if(!strongPassword(password, true)) return;
964
965         var req = new Request(UPDATE_PASSWORD, G.user.session, password, curpassword );
966         req.send(true);
967         if(req.result()) {
968         grabUser(null, true);
969                 hideMe($('myopac_update_password_row'));
970                 userShown = false;
971                 alertId('myopac_password_success');
972                 myOPACShowUser(true);
973                 return;
974         }
975
976         alert($('myopac_password_failure').innerHTML);
977 }
978
979 function myOPACUpdatePhone(which) {
980         if(!which) return;
981         var tr = $('myopac_update_phone'+which+'_row');
982         var input = $('myopac_new_phone'+which);
983         if(!tr || !input) return;
984         var val = input.value;
985         if(!val.match(REGEX_PHONE)) {
986                 alert("Not a valid phone number. Should be in the form ###-###-####");
987                 input.focus();
988                 input.select();
989                 return;
990         }
991         
992         var newUser = fleshedUser.clone(true);
993         switch(which) {
994                 case "1": newUser.day_phone(val); break;
995                 case "2": newUser.evening_phone(val); break;
996                 case "3": newUser.other_phone(val); break;
997         }
998         
999         newUser.ischanged("t");
1000         var resp = fieldmapper.standardRequest(['open-ils.actor', 'open-ils.actor.patron.update'], {async:false, params:[G.user.session, newUser]});
1001         if(typeof(resp.desc)=="undefined") {
1002                 hideMe(tr);
1003                 userShown = false;
1004                 fleshedUser = resp;
1005                 alert("Phone number updated successfully.");
1006                 myOPACShowUser(true);
1007         } else {
1008                 alert(resp.textcode+'\n'+resp.desc);
1009                 input.focus();
1010                 input.select();
1011         }
1012 }
1013
1014 function myOPACUpdateUsername() {
1015         var username = $('myopac_new_username').value;
1016         if(username == null || username == "") {
1017                 alert($('myopac_username_error').innerHTML);
1018                 return;
1019         }
1020
1021         if( username.match(/.*\s.*/) ) {
1022                 alert($('myopac_invalid_username').innerHTML);
1023                 return;
1024         }
1025
1026     r = fetchOrgSettingDefault(globalOrgTree.id(), 'opac.barcode_regex');
1027     if(r) REGEX_BARCODE = new RegExp(r);
1028
1029     if(username.match(REGEX_BARCODE)) {
1030         alert($('myopac_invalid_username').innerHTML);
1031         return;
1032     }
1033
1034         /* first see if the requested username is taken */
1035         var req = new Request(CHECK_USERNAME, G.user.session, username);
1036         req.send(true);
1037         var res = req.result();
1038         if( res !== null && res != G.user.id() ) {
1039                 alertId('myopac_username_dup');
1040                 return;
1041         }
1042
1043         var req = new Request(UPDATE_USERNAME, G.user.session, username );
1044         req.send(true);
1045         if(req.result()) {
1046
1047                 var evt;
1048                 var res = req.result();
1049                 if(evt = checkILSEvent(res)) {
1050                         alertILSEvent(res);
1051                         return;
1052                 }
1053
1054         grabUser(null, true);
1055                 hideMe($('myopac_update_username_row'));
1056                 userShown = false;
1057                 alertId('myopac_username_success');
1058                 myOPACShowUser(true);
1059                 return;
1060         }
1061
1062         alert($('myopac_username_failure').innerHTML);
1063 }
1064
1065 function iForgotMyPassword(un,email) {
1066         if(!un) return;
1067         //var type = un.match(/^\d+$/)?'barcode':'username';
1068         fieldmapper.standardRequest(PASS_RESET,{params:['barcode',un,email],oncomplete:function(rr){
1069                 var resp = rr.recv().content();
1070                 if(resp.textcode=="ACTOR_USER_NOT_FOUND") fieldmapper.standardRequest(PASS_RESET,{params:['username',un,email],oncomplete:function(r){
1071                         var resp2 = r.recv().content();
1072                         if(resp2.textcode) alert(resp2.textcode+'\n'+resp2.desc);
1073                         else { alert("Password reset request sent successfully."); unHideMe($('login_box'));hideMe($('forget_pw')); }
1074                 }}); else {
1075                         if(resp.textcode) alert(resp.textcode+'\n'+resp.desc);
1076                         else { alert("Password reset request sent successfully."); unHideMe($('login_box'));hideMe($('forget_pw')); }
1077                 }
1078         }});
1079 }
1080
1081 function myOPACShowBookbags(force) {
1082         if(anonListCache.length) drawAnonLists(force); else hideMe($('acct_list_template2'));
1083         var wrapper = $('acct_lists_prime');
1084         if(!containerTemplate) containerTemplate = wrapper.removeChild($('acct_list_template')); else if(!force) return;
1085         if(!wrapper) return;
1086         
1087         var containers = containerFetchAll();
1088         
1089         var found = false;
1090         for(var i in containers) {
1091                 found = true;
1092                 var cont = containers[i];
1093                 fleshedContainers[cont.id()] = cont;
1094                 var temp = containerTemplate.cloneNode(true);
1095                 var title = $n(temp, 'list_name');
1096                 var share = $n(temp, 'share_list_link');
1097                 var rem = $n(temp, 'remove_list');
1098                 if(!title || !share || !rem) continue;
1099                 
1100                 title.appendChild(text(cont.name()));
1101                 share.onclick = myListAction;
1102                 share.listID = cont.id();
1103                 rem.value='delete';
1104                 rem.onclick = myListAction;
1105                 rem.listID = cont.id();
1106                 temp.setAttribute("id",temp.id + '_' + cont.id());
1107                 
1108                 if(containers.length>1) {
1109                         var sel = $n(temp, 'list_actions');
1110                         var optg = elem('optgroup', {label:'Move Items to'});
1111                         for(var n in containers) {
1112                                 var cont2 = containers[n];
1113                                 if(cont2.id()==cont.id()) continue;
1114                                 var opt = elem('option', {value:'move', container:cont2.id()}, cont2.name());
1115                                 optg.appendChild(opt);
1116                         }
1117                         sel.appendChild(optg);
1118                 }
1119                 
1120                 if(isTrue(cont.pub())) {
1121                         share.value='hide';
1122                         share.innerHTML = 'Un-share';
1123                         share.title='';
1124                         var link = $n(temp,'share_list_rss');
1125                         link.setAttribute('href', buildExtrasLink( 'feed/bookbag/rss2-full/'+cont.id(), false));
1126                         unHideMe(link);
1127                 } else {
1128                         share.value='share';
1129                         share.title = 'Sharing this bookbag will allow the contents of the bookbag to be seen by others.'
1130                         share.innerHTML = 'Share';
1131                 }
1132                 
1133                 wrapper.appendChild(temp);
1134                 // grab container items after adding each list to the page
1135                 fieldmapper.standardRequest(FETCH_CONTAINER_DETAILS,{params:[G.user.session, 'biblio', cont.id()],oncomplete:drawListItemRows});
1136         }
1137         
1138         if(!found) wrapper.appendChild(elem("div",{style:"font-weight:bold;text-align:center;font-size:14px;"},"You have not created any lists"));
1139         
1140         var anonSel = $('sel_all_list_anon');
1141         if(!anonSel) return;
1142         var optg2 = elem('optgroup', {label:'Move Items to', id:'anon_list_grp'});
1143         for(var t in containers) {
1144                 var cont3 = containers[t];
1145                 var opt2 = elem('option', {value:'move', container:cont3.id()}, cont3.name());
1146                 optg2.appendChild(opt2);
1147         }
1148         anonSel.appendChild(optg2);
1149 }
1150
1151 function drawAnonLists() {
1152         var cont = $('acct_list_template2');
1153         var bibs = fieldmapper.standardRequest(FETCH_MODS_SLIM, {async:false, params:[anonListCache]});
1154         
1155         var tbody = $('anon_list_tbody');
1156         for(var i in bibs) {
1157                 var it = bibs[i];
1158                 var tr = elem('tr');
1159                 var td1 = elem('td',{style:'padding-right:5px;'},it.title());
1160                 var td2 = elem('td',{width:'1'});
1161                 var el = elem('input',{type:'checkbox', name:'list_action_chbx', recordid:it.doc_id()+'', doc_id:it.doc_id()+'', listID:'anon'});
1162                 el.className = "list_action_chbx";
1163                 el.name="list_action_chbx";
1164                 td2.appendChild(el);
1165                 tr.appendChild(td2);
1166                 tr.appendChild(td1);
1167                 tbody.appendChild(tr);
1168         }
1169         unHideMe(cont);
1170 }
1171
1172 function drawListItemRows(r) {
1173         var fleshed = r.recv().content();
1174         if(!fleshed) return;
1175         var modsArr = [];
1176         
1177         var temp = $('acct_list_template_'+fleshed.id());
1178         if(!temp) return;
1179         var tbody = $n(temp,'list_tbody');
1180         if(!tbody) return;
1181         var items = fleshed.items();
1182
1183         for(var i=0; i<items.length; i++) {
1184                 var it = items[i];
1185                 var tr = elem('tr');
1186                 var td1 = elem('td',{'class':'list_item_title_'+it.target_biblio_record_entry(), style:'padding-right:5px;'});
1187                 td1.className = 'list_item_title_'+it.target_biblio_record_entry();
1188                 var td2 = elem('td',{width:'1'});
1189                 
1190                 var el = elem('input',{type:'checkbox', name:'list_action_chbx','class':'list_action_chbx', recordid:it.target_biblio_record_entry()+'', itemID:it.id()+'',listID:fleshed.id()});
1191                 el.name="list_action_chbx";
1192                 el.className="list_action_chbx";
1193                 td2.appendChild(el);
1194                 tr.appendChild(td2);
1195                 tr.appendChild(td1);
1196                 tbody.appendChild(tr);
1197                 modsArr[i] = it.target_biblio_record_entry();
1198         }
1199         
1200         // yay for batching requests
1201         if(modsArr.length) {
1202                 fieldmapper.standardRequest(FETCH_MODS_SLIM,{params:[modsArr],oncomplete:drawListItemTitles});
1203         } else {
1204                 var tr = elem('tr');
1205                 var td = elem('td',{style:"font-weight:bold;font-size:14px;",align:'center'},'No items have been added to this list yet.');
1206                 td.style.fontWeight='bold';
1207                 td.style.fontSize='14px';
1208                 tr.appendChild(td);
1209                 tbody.appendChild(tr);
1210         }
1211 }
1212
1213 function drawListItemTitles(r) {
1214         var rec = r.recv().content();
1215         if(!rec) return;
1216         for(var i in rec) {
1217                 var rows = dojo.query('.list_item_title_'+rec[i].doc_id()); //$c('list_item_title_'+rec[i].doc_id());
1218                 for(var n in rows) rows[n].innerHTML = rec[i].title();
1219         }
1220 }
1221
1222 function addMyList() {
1223         var name = $('mylist_new').value;       
1224         if(!name) return false;
1225
1226         var exists = false;
1227         for( var c in fleshedContainers ) { exists = true; break; }
1228
1229         /* let them know what they are getting into... */
1230         if(!exists) if(!confirm($('bb_create_warning').innerHTML)) return false;
1231         var result = containerCreate( name, $('shareListYes').checked );
1232         var code = checkILSEvent(result);
1233         if(code) { alertILSEvent(result); return false; }
1234         //if(result) alert($('myopac_bb_update_success').innerHTML);
1235         $('mylist_new').value="";
1236         $('shareListYes').checked = false;
1237         $('shareListNo').checked = true;
1238         reloadMyLists();
1239         return true;
1240 }
1241
1242 function myListAction() {
1243         if(!this.listID) return;
1244         switch(this.value) {
1245                 case "delete": myOPACDeleteBookbag(this.listID); break;
1246                 case  "share": myOPACMakeBBPublished(this.listID); break;
1247                 case   "hide": myOPACMakeBBPublished(this.listID, true); break;
1248         }
1249 }
1250
1251 function myOPACMakeBBPublished(bbid, hideme) {
1252         var bb = fleshedContainers[bbid];
1253
1254         if(hideme) {
1255                 if(!confirm($('myopac_make_unpublished_confirm').innerHTML)) return;
1256                 bb.pub('f');
1257         } else {
1258                 if(!confirm($('myopac_make_published_confirm').innerHTML)) return;
1259                 bb.pub('t');
1260         }
1261
1262         var result = containerUpdate(bb);
1263         var code = checkILSEvent(result);
1264         if(code) { alertILSEvent(result); return; }
1265
1266         //alert($('myopac_bb_update_success').innerHTML);
1267         reloadMyLists();
1268 }
1269
1270 function myOPACDeleteBookbag(id) {
1271         if( confirm( $('myopac_delete_bookbag_warn').innerHTML ) ) {
1272                 var result = containerDelete(id);
1273                 var code = checkILSEvent(result);
1274                 if(code) { alertILSEvent(result); return; }
1275                 //alert($('myopac_bb_update_success').innerHTML);
1276                 //hideMe($('myopac_bookbag_items_table'));
1277                 //hideMe($('myopac_bookbag_items_name'));
1278                 //hideMe($('myopac_bookbag_no_items'));
1279                 reloadMyLists();
1280         }
1281 }
1282
1283 function reloadMyLists() {
1284         var prime = $('acct_lists_prime');
1285         var anon = $('anon_list_tbody');
1286         $('sel_all_list_anon').removeChild($('anon_list_grp'));
1287         removeChildren(prime);
1288         removeChildren(anon);
1289         prime.innerHTML = "";
1290         myOPACShowBookbags(true);
1291 }
1292
1293 function removeSelectedItems(parent) {
1294         if(!parent) parent = $('acct_lists_prime');
1295         var items = dojo.query('input[name=list_action_chbx]', parent);
1296         if(!items.length) items = dojo.query('.list_action_chbx', parent);
1297         items.filter(function(item, index, arr){return item.checked;},this);
1298         
1299         if(items.length) {
1300                 for(var i=0; i<items.length; i++) {
1301                         var box = items[i];
1302                         if(box.checked) {
1303                                 containerRemoveItem(box.getAttribute("itemID"));
1304                         }
1305                 }
1306         }
1307 }
1308
1309 var finesShown = false;
1310 function myOPACShowFines(showing) {
1311         if(finesShown) return; finesShown = true;
1312         var req = new Request(FETCH_FINES_SUMMARY, G.user.session, G.user.id() );
1313     req.request.__showing = showing;
1314         req.callback(_myOPACShowFines);
1315         req.send();
1316 }
1317
1318 function _myOPACShowFines(r) {
1319         hideMe($('myopac_fines_summary_loading'));
1320         unHideMe($('myopac_fines_summary_row'));
1321
1322         var summary = r.getResultObject();
1323         var total       = "0.00";
1324         var paid        = "0.00";
1325         var balance = "0.00";
1326         var balance2 = "0.00";
1327         
1328         if( instanceOf(summary,mous) ) {
1329                 total           = _finesFormatNumber(summary.total_owed());
1330                 paid            = _finesFormatNumber(summary.total_paid());
1331                 balance = _finesFormatNumber(summary.balance_owed());
1332                 balance2 = _finesFormatNumber(summary.balance_owed());
1333
1334                 var req = new Request(
1335             'open-ils.actor:open-ils.actor.user.transactions.have_balance.fleshed', 
1336             G.user.session, G.user.id() );
1337
1338                 req.callback(myOPACShowTransactions);
1339                 req.send();
1340         }
1341
1342         if(parseFloat(balance2) > 0.00) {
1343                 var bal = $('myopac_sum_fines_bal');
1344                 bal.style.color = "red";
1345                 appendClear(bal,text("$"+balance2));
1346                 unHideMe($('myopac_sum_fines'));
1347                 unHideMe($('myopac_sum_fines_slim'));
1348                 unHideMe($('pay_fines_btn'));
1349         if(!r.__showing)
1350                     showFinesDiv($('show_fines_link'));
1351         }
1352
1353         appendClear($('myopac_fines_summary_total'), text(total));
1354         appendClear($('myopac_fines_summary_paid'), text(paid));
1355         appendClear($('myopac_fines_summary_balance'), text(balance));
1356 }
1357
1358 function _finesFormatNumber(num) {
1359         if(isNull(num)) num = 0;
1360         num = num + "";
1361         if(num.length < 2 || !num.match(/\./)) num += ".00";
1362         if(num.match(/\./) && num.charAt(num.length-2) == '.') num += "0";
1363         return num;
1364 }
1365
1366 function _trimTime(time) { 
1367         if(!time) return ""; 
1368     var d = dojo.date.stamp.fromISOString(time);
1369     if(!d) return ""; /* date parse failed */
1370     return d.iso8601Format('YMD');
1371 }
1372
1373 function _trimSeconds(time) { 
1374     if(!time) return ""; 
1375     var d = dojo.date.stamp.fromISOString(time);
1376     if(!d) return ""; /* date parse failed */
1377     return d.iso8601Format('YMDHM',null,true,true);
1378 }
1379
1380 function myOPACShowTransactions(r) {
1381         var transactions = r.getResultObject();
1382         
1383         if(!myopacGenericTransTemplate) myopacGenericTransTemplate = $('myopac_trans_tbody').removeChild($('myopac_trans_row'));
1384         if(!myopacCircTransTemplate) myopacCircTransTemplate = $('myopac_circ_trans_tbody').removeChild($('myopac_circ_trans_row'));
1385         removeChildren($('myopac_trans_tbody'));
1386         removeChildren($('myopac_circ_trans_tbody'));
1387         transCache = [];
1388
1389     transactions = transactions.sort(
1390         function(a, b) {
1391             if(a.transaction.xact_start() < b.transaction.xact_start())
1392                 return 1;
1393             else return -1;
1394         }
1395     );
1396         
1397         for( var idx in transactions ) {
1398                 var trans       = transactions[idx].transaction;
1399                 var record      = transactions[idx].record;
1400                 var circ        = transactions[idx].circ;
1401         if(trans.balance_owed() <= 0) continue; // XXX don't show negative-balance transactions for now
1402                 transCache[trans.id()] = transactions[idx];
1403
1404                 if(trans.xact_type() == 'circulation') myOPACShowCircTransaction(trans, record, circ);
1405                 else if(trans.xact_type() == 'grocery') myopacShowGenericTransaction( trans );
1406         }
1407 }
1408
1409 // for toggling between payments and fines tabs
1410 function showFinesTab() {
1411     hideMe($("myopac_payments_div"));
1412     unHideMe($("pay-fines-image"));
1413     unHideMe($("myopac_trans_div"));
1414     unHideMe($("myopac_circ_trans_div"));
1415     $('acct_fines_tab').style.background="url('/opac/skin/kcls/graphics/acct_fines_on.jpg') no-repeat bottom";
1416     $('acct_payments_tab').style.background="url('/opac/skin/kcls/graphics/acct_payments_off.jpg') no-repeat bottom";
1417 }
1418
1419 var paymentsDrawn = false;
1420 function myopacDrawPayments() {
1421
1422     unHideMe($("myopac_payments_div")); 
1423     hideMe($("myopac_circ_trans_div"));
1424     hideMe($("myopac_trans_div"));
1425     hideMe($("pay-fines-image"));
1426     $('acct_fines_tab').style.background="url('/opac/skin/kcls/graphics/acct_fines_off.jpg') no-repeat bottom";
1427     $('acct_payments_tab').style.background="url('/opac/skin/kcls/graphics/acct_payments_on.jpg') no-repeat bottom";
1428
1429     if(paymentsDrawn) return;
1430     paymentsDrawn = true;
1431     progressDialog.show(true);
1432
1433     var before = new Date()
1434     before.setFullYear(before.getFullYear() - 1);
1435     // KCLS limits payment history view to 1 year.  This will eventually be expanded 
1436     // out to a history view page, but for now, just fetch what's needed.
1437     var req = new Request(
1438         'open-ils.actor:open-ils.actor.user.payments.retrieve', 
1439         G.user.session, G.user.id(), 
1440         {"where":{"payment_ts":{">=":dojo.date.stamp.toISOString(before)}}});
1441
1442     req.callback(_myopacDrawPayments);
1443     req.send();
1444 }
1445
1446 function _myopacDrawPayments(r) {
1447
1448     progressDialog.hide();
1449     var payments = r.getResultObject();
1450     var tbody = $('myopac_payments_tbody');
1451     rowTmpl = tbody.removeChild($('myopac_payments_tmpl'));
1452
1453     dojo.forEach(payments,
1454         function(payment) {
1455             var row = rowTmpl.cloneNode(true);
1456             $n(row, 'date').innerHTML =  dojo.date.locale.format( 
1457                 dojo.date.stamp.fromISOString(payment.mp.payment_ts()),
1458                 {selector:'date', fullYear: true}
1459             );
1460             $n(row, 'for').innerHTML = (payment.title) ? payment.title : payment.last_billing_type;
1461             $n(row, 'amount').innerHTML += Number(payment.mp.amount()).toFixed(2);
1462             if(payment.mp.payment_type() == 'credit_card_payment') {
1463                 $n(row, 'print_recpt').onclick = function () { printPaymentReceipt([payment.mp.id()]) };
1464                 $n(row, 'email_recpt').onclick = function () { emailPaymentReceipt([payment.mp.id()]) };
1465             } else {
1466                 $n(row, 'print_recpt').parentNode.style.visibility = 'hidden';
1467             }
1468             tbody.appendChild(row);
1469         }
1470     );
1471 }
1472
1473 function emailPaymentReceipt(paymentIds, callback, noprog) {
1474
1475     if(!G.user.email()) {
1476         if(callback) callback();
1477         return;
1478     }
1479
1480     if(!noprog) progressDialog.show(true);
1481
1482     fieldmapper.standardRequest(
1483         ['open-ils.circ', 'open-ils.circ.money.payment_receipt.email'],
1484         {
1485             async : true,
1486             params : [G.user.session, paymentIds],
1487             oncomplete : function(r) {
1488                 if(!noprog) progressDialog.hide();
1489                 openils.Util.readResponse(r);
1490                 if(callback) callback();
1491             }
1492         }
1493     );
1494 }
1495
1496 function dateFromISO(d) {
1497     if(!d) return '';
1498     return dojo.date.locale.format( 
1499         dojo.date.stamp.fromISOString(d),
1500         {selector:'date', fullYear: true}
1501     );
1502 }
1503
1504 function myopacShowGenericTransaction( trans ) {
1505         var tbody = $('myopac_trans_tbody');
1506
1507         var row = myopacGenericTransTemplate.cloneNode(true);
1508         $n(row,'myopac_trans_start').appendChild(text(dateFromISO(trans.xact_start())));
1509         $n(row,'myopac_trans_last_payment').appendChild(text(dateFromISO(trans.last_payment_ts())));
1510         $n(row,'myopac_trans_init_amount').appendChild(text(_finesFormatNumber(trans.total_owed())));
1511         $n(row,'myopac_trans_total_paid').appendChild(text(_finesFormatNumber(trans.total_paid())));
1512         $n(row,'myopac_trans_balance').appendChild(text(_finesFormatNumber(trans.balance_owed())));
1513         $n(row,'selector').balance_owed = trans.balance_owed();
1514         $n(row,'selector').setAttribute("xact", trans.id());
1515     if(trans.balance_owed() <= 0) {
1516         $n(row,'selector').disabled = true;
1517     }
1518
1519
1520         var req = new Request(FETCH_MONEY_BILLING, G.user.session, trans.id());
1521         req.send(true);
1522         var bills = req.result();
1523         if(bills && bills[0]) $n(row,'myopac_trans_bill_type').appendChild(text(bills[0].billing_type()));
1524
1525         tbody.appendChild(row);
1526         unHideMe($('myopac_trans_div'));
1527 }
1528
1529 function myOPACShowCircTransaction(trans, record, circ) {
1530         var tbody = $('myopac_circ_trans_tbody');
1531
1532         var row = myopacCircTransTemplate.cloneNode(true);
1533         if(record) {
1534                 buildTitleDetailLink(record, $n(row,'myopac_circ_trans_title'));
1535                 $n(row,'myopac_circ_trans_author').appendChild(text(normalize(truncate(record.author(), 65))));
1536         } else {
1537                 var req = new Request( FETCH_COPY, circ.target_copy() );
1538                 req.alertEvents = false;
1539                 req.send(true);
1540                 var copy = req.result();
1541                 if( copy ) {
1542                         $n(row,'myopac_circ_trans_title').appendChild(text(copy.dummy_title()));
1543                         $n(row,'myopac_circ_trans_author').appendChild(text(copy.dummy_author()));
1544                 }
1545         }
1546         
1547         $n(row,'myopac_circ_trans_start').appendChild(text(dateFromISO(trans.xact_start())));
1548
1549     var due = dateFromISO(circ.due_date());
1550         var checkin = dateFromISO(circ.stop_fines_time());
1551
1552         $n(row,'myopac_circ_trans_due').appendChild(text(due))
1553         if(checkin) appendClear($n(row,'myopac_circ_trans_finished'), text(checkin));
1554         if(circ.stop_fines() == 'LOST') appendClear($n(row,'myopac_circ_trans_finished'), text(circ.stop_fines()));
1555         if(circ.stop_fines() == 'CLAIMSRETURNED') appendClear($n(row,'myopac_circ_trans_finished'), text(""));
1556         $n(row,'myopac_circ_trans_balance').appendChild(text(_finesFormatNumber(trans.balance_owed())));
1557         $n(row,'selector').balance_owed = trans.balance_owed();
1558         $n(row,'selector').setAttribute("xact",trans.id()); 
1559     if(trans.balance_owed() <= 0) {
1560         $n(row,'selector').disabled = true;
1561     }
1562
1563         tbody.appendChild(row);
1564         unHideMe($('myopac_circ_trans_div'));
1565 }
1566
1567 function showFinesDiv(el) {
1568         if(!el) return;
1569         if($('myopac_fines_div').className.indexOf('hide_me')>=0) { 
1570                 unHideMe($('myopac_fines_div'));
1571                 el.innerHTML="Hide Overdue Materials";
1572         if(!finesShown) {
1573             myOPACShowFines(true);
1574         }
1575         } else {
1576                 hideMe($('myopac_fines_div'));
1577                 el.innerHTML="Show Overdue Materials";
1578         }
1579 }
1580
1581 var ecom_event_map = {
1582     CREDIT_PROCESSOR_DECLINED_TRANSACTION : 
1583         'Sorry. Your payment has been declined. Please confirm your information is entered correctly or contact your credit card company.',
1584     CREDIT_PROCESSOR_INVALID_CC_NUMBER : 
1585         'The credit card number entered is not valid.  Please confirm your information is entered correctly or contact your credit card company.',
1586     SUCCESS : 'Your payment has been approved' 
1587 }
1588
1589 function showPaymentForm() {
1590         unHideMe($('pay_fines_now'));
1591         hideMe($('acct_sum'));
1592     hideMe($('cc-payment-error-message'));
1593         drawPayFinesPage(
1594                 G.user,
1595                 getSelectedFinesTotal(),
1596                 getSelectedFineTransactions(),
1597                 function(resp) {
1598             unHideMe($('cc-payment-error-message'));
1599                         if(resp.textcode) {
1600                 var message = ecom_event_map[resp.textcode] || resp.textcode+'\n'+resp.desc + '';
1601                 if(resp.payload && resp.payload.error_message) 
1602                     message += '<br/>' + resp.payload.error_message
1603                 $('cc-payment-error-message').innerHTML = message;
1604                                 return;
1605                         }
1606                         G.user.last_xact_id(resp.last_xact_id); // update to match latest from server
1607             $('cc-payment-error-message').innerHTML = ecom_event_map.SUCCESS;
1608                         printPaymentReceipt(resp.payments, function() { location.href = location.href; } );
1609             emailPaymentReceipt(resp.payments, null, false);
1610             /*
1611                         hideMe($('pay_fines_now'));unHideMe($('acct_sum'));
1612                         finesShown = false;
1613                         myOPACShowFines();              
1614                         showFinesDiv($('show_fines_link'));
1615             */
1616                 }
1617         );
1618 }
1619
1620 function getSelectedFinesTotal() {
1621     var total = 0;
1622     dojo.forEach(
1623         dojo.query("[name=selector]", $('myopac_circ_trans_tbody')),
1624         function(input) { if(input.checked && input.getAttribute("xact")) total += Number(input.balance_owed); }
1625     );
1626         
1627     dojo.forEach(
1628         dojo.query("[name=selector]", $('myopac_trans_tbody')),
1629         function(input) { if(input.checked && input.getAttribute("xact")) total += Number(input.balance_owed); }
1630     );
1631     return total.toFixed(2);
1632 }
1633
1634 function getSelectedFineTransactions() {
1635         var set1 = dojo.query("[name=selector]", $('myopac_circ_trans_tbody')).
1636         filter(function (o) { return o.checked }).
1637                 map(function (o) {return [o.getAttribute("xact"), Number(o.balance_owed).toFixed(2)];}
1638         );
1639         var set2 = dojo.query("[name=selector]", $('myopac_trans_tbody')).
1640         filter(function (o) { return o.checked }).
1641                 map(function (o) {return [o.getAttribute("xact"), Number(o.balance_owed).toFixed(2)];}
1642         );
1643         var obj = set1.concat(set2);
1644         return obj.filter(function(el){return el[0]==null?false:true;});
1645 }
1646
1647 var payFinesDrawn = false;
1648 function drawPayFinesPage(patron, total, xacts, onPaymentSubmit) {
1649     if (typeof(this.authtoken) == "undefined")
1650         this.authtoken = patron.session;
1651
1652     dojo.query("span", "oils-selfck-cc-payment-summary")[0].innerHTML = total;
1653
1654     $('myopac-cc-email').innerHTML = patron.email();
1655     oilsSelfckCCNumber.attr('value', '');
1656     oilsSelfckCCCVV.attr('value', '');
1657     oilsSelfckCCMonth.attr('value', '01');
1658     oilsSelfckCCYear.attr('value', new Date().getFullYear());
1659     oilsSelfckCCFName.attr('value', patron.first_given_name());
1660     oilsSelfckCCLName.attr('value', patron.family_name());
1661
1662     var addr = patron.billing_address() || patron.mailing_address();
1663
1664     if (typeof(addr) != "object") {
1665         /* still don't have usable address? try getting better user object. */
1666         fieldmapper.standardRequest(
1667             FETCH_FULL_USER, {
1668                 "params": [patron.session, patron.id(), ["billing_address", "mailing_address"]],
1669                 "async": false,
1670                 "oncomplete": function(r) {
1671                     var usr = r.recv().content();
1672                     if (usr) addr = usr.billing_address() || usr.mailing_address();
1673                 }
1674             }
1675         );
1676     }
1677
1678     if (addr) {
1679         //oilsSelfckCCStreet.attr('value', addr.street1()+' '+addr.street2());
1680         oilsSelfckCCCity.attr('value', addr.city());
1681         oilsSelfckCCState.attr('value', addr.state());
1682         oilsSelfckCCZip.attr('value', addr.post_code());
1683     }
1684
1685     dojo.connect(oilsSelfckEditDetails, 'onChange',
1686         function(newVal) {
1687             dojo.forEach(
1688                 [oilsSelfckCCFName, oilsSelfckCCLName, oilsSelfckCCStreet, oilsSelfckCCCity, oilsSelfckCCState, oilsSelfckCCZip],
1689                 function(dij) { dij.attr('disabled', !newVal); }
1690             );
1691         }
1692     );
1693
1694     if(!payFinesDrawn) {
1695                 dojo.connect(oilsSelfckCCSubmit, 'onClick',
1696           function() {
1697             hideMe($('pay_fines_now'));
1698             unHideMe($('pay_fines_confirm'));
1699
1700             $('pay_fines_confirm_amount').innerHTML = 
1701                 dojo.query("span", "oils-selfck-cc-payment-summary")[0].innerHTML;
1702
1703             dojo.connect(payConfirmSubmit, 'onClick', 
1704                 function() { 
1705                     if (typeof(progressDialog) != "undefined") progressDialog.show(true);
1706                     unHideMe($('pay_fines_now'));
1707                     hideMe($('pay_fines_confirm'));
1708                     sendCCPayment(patron, xacts, onPaymentSubmit);
1709                 } 
1710             );
1711
1712             dojo.connect(payConfirmCancel, 'onClick', 
1713                 function() { 
1714                     unHideMe($('pay_fines_now'));
1715                     hideMe($('pay_fines_confirm'));
1716                 }
1717             );
1718
1719             /*
1720                         if(!confirm("Are you sure?")) return;
1721             sendCCPayment(patron, xacts, onPaymentSubmit);
1722             */
1723           }
1724         );
1725                 payFinesDrawn = true;
1726         }
1727         
1728         var selFines = $('selectedFines');
1729         removeChildren(selFines);
1730         for(var i in xacts) {
1731                 var xact = transCache[xacts[i][0]];
1732                 if(!xact) continue;
1733                 var tr = elem('tr');
1734                 var td1 = elem('td', {}, xact["record"]?xact.record.title():xact.transaction.last_billing_type());
1735                 var td2 = elem('td', {'nowrap':'nowrap', 'valign':'top'}, '$'+xact.transaction.balance_owed());
1736                 td2.style.paddingLeft = '5px';
1737                 td2.style.color = 'red';
1738                 tr.appendChild(td1);
1739                 tr.appendChild(td2);
1740                 selFines.appendChild(tr);
1741         }
1742 }
1743
1744 function sendCCPayment(patron, xacts, onPaymentSubmit) {
1745     // in this context, patron will always be G.user.  set it explicitly 
1746     // to pick up the latest last_xact_id value
1747     patron = G.user;
1748
1749     var args = {
1750         userid : patron.id(),
1751         payment_type : 'credit_card_payment',
1752         payments : xacts,
1753         cc_args : {
1754             where_process : 1,
1755             //type :  'MasterCard',//oilsSelfckCCType.attr('value'),
1756             number : oilsSelfckCCNumber.attr('value'),
1757             cvv2 : oilsSelfckCCCVV.attr('value'),
1758             expire_year : oilsSelfckCCYear.attr('value'),
1759             expire_month : oilsSelfckCCMonth.attr('value'),
1760             billing_first : oilsSelfckCCFName.attr('value'),
1761             billing_last : oilsSelfckCCLName.attr('value'),
1762             billing_address : oilsSelfckCCStreet.attr('value'),
1763             billing_city : oilsSelfckCCCity.attr('value'),
1764             billing_state : oilsSelfckCCState.attr('value'),
1765             billing_zip : oilsSelfckCCZip.attr('value')
1766         }
1767     }
1768
1769     var resp = fieldmapper.standardRequest(PAY_BILLS,{params : [patron.session, args, patron.last_xact_id()]});
1770     if (typeof(progressDialog) != "undefined")
1771         progressDialog.hide();
1772
1773     if (typeof(onPaymentSubmit) == "function") {
1774         onPaymentSubmit(resp);
1775     } else {
1776         var evt = openils.Event.parse(resp);
1777         if (evt) alert(evt);
1778     }
1779 }
1780
1781 function myopacSelectedHoldsRows() {
1782     var r = [];
1783         var cb;
1784     var rows = dojo.query('[name=acct_holds_temp]',$("holds_temp_parent"));
1785     for(var i = 0; i < rows.length; i++) {
1786         cb = $n(rows[i], 'check_all_holds');
1787         if(cb && cb.checked)
1788             r.push(rows[i]);
1789     }
1790     return r;
1791 }
1792
1793 var myopacProcessedHolds = 0;
1794 var myopacHoldsToProcess = 0;
1795 function myopacDoHoldAction() {
1796     var selectedRows = myopacSelectedHoldsRows();
1797     var action = getSelectorVal($('acct_holds_actions'));
1798     $('myopac_holds_actions_none').selected = true;
1799     if(selectedRows.length == 0) return;
1800
1801     myopacProcessedHolds = 0;
1802
1803     if(!confirmId('myopac.holds.'+action+'.confirm')) return;
1804     //myopacSelectNoneHolds(); /* clear the selection */
1805
1806
1807     /* first, let's collect the holds that actually need processing and
1808         collect the full process count while we're at it */
1809     var holds = [];
1810     for(var i = 0; i < selectedRows.length; i++) {
1811                 var ahold = $n(selectedRows[i],'check_all_holds');
1812         var hold = holdsCache[holdsCacheMap[ahold.holdid]];
1813         var qstats = hold.status;
1814         switch(action) {
1815             case 'cancel':
1816                 holds.push(hold.hold);
1817                 break;
1818             case 'thaw_date':
1819             case 'thaw':
1820                 if(isTrue(hold.hold.frozen()))
1821                     holds.push(hold.hold);
1822                 break;
1823             case 'freeze':
1824                 if(!isTrue(hold.hold.frozen()) && qstats < 3)
1825                     holds.push(hold.hold);
1826                 break;
1827         }
1828     }
1829
1830     myopacHoldsToProcess = holds;
1831     if(myopacHoldsToProcess.length == 0) return;
1832
1833     if(action == 'thaw_date' || action == 'freeze') 
1834         myopacDrawHoldThawDateForm();
1835     else
1836     myopacProcessHolds(action);
1837 }
1838
1839 function myopacDrawHoldThawDateForm() {
1840     hideMe($('myopac_holds_div'));
1841     unHideMe($('myopac_holds_thaw_date_form'));
1842     $('myopac_holds_thaw_date_input').focus();
1843 }
1844
1845 function myopacApplyThawDate() {
1846     var dateString = dijit.byId('myopac_holds_thaw_date_input').getValue();
1847     if(dateString) {
1848         dateString = dojo.date.stamp.toISOString(dateString);
1849         if(dateString) {
1850             dateString = holdsVerifyThawDate(dateString);
1851             if(!dateString) return;
1852         } else {
1853             dateString = null;
1854         }
1855     }
1856         unHideMe($('myopac_holds_div'));
1857     hideMe($('myopac_holds_thaw_date_form'));
1858     myopacProcessHolds('freeze', dateString);
1859 }
1860
1861
1862 function myopacProcessHolds(action, thawDate) {
1863         progressDialog.show(true);
1864    // myopacShowHoldProcessing();
1865     /* now we process them */
1866     for(var i = 0; i < myopacHoldsToProcess.length; i++) {
1867         var hold = myopacHoldsToProcess[i];
1868         
1869         var req;
1870         switch(action) { 
1871
1872             case 'cancel':
1873                 req = new Request(CANCEL_HOLD, G.user.session, hold.id());
1874                 break;
1875     
1876             case 'thaw':
1877                 hold.frozen('f');
1878                 hold.thaw_date(null);
1879                 req = new Request(UPDATE_HOLD, G.user.session, hold);
1880                 break;
1881
1882             case 'thaw_date':
1883             case 'freeze':
1884                 hold.frozen('t');
1885                 hold.thaw_date(thawDate); 
1886                 req = new Request(UPDATE_HOLD, G.user.session, hold);
1887                 break;
1888                 //thawDate = prompt($('myopac.holds.freeze.select_thaw').innerHTML);
1889
1890         }
1891
1892         req.callback(myopacBatchHoldCallback);
1893         req.send();
1894         req = null;
1895     }
1896 }
1897
1898 function myopacBatchHoldCallback(r) {
1899         var res = r.getResultObject();
1900         myopacHoldsToProcess = grep(myopacHoldsToProcess, function(i) { return (i.id() != res); }); 
1901         if(!myopacHoldsToProcess || ++myopacProcessedHolds >= myopacHoldsToProcess.length) {
1902           //alert(res);
1903           progressDialog.hide();
1904           myopacForceHoldsRedraw = true;
1905           $('check_all_holds').checked = false;
1906           drawHoldsPage();
1907         }
1908 }
1909
1910 function myOPACRenewSelected() {
1911    var rows = dojo.query('input[name=check_all_checked]',$('checked_temp_parent')).filter(function(n,i){ return n.checked; });
1912    __renew_circs = [];
1913         if(!rows.length || !confirm($('myopac_renew_confirm').innerHTML)) return;
1914    __success_count = 0;
1915    __fail_count = 0;
1916
1917    for( var i = 0; i < rows.length; i++ ) {
1918       var row = rows[i];
1919       var circ_id = row.getAttribute('circid');
1920
1921            var circ;
1922            for( var j = 0; j != itemsOutCache.length; j++ ) 
1923                    if(itemsOutCache[j].circ.id() == circ_id)
1924                            circ = itemsOutCache[j].circ;
1925
1926       __renew_circs.push(circ);
1927    }
1928
1929     if( __renew_circs.length == 0 ) return;
1930
1931     //unHideMe($('my_renewing'));
1932     //moClearCheckedTable();
1933
1934     for( var i = 0; i < __renew_circs.length; i++ ) {
1935         var circ = __renew_circs[i];
1936         moRenewCirc( circ.target_copy(), G.user.id(), circ );
1937     }
1938 }
1939
1940 var __renew_circs = [];
1941 var __rewnew_errors = [];
1942 var __success_count = 0;
1943 var __fail_count = 0;
1944 function moRenewCirc(copy_id, user_id, circ) {
1945
1946    _debug('renewing circ ' + circ.id() + ' with copy ' + copy_id);
1947    var req = new Request(RENEW_CIRC, G.user.session, 
1948       {  patron : user_id, 
1949          copyid : copy_id, 
1950          opac_renewal : 1
1951       } 
1952    );
1953
1954    req.request.alertEvent = false;
1955    req.callback(myHandleRenewResponse);
1956    req.request.circ = circ;
1957    req.send();
1958 }
1959
1960 /* handles the circ renew results */
1961
1962 function myHandleRenewResponse(r) {
1963    try{ var res = r.getResultObject(); } catch(e){ alert("Renew Error\n\n"+e); __renew_circs = []; __rewnew_errors = []; return; }
1964    var circ = r.circ;
1965
1966    /* remove this circ from the list of circs to renew */
1967    if(checkILSEvent(res) || checkILSEvent(res[0])) {
1968            var str1 = truncate(mvrObjCache[circ.target_copy()].title(),65)+'\n';
1969            if(res.ilsevent) str1 += res.ilsevent+': '+res.desc+'\n'; else for(var i in res) str1 += res[i].ilsevent+': '+res[i].desc+'\n';
1970            __rewnew_errors[circ.id()] = str1;
1971    }
1972    __renew_circs = grep(__renew_circs, function(i) { return (i.id() != circ.id()); });
1973    _debug("handling renew result for " + circ.id());
1974
1975    if(checkILSEvent(res) || checkILSEvent(res[0])) __fail_count++;
1976       //alertIdText('myopac_renew_fail', __circ_titles[circ.id()]);
1977    else __success_count++;
1978    
1979    if(__renew_circs) return; /* more to come */
1980    __renew_circs = [];
1981    
1982    var str = "";
1983    if(__success_count) str+= __success_count+" items renewed successfully";
1984    if(__fail_count) str+=__fail_count+" items did not renew.";
1985    str+='\n\n';
1986    for(var i in __rewnew_errors) str+=__rewnew_errors[i]+'\n';
1987    
1988    if(__success_count || __fail_count) alert(str);
1989    __rewnew_errors = [];
1990
1991         //if( __success_count > 0 )
1992     //  alertIdText('myopac_renew_success', __success_count);
1993
1994    hideMe($('my_renewing'));
1995    checkedDrawn = false;
1996     drawCheckedPage();
1997         $('check_all_checked').checked = false;
1998 }
1999
2000
2001 function moveToNewList(parent, dest) {
2002         if(!parent || !dest) return;
2003         
2004         var items = dojo.query('input[name=list_action_chbx]', parent);
2005         if(!items.length) items = dojo.query('.list_action_chbx', parent);
2006         items.filter(function(item, index, arr){return item.checked;},this);
2007         
2008         if(items.length) {
2009                 for(var i=0; i<items.length; i++) {
2010                         var box = items[i];
2011                         if(box.checked) {
2012                                 containerCreateItem(dest, box.getAttribute("recordid"));
2013                         }
2014                 }
2015         }
2016 }
2017
2018 function listSaveAction() {
2019         var lists = dojo.query('select[name=list_actions]',$('temp_wrapper')).filter(function(n,i){
2020                 return n.options[n.selectedIndex].value!="0"
2021         });
2022         
2023         if(lists.length) { if(!confirm("Proceed with the selected action(s)?")) return; } else return;
2024         progressDialog.show(true);
2025         var updateHolds = false;
2026         var updateLists = false;
2027         
2028         lists.forEach(function(n,i){
2029                 var val = n.options[n.selectedIndex].value;
2030                 if(val=="0") return;
2031                 var p = n.parentNode.parentNode.parentNode.parentNode.parentNode;
2032                 switch(val) {
2033                         case "hold": batchHoldMyList(null, p); updateHolds = true; break;
2034                         case "move": if(n.id=='sel_all_list_anon') delSelCache(p, 'list_action_chbx');
2035                                                                 else removeSelectedItems(p); moveToNewList(p, n.options[n.selectedIndex].getAttribute("container"));
2036                                                   updateLists = true; break;
2037                         case "remove": if(n.id=='sel_all_list_anon') delSelCache(p, 'list_action_chbx');
2038                                                                 else removeSelectedItems(p);
2039                                                    updateLists = true; break;
2040                 }
2041                 setSelector(n, "0");
2042         });
2043         
2044         if(updateLists) reloadMyLists();
2045         if(updateHolds) { myopacForceHoldsRedraw = true; drawHoldsPage(); }
2046         progressDialog.hide();
2047 }
2048
2049 var itemsOutHistoryInitialFetch = false;
2050 function switchSubPage(page, subpage) {
2051         if(!page || !subpage) return;
2052         
2053         var pg = subPageObjs[page]; if(!pg) return;
2054         var spg = subPageObjs[page][subpage]; if(!spg) return;
2055         
2056         for(var i in pg) { if(i!='header') for(var n in pg[i]){ if(pg[i][n] || n!='header') hideMe(pg[i][n]); } }
2057         for(var t in spg) { if(spg[t] || t!='header') unHideMe(spg[t]); }
2058         
2059         pg.header.innerHTML = spg.header;
2060
2061     if(page == 'checked' && subpage == 'hist') {
2062         if(!itemsOutHistoryInitialFetch) {
2063             itemsOutHistoryInitialFetch = true;
2064             progressDialog.show(true);
2065                 fieldmapper.standardRequest(FETCH_CHECKED_HISTORY, {async:true, params:[G.user.session, G.user.id(), {'limit':CIRC_HIST_PAGE_LIMIT, 'offset':0}],
2066                         oncomplete:function(r) {
2067                     progressDialog.hide();
2068                     itemsOutHistory = openils.Util.readResponse(r);
2069                     drawCircHistory();
2070                         }
2071                 });
2072         }
2073     }
2074 }
2075
2076 function doBatchAnonHolds() {
2077         var error = {err:""};
2078         var resp = placeBatchHold(holdsList, G.user.home_ou(), error);
2079         if(resp == -1) alert("Unable to place holds"); else {
2080           alert(resp+" hold"+(resp==1?"":"s")+" placed successfully\n\n"+error.err);
2081         }
2082         
2083         holdsList = null;
2084         myopacForceHoldsRedraw = true;
2085         drawHoldsPage();
2086 }
2087
2088 function myOPACUpdateHomeOU() {
2089         var sel = $('myopac_new_home');
2090         
2091 }
2092
2093 var sortOrder = true;
2094 function sortHolds(by) {
2095         if(!by) return;
2096         
2097         sortOrder = !sortOrder;
2098         switch(by.toLowerCase()) {
2099                 case "format":
2100                 holdsCache = holdsCache.sort(function(a, b) {
2101                         if(sortOrder) return get998dValue(imgFormatCache[a.hold.id()])<get998dValue(imgFormatCache[b.hold.id()])?-1:1;
2102                                 else      return get998dValue(imgFormatCache[a.hold.id()])>get998dValue(imgFormatCache[b.hold.id()])?-1:1;
2103                 });
2104                 break;
2105                 case "title":
2106                 holdsCache = holdsCache.sort(function(a, b) {
2107                         if(sortOrder) return a.mvr.title()<b.mvr.title()?-1:1;
2108                                 else      return a.mvr.title()>b.mvr.title()?-1:1;
2109                 });
2110                 break;
2111                 case "pickup":
2112                 holdsCache = holdsCache.sort(function(a, b) {
2113                         if(sortOrder) return findOrgUnit(a.hold.pickup_lib()).name()<findOrgUnit(b.hold.pickup_lib()).name()?-1:1;
2114                                 else      return findOrgUnit(a.hold.pickup_lib()).name()>findOrgUnit(b.hold.pickup_lib()).name()?-1:1;
2115                 });
2116                 break;
2117                 case "author":
2118                 holdsCache = holdsCache.sort(function(a, b) {
2119                         if(sortOrder) return a.mvr.author()<b.mvr.author()?-1:1;
2120                                 else      return a.mvr.author()>b.mvr.author()?-1:1;
2121                 });
2122                 break;
2123                 case "status":
2124                 if(sortOrder) {
2125                         drawHoldsPage(true);
2126                         return;
2127                 } else {
2128                         holdsCache = holdsCache.sort(function(a, b) {
2129                                 if(a.status==4) return 1;
2130                                 if(b.status==4) return -1;
2131                                 if(isTrue(a.hold.frozen())) return -1;
2132                                 if(isTrue(b.hold.frozen())) return 1;
2133                                 return dojo.date.stamp.fromISOString(a.hold.request_time()) > dojo.date.stamp.fromISOString(b.hold.request_time())?-1:1;
2134                         });                     
2135                 }
2136         }
2137         
2138         drawHoldsPage(false);
2139 }
2140
2141 function sortChecked(by) {
2142         if(!by) return;
2143         
2144         sortOrder = !sortOrder;
2145         switch(by.toLowerCase()) {
2146                 case "title":
2147                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2148                         if(sortOrder) return a.record.title()<b.record.title()?-1:1;
2149                                 else      return a.record.title()>b.record.title()?-1:1;
2150                 });
2151                 break;
2152                 case "author":
2153                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2154                         if(sortOrder) return a.record.author()<b.record.author()?-1:1;
2155                                 else      return a.record.author()>b.record.author()?-1:1;
2156                 });
2157                 break;
2158                 case "due":
2159                 if(sortOrder) {
2160                         drawCheckedPage(); return;
2161                 } else itemsOutCache = itemsOutCache.sort(function(a, b) {
2162                         return dojo.date.stamp.fromISOString(a.circ.due_date()) < dojo.date.stamp.fromISOString(b.circ.due_date())?1:-1;
2163                 });
2164                 break;
2165                 case "barcode":
2166                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2167                         if(sortOrder) return Number(a.copy.barcode())<Number(b.copy.barcode())?-1:1;
2168                                 else      return Number(a.copy.barcode())>Number(b.copy.barcode())?-1:1;
2169                 });
2170                 break;
2171                 case "cn":
2172                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2173                         if(sortOrder) return callNumCache[a.copy.call_number()]<callNumCache[b.copy.call_number()] ?-1:1;
2174                                 else      return callNumCache[a.copy.call_number()]>callNumCache[b.copy.call_number()]?-1:1;
2175                 });
2176                 break;
2177                 case "renews":
2178                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2179                         if(sortOrder) return Number(a.circ.renewal_remaining())<Number(b.circ.renewal_remaining())?-1:1;
2180                                 else      return Number(a.circ.renewal_remaining())>Number(b.circ.renewal_remaining())?-1:1;
2181                 });
2182                 break;
2183         }
2184         
2185         drawCheckedPage(false);
2186 }
2187
2188 function sortCheckedHist(by) {
2189         if(!by) return;
2190         
2191         sortOrder = !sortOrder;
2192         switch(by.toLowerCase()) {
2193                 case "title":
2194                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2195                         if(mvrObjCache[a.circ.target_copy()].title()==null) return 1;
2196                         if(mvrObjCache[b.circ.target_copy()].title()==null) return -1;
2197                         if(sortOrder) return mvrObjCache[a.circ.target_copy()].title().toLowerCase()<mvrObjCache[b.circ.target_copy()].title().toLowerCase()?-1:1;
2198                                 else      return mvrObjCache[a.circ.target_copy()].title().toLowerCase()>mvrObjCache[b.circ.target_copy()].title().toLowerCase()?-1:1;
2199                 });
2200                 break;
2201                 case "author":
2202                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2203                         if(mvrObjCache[a.circ.target_copy()].author()==null) return 1;
2204                         if(mvrObjCache[b.circ.target_copy()].author()==null) return -1;
2205                         if(sortOrder) return mvrObjCache[a.circ.target_copy()].author().toLowerCase()<mvrObjCache[b.circ.target_copy()].author().toLowerCase()?1:-1;
2206                                 else      return mvrObjCache[a.circ.target_copy()].author().toLowerCase()>mvrObjCache[b.circ.target_copy()].author().toLowerCase()?1:-1;
2207                 });
2208                 break;
2209                 case "duedate":
2210                 if(sortOrder) {
2211                         drawCheckedPage(); return;
2212                 } else itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2213                         return dojo.date.stamp.fromISOString(a.circ.due_date()) < dojo.date.stamp.fromISOString(b.circ.due_date())?1:-1;
2214                 });
2215                 break;
2216                 case "cn":
2217                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2218                                                                                 // ya, i know, but it gets the job done.
2219                         if(sortOrder) return callNumCache[copyObjCache[a.circ.target_copy()].call_number()].label().toLowerCase()<callNumCache[copyObjCache[b.circ.target_copy()].call_number()].label().toLowerCase()?-1:1;
2220                                 else      return callNumCache[copyObjCache[a.circ.target_copy()].call_number()].label().toLowerCase()>callNumCache[copyObjCache[b.circ.target_copy()].call_number()].label().toLowerCase()?-1:1;
2221                 });
2222                 break;
2223         }
2224         
2225         drawCircHistory(false);
2226 }
2227
2228 function printData(data, numItems, callback) {
2229
2230     //unHideMe($('receipt-print-frame-wrapper'));
2231     receiptPrintDialog.show();
2232
2233     var frame = window["receipt-frame"];
2234     frame.document.body.innerHTML = data;
2235
2236     var cancel = $('receipt-view-print-cancel');
2237     cancel.onclick = function() {
2238         frame.document.body.innerHTML = '';
2239         receiptPrintDialog.hide();
2240         if(callback) callback();
2241     }
2242
2243     // user clicked the X control to close the dialog
2244     dojo.connect(receiptPrintDialog, 'onCancel', 
2245         function() { 
2246             frame.document.body.innerHTML = '';
2247             if(callback) callback();
2248         } 
2249     ); 
2250
2251     $('receipt-view-print-button').onclick = function() {
2252         frame.focus();
2253         frame.print();
2254         var sleepTime = 1000;
2255         if(numItems > 0) 
2256             sleepTime += (numItems / 2) * 1000;
2257
2258         setTimeout(
2259             function() { 
2260                 cancel.onclick();
2261                 if(callback) callback(); // fire optional post-print callback
2262             },
2263             sleepTime 
2264         );
2265     };
2266 }
2267
2268
2269 function printPaymentReceipt(paymentIds, callback, noprog) {
2270     if(!noprog) progressDialog.show(true);
2271
2272     fieldmapper.standardRequest(
2273         ['open-ils.circ', 'open-ils.circ.money.payment_receipt.print'],
2274         {
2275             async : true,
2276             params : [G.user.session, paymentIds],
2277             oncomplete : function(r) {
2278                 var resp = openils.Util.readResponse(r);
2279                 var output = "";
2280                                 if(resp) output = resp.template_output();
2281                 if(!noprog) progressDialog.hide();
2282                 if(output) {
2283                     printData(output.data(), 1, callback); 
2284                 } else {
2285                     var error = resp.error_output();
2286                     if(error) {
2287                         throw new Error("Error creating receipt: " + error.data());
2288                     } else {
2289                         throw new Error("No receipt data returned from server");
2290                     }
2291                 }
2292             }
2293         }
2294     );
2295 }
2296
2297 function printFinesReceipt(callback) {
2298     progressDialog.show(true);
2299
2300     var params = [
2301         G.user.session, 
2302         G.user.ws_ou(),
2303         null,
2304         'format.selfcheck.fines',
2305         'print-on-demand',
2306         [G.user.id()]
2307     ];
2308
2309     fieldmapper.standardRequest(
2310         ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
2311         {   
2312             async : true,
2313             params : params,
2314             oncomplete : function(r) {
2315                 progressDialog.hide();
2316                 var resp = openils.Util.readResponse(r);
2317                 var output = resp.template_output();
2318                 if(output) {
2319                     printData(output.data(), 240, callback); 
2320                 } else {
2321                     var error = resp.error_output();
2322                     if(error) {
2323                         throw new Error("Error creating receipt: " + error.data());
2324                     } else {
2325                         throw new Error("No receipt data returned from server");
2326                     }
2327                 }
2328             }
2329         }
2330     );
2331 }
2332
2333 function buildOrgSelAlt(selector, org, offset, namecol) {
2334  if(!namecol) namecol = 'name';
2335  if(!showXUL && !isTrue(org.opac_visible())) return; // for some reason, isXUL() is rather slow when used in a decently sized loop.
2336  insertSelectorVal( selector, -1,
2337  org[namecol](), org.id(), null, findOrgDepth(org) - offset );
2338  var kids = org.children();
2339  if (kids) {
2340  for( var c = 0; c < kids.length; c++ )
2341  buildOrgSelAlt( selector, kids[c], offset, namecol);
2342  }
2343 }
2344
2345 // alternative to checkAll that does not select <= balances... needs testing
2346 function checkAllXact(parent, id, name, balanceName) {//Object, string
2347         var obj = typeof(id)=="object"?id:$(id);
2348         if(!parent || !obj) return;
2349         if(!name) name = id.toString();
2350     dojo.forEach(parent.childNodes, 
2351         function(row) {
2352             if(row.nodeName.match(/tr/i)) {
2353                 var input = dojo.query('input[name='+name+']', row)[0];
2354                 var balance = dojo.query('input[name='+balanceName+']', row)[0];
2355                 if(Number(balance) > 0)
2356                             balance.checked = obj.checked;
2357             }
2358         }
2359     );
2360     /*
2361         var nodes = dojo.query('input[name='+name+']', parent);
2362         if(!nodes.length) nodes = dojo.query('.'+name, parent);
2363         nodes.forEach(function(node, index){
2364                 node.checked = obj.checked;
2365         });
2366     */
2367 }