d753877b861c391eafadd9e16bc18a8447d930d3
[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                 transCache[trans.id()] = transactions[idx];
1399
1400                 if(trans.xact_type() == 'circulation') myOPACShowCircTransaction(trans, record, circ);
1401                 else if(trans.xact_type() == 'grocery') myopacShowGenericTransaction( trans );
1402         }
1403 }
1404
1405 // for toggling between payments and fines tabs
1406 function showFinesTab() {
1407     hideMe($("myopac_payments_div"));
1408     unHideMe($("pay-fines-image"));
1409     unHideMe($("myopac_trans_div"));
1410     unHideMe($("myopac_circ_trans_div"));
1411     $('acct_fines_tab').style.background="url('/opac/skin/kcls/graphics/acct_fines_on.jpg') no-repeat bottom";
1412     $('acct_payments_tab').style.background="url('/opac/skin/kcls/graphics/acct_payments_off.jpg') no-repeat bottom";
1413 }
1414
1415 var paymentsDrawn = false;
1416 function myopacDrawPayments() {
1417
1418     unHideMe($("myopac_payments_div")); 
1419     hideMe($("myopac_circ_trans_div"));
1420     hideMe($("myopac_trans_div"));
1421     hideMe($("pay-fines-image"));
1422     $('acct_fines_tab').style.background="url('/opac/skin/kcls/graphics/acct_fines_off.jpg') no-repeat bottom";
1423     $('acct_payments_tab').style.background="url('/opac/skin/kcls/graphics/acct_payments_on.jpg') no-repeat bottom";
1424
1425     if(paymentsDrawn) return;
1426     paymentsDrawn = true;
1427     progressDialog.show(true);
1428
1429     var before = new Date()
1430     before.setFullYear(before.getFullYear() - 1);
1431     // KCLS limits payment history view to 1 year.  This will eventually be expanded 
1432     // out to a history view page, but for now, just fetch what's needed.
1433     var req = new Request(
1434         'open-ils.actor:open-ils.actor.user.payments.retrieve', 
1435         G.user.session, G.user.id(), 
1436         {"where":{"payment_ts":{">=":dojo.date.stamp.toISOString(before)}}});
1437
1438     req.callback(_myopacDrawPayments);
1439     req.send();
1440 }
1441
1442 function _myopacDrawPayments(r) {
1443
1444     progressDialog.hide();
1445     var payments = r.getResultObject();
1446     var tbody = $('myopac_payments_tbody');
1447     rowTmpl = tbody.removeChild($('myopac_payments_tmpl'));
1448
1449     dojo.forEach(payments,
1450         function(payment) {
1451             var row = rowTmpl.cloneNode(true);
1452             $n(row, 'date').innerHTML =  dojo.date.locale.format( 
1453                 dojo.date.stamp.fromISOString(payment.mp.payment_ts()),
1454                 {selector:'date', fullYear: true}
1455             );
1456             $n(row, 'for').innerHTML = (payment.title) ? payment.title : payment.last_billing_type;
1457             $n(row, 'amount').innerHTML += Number(payment.mp.amount()).toFixed(2);
1458             if(payment.mp.payment_type() == 'credit_card_payment') {
1459                 $n(row, 'print_recpt').onclick = function () { printPaymentReceipt([payment.mp.id()]) };
1460                 $n(row, 'email_recpt').onclick = function () { emailPaymentReceipt([payment.mp.id()]) };
1461             } else {
1462                 $n(row, 'print_recpt').parentNode.style.visibility = 'hidden';
1463             }
1464             tbody.appendChild(row);
1465         }
1466     );
1467 }
1468
1469 function emailPaymentReceipt(paymentIds, callback, noprog) {
1470
1471     if(!G.user.email()) {
1472         if(callback) callback();
1473         return;
1474     }
1475
1476     if(!noprog) progressDialog.show(true);
1477
1478     fieldmapper.standardRequest(
1479         ['open-ils.circ', 'open-ils.circ.money.payment_receipt.email'],
1480         {
1481             async : true,
1482             params : [G.user.session, paymentIds],
1483             oncomplete : function(r) {
1484                 if(!noprog) progressDialog.hide();
1485                 openils.Util.readResponse(r);
1486                 if(callback) callback();
1487             }
1488         }
1489     );
1490 }
1491
1492 function dateFromISO(d) {
1493     if(!d) return '';
1494     return dojo.date.locale.format( 
1495         dojo.date.stamp.fromISOString(d),
1496         {selector:'date', fullYear: true}
1497     );
1498 }
1499
1500 function myopacShowGenericTransaction( trans ) {
1501         var tbody = $('myopac_trans_tbody');
1502
1503         var row = myopacGenericTransTemplate.cloneNode(true);
1504         $n(row,'myopac_trans_start').appendChild(text(dateFromISO(trans.xact_start())));
1505         $n(row,'myopac_trans_last_payment').appendChild(text(dateFromISO(trans.last_payment_ts())));
1506         $n(row,'myopac_trans_init_amount').appendChild(text(_finesFormatNumber(trans.total_owed())));
1507         $n(row,'myopac_trans_total_paid').appendChild(text(_finesFormatNumber(trans.total_paid())));
1508         $n(row,'myopac_trans_balance').appendChild(text(_finesFormatNumber(trans.balance_owed())));
1509         $n(row,'selector').balance_owed = trans.balance_owed();
1510         $n(row,'selector').setAttribute("xact", trans.id());
1511     if(trans.balance_owed() <= 0) {
1512         $n(row,'selector').disabled = true;
1513     }
1514
1515
1516         var req = new Request(FETCH_MONEY_BILLING, G.user.session, trans.id());
1517         req.send(true);
1518         var bills = req.result();
1519         if(bills && bills[0]) $n(row,'myopac_trans_bill_type').appendChild(text(bills[0].billing_type()));
1520
1521         tbody.appendChild(row);
1522         unHideMe($('myopac_trans_div'));
1523 }
1524
1525 function myOPACShowCircTransaction(trans, record, circ) {
1526         var tbody = $('myopac_circ_trans_tbody');
1527
1528         var row = myopacCircTransTemplate.cloneNode(true);
1529         if(record) {
1530                 buildTitleDetailLink(record, $n(row,'myopac_circ_trans_title'));
1531                 $n(row,'myopac_circ_trans_author').appendChild(text(normalize(truncate(record.author(), 65))));
1532         } else {
1533                 var req = new Request( FETCH_COPY, circ.target_copy() );
1534                 req.alertEvents = false;
1535                 req.send(true);
1536                 var copy = req.result();
1537                 if( copy ) {
1538                         $n(row,'myopac_circ_trans_title').appendChild(text(copy.dummy_title()));
1539                         $n(row,'myopac_circ_trans_author').appendChild(text(copy.dummy_author()));
1540                 }
1541         }
1542         
1543         $n(row,'myopac_circ_trans_start').appendChild(text(dateFromISO(trans.xact_start())));
1544
1545     var due = dateFromISO(circ.due_date());
1546         var checkin = dateFromISO(circ.stop_fines_time());
1547
1548         $n(row,'myopac_circ_trans_due').appendChild(text(due))
1549         if(checkin) appendClear($n(row,'myopac_circ_trans_finished'), text(checkin));
1550         if(circ.stop_fines() == 'LOST') appendClear($n(row,'myopac_circ_trans_finished'), text(circ.stop_fines()));
1551         if(circ.stop_fines() == 'CLAIMSRETURNED') appendClear($n(row,'myopac_circ_trans_finished'), text(""));
1552         $n(row,'myopac_circ_trans_balance').appendChild(text(_finesFormatNumber(trans.balance_owed())));
1553         $n(row,'selector').balance_owed = trans.balance_owed();
1554         $n(row,'selector').setAttribute("xact",trans.id()); 
1555     if(trans.balance_owed() <= 0) {
1556         $n(row,'selector').disabled = true;
1557     }
1558
1559         tbody.appendChild(row);
1560         unHideMe($('myopac_circ_trans_div'));
1561 }
1562
1563 function showFinesDiv(el) {
1564         if(!el) return;
1565         if($('myopac_fines_div').className.indexOf('hide_me')>=0) { 
1566                 unHideMe($('myopac_fines_div'));
1567                 el.innerHTML="Hide Overdue Materials";
1568         if(!finesShown) {
1569             myOPACShowFines(true);
1570         }
1571         } else {
1572                 hideMe($('myopac_fines_div'));
1573                 el.innerHTML="Show Overdue Materials";
1574         }
1575 }
1576
1577 var ecom_event_map = {
1578     CREDIT_PROCESSOR_DECLINED_TRANSACTION : 
1579         'Sorry. Your payment has been declined. Please confirm your information is entered correctly or contact your credit card company.',
1580     CREDIT_PROCESSOR_INVALID_CC_NUMBER : 
1581         'The credit card number entered is not valid.  Please confirm your information is entered correctly or contact your credit card company.',
1582     SUCCESS : 'Your payment has been approved' 
1583 }
1584
1585 function showPaymentForm() {
1586         unHideMe($('pay_fines_now'));
1587         hideMe($('acct_sum'));
1588     hideMe($('cc-payment-error-message'));
1589         drawPayFinesPage(
1590                 G.user,
1591                 getSelectedFinesTotal(),
1592                 getSelectedFineTransactions(),
1593                 function(resp) {
1594             unHideMe($('cc-payment-error-message'));
1595                         if(resp.textcode) {
1596                 var message = ecom_event_map[resp.textcode] || resp.textcode+'\n'+resp.desc + '';
1597                 $('cc-payment-error-message').innerHTML = message;
1598                                 return;
1599                         }
1600                         G.user.last_xact_id(resp.last_xact_id); // update to match latest from server
1601             $('cc-payment-error-message').innerHTML = ecom_event_map.SUCCESS;
1602                         printPaymentReceipt(resp.payments, function() { location.href = location.href; } );
1603             emailPaymentReceipt(resp.payments, null, false);
1604             /*
1605                         hideMe($('pay_fines_now'));unHideMe($('acct_sum'));
1606                         finesShown = false;
1607                         myOPACShowFines();              
1608                         showFinesDiv($('show_fines_link'));
1609             */
1610                 }
1611         );
1612 }
1613
1614 function getSelectedFinesTotal() {
1615     var total = 0;
1616     dojo.forEach(
1617         dojo.query("[name=selector]", $('myopac_circ_trans_tbody')),
1618         function(input) { if(input.checked && input.getAttribute("xact")) total += Number(input.balance_owed); }
1619     );
1620         
1621     dojo.forEach(
1622         dojo.query("[name=selector]", $('myopac_trans_tbody')),
1623         function(input) { if(input.checked && input.getAttribute("xact")) total += Number(input.balance_owed); }
1624     );
1625     return total.toFixed(2);
1626 }
1627
1628 function getSelectedFineTransactions() {
1629         var set1 = dojo.query("[name=selector]", $('myopac_circ_trans_tbody')).
1630         filter(function (o) { return o.checked }).
1631                 map(function (o) {return [o.getAttribute("xact"), Number(o.balance_owed).toFixed(2)];}
1632         );
1633         var set2 = dojo.query("[name=selector]", $('myopac_trans_tbody')).
1634         filter(function (o) { return o.checked }).
1635                 map(function (o) {return [o.getAttribute("xact"), Number(o.balance_owed).toFixed(2)];}
1636         );
1637         var obj = set1.concat(set2);
1638         return obj.filter(function(el){return el[0]==null?false:true;});
1639 }
1640
1641 var payFinesDrawn = false;
1642 function drawPayFinesPage(patron, total, xacts, onPaymentSubmit) {
1643     if (typeof(this.authtoken) == "undefined")
1644         this.authtoken = patron.session;
1645
1646     dojo.query("span", "oils-selfck-cc-payment-summary")[0].innerHTML = total;
1647
1648     $('myopac-cc-email').innerHTML = patron.email();
1649     oilsSelfckCCNumber.attr('value', '');
1650     oilsSelfckCCCVV.attr('value', '');
1651     oilsSelfckCCMonth.attr('value', '01');
1652     oilsSelfckCCYear.attr('value', new Date().getFullYear());
1653     oilsSelfckCCFName.attr('value', patron.first_given_name());
1654     oilsSelfckCCLName.attr('value', patron.family_name());
1655
1656     var addr = patron.billing_address() || patron.mailing_address();
1657
1658     if (typeof(addr) != "object") {
1659         /* still don't have usable address? try getting better user object. */
1660         fieldmapper.standardRequest(
1661             FETCH_FULL_USER, {
1662                 "params": [patron.session, patron.id(), ["billing_address", "mailing_address"]],
1663                 "async": false,
1664                 "oncomplete": function(r) {
1665                     var usr = r.recv().content();
1666                     if (usr) addr = usr.billing_address() || usr.mailing_address();
1667                 }
1668             }
1669         );
1670     }
1671
1672     if (addr) {
1673         //oilsSelfckCCStreet.attr('value', addr.street1()+' '+addr.street2());
1674         oilsSelfckCCCity.attr('value', addr.city());
1675         oilsSelfckCCState.attr('value', addr.state());
1676         oilsSelfckCCZip.attr('value', addr.post_code());
1677     }
1678
1679     dojo.connect(oilsSelfckEditDetails, 'onChange',
1680         function(newVal) {
1681             dojo.forEach(
1682                 [oilsSelfckCCFName, oilsSelfckCCLName, oilsSelfckCCStreet, oilsSelfckCCCity, oilsSelfckCCState, oilsSelfckCCZip],
1683                 function(dij) { dij.attr('disabled', !newVal); }
1684             );
1685         }
1686     );
1687
1688     if(!payFinesDrawn) {
1689                 dojo.connect(oilsSelfckCCSubmit, 'onClick',
1690           function() {
1691             hideMe($('pay_fines_now'));
1692             unHideMe($('pay_fines_confirm'));
1693
1694             $('pay_fines_confirm_amount').innerHTML = 
1695                 dojo.query("span", "oils-selfck-cc-payment-summary")[0].innerHTML;
1696
1697             dojo.connect(payConfirmSubmit, 'onClick', 
1698                 function() { 
1699                     if (typeof(progressDialog) != "undefined") progressDialog.show(true);
1700                     unHideMe($('pay_fines_now'));
1701                     hideMe($('pay_fines_confirm'));
1702                     sendCCPayment(patron, xacts, onPaymentSubmit);
1703                 } 
1704             );
1705
1706             dojo.connect(payConfirmCancel, 'onClick', 
1707                 function() { 
1708                     unHideMe($('pay_fines_now'));
1709                     hideMe($('pay_fines_confirm'));
1710                 }
1711             );
1712
1713             /*
1714                         if(!confirm("Are you sure?")) return;
1715             sendCCPayment(patron, xacts, onPaymentSubmit);
1716             */
1717           }
1718         );
1719                 payFinesDrawn = true;
1720         }
1721         
1722         var selFines = $('selectedFines');
1723         removeChildren(selFines);
1724         for(var i in xacts) {
1725                 var xact = transCache[xacts[i][0]];
1726                 if(!xact) continue;
1727                 var tr = elem('tr');
1728                 var td1 = elem('td', {}, xact["record"]?xact.record.title():xact.transaction.last_billing_type());
1729                 var td2 = elem('td', {'nowrap':'nowrap', 'valign':'top'}, '$'+xact.transaction.balance_owed());
1730                 td2.style.paddingLeft = '5px';
1731                 td2.style.color = 'red';
1732                 tr.appendChild(td1);
1733                 tr.appendChild(td2);
1734                 selFines.appendChild(tr);
1735         }
1736 }
1737
1738 function sendCCPayment(patron, xacts, onPaymentSubmit) {
1739     // in this context, patron will always be G.user.  set it explicitly 
1740     // to pick up the latest last_xact_id value
1741     patron = G.user;
1742
1743     var args = {
1744         userid : patron.id(),
1745         payment_type : 'credit_card_payment',
1746         payments : xacts,
1747         cc_args : {
1748             where_process : 1,
1749             //type :  'MasterCard',//oilsSelfckCCType.attr('value'),
1750             number : oilsSelfckCCNumber.attr('value'),
1751             cvv2 : oilsSelfckCCCVV.attr('value'),
1752             expire_year : oilsSelfckCCYear.attr('value'),
1753             expire_month : oilsSelfckCCMonth.attr('value'),
1754             billing_first : oilsSelfckCCFName.attr('value'),
1755             billing_last : oilsSelfckCCLName.attr('value'),
1756             billing_address : oilsSelfckCCStreet.attr('value'),
1757             billing_city : oilsSelfckCCCity.attr('value'),
1758             billing_state : oilsSelfckCCState.attr('value'),
1759             billing_zip : oilsSelfckCCZip.attr('value')
1760         }
1761     }
1762
1763     var resp = fieldmapper.standardRequest(PAY_BILLS,{params : [patron.session, args, patron.last_xact_id()]});
1764     if (typeof(progressDialog) != "undefined")
1765         progressDialog.hide();
1766
1767     if (typeof(onPaymentSubmit) == "function") {
1768         onPaymentSubmit(resp);
1769     } else {
1770         var evt = openils.Event.parse(resp);
1771         if (evt) alert(evt);
1772     }
1773 }
1774
1775 function myopacSelectedHoldsRows() {
1776     var r = [];
1777         var cb;
1778     var rows = dojo.query('[name=acct_holds_temp]',$("holds_temp_parent"));
1779     for(var i = 0; i < rows.length; i++) {
1780         cb = $n(rows[i], 'check_all_holds');
1781         if(cb && cb.checked)
1782             r.push(rows[i]);
1783     }
1784     return r;
1785 }
1786
1787 var myopacProcessedHolds = 0;
1788 var myopacHoldsToProcess = 0;
1789 function myopacDoHoldAction() {
1790     var selectedRows = myopacSelectedHoldsRows();
1791     var action = getSelectorVal($('acct_holds_actions'));
1792     $('myopac_holds_actions_none').selected = true;
1793     if(selectedRows.length == 0) return;
1794
1795     myopacProcessedHolds = 0;
1796
1797     if(!confirmId('myopac.holds.'+action+'.confirm')) return;
1798     //myopacSelectNoneHolds(); /* clear the selection */
1799
1800
1801     /* first, let's collect the holds that actually need processing and
1802         collect the full process count while we're at it */
1803     var holds = [];
1804     for(var i = 0; i < selectedRows.length; i++) {
1805                 var ahold = $n(selectedRows[i],'check_all_holds');
1806         var hold = holdsCache[holdsCacheMap[ahold.holdid]];
1807         var qstats = hold.status;
1808         switch(action) {
1809             case 'cancel':
1810                 holds.push(hold.hold);
1811                 break;
1812             case 'thaw_date':
1813             case 'thaw':
1814                 if(isTrue(hold.hold.frozen()))
1815                     holds.push(hold.hold);
1816                 break;
1817             case 'freeze':
1818                 if(!isTrue(hold.hold.frozen()) && qstats < 3)
1819                     holds.push(hold.hold);
1820                 break;
1821         }
1822     }
1823
1824     myopacHoldsToProcess = holds;
1825     if(myopacHoldsToProcess.length == 0) return;
1826
1827     if(action == 'thaw_date' || action == 'freeze') 
1828         myopacDrawHoldThawDateForm();
1829     else
1830     myopacProcessHolds(action);
1831 }
1832
1833 function myopacDrawHoldThawDateForm() {
1834     hideMe($('myopac_holds_div'));
1835     unHideMe($('myopac_holds_thaw_date_form'));
1836     $('myopac_holds_thaw_date_input').focus();
1837 }
1838
1839 function myopacApplyThawDate() {
1840     var dateString = dijit.byId('myopac_holds_thaw_date_input').getValue();
1841     if(dateString) {
1842         dateString = dojo.date.stamp.toISOString(dateString);
1843         if(dateString) {
1844             dateString = holdsVerifyThawDate(dateString);
1845             if(!dateString) return;
1846         } else {
1847             dateString = null;
1848         }
1849     }
1850         unHideMe($('myopac_holds_div'));
1851     hideMe($('myopac_holds_thaw_date_form'));
1852     myopacProcessHolds('freeze', dateString);
1853 }
1854
1855
1856 function myopacProcessHolds(action, thawDate) {
1857         progressDialog.show(true);
1858    // myopacShowHoldProcessing();
1859     /* now we process them */
1860     for(var i = 0; i < myopacHoldsToProcess.length; i++) {
1861         var hold = myopacHoldsToProcess[i];
1862         
1863         var req;
1864         switch(action) { 
1865
1866             case 'cancel':
1867                 req = new Request(CANCEL_HOLD, G.user.session, hold.id());
1868                 break;
1869     
1870             case 'thaw':
1871                 hold.frozen('f');
1872                 hold.thaw_date(null);
1873                 req = new Request(UPDATE_HOLD, G.user.session, hold);
1874                 break;
1875
1876             case 'thaw_date':
1877             case 'freeze':
1878                 hold.frozen('t');
1879                 hold.thaw_date(thawDate); 
1880                 req = new Request(UPDATE_HOLD, G.user.session, hold);
1881                 break;
1882                 //thawDate = prompt($('myopac.holds.freeze.select_thaw').innerHTML);
1883
1884         }
1885
1886         req.callback(myopacBatchHoldCallback);
1887         req.send();
1888         req = null;
1889     }
1890 }
1891
1892 function myopacBatchHoldCallback(r) {
1893         var res = r.getResultObject();
1894         myopacHoldsToProcess = grep(myopacHoldsToProcess, function(i) { return (i.id() != res); }); 
1895         if(!myopacHoldsToProcess || ++myopacProcessedHolds >= myopacHoldsToProcess.length) {
1896           //alert(res);
1897           progressDialog.hide();
1898           myopacForceHoldsRedraw = true;
1899           $('check_all_holds').checked = false;
1900           drawHoldsPage();
1901         }
1902 }
1903
1904 function myOPACRenewSelected() {
1905    var rows = dojo.query('input[name=check_all_checked]',$('checked_temp_parent')).filter(function(n,i){ return n.checked; });
1906    __renew_circs = [];
1907         if(!rows.length || !confirm($('myopac_renew_confirm').innerHTML)) return;
1908    __success_count = 0;
1909    __fail_count = 0;
1910
1911    for( var i = 0; i < rows.length; i++ ) {
1912       var row = rows[i];
1913       var circ_id = row.getAttribute('circid');
1914
1915            var circ;
1916            for( var j = 0; j != itemsOutCache.length; j++ ) 
1917                    if(itemsOutCache[j].circ.id() == circ_id)
1918                            circ = itemsOutCache[j].circ;
1919
1920       __renew_circs.push(circ);
1921    }
1922
1923     if( __renew_circs.length == 0 ) return;
1924
1925     //unHideMe($('my_renewing'));
1926     //moClearCheckedTable();
1927
1928     for( var i = 0; i < __renew_circs.length; i++ ) {
1929         var circ = __renew_circs[i];
1930         moRenewCirc( circ.target_copy(), G.user.id(), circ );
1931     }
1932 }
1933
1934 var __renew_circs = [];
1935 var __rewnew_errors = [];
1936 var __success_count = 0;
1937 var __fail_count = 0;
1938 function moRenewCirc(copy_id, user_id, circ) {
1939
1940    _debug('renewing circ ' + circ.id() + ' with copy ' + copy_id);
1941    var req = new Request(RENEW_CIRC, G.user.session, 
1942       {  patron : user_id, 
1943          copyid : copy_id, 
1944          opac_renewal : 1
1945       } 
1946    );
1947
1948    req.request.alertEvent = false;
1949    req.callback(myHandleRenewResponse);
1950    req.request.circ = circ;
1951    req.send();
1952 }
1953
1954 /* handles the circ renew results */
1955
1956 function myHandleRenewResponse(r) {
1957    try{ var res = r.getResultObject(); } catch(e){ alert("Renew Error\n\n"+e); __renew_circs = []; __rewnew_errors = []; return; }
1958    var circ = r.circ;
1959
1960    /* remove this circ from the list of circs to renew */
1961    if(checkILSEvent(res) || checkILSEvent(res[0])) {
1962            var str1 = truncate(mvrObjCache[circ.target_copy()].title(),65)+'\n';
1963            if(res.ilsevent) str1 += res.ilsevent+': '+res.desc+'\n'; else for(var i in res) str1 += res[i].ilsevent+': '+res[i].desc+'\n';
1964            __rewnew_errors[circ.id()] = str1;
1965    }
1966    __renew_circs = grep(__renew_circs, function(i) { return (i.id() != circ.id()); });
1967    _debug("handling renew result for " + circ.id());
1968
1969    if(checkILSEvent(res) || checkILSEvent(res[0])) __fail_count++;
1970       //alertIdText('myopac_renew_fail', __circ_titles[circ.id()]);
1971    else __success_count++;
1972    
1973    if(__renew_circs) return; /* more to come */
1974    __renew_circs = [];
1975    
1976    var str = "";
1977    if(__success_count) str+= __success_count+" items renewed successfully";
1978    if(__fail_count) str+=__fail_count+" items did not renew.";
1979    str+='\n\n';
1980    for(var i in __rewnew_errors) str+=__rewnew_errors[i]+'\n';
1981    
1982    if(__success_count || __fail_count) alert(str);
1983    __rewnew_errors = [];
1984
1985         //if( __success_count > 0 )
1986     //  alertIdText('myopac_renew_success', __success_count);
1987
1988    hideMe($('my_renewing'));
1989    checkedDrawn = false;
1990     drawCheckedPage();
1991         $('check_all_checked').checked = false;
1992 }
1993
1994
1995 function moveToNewList(parent, dest) {
1996         if(!parent || !dest) return;
1997         
1998         var items = dojo.query('input[name=list_action_chbx]', parent);
1999         if(!items.length) items = dojo.query('.list_action_chbx', parent);
2000         items.filter(function(item, index, arr){return item.checked;},this);
2001         
2002         if(items.length) {
2003                 for(var i=0; i<items.length; i++) {
2004                         var box = items[i];
2005                         if(box.checked) {
2006                                 containerCreateItem(dest, box.getAttribute("recordid"));
2007                         }
2008                 }
2009         }
2010 }
2011
2012 function listSaveAction() {
2013         var lists = dojo.query('select[name=list_actions]',$('temp_wrapper')).filter(function(n,i){
2014                 return n.options[n.selectedIndex].value!="0"
2015         });
2016         
2017         if(lists.length) { if(!confirm("Proceed with the selected action(s)?")) return; } else return;
2018         progressDialog.show(true);
2019         var updateHolds = false;
2020         var updateLists = false;
2021         
2022         lists.forEach(function(n,i){
2023                 var val = n.options[n.selectedIndex].value;
2024                 if(val=="0") return;
2025                 var p = n.parentNode.parentNode.parentNode.parentNode.parentNode;
2026                 switch(val) {
2027                         case "hold": batchHoldMyList(null, p); updateHolds = true; break;
2028                         case "move": if(n.id=='sel_all_list_anon') delSelCache(p, 'list_action_chbx');
2029                                                                 else removeSelectedItems(p); moveToNewList(p, n.options[n.selectedIndex].getAttribute("container"));
2030                                                   updateLists = true; break;
2031                         case "remove": if(n.id=='sel_all_list_anon') delSelCache(p, 'list_action_chbx');
2032                                                                 else removeSelectedItems(p);
2033                                                    updateLists = true; break;
2034                 }
2035                 setSelector(n, "0");
2036         });
2037         
2038         if(updateLists) reloadMyLists();
2039         if(updateHolds) { myopacForceHoldsRedraw = true; drawHoldsPage(); }
2040         progressDialog.hide();
2041 }
2042
2043 var itemsOutHistoryInitialFetch = false;
2044 function switchSubPage(page, subpage) {
2045         if(!page || !subpage) return;
2046         
2047         var pg = subPageObjs[page]; if(!pg) return;
2048         var spg = subPageObjs[page][subpage]; if(!spg) return;
2049         
2050         for(var i in pg) { if(i!='header') for(var n in pg[i]){ if(pg[i][n] || n!='header') hideMe(pg[i][n]); } }
2051         for(var t in spg) { if(spg[t] || t!='header') unHideMe(spg[t]); }
2052         
2053         pg.header.innerHTML = spg.header;
2054
2055     if(page == 'checked' && subpage == 'hist') {
2056         if(!itemsOutHistoryInitialFetch) {
2057             itemsOutHistoryInitialFetch = true;
2058             progressDialog.show(true);
2059                 fieldmapper.standardRequest(FETCH_CHECKED_HISTORY, {async:true, params:[G.user.session, G.user.id(), {'limit':CIRC_HIST_PAGE_LIMIT, 'offset':0}],
2060                         oncomplete:function(r) {
2061                     progressDialog.hide();
2062                     itemsOutHistory = openils.Util.readResponse(r);
2063                     drawCircHistory();
2064                         }
2065                 });
2066         }
2067     }
2068 }
2069
2070 function doBatchAnonHolds() {
2071         var error = {err:""};
2072         var resp = placeBatchHold(holdsList, G.user.home_ou(), error);
2073         if(resp == -1) alert("Unable to place holds"); else {
2074           alert(resp+" hold"+(resp==1?"":"s")+" placed successfully\n\n"+error.err);
2075         }
2076         
2077         holdsList = null;
2078         myopacForceHoldsRedraw = true;
2079         drawHoldsPage();
2080 }
2081
2082 function myOPACUpdateHomeOU() {
2083         var sel = $('myopac_new_home');
2084         
2085 }
2086
2087 var sortOrder = true;
2088 function sortHolds(by) {
2089         if(!by) return;
2090         
2091         sortOrder = !sortOrder;
2092         switch(by.toLowerCase()) {
2093                 case "format":
2094                 holdsCache = holdsCache.sort(function(a, b) {
2095                         if(sortOrder) return get998dValue(imgFormatCache[a.hold.id()])<get998dValue(imgFormatCache[b.hold.id()])?-1:1;
2096                                 else      return get998dValue(imgFormatCache[a.hold.id()])>get998dValue(imgFormatCache[b.hold.id()])?-1:1;
2097                 });
2098                 break;
2099                 case "title":
2100                 holdsCache = holdsCache.sort(function(a, b) {
2101                         if(sortOrder) return a.mvr.title()<b.mvr.title()?-1:1;
2102                                 else      return a.mvr.title()>b.mvr.title()?-1:1;
2103                 });
2104                 break;
2105                 case "pickup":
2106                 holdsCache = holdsCache.sort(function(a, b) {
2107                         if(sortOrder) return findOrgUnit(a.hold.pickup_lib()).name()<findOrgUnit(b.hold.pickup_lib()).name()?-1:1;
2108                                 else      return findOrgUnit(a.hold.pickup_lib()).name()>findOrgUnit(b.hold.pickup_lib()).name()?-1:1;
2109                 });
2110                 break;
2111                 case "author":
2112                 holdsCache = holdsCache.sort(function(a, b) {
2113                         if(sortOrder) return a.mvr.author()<b.mvr.author()?-1:1;
2114                                 else      return a.mvr.author()>b.mvr.author()?-1:1;
2115                 });
2116                 break;
2117                 case "status":
2118                 if(sortOrder) {
2119                         drawHoldsPage(true);
2120                         return;
2121                 } else {
2122                         holdsCache = holdsCache.sort(function(a, b) {
2123                                 if(a.status==4) return 1;
2124                                 if(b.status==4) return -1;
2125                                 if(isTrue(a.hold.frozen())) return -1;
2126                                 if(isTrue(b.hold.frozen())) return 1;
2127                                 return dojo.date.stamp.fromISOString(a.hold.request_time()) > dojo.date.stamp.fromISOString(b.hold.request_time())?-1:1;
2128                         });                     
2129                 }
2130         }
2131         
2132         drawHoldsPage(false);
2133 }
2134
2135 function sortChecked(by) {
2136         if(!by) return;
2137         
2138         sortOrder = !sortOrder;
2139         switch(by.toLowerCase()) {
2140                 case "title":
2141                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2142                         if(sortOrder) return a.record.title()<b.record.title()?-1:1;
2143                                 else      return a.record.title()>b.record.title()?-1:1;
2144                 });
2145                 break;
2146                 case "author":
2147                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2148                         if(sortOrder) return a.record.author()<b.record.author()?-1:1;
2149                                 else      return a.record.author()>b.record.author()?-1:1;
2150                 });
2151                 break;
2152                 case "due":
2153                 if(sortOrder) {
2154                         drawCheckedPage(); return;
2155                 } else itemsOutCache = itemsOutCache.sort(function(a, b) {
2156                         return dojo.date.stamp.fromISOString(a.circ.due_date()) < dojo.date.stamp.fromISOString(b.circ.due_date())?1:-1;
2157                 });
2158                 break;
2159                 case "barcode":
2160                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2161                         if(sortOrder) return Number(a.copy.barcode())<Number(b.copy.barcode())?-1:1;
2162                                 else      return Number(a.copy.barcode())>Number(b.copy.barcode())?-1:1;
2163                 });
2164                 break;
2165                 case "cn":
2166                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2167                         if(sortOrder) return callNumCache[a.copy.call_number()]<callNumCache[b.copy.call_number()] ?-1:1;
2168                                 else      return callNumCache[a.copy.call_number()]>callNumCache[b.copy.call_number()]?-1:1;
2169                 });
2170                 break;
2171                 case "renews":
2172                 itemsOutCache = itemsOutCache.sort(function(a, b) {
2173                         if(sortOrder) return Number(a.circ.renewal_remaining())<Number(b.circ.renewal_remaining())?-1:1;
2174                                 else      return Number(a.circ.renewal_remaining())>Number(b.circ.renewal_remaining())?-1:1;
2175                 });
2176                 break;
2177         }
2178         
2179         drawCheckedPage(false);
2180 }
2181
2182 function sortCheckedHist(by) {
2183         if(!by) return;
2184         
2185         sortOrder = !sortOrder;
2186         switch(by.toLowerCase()) {
2187                 case "title":
2188                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2189                         if(mvrObjCache[a.circ.target_copy()].title()==null) return 1;
2190                         if(mvrObjCache[b.circ.target_copy()].title()==null) return -1;
2191                         if(sortOrder) return mvrObjCache[a.circ.target_copy()].title().toLowerCase()<mvrObjCache[b.circ.target_copy()].title().toLowerCase()?-1:1;
2192                                 else      return mvrObjCache[a.circ.target_copy()].title().toLowerCase()>mvrObjCache[b.circ.target_copy()].title().toLowerCase()?-1:1;
2193                 });
2194                 break;
2195                 case "author":
2196                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2197                         if(mvrObjCache[a.circ.target_copy()].author()==null) return 1;
2198                         if(mvrObjCache[b.circ.target_copy()].author()==null) return -1;
2199                         if(sortOrder) return mvrObjCache[a.circ.target_copy()].author().toLowerCase()<mvrObjCache[b.circ.target_copy()].author().toLowerCase()?1:-1;
2200                                 else      return mvrObjCache[a.circ.target_copy()].author().toLowerCase()>mvrObjCache[b.circ.target_copy()].author().toLowerCase()?1:-1;
2201                 });
2202                 break;
2203                 case "duedate":
2204                 if(sortOrder) {
2205                         drawCheckedPage(); return;
2206                 } else itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2207                         return dojo.date.stamp.fromISOString(a.circ.due_date()) < dojo.date.stamp.fromISOString(b.circ.due_date())?1:-1;
2208                 });
2209                 break;
2210                 case "cn":
2211                 itemsOutHistory = itemsOutHistory.sort(function(a, b) {
2212                                                                                 // ya, i know, but it gets the job done.
2213                         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;
2214                                 else      return callNumCache[copyObjCache[a.circ.target_copy()].call_number()].label().toLowerCase()>callNumCache[copyObjCache[b.circ.target_copy()].call_number()].label().toLowerCase()?-1:1;
2215                 });
2216                 break;
2217         }
2218         
2219         drawCircHistory(false);
2220 }
2221
2222 function printData(data, numItems, callback) {
2223
2224     //unHideMe($('receipt-print-frame-wrapper'));
2225     receiptPrintDialog.show();
2226
2227     var frame = window["receipt-frame"];
2228     frame.document.body.innerHTML = data;
2229
2230     var cancel = $('receipt-view-print-cancel');
2231     cancel.onclick = function() {
2232         frame.document.body.innerHTML = '';
2233        // hideMe($('receipt-print-frame-wrapper'));
2234         receiptPrintDialog.hide();
2235         if(callback) callback();
2236     }
2237
2238     $('receipt-view-print-button').onclick = function() {
2239         frame.focus();
2240         frame.print();
2241         var sleepTime = 1000;
2242         if(numItems > 0) 
2243             sleepTime += (numItems / 2) * 1000;
2244
2245         setTimeout(
2246             function() { 
2247                 cancel.onclick();
2248                 if(callback) callback(); // fire optional post-print callback
2249             },
2250             sleepTime 
2251         );
2252     };
2253 }
2254
2255
2256 function printPaymentReceipt(paymentIds, callback, noprog) {
2257     if(!noprog) progressDialog.show(true);
2258
2259     fieldmapper.standardRequest(
2260         ['open-ils.circ', 'open-ils.circ.money.payment_receipt.print'],
2261         {
2262             async : true,
2263             params : [G.user.session, paymentIds],
2264             oncomplete : function(r) {
2265                 var resp = openils.Util.readResponse(r);
2266                 var output = "";
2267                                 if(resp) output = resp.template_output();
2268                 if(!noprog) progressDialog.hide();
2269                 if(output) {
2270                     printData(output.data(), 1, callback); 
2271                 } else {
2272                     var error = resp.error_output();
2273                     if(error) {
2274                         throw new Error("Error creating receipt: " + error.data());
2275                     } else {
2276                         throw new Error("No receipt data returned from server");
2277                     }
2278                 }
2279             }
2280         }
2281     );
2282 }
2283
2284 function printFinesReceipt(callback) {
2285     progressDialog.show(true);
2286
2287     var params = [
2288         G.user.session, 
2289         G.user.ws_ou(),
2290         null,
2291         'format.selfcheck.fines',
2292         'print-on-demand',
2293         [G.user.id()]
2294     ];
2295
2296     fieldmapper.standardRequest(
2297         ['open-ils.circ', 'open-ils.circ.fire_user_trigger_events'],
2298         {   
2299             async : true,
2300             params : params,
2301             oncomplete : function(r) {
2302                 progressDialog.hide();
2303                 var resp = openils.Util.readResponse(r);
2304                 var output = resp.template_output();
2305                 if(output) {
2306                     printData(output.data(), 240, callback); 
2307                 } else {
2308                     var error = resp.error_output();
2309                     if(error) {
2310                         throw new Error("Error creating receipt: " + error.data());
2311                     } else {
2312                         throw new Error("No receipt data returned from server");
2313                     }
2314                 }
2315             }
2316         }
2317     );
2318 }
2319
2320 function buildOrgSelAlt(selector, org, offset, namecol) {
2321  if(!namecol) namecol = 'name';
2322  if(!showXUL && !isTrue(org.opac_visible())) return; // for some reason, isXUL() is rather slow when used in a decently sized loop.
2323  insertSelectorVal( selector, -1,
2324  org[namecol](), org.id(), null, findOrgDepth(org) - offset );
2325  var kids = org.children();
2326  if (kids) {
2327  for( var c = 0; c < kids.length; c++ )
2328  buildOrgSelAlt( selector, kids[c], offset, namecol);
2329  }
2330 }