59a7e29509d49635316cb2bc379809a5d44de1b8
[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                 $('cc-payment-error-message').innerHTML = message;
1599                                 return;
1600                         }
1601                         G.user.last_xact_id(resp.last_xact_id); // update to match latest from server
1602             $('cc-payment-error-message').innerHTML = ecom_event_map.SUCCESS;
1603                         printPaymentReceipt(resp.payments, function() { location.href = location.href; } );
1604             emailPaymentReceipt(resp.payments, null, false);
1605             /*
1606                         hideMe($('pay_fines_now'));unHideMe($('acct_sum'));
1607                         finesShown = false;
1608                         myOPACShowFines();              
1609                         showFinesDiv($('show_fines_link'));
1610             */
1611                 }
1612         );
1613 }
1614
1615 function getSelectedFinesTotal() {
1616     var total = 0;
1617     dojo.forEach(
1618         dojo.query("[name=selector]", $('myopac_circ_trans_tbody')),
1619         function(input) { if(input.checked && input.getAttribute("xact")) total += Number(input.balance_owed); }
1620     );
1621         
1622     dojo.forEach(
1623         dojo.query("[name=selector]", $('myopac_trans_tbody')),
1624         function(input) { if(input.checked && input.getAttribute("xact")) total += Number(input.balance_owed); }
1625     );
1626     return total.toFixed(2);
1627 }
1628
1629 function getSelectedFineTransactions() {
1630         var set1 = dojo.query("[name=selector]", $('myopac_circ_trans_tbody')).
1631         filter(function (o) { return o.checked }).
1632                 map(function (o) {return [o.getAttribute("xact"), Number(o.balance_owed).toFixed(2)];}
1633         );
1634         var set2 = dojo.query("[name=selector]", $('myopac_trans_tbody')).
1635         filter(function (o) { return o.checked }).
1636                 map(function (o) {return [o.getAttribute("xact"), Number(o.balance_owed).toFixed(2)];}
1637         );
1638         var obj = set1.concat(set2);
1639         return obj.filter(function(el){return el[0]==null?false:true;});
1640 }
1641
1642 var payFinesDrawn = false;
1643 function drawPayFinesPage(patron, total, xacts, onPaymentSubmit) {
1644     if (typeof(this.authtoken) == "undefined")
1645         this.authtoken = patron.session;
1646
1647     dojo.query("span", "oils-selfck-cc-payment-summary")[0].innerHTML = total;
1648
1649     $('myopac-cc-email').innerHTML = patron.email();
1650     oilsSelfckCCNumber.attr('value', '');
1651     oilsSelfckCCCVV.attr('value', '');
1652     oilsSelfckCCMonth.attr('value', '01');
1653     oilsSelfckCCYear.attr('value', new Date().getFullYear());
1654     oilsSelfckCCFName.attr('value', patron.first_given_name());
1655     oilsSelfckCCLName.attr('value', patron.family_name());
1656
1657     var addr = patron.billing_address() || patron.mailing_address();
1658
1659     if (typeof(addr) != "object") {
1660         /* still don't have usable address? try getting better user object. */
1661         fieldmapper.standardRequest(
1662             FETCH_FULL_USER, {
1663                 "params": [patron.session, patron.id(), ["billing_address", "mailing_address"]],
1664                 "async": false,
1665                 "oncomplete": function(r) {
1666                     var usr = r.recv().content();
1667                     if (usr) addr = usr.billing_address() || usr.mailing_address();
1668                 }
1669             }
1670         );
1671     }
1672
1673     if (addr) {
1674         //oilsSelfckCCStreet.attr('value', addr.street1()+' '+addr.street2());
1675         oilsSelfckCCCity.attr('value', addr.city());
1676         oilsSelfckCCState.attr('value', addr.state());
1677         oilsSelfckCCZip.attr('value', addr.post_code());
1678     }
1679
1680     dojo.connect(oilsSelfckEditDetails, 'onChange',
1681         function(newVal) {
1682             dojo.forEach(
1683                 [oilsSelfckCCFName, oilsSelfckCCLName, oilsSelfckCCStreet, oilsSelfckCCCity, oilsSelfckCCState, oilsSelfckCCZip],
1684                 function(dij) { dij.attr('disabled', !newVal); }
1685             );
1686         }
1687     );
1688
1689     if(!payFinesDrawn) {
1690                 dojo.connect(oilsSelfckCCSubmit, 'onClick',
1691           function() {
1692             hideMe($('pay_fines_now'));
1693             unHideMe($('pay_fines_confirm'));
1694
1695             $('pay_fines_confirm_amount').innerHTML = 
1696                 dojo.query("span", "oils-selfck-cc-payment-summary")[0].innerHTML;
1697
1698             dojo.connect(payConfirmSubmit, 'onClick', 
1699                 function() { 
1700                     if (typeof(progressDialog) != "undefined") progressDialog.show(true);
1701                     unHideMe($('pay_fines_now'));
1702                     hideMe($('pay_fines_confirm'));
1703                     sendCCPayment(patron, xacts, onPaymentSubmit);
1704                 } 
1705             );
1706
1707             dojo.connect(payConfirmCancel, 'onClick', 
1708                 function() { 
1709                     unHideMe($('pay_fines_now'));
1710                     hideMe($('pay_fines_confirm'));
1711                 }
1712             );
1713
1714             /*
1715                         if(!confirm("Are you sure?")) return;
1716             sendCCPayment(patron, xacts, onPaymentSubmit);
1717             */
1718           }
1719         );
1720                 payFinesDrawn = true;
1721         }
1722         
1723         var selFines = $('selectedFines');
1724         removeChildren(selFines);
1725         for(var i in xacts) {
1726                 var xact = transCache[xacts[i][0]];
1727                 if(!xact) continue;
1728                 var tr = elem('tr');
1729                 var td1 = elem('td', {}, xact["record"]?xact.record.title():xact.transaction.last_billing_type());
1730                 var td2 = elem('td', {'nowrap':'nowrap', 'valign':'top'}, '$'+xact.transaction.balance_owed());
1731                 td2.style.paddingLeft = '5px';
1732                 td2.style.color = 'red';
1733                 tr.appendChild(td1);
1734                 tr.appendChild(td2);
1735                 selFines.appendChild(tr);
1736         }
1737 }
1738
1739 function sendCCPayment(patron, xacts, onPaymentSubmit) {
1740     // in this context, patron will always be G.user.  set it explicitly 
1741     // to pick up the latest last_xact_id value
1742     patron = G.user;
1743
1744     var args = {
1745         userid : patron.id(),
1746         payment_type : 'credit_card_payment',
1747         payments : xacts,
1748         cc_args : {
1749             where_process : 1,
1750             //type :  'MasterCard',//oilsSelfckCCType.attr('value'),
1751             number : oilsSelfckCCNumber.attr('value'),
1752             cvv2 : oilsSelfckCCCVV.attr('value'),
1753             expire_year : oilsSelfckCCYear.attr('value'),
1754             expire_month : oilsSelfckCCMonth.attr('value'),
1755             billing_first : oilsSelfckCCFName.attr('value'),
1756             billing_last : oilsSelfckCCLName.attr('value'),
1757             billing_address : oilsSelfckCCStreet.attr('value'),
1758             billing_city : oilsSelfckCCCity.attr('value'),
1759             billing_state : oilsSelfckCCState.attr('value'),
1760             billing_zip : oilsSelfckCCZip.attr('value')
1761         }
1762     }
1763
1764     var resp = fieldmapper.standardRequest(PAY_BILLS,{params : [patron.session, args, patron.last_xact_id()]});
1765     if (typeof(progressDialog) != "undefined")
1766         progressDialog.hide();
1767
1768     if (typeof(onPaymentSubmit) == "function") {
1769         onPaymentSubmit(resp);
1770     } else {
1771         var evt = openils.Event.parse(resp);
1772         if (evt) alert(evt);
1773     }
1774 }
1775
1776 function myopacSelectedHoldsRows() {
1777     var r = [];
1778         var cb;
1779     var rows = dojo.query('[name=acct_holds_temp]',$("holds_temp_parent"));
1780     for(var i = 0; i < rows.length; i++) {
1781         cb = $n(rows[i], 'check_all_holds');
1782         if(cb && cb.checked)
1783             r.push(rows[i]);
1784     }
1785     return r;
1786 }
1787
1788 var myopacProcessedHolds = 0;
1789 var myopacHoldsToProcess = 0;
1790 function myopacDoHoldAction() {
1791     var selectedRows = myopacSelectedHoldsRows();
1792     var action = getSelectorVal($('acct_holds_actions'));
1793     $('myopac_holds_actions_none').selected = true;
1794     if(selectedRows.length == 0) return;
1795
1796     myopacProcessedHolds = 0;
1797
1798     if(!confirmId('myopac.holds.'+action+'.confirm')) return;
1799     //myopacSelectNoneHolds(); /* clear the selection */
1800
1801
1802     /* first, let's collect the holds that actually need processing and
1803         collect the full process count while we're at it */
1804     var holds = [];
1805     for(var i = 0; i < selectedRows.length; i++) {
1806                 var ahold = $n(selectedRows[i],'check_all_holds');
1807         var hold = holdsCache[holdsCacheMap[ahold.holdid]];
1808         var qstats = hold.status;
1809         switch(action) {
1810             case 'cancel':
1811                 holds.push(hold.hold);
1812                 break;
1813             case 'thaw_date':
1814             case 'thaw':
1815                 if(isTrue(hold.hold.frozen()))
1816                     holds.push(hold.hold);
1817                 break;
1818             case 'freeze':
1819                 if(!isTrue(hold.hold.frozen()) && qstats < 3)
1820                     holds.push(hold.hold);
1821                 break;
1822         }
1823     }
1824
1825     myopacHoldsToProcess = holds;
1826     if(myopacHoldsToProcess.length == 0) return;
1827
1828     if(action == 'thaw_date' || action == 'freeze') 
1829         myopacDrawHoldThawDateForm();
1830     else
1831     myopacProcessHolds(action);
1832 }
1833
1834 function myopacDrawHoldThawDateForm() {
1835     hideMe($('myopac_holds_div'));
1836     unHideMe($('myopac_holds_thaw_date_form'));
1837     $('myopac_holds_thaw_date_input').focus();
1838 }
1839
1840 function myopacApplyThawDate() {
1841     var dateString = dijit.byId('myopac_holds_thaw_date_input').getValue();
1842     if(dateString) {
1843         dateString = dojo.date.stamp.toISOString(dateString);
1844         if(dateString) {
1845             dateString = holdsVerifyThawDate(dateString);
1846             if(!dateString) return;
1847         } else {
1848             dateString = null;
1849         }
1850     }
1851         unHideMe($('myopac_holds_div'));
1852     hideMe($('myopac_holds_thaw_date_form'));
1853     myopacProcessHolds('freeze', dateString);
1854 }
1855
1856
1857 function myopacProcessHolds(action, thawDate) {
1858         progressDialog.show(true);
1859    // myopacShowHoldProcessing();
1860     /* now we process them */
1861     for(var i = 0; i < myopacHoldsToProcess.length; i++) {
1862         var hold = myopacHoldsToProcess[i];
1863         
1864         var req;
1865         switch(action) { 
1866
1867             case 'cancel':
1868                 req = new Request(CANCEL_HOLD, G.user.session, hold.id());
1869                 break;
1870     
1871             case 'thaw':
1872                 hold.frozen('f');
1873                 hold.thaw_date(null);
1874                 req = new Request(UPDATE_HOLD, G.user.session, hold);
1875                 break;
1876
1877             case 'thaw_date':
1878             case 'freeze':
1879                 hold.frozen('t');
1880                 hold.thaw_date(thawDate); 
1881                 req = new Request(UPDATE_HOLD, G.user.session, hold);
1882                 break;
1883                 //thawDate = prompt($('myopac.holds.freeze.select_thaw').innerHTML);
1884
1885         }
1886
1887         req.callback(myopacBatchHoldCallback);
1888         req.send();
1889         req = null;
1890     }
1891 }
1892
1893 function myopacBatchHoldCallback(r) {
1894         var res = r.getResultObject();
1895         myopacHoldsToProcess = grep(myopacHoldsToProcess, function(i) { return (i.id() != res); }); 
1896         if(!myopacHoldsToProcess || ++myopacProcessedHolds >= myopacHoldsToProcess.length) {
1897           //alert(res);
1898           progressDialog.hide();
1899           myopacForceHoldsRedraw = true;
1900           $('check_all_holds').checked = false;
1901           drawHoldsPage();
1902         }
1903 }
1904
1905 function myOPACRenewSelected() {
1906    var rows = dojo.query('input[name=check_all_checked]',$('checked_temp_parent')).filter(function(n,i){ return n.checked; });
1907    __renew_circs = [];
1908         if(!rows.length || !confirm($('myopac_renew_confirm').innerHTML)) return;
1909    __success_count = 0;
1910    __fail_count = 0;
1911
1912    for( var i = 0; i < rows.length; i++ ) {
1913       var row = rows[i];
1914       var circ_id = row.getAttribute('circid');
1915
1916            var circ;
1917            for( var j = 0; j != itemsOutCache.length; j++ ) 
1918                    if(itemsOutCache[j].circ.id() == circ_id)
1919                            circ = itemsOutCache[j].circ;
1920
1921       __renew_circs.push(circ);
1922    }
1923
1924     if( __renew_circs.length == 0 ) return;
1925
1926     //unHideMe($('my_renewing'));
1927     //moClearCheckedTable();
1928
1929     for( var i = 0; i < __renew_circs.length; i++ ) {
1930         var circ = __renew_circs[i];
1931         moRenewCirc( circ.target_copy(), G.user.id(), circ );
1932     }
1933 }
1934
1935 var __renew_circs = [];
1936 var __rewnew_errors = [];
1937 var __success_count = 0;
1938 var __fail_count = 0;
1939 function moRenewCirc(copy_id, user_id, circ) {
1940
1941    _debug('renewing circ ' + circ.id() + ' with copy ' + copy_id);
1942    var req = new Request(RENEW_CIRC, G.user.session, 
1943       {  patron : user_id, 
1944          copyid : copy_id, 
1945          opac_renewal : 1
1946       } 
1947    );
1948
1949    req.request.alertEvent = false;
1950    req.callback(myHandleRenewResponse);
1951    req.request.circ = circ;
1952    req.send();
1953 }
1954
1955 /* handles the circ renew results */
1956
1957 function myHandleRenewResponse(r) {
1958    try{ var res = r.getResultObject(); } catch(e){ alert("Renew Error\n\n"+e); __renew_circs = []; __rewnew_errors = []; return; }
1959    var circ = r.circ;
1960
1961    /* remove this circ from the list of circs to renew */
1962    if(checkILSEvent(res) || checkILSEvent(res[0])) {
1963            var str1 = truncate(mvrObjCache[circ.target_copy()].title(),65)+'\n';
1964            if(res.ilsevent) str1 += res.ilsevent+': '+res.desc+'\n'; else for(var i in res) str1 += res[i].ilsevent+': '+res[i].desc+'\n';
1965            __rewnew_errors[circ.id()] = str1;
1966    }
1967    __renew_circs = grep(__renew_circs, function(i) { return (i.id() != circ.id()); });
1968    _debug("handling renew result for " + circ.id());
1969
1970    if(checkILSEvent(res) || checkILSEvent(res[0])) __fail_count++;
1971       //alertIdText('myopac_renew_fail', __circ_titles[circ.id()]);
1972    else __success_count++;
1973    
1974    if(__renew_circs) return; /* more to come */
1975    __renew_circs = [];
1976    
1977    var str = "";
1978    if(__success_count) str+= __success_count+" items renewed successfully";
1979    if(__fail_count) str+=__fail_count+" items did not renew.";
1980    str+='\n\n';
1981    for(var i in __rewnew_errors) str+=__rewnew_errors[i]+'\n';
1982    
1983    if(__success_count || __fail_count) alert(str);
1984    __rewnew_errors = [];
1985
1986         //if( __success_count > 0 )
1987     //  alertIdText('myopac_renew_success', __success_count);
1988
1989    hideMe($('my_renewing'));
1990    checkedDrawn = false;
1991     drawCheckedPage();
1992         $('check_all_checked').checked = false;
1993 }
1994
1995
1996 function moveToNewList(parent, dest) {
1997         if(!parent || !dest) return;
1998         
1999         var items = dojo.query('input[name=list_action_chbx]', parent);
2000         if(!items.length) items = dojo.query('.list_action_chbx', parent);
2001         items.filter(function(item, index, arr){return item.checked;},this);
2002         
2003         if(items.length) {
2004                 for(var i=0; i<items.length; i++) {
2005                         var box = items[i];
2006                         if(box.checked) {
2007                                 containerCreateItem(dest, box.getAttribute("recordid"));
2008                         }
2009                 }
2010         }
2011 }
2012
2013 function listSaveAction() {
2014         var lists = dojo.query('select[name=list_actions]',$('temp_wrapper')).filter(function(n,i){
2015                 return n.options[n.selectedIndex].value!="0"
2016         });
2017         
2018         if(lists.length) { if(!confirm("Proceed with the selected action(s)?")) return; } else return;
2019         progressDialog.show(true);
2020         var updateHolds = false;
2021         var updateLists = false;
2022         
2023         lists.forEach(function(n,i){
2024                 var val = n.options[n.selectedIndex].value;
2025                 if(val=="0") return;
2026                 var p = n.parentNode.parentNode.parentNode.parentNode.parentNode;
2027                 switch(val) {
2028                         case "hold": batchHoldMyList(null, p); updateHolds = true; break;
2029                         case "move": if(n.id=='sel_all_list_anon') delSelCache(p, 'list_action_chbx');
2030                                                                 else removeSelectedItems(p); moveToNewList(p, n.options[n.selectedIndex].getAttribute("container"));
2031                                                   updateLists = true; break;
2032                         case "remove": if(n.id=='sel_all_list_anon') delSelCache(p, 'list_action_chbx');
2033                                                                 else removeSelectedItems(p);
2034                                                    updateLists = true; break;
2035                 }
2036                 setSelector(n, "0");
2037         });
2038         
2039         if(updateLists) reloadMyLists();
2040         if(updateHolds) { myopacForceHoldsRedraw = true; drawHoldsPage(); }
2041         progressDialog.hide();
2042 }
2043
2044 var itemsOutHistoryInitialFetch = false;
2045 function switchSubPage(page, subpage) {
2046         if(!page || !subpage) return;
2047         
2048         var pg = subPageObjs[page]; if(!pg) return;
2049         var spg = subPageObjs[page][subpage]; if(!spg) return;
2050         
2051         for(var i in pg) { if(i!='header') for(var n in pg[i]){ if(pg[i][n] || n!='header') hideMe(pg[i][n]); } }
2052         for(var t in spg) { if(spg[t] || t!='header') unHideMe(spg[t]); }
2053         
2054         pg.header.innerHTML = spg.header;
2055
2056     if(page == 'checked' && subpage == 'hist') {
2057         if(!itemsOutHistoryInitialFetch) {
2058             itemsOutHistoryInitialFetch = true;
2059             progressDialog.show(true);
2060                 fieldmapper.standardRequest(FETCH_CHECKED_HISTORY, {async:true, params:[G.user.session, G.user.id(), {'limit':CIRC_HIST_PAGE_LIMIT, 'offset':0}],
2061                         oncomplete:function(r) {
2062                     progressDialog.hide();
2063                     itemsOutHistory = openils.Util.readResponse(r);
2064                     drawCircHistory();
2065                         }
2066                 });
2067         }
2068     }
2069 }
2070
2071 function doBatchAnonHolds() {
2072         var error = {err:""};
2073         var resp = placeBatchHold(holdsList, G.user.home_ou(), error);
2074         if(resp == -1) alert("Unable to place holds"); else {
2075           alert(resp+" hold"+(resp==1?"":"s")+" placed successfully\n\n"+error.err);
2076         }
2077         
2078         holdsList = null;
2079         myopacForceHoldsRedraw = true;
2080         drawHoldsPage();
2081 }
2082
2083 function myOPACUpdateHomeOU() {
2084         var sel = $('myopac_new_home');
2085         
2086 }
2087
2088 var sortOrder = true;
2089 function sortHolds(by) {
2090         if(!by) return;
2091         
2092         sortOrder = !sortOrder;
2093         switch(by.toLowerCase()) {
2094                 case "format":
2095                 holdsCache = holdsCache.sort(function(a, b) {
2096                         if(sortOrder) return get998dValue(imgFormatCache[a.hold.id()])<get998dValue(imgFormatCache[b.hold.id()])?-1:1;
2097                                 else      return get998dValue(imgFormatCache[a.hold.id()])>get998dValue(imgFormatCache[b.hold.id()])?-1:1;
2098                 });
2099                 break;
2100                 case "title":
2101                 holdsCache = holdsCache.sort(function(a, b) {
2102                         if(sortOrder) return a.mvr.title()<b.mvr.title()?-1:1;
2103                                 else      return a.mvr.title()>b.mvr.title()?-1:1;
2104                 });
2105                 break;
2106                 case "pickup":
2107                 holdsCache = holdsCache.sort(function(a, b) {
2108                         if(sortOrder) return findOrgUnit(a.hold.pickup_lib()).name()<findOrgUnit(b.hold.pickup_lib()).name()?-1:1;
2109                                 else      return findOrgUnit(a.hold.pickup_lib()).name()>findOrgUnit(b.hold.pickup_lib()).name()?-1:1;
2110                 });
2111                 break;
2112                 case "author":
2113                 holdsCache = holdsCache.sort(function(a, b) {
2114                         if(sortOrder) return a.mvr.author()<b.mvr.author()?-1:1;
2115                                 else      return a.mvr.author()>b.mvr.author()?-1:1;
2116                 });
2117                 break;
2118                 case "status":
2119                 if(sortOrder) {
2120                         drawHoldsPage(true);
2121                         return;
2122                 } else {
2123                         holdsCache = holdsCache.sort(function(a, b) {
2124                                 if(a.status==4) return 1;
2125                                 if(b.status==4) return -1;
2126                                 if(isTrue(a.hold.frozen())) return -1;
2127                                 if(isTrue(b.hold.frozen())) return 1;
2128                                 return dojo.date.stamp.fromISOString(a.hold.request_time()) > dojo.date.stamp.fromISOString(b.hold.request_time())?-1:1;
2129                         });                     
2130                 }
2131         }
2132         
2133         drawHoldsPage(false);
2134 }
2135
2136 function sortChecked(by) {
2137         if(!by) return;
2138         
2139         sortOrder = !sortOrder;
2140         switch(by.toLowerCase()) {
2141                 case "title":
2142                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2143                         if(sortOrder) return a.record.title()<b.record.title()?-1:1;
2144                                 else      return a.record.title()>b.record.title()?-1:1;
2145                 });
2146                 break;
2147                 case "author":
2148                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2149                         if(sortOrder) return a.record.author()<b.record.author()?-1:1;
2150                                 else      return a.record.author()>b.record.author()?-1:1;
2151                 });
2152                 break;
2153                 case "due":
2154                 if(sortOrder) {
2155                         drawCheckedPage(); return;
2156                 } else itemsOutCache = itemsOutCache.sort(function(a, b) {
2157                         return dojo.date.stamp.fromISOString(a.circ.due_date()) < dojo.date.stamp.fromISOString(b.circ.due_date())?1:-1;
2158                 });
2159                 break;
2160                 case "barcode":
2161                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2162                         if(sortOrder) return Number(a.copy.barcode())<Number(b.copy.barcode())?-1:1;
2163                                 else      return Number(a.copy.barcode())>Number(b.copy.barcode())?-1:1;
2164                 });
2165                 break;
2166                 case "cn":
2167                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2168                         if(sortOrder) return callNumCache[a.copy.call_number()]<callNumCache[b.copy.call_number()] ?-1:1;
2169                                 else      return callNumCache[a.copy.call_number()]>callNumCache[b.copy.call_number()]?-1:1;
2170                 });
2171                 break;
2172                 case "renews":
2173                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2174                         if(sortOrder) return Number(a.circ.renewal_remaining())<Number(b.circ.renewal_remaining())?-1:1;
2175                                 else      return Number(a.circ.renewal_remaining())>Number(b.circ.renewal_remaining())?-1:1;
2176                 });
2177                 break;
2178         }
2179         
2180         drawCheckedPage(false);
2181 }
2182
2183 function sortCheckedHist(by) {
2184         if(!by) return;
2185         
2186         sortOrder = !sortOrder;
2187         switch(by.toLowerCase()) {
2188                 case "title":
2189                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2190                         if(mvrObjCache[a.circ.target_copy()].title()==null) return 1;
2191                         if(mvrObjCache[b.circ.target_copy()].title()==null) return -1;
2192                         if(sortOrder) return mvrObjCache[a.circ.target_copy()].title().toLowerCase()<mvrObjCache[b.circ.target_copy()].title().toLowerCase()?-1:1;
2193                                 else      return mvrObjCache[a.circ.target_copy()].title().toLowerCase()>mvrObjCache[b.circ.target_copy()].title().toLowerCase()?-1:1;
2194                 });
2195                 break;
2196                 case "author":
2197                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2198                         if(mvrObjCache[a.circ.target_copy()].author()==null) return 1;
2199                         if(mvrObjCache[b.circ.target_copy()].author()==null) return -1;
2200                         if(sortOrder) return mvrObjCache[a.circ.target_copy()].author().toLowerCase()<mvrObjCache[b.circ.target_copy()].author().toLowerCase()?1:-1;
2201                                 else      return mvrObjCache[a.circ.target_copy()].author().toLowerCase()>mvrObjCache[b.circ.target_copy()].author().toLowerCase()?1:-1;
2202                 });
2203                 break;
2204                 case "duedate":
2205                 if(sortOrder) {
2206                         drawCheckedPage(); return;
2207                 } else itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2208                         return dojo.date.stamp.fromISOString(a.circ.due_date()) < dojo.date.stamp.fromISOString(b.circ.due_date())?1:-1;
2209                 });
2210                 break;
2211                 case "cn":
2212                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2213                                                                                 // ya, i know, but it gets the job done.
2214                         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;
2215                                 else      return callNumCache[copyObjCache[a.circ.target_copy()].call_number()].label().toLowerCase()>callNumCache[copyObjCache[b.circ.target_copy()].call_number()].label().toLowerCase()?-1:1;
2216                 });
2217                 break;
2218         }
2219         
2220         drawCircHistory(false);
2221 }
2222
2223 function printData(data, numItems, callback) {
2224
2225     //unHideMe($('receipt-print-frame-wrapper'));
2226     receiptPrintDialog.show();
2227
2228     var frame = window["receipt-frame"];
2229     frame.document.body.innerHTML = data;
2230
2231     var cancel = $('receipt-view-print-cancel');
2232     cancel.onclick = function() {
2233         frame.document.body.innerHTML = '';
2234        // hideMe($('receipt-print-frame-wrapper'));
2235         receiptPrintDialog.hide();
2236         if(callback) callback();
2237     }
2238
2239     $('receipt-view-print-button').onclick = function() {
2240         frame.focus();
2241         frame.print();
2242         var sleepTime = 1000;
2243         if(numItems > 0) 
2244             sleepTime += (numItems / 2) * 1000;
2245
2246         setTimeout(
2247             function() { 
2248                 cancel.onclick();
2249                 if(callback) callback(); // fire optional post-print callback
2250             },
2251             sleepTime 
2252         );
2253     };
2254 }
2255
2256
2257 function printPaymentReceipt(paymentIds, callback, noprog) {
2258     if(!noprog) progressDialog.show(true);
2259
2260     fieldmapper.standardRequest(
2261         ['open-ils.circ', 'open-ils.circ.money.payment_receipt.print'],
2262         {
2263             async : true,
2264             params : [G.user.session, paymentIds],
2265             oncomplete : function(r) {
2266                 var resp = openils.Util.readResponse(r);
2267                 var output = "";
2268                                 if(resp) output = resp.template_output();
2269                 if(!noprog) progressDialog.hide();
2270                 if(output) {
2271                     printData(output.data(), 1, callback); 
2272                 } else {
2273                     var error = resp.error_output();
2274                     if(error) {
2275                         throw new Error("Error creating receipt: " + error.data());
2276                     } else {
2277                         throw new Error("No receipt data returned from server");
2278                     }
2279                 }
2280             }
2281         }
2282     );
2283 }
2284
2285 function printFinesReceipt(callback) {
2286     progressDialog.show(true);
2287
2288     var params = [
2289         G.user.session, 
2290         G.user.ws_ou(),
2291         null,
2292         'format.selfcheck.fines',
2293         'print-on-demand',
2294         [G.user.id()]
2295     ];
2296
2297     fieldmapper.standardRequest(
2298         ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
2299         {   
2300             async : true,
2301             params : params,
2302             oncomplete : function(r) {
2303                 progressDialog.hide();
2304                 var resp = openils.Util.readResponse(r);
2305                 var output = resp.template_output();
2306                 if(output) {
2307                     printData(output.data(), 240, callback); 
2308                 } else {
2309                     var error = resp.error_output();
2310                     if(error) {
2311                         throw new Error("Error creating receipt: " + error.data());
2312                     } else {
2313                         throw new Error("No receipt data returned from server");
2314                     }
2315                 }
2316             }
2317         }
2318     );
2319 }
2320
2321 function buildOrgSelAlt(selector, org, offset, namecol) {
2322  if(!namecol) namecol = 'name';
2323  if(!showXUL && !isTrue(org.opac_visible())) return; // for some reason, isXUL() is rather slow when used in a decently sized loop.
2324  insertSelectorVal( selector, -1,
2325  org[namecol](), org.id(), null, findOrgDepth(org) - offset );
2326  var kids = org.children();
2327  if (kids) {
2328  for( var c = 0; c < kids.length; c++ )
2329  buildOrgSelAlt( selector, kids[c], offset, namecol);
2330  }
2331 }
2332
2333 // alternative to checkAll that does not select <= balances... needs testing
2334 function checkAllXact(parent, id, name, balanceName) {//Object, string
2335         var obj = typeof(id)=="object"?id:$(id);
2336         if(!parent || !obj) return;
2337         if(!name) name = id.toString();
2338     dojo.forEach(parent.childNodes, 
2339         function(row) {
2340             if(row.nodeName.match(/tr/i)) {
2341                 var input = dojo.query('input[name='+name+']', row)[0];
2342                 var balance = dojo.query('input[name='+balanceName+']', row)[0];
2343                 if(Number(balance) > 0)
2344                             balance.checked = obj.checked;
2345             }
2346         }
2347     );
2348     /*
2349         var nodes = dojo.query('input[name='+name+']', parent);
2350         if(!nodes.length) nodes = dojo.query('.'+name, parent);
2351         nodes.forEach(function(node, index){
2352                 node.checked = obj.checked;
2353         });
2354     */
2355 }