implemented hard hold ceilings and basis for soft ceilings
[Evergreen.git] / Open-ILS / web / opac / skin / default / js / holds.js
1 var holdsOrgSelectorBuilt = false;
2 var holdArgs;
3
4 /* 
5 note: metarecord holds have a holdable_formats field that contains
6 item_type(s)-item_forms(s)-language
7 item_form and language are optional - if language exist and no 
8 item_form is specified, use item_type(s)--language
9 */
10
11 var noEmailMessage;
12 var noEmailMessageXUL;
13
14 function holdsHandleStaff() {
15         swapCanvas($('xulholds_box'));
16         $('xul_recipient_barcode').focus();
17         $('xul_recipient_barcode').onkeypress = function(evt) 
18                 {if(userPressedEnter(evt)) { _holdsHandleStaff(); } };
19         $('xul_recipient_barcode_submit').onclick = _holdsHandleStaff;
20         $('xul_recipient_me').onclick = _holdsHandleStaffMe;
21
22         $('xul_recipient_barcode').onkeyup = function(evt) {
23         if($('xul_recipient_barcode').value == '') 
24             $('xul_recipient_me').disabled = false;
25         else
26             $('xul_recipient_me').disabled = true;
27     };
28 }
29
30 function _holdsHandleStaffMe() {
31         holdArgs.recipient = G.user;
32         holdsDrawEditor();
33 }
34
35 function _holdsHandleStaff() {
36         var barcode = $('xul_recipient_barcode').value;
37         var user = grabUserByBarcode( G.user.session, barcode );
38
39         var evt;
40         if(evt = checkILSEvent(user)) {
41                 alertILSEvent(user);
42                 return;
43         }
44
45         if(!barcode || !user) {
46                 alertId('holds_invalid_recipient', barcode);
47                 return
48         }
49
50         holdArgs.recipient = user;
51         holdsDrawEditor();
52 }
53
54
55
56 /** args:
57   * record, volume, copy (ids)
58   * request, recipient, editHold (objects)
59   */
60
61 function holdsDrawEditor(args) {
62
63         holdArgs = (args) ? args : holdArgs;
64
65     if(!noEmailMessage)
66         noEmailMessage = $('holds_email').removeChild($('holds.no_email'));
67
68     if(!noEmailMessageXUL)
69         noEmailMessageXUL = $('holds_email').removeChild($('holds.no_email.xul'));
70
71         if(isXUL() && holdArgs.recipient == null 
72                         && holdArgs.editHold == null) {
73                 holdsHandleStaff();
74                 return;
75         }
76
77         if(!holdArgs.recipient) holdArgs.recipient = G.user;
78         if(!holdArgs.requestor) holdArgs.requestor = G.user;
79
80         if(!(holdArgs.requestor && holdArgs.requestor.session)) {
81                 detachAllEvt('common','locationChanged');
82                 attachEvt('common','loggedIn', holdsDrawEditor)
83                 initLogin();
84                 return;
85         }
86
87         if(holdArgs.editHold) // flesh the args with the existing hold 
88                 holdArgsFromHold(holdArgs.editHold, holdArgs);
89
90         holdsDrawWindow();
91 }
92
93
94 // updates the edit window with the existing hold's data 
95 function _holdsUpdateEditHold() {
96
97         var hold = holdArgs.editHold;
98         var status = holdArgs.status;
99
100         var orgsel = $('holds_org_selector');
101     var frozenbox = $('holds_frozen_chkbox');
102
103         setSelector(orgsel, hold.pickup_lib());
104
105         if( hold.capture_time() || status > 2 ) {
106                 orgsel.disabled = true;
107         frozenbox.disabled = true;
108         $('holds_frozen_thaw_input').disabled = true;
109
110     } else {
111                 orgsel.disabled = false;
112         frozenbox.disabled = false;
113     }
114
115
116         $('holds_submit').onclick = holdsEditHold;
117         $('holds_update').onclick = holdsEditHold;
118
119         if(hold.phone_notify()) {
120                 $('holds_enable_phone').checked = true;
121                 $('holds_phone').value = hold.phone_notify();
122
123         } else {
124                 $('holds_phone').disabled = true;
125                 $('holds_enable_phone').checked = false;
126         }
127
128         if(isTrue(hold.email_notify())) {
129                 $('holds_enable_email').checked = true;
130
131         } else {
132                 $('holds_enable_email').checked = false;
133         }
134
135     /* populate the hold freezing info */
136     if(!frozenbox.disabled && isTrue(hold.frozen())) {
137         frozenbox.checked = true;
138         unHideMe($('hold_frozen_thaw_row'));
139         if(hold.thaw_date()) {
140             $('holds_frozen_thaw_input').value = hold.thaw_date();
141         } else {
142             $('holds_frozen_thaw_input').value = '';
143         }
144     } else {
145         frozenbox.checked = false;
146         $('holds_frozen_thaw_input').value = '';
147         hideMe($('hold_frozen_thaw_row'));
148     }
149 }
150
151 function holdsEditHold() {
152         var hold = holdsBuildHoldFromWindow();
153         if(!hold) return;
154         holdsUpdate(hold);
155         showCanvas();
156         if(holdArgs.onComplete)
157                 holdArgs.onComplete(hold);
158 }
159
160 function holdArgsFromHold(hold, oargs) {
161         var args = (oargs) ? oargs : {};
162         args.type = hold.hold_type();
163         var target = hold.target();
164
165         switch(args.type) {
166                 case 'M':
167                         args.metarecord = target;
168                         break;
169                 case 'T':
170                         args.record = target;
171                         break;
172                 case 'V':
173                         args.volume = target;
174                         break;
175                 case 'C':
176                         args.copy = target;
177                         break;
178         }
179         return args;
180 }
181
182 function holdFetchObjects(hold, doneCallback) {
183
184         var args = (hold) ? holdArgsFromHold(hold) : holdArgs;
185
186         var type = args.type;
187
188         if( type == 'C' ) {
189
190                 if( args.copyObject ) {
191
192                         args.copy = args.copyObject.id();
193                         args.volume = args.copyObject.call_number();
194                         _h_set_vol(args, doneCallback);
195
196                 } else {
197                         var creq = new Request(FETCH_COPY, args.copy);
198
199                         creq.callback(
200                                 function(r) {
201                                         var cp = r.getResultObject();
202                                         args.copyObject = cp;
203                                         args.volume = args.copyObject.call_number();
204                                         _h_set_vol(args, doneCallback);
205                                 }
206                         );
207                         creq.send();
208                 }
209         } else {
210                 if( type == 'V' ) {
211                         _h_set_vol(args, doneCallback);
212
213                 } else {
214                         if( type == 'T' ) {
215                                 _h_set_rec(args, doneCallback);
216                         } else {
217                                 _h_set_rec_descriptors(args, doneCallback);
218                         }
219                 }
220         }
221
222         return args;
223 }
224
225 function _h_set_vol(args, doneCallback) {
226
227         if( args.volumeObject ) {
228                 args.volume = args.volumeObject.id();
229                 args.record = args.volumeObject.record();
230                 _h_set_rec(args, doneCallback);
231
232         } else {
233
234                 var vreq = new Request(FETCH_VOLUME, args.volume);
235                 vreq.callback(
236                         function(r) {
237                                 var vol = r.getResultObject();
238                                 args.volumeObject = vol;
239                                 args.record = vol.record();
240                                 _h_set_rec(args, doneCallback);
241                         }
242                 );
243                 vreq.send();
244         }
245 }
246
247 function _h_set_rec(args, doneCallback) {
248
249         if(args.recordObject) 
250                 args.record = args.recordObject.doc_id();
251         else 
252                 args.recordObject = findRecord( args.record, 'T' );
253         
254         if( args.type == 'T' || args.type == 'M' ) 
255                 _h_set_rec_descriptors(args, doneCallback);
256         else 
257                 if(doneCallback) doneCallback(args);
258 }
259
260
261 function _h_set_rec_descriptors(args, doneCallback) {
262
263         // grab the list of record desciptors attached to this records metarecord 
264         if( ! args.recordDescriptors )  {
265                 var params = { record: args.record };
266
267                 if( ! args.record ) {
268                         if( args.metarecord )
269                                 params = { metarecord : args.metarecord };
270                         else 
271                                 params = { metarecord : args.metarecordObject.doc_id() };
272                 }
273
274                 var req = new Request(FETCH_MR_DESCRIPTORS, params );
275                 req.callback(
276                         function(r) {
277                                 var data = r.getResultObject();
278                                 args.recordDescriptors = data.descriptors;
279                                 args.metarecord = data.metarecord;
280                                 if( args.type == 'M' && ! args.metarecordObject) 
281                                         args.metarecordObject = findRecord(args.metarecord, 'M');       
282                                  
283                                 if(doneCallback) doneCallback(args);
284                         }
285                 );
286                 req.send();
287
288         } else {
289                 if(doneCallback) doneCallback(args);
290         }
291
292         return args;
293 }
294
295
296
297 function holdsDrawWindow() {
298         swapCanvas($('holds_box'));
299         $('holds_cancel').onclick = function(){ runEvt('common', 'holdUpdateCanceled'), showCanvas() };
300         $('holds_submit').onclick = function(){holdsPlaceHold(holdsBuildHoldFromWindow())};
301         $('holds_update').onclick = function(){holdsPlaceHold(holdsBuildHoldFromWindow())};
302         holdFetchObjects(null, 
303                 function(){
304                         __holdsDrawWindow();
305
306                         if(holdArgs.editHold) {
307                                 hideMe($('holds_submit'));
308                                 unHideMe($('holds_update'));
309                                 var req = new Request(FETCH_HOLD_STATUS, 
310                                         G.user.session, holdArgs.editHold.id());
311                                 req.send(true);
312                                 holdArgs.status = req.result();
313                                 _holdsUpdateEditHold();
314                         }  
315                 }
316         );
317 }
318
319 function __holdsDrawWindow() {
320
321         var rec = holdArgs.recordObject;
322         var vol = holdArgs.volumeObject;
323         var copy = holdArgs.copyObject;
324         var mr = holdArgs.metarecordObject;
325
326         rec = (rec) ? rec : mr;
327
328         if(!holdsOrgSelectorBuilt) {
329                 holdsBuildOrgSelector(null,0);
330                 holdsOrgSelectorBuilt = true;
331                 var selector = $('holds_org_selector');
332
333                 /*
334                 var o_loc = findOrgUnit(getOrigLocation());
335                 var t = findOrgType(o_loc.ou_type());
336                 if( t.can_have_users() ) 
337                         setSelector(selector, o_loc.id());
338                 else 
339                 */
340
341                 setSelector(selector, holdArgs.recipient.home_ou());
342         
343         }
344
345         /*
346         if(isXUL()) {
347                 var dsel = $('holds_depth_selector');
348                 unHideMe($('holds_depth_selector_row'));
349                 if(dsel.getElementsByTagName('option').length == 0) {
350                         var types = globalOrgTypes;
351                         var depth = findOrgDepth(G.user.ws_ou());
352                         iterate(types, 
353                                 function(t) {
354                                         if(t.depth() > depth) return;
355                                         insertSelectorVal(dsel, -1, t.opac_label(), t.depth());
356                                 }
357                         );
358                 }
359         }
360         */
361
362         appendClear($('holds_recipient'), text(
363                 holdArgs.recipient.family_name() + ', ' +  
364                         holdArgs.recipient.first_given_name()));
365         appendClear($('holds_title'), text(rec.title()));
366         appendClear($('holds_author'), text(rec.author()));
367
368         if( holdArgs.type == 'V' || holdArgs.type == 'C' ) {
369
370                 unHideMe($('holds_type_row'));
371                 unHideMe($('holds_cn_row'));
372                 appendClear($('holds_cn'), text(holdArgs.volumeObject.label()));
373
374                 if( holdArgs.type == 'V'  ) {
375                         unHideMe($('holds_is_cn'));
376                         hideMe($('holds_is_copy'));
377
378                 } else {
379                         hideMe($('holds_is_cn'));
380                         unHideMe($('holds_is_copy'));
381                         unHideMe($('holds_copy_row'));
382                         appendClear($('holds_copy'), text(holdArgs.copyObject.barcode()));
383                 }
384
385         } else {
386                 hideMe($('holds_type_row'));
387                 hideMe($('holds_copy_row'));
388                 hideMe($('holds_cn_row'));
389         }
390
391         removeChildren($('holds_format'));
392
393         var mods_formats = rec.types_of_resource();
394         var formats;
395
396         if(holdArgs.recordDescriptors)
397                 formats = holdArgs.recordDescriptors[0].item_type();
398
399         if( holdArgs.type == 'T' ) {
400                 var desc = grep( holdArgs.recordDescriptors,
401                         function(i) {
402                                 return (i.record() == holdArgs.record); 
403                         }
404                 );
405                 formats = desc[0].item_type();
406         }
407
408         if( holdArgs.type == 'M' ) {
409                 var data = holdsParseMRFormats(holdArgs.editHold.holdable_formats());
410                 mods_formats = data.mods_formats;
411                 formats = data.formats;
412         }
413
414
415         for( var i in mods_formats ) {
416                 var res = mods_formats[i];
417                 var img = elem("img");
418                 setResourcePic(img, res);
419                 $('holds_format').appendChild(img);
420                 if(formats)
421                         $('holds_format').appendChild(text(' '+ MARCTypeToFriendly(formats[i]) +' '));
422                 else
423                         $('holds_format').appendChild(text(' '+ mods_formats[i] +' '));
424                 $('holds_format').appendChild(elem('br'));
425         }
426
427
428         $('holds_phone').value = holdArgs.recipient.day_phone();
429         appendClear( $('holds_email'), text(holdArgs.recipient.email()));
430
431         var pref = G.user.prefs[PREF_HOLD_NOTIFY];
432
433         if(pref) {
434                 if( ! pref.match(/email/i) ) 
435                         $('holds_enable_email').checked = false;
436
437                 if( ! pref.match(/phone/i) ) {
438                         $('holds_phone').disabled = true;
439                         $('holds_enable_phone').checked = false;
440                 }
441         }
442
443     //if(!G.user.email()) {
444     if(!holdArgs.recipient.email()) {
445                 $('holds_enable_email').checked = false;        
446                 $('holds_enable_email').disabled = true;
447         var message;
448         if(isXUL()) {
449             message = noEmailMessageXUL.cloneNode(true);
450                 appendClear($('holds_email'), message);
451         } else {
452             message = noEmailMessage.cloneNode(true);
453                 appendClear($('holds_email'), message);
454             $('holds.no_email.my_account').setAttribute('href', buildOPACLink({page:MYOPAC},null,true));
455         }
456         unHideMe(message);
457     }
458
459         if(!$('holds_phone').value) 
460                 $('holds_enable_phone').checked = false;        
461
462         appendClear($('holds_physical_desc'), text(rec.physical_description()));
463
464         if(holdArgs.type == 'M') hideMe($('hold_physical_desc_row'));
465
466         holdsSetFormatSelector();
467 }
468
469 function holdsParseMRFormats(str) {
470         var data = str.split(/-/);      
471
472         var formats = [];
473         var mods_formats = [];
474
475         for( var i = 0; i < data[0].length; i++ ) {
476                 formats.push( data[0].charAt(i) );
477                 mods_formats.push( MARCFormatToMods( formats[i] ) );
478         }
479         
480         formats = uniquify(formats);
481         mods_formats = uniquify(mods_formats);
482
483         return {
484                 formats                 : formats,
485                 mods_formats    : mods_formats,
486                 lang                            : data[2],
487                 largeprint              : data[1]
488         };
489 }
490
491
492 function holdsSetFormatSelector() {
493         var type = holdArgs.type;
494         if( type == 'C' || type == 'V' || holdArgs.editHold ) return;
495
496         var data                                = holdsGetFormats();
497         var avail_formats       = data.avail_formats;
498         var sel_formats = data.sel_formats;
499         holdArgs.language = data.lang;
500
501         unHideMe($('holds_alt_formats_row_extras'));
502         var selector = $('hold_alt_form_selector');
503
504         for( var i = 0; i < avail_formats.length; i++ ) {
505                 var form = avail_formats[i];
506                 unHideMe(findSelectorOptByValue(selector, form));
507         }
508 }
509
510
511 function holdsGetFormats() {
512
513         var lang;
514         var formats = [];
515         var sformats = []; // selected formats 
516
517         var type = holdArgs.type;
518         var desc = holdArgs.recordDescriptors;
519         var rec = holdArgs.record;
520         var mrec = holdArgs.metarecord;
521
522         if( type == 'T' ) {
523
524                 for( var i = 0; i < desc.length; i++ ) {
525                         var d = desc[i];
526                         if( d.record() == holdArgs.record ) {
527                                 lang = d.item_lang();
528                                 holdArgs.myFormat =  _t_f_2_format(d.item_type(), d.item_form());
529                                 sformats.push(holdArgs.myFormat);
530                                 break;
531                         }
532                 }
533         }
534
535         for( var i = 0; i < desc.length; i++ ) {
536                 var d = desc[i];
537                 if( d.item_lang() != lang ) continue;
538                 formats.push( _t_f_2_format(d.item_type(), d.item_form()));
539         }
540
541         formats = uniquify(formats);
542
543         return {
544                 lang : lang,
545                 avail_formats : formats, 
546                 sel_formats : sformats
547         };
548 }
549
550
551
552 function _t_f_2_format(type, form) {
553         if( form == 'd' ) return 'at-d';
554         return (type == 'a' || type == 't') ? 'at' : 
555                 ( type == 'i' || type == 'g' || type == 'j' ) ? type : null;
556 }
557
558 function holdsSetSelectedFormats() {
559
560         var cn = $('holds_alt_formats_row').className;
561         if( cn && cn.match(/hide_me/) ) return;
562
563         var selector = $('hold_alt_form_selector');
564         var vals = getSelectedList(selector);
565
566         if(vals.length == 0) return;
567
568         var fstring = "";
569
570         if( contains(vals, 'at-d') ) {
571                 if( contains(vals, 'at') )
572                         fstring = 'at';
573                 else 
574                         fstring = 'at-d';
575         } else {
576                 if( contains(vals, 'at') )
577                         fstring = 'at';
578         }
579
580         for( var i = 0; i < vals.length; i++ ) {
581                 var val = vals[i];
582                 if( !val.match(/at/) ) fstring = val + fstring;
583         }
584
585         if( holdArgs.language ) {
586                 if( fstring.match(/-/) )
587                         fstring = fstring + '-' + holdArgs.language;
588                 else
589                         fstring = fstring + '--' + holdArgs.language;
590         }
591
592
593         return fstring;
594 }
595
596
597 function holdsCheckPossibility(pickuplib, hold, recurse) {
598
599         var args = { 
600                 titleid : holdArgs.record,
601                 volume_id : holdArgs.volume,
602                 copy_id : holdArgs.copy,
603                 hold_type : holdArgs.type,
604                 patronid : holdArgs.recipient.id(),
605                 depth : 0, 
606                 pickup_lib : pickuplib 
607         };
608
609         if(recurse) {
610                 /* if we're calling create again (recursing), 
611                         we know that the hold possibility check already succeeded */
612                 holdHandleCreateResponse({_recurse:true, _hold:hold}, true );
613
614         } else {
615                 _debug("hold possible args = "+js2JSON(args));
616         
617                 var req = new Request(CHECK_HOLD_POSSIBLE, G.user.session, args );
618         
619                 req.request.alertEvent = false;
620                 req.request._hold = hold;
621                 req.request._recurse = recurse;
622                 req.callback(holdHandleCreateResponse);
623                 req.send();
624         }
625 }
626
627
628 function holdsBuildOrgSelector(node) {
629
630         if(!node) node = globalOrgTree;
631     if(!isTrue(node.opac_visible())) return;
632
633         var selector = $('holds_org_selector');
634         var index = selector.options.length;
635
636         var type = findOrgType(node.ou_type());
637         var indent = type.depth() - 1;
638         var opt = setSelectorVal( selector, index, node.name(), node.id(), null, indent );
639         if(!type.can_have_users()) {
640                 opt.disabled = true;
641                 addCSSClass(opt, 'disabled_option');
642         }
643         
644         for( var i in node.children() ) {
645                 var child = node.children()[i];
646                 if(child) holdsBuildOrgSelector(child);
647         }
648 }
649
650 function holdsBuildHoldFromWindow() {
651
652         var org = getSelectorVal($('holds_org_selector'));
653         var node = findOrgUnit(org);
654         var ntype = findOrgType(node.ou_type());
655         if(!ntype.can_have_users()) {
656                 alertId('holds_pick_good_org');
657                 return;
658         }
659
660         var hold = new ahr();
661         if(holdArgs.editHold) {
662                 hold = holdArgs.editHold;
663                 holdArgs.editHold = null;
664         }
665
666         if( $('holds_enable_phone').checked ) {
667                 var phone = $('holds_phone').value;
668                 if( !phone || !phone.match(REGEX_PHONE) ) {
669                         alert($('holds_bad_phone').innerHTML);
670                         return null;
671                 }
672                 hold.phone_notify(phone);
673
674         } else {
675                 hold.phone_notify("");
676         }
677
678         if( $('holds_enable_email').checked ) 
679                 hold.email_notify(1);
680         else
681                 hold.email_notify(0);
682
683         var target;
684
685         switch(holdArgs.type) {
686                 case 'M':
687                         target = holdArgs.metarecord; break;
688                 case 'T':
689                         target = holdArgs.record; break;
690                 case 'V':
691                         target = holdArgs.volume; break;
692                 case 'C':
693                         target = holdArgs.copy; break;
694         }
695
696
697
698         hold.pickup_lib(org); 
699         //hold.request_lib(org); 
700         hold.requestor(holdArgs.requestor.id());
701         hold.usr(holdArgs.recipient.id());
702         hold.target(target);
703         hold.hold_type(holdArgs.type);
704
705     // see if this hold should be frozen and for how long
706     if($('holds_frozen_chkbox').checked) {
707         hold.frozen('t');
708         unHideMe($('hold_frozen_thaw_row'));
709         thawDate = $('holds_frozen_thaw_input').value;
710         if(thawDate) 
711             hold.thaw_date(thawDate);
712         else
713             hold.thaw_date(null);
714     }
715
716         //check for alternate hold formats 
717         var fstring = holdsSetSelectedFormats();
718         if(fstring) { 
719                 hold.hold_type('M'); 
720                 hold.holdable_formats(fstring);
721                 hold.target(holdArgs.metarecord);
722         }
723         return hold;
724 }
725         
726 function holdsPlaceHold(hold, recurse) {
727         if(!hold) return;
728         swapCanvas($('check_holds_box'));
729         holdsCheckPossibility(hold.pickup_lib(), hold, recurse);
730 }
731
732
733 function holdHandleCreateResponse(r, recurse) {
734
735         if(!recurse) {
736                 var res = r.getResultObject();
737                 if(checkILSEvent(res) || res.success != 1) {
738                         if(res.success != 1) {
739                                 alert($('hold_not_allowed').innerHTML);
740                         } else {
741                                 if( res.textcode == 'PATRON_BARRED' ) {
742                                         alertId('hold_failed_patron_barred');
743                         } else {
744                                         alert($('hold_not_allowed').innerHTML);
745                                 }
746                         }
747                         swapCanvas($('holds_box'));
748                         return;
749                 }
750         r._hold.selection_depth(res.depth);
751         }       
752
753         holdCreateHold(r._recurse, r._hold);
754 }
755
756
757 function holdCreateHold( recurse, hold ) {
758         var method = CREATE_HOLD;
759         if(recurse) method = CREATE_HOLD_OVERRIDE;
760         var req = new Request( method, holdArgs.requestor.session, hold );
761         req.request.alertEvent = false;
762         req.send(true);
763         var res = req.result();
764         holdProcessResult(hold, res, recurse);
765         
766         showCanvas();
767
768         runEvt('common', 'holdUpdated');
769 }
770
771
772 function holdProcessResult( hold, res, recurse ) {
773
774         if( res == '1' ) {
775                 alert($('holds_success').innerHTML);
776                 holdArgs = null;
777
778         } else {
779
780                 if( recurse ) {
781                         alert($('holds_failure').innerHTML);
782                         return;
783                 }
784
785                 if( grep(res, function(e) { return (e.textcode == 'HOLD_EXISTS'); }) ) {
786                         if( fetchPermOrgs('HOLD_EXISTS.override')[0] != -1 ) {
787                                 if( confirm($('hold_dup_exists_override').innerHTML) ) {
788                                         return holdsPlaceHold(hold, true);
789                                 }
790
791                         } else {
792                                 return alert($('hold_dup_exists').innerHTML);
793                         }
794                 }
795
796                 alert($('holds_failure').innerHTML);
797         }
798 }
799
800
801 function holdsCancel(holdid, user) {
802         if(!user) user = G.user;
803         var req = new Request(CANCEL_HOLD, user.session, holdid);
804         req.send(true);
805         return req.result();
806         runEvt('common', 'holdUpdated');
807 }
808
809 function holdsUpdate(hold, user) {
810         if(!user) user = G.user;
811         var req = new Request(UPDATE_HOLD, user.session, hold);
812         req.send(true);
813         var x = req.result(); // cause an exception if there is one 
814         runEvt('common', 'holdUpdated');
815 }
816
817