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