]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/booking/reservation.js
Patch from Lebbeous Fogle-Weekley providing final, cut-1 cleanup of booking
[working/Evergreen.git] / Open-ILS / web / js / ui / default / booking / reservation.js
1 /*
2  * Details, details...
3  */
4 dojo.require("fieldmapper.OrgUtils");
5 dojo.require("openils.PermaCrud");
6 dojo.require("openils.widget.OrgUnitFilteringSelect");
7 dojo.require("dojo.data.ItemFileReadStore");
8 dojo.require("dijit.form.DateTextBox");
9 dojo.require("dijit.form.TimeTextBox");
10 dojo.requireLocalization("openils.booking", "reservation");
11
12 /*
13  * Globals; prototypes and their instances
14  */
15 var localeStrings = dojo.i18n.getLocalization("openils.booking", "reservation");
16 var pcrud = new openils.PermaCrud();
17 var opts;
18 var our_brt;
19 var pickup_lib_selected;
20 var brt_list = [];
21 var brsrc_index = {};
22 var bresv_index = {};
23 var just_reserved_now = {};
24
25 function AttrValueTable() { this.t = {}; }
26 AttrValueTable.prototype.set = function(attr, value) { this.t[attr] = value; };
27 AttrValueTable.prototype.update_from_selector = function(selector) {
28     var attr  = selector.name.match(/_(\d+)$/)[1];
29     var value = selector.options[selector.selectedIndex].value;
30     if (attr)
31         attr_value_table.set(attr, value);
32 };
33 AttrValueTable.prototype.get_all_values = function() {
34     var values = [];
35     for (var k in this.t) {
36         if (this.t[k] != undefined && this.t[k] != "")
37             values.push(this.t[k]);
38     }
39     return values;
40 };
41 var attr_value_table =  new AttrValueTable();
42
43 function TimestampRange() {
44     this.start = new Date();
45     this.end = new Date();
46
47     this.validity = {"start": false, "end": false};
48     this.nodes = {
49         "start": {"date": undefined, "time": undefined},
50         "end": {"date": undefined, "time": undefined}
51     };
52     this.saved_style_properties = {};
53     this.invalid_style_properties = {
54         "backgroundColor": "#ffcccc",
55         "color": "#990000",
56         "borderColor": "#990000",
57         "fontWeight": "bold"
58     };
59 }
60 TimestampRange.prototype.get_timestamp = function(when) {
61     return this.any_widget.serialize(this[when]).
62         replace("T", " ").substr(0, 19);
63 };
64 TimestampRange.prototype.get_range = function() {
65     return this.is_backwards() ?
66         [this.get_timestamp("end"), this.get_timestamp("start")] :
67         [this.get_timestamp("start"), this.get_timestamp("end")];
68 };
69 TimestampRange.prototype.update_from_widget = function(widget) {
70     var when = widget.id.match(/(start|end)/)[1];
71     var which = widget.id.match(/(date|time)/)[1];
72
73     if (this.any_widget == undefined)
74         this.any_widget = widget;
75     if (this.nodes[when][which] == undefined)
76         this.nodes[when][which] = widget.domNode; /* We'll need this later */
77
78     if (when && which) {
79         this.update_timestamp(when, which, widget.value);
80     }
81
82     this.compute_validity();
83     this.paint_validity();
84 };
85 TimestampRange.prototype.compute_validity = function() {
86     if (Math.abs(this.start - this.end) < 1000) {
87         this.validity.end = false;
88     } else {
89         if (this.start < this.current_minimum())
90             this.validity.start = false;
91         else
92             this.validity.start = true;
93
94         if (this.end < this.current_minimum())
95             this.validity.end = false;
96         else
97             this.validity.end = true;
98     }
99 };
100 /* This method provides the minimum timestamp that is considered valid. For
101  * now it's arbitrarily "now + 15 minutes", meaning that all reservations
102  * must be made at least 15 minutes in the future.
103  *
104  * For reasons of keeping the middle layer happy, this should always return
105  * a time that is at least somewhat in the future. The ML isn't able to target
106  * any resources for a reservation with a start date that isn't in the future.
107  */
108 TimestampRange.prototype.current_minimum = function() {
109     /* XXX This is going to be a problem with local clocks that are off. */
110     var n = new Date();
111     n.setTime(n.getTime() + 1000 * 900); /* XXX 15 minutes; stop hardcoding! */
112     return n;
113 };
114 TimestampRange.prototype.update_timestamp = function(when, which, value) {
115     if (which == "date") {
116         this[when].setFullYear(value.getFullYear());
117         this[when].setMonth(value.getMonth());
118         this[when].setDate(value.getDate());
119     } else {    /* "time" */
120         this[when].setHours(value.getHours());
121         this[when].setMinutes(value.getMinutes());
122         this[when].setSeconds(0);
123     }
124 };
125 TimestampRange.prototype.is_backwards = function() {
126     return (this.start > this.end);
127 };
128 TimestampRange.prototype.paint_validity = function()  {
129     for (var when in this.validity) {
130         if (this.validity[when]) {
131             this.paint_valid_node(this.nodes[when].date);
132             this.paint_valid_node(this.nodes[when].time);
133         } else {
134             this.paint_invalid_node(this.nodes[when].date);
135             this.paint_invalid_node(this.nodes[when].time);
136         }
137     }
138 };
139 TimestampRange.prototype.paint_invalid_node = function(node) {
140     if (node) {
141         /* Just toggling the class of something would be better than
142          * manually setting style here, but I haven't been able to get that
143          * to play nicely with dojo's styling of the date/time textboxen.
144          */
145         if (this.saved_style_properties.backgroundColor == undefined) {
146             for (var k in this.invalid_style_properties) {
147                 this.saved_style_properties[k] = node.style[k];
148             }
149         }
150         for (var k in this.invalid_style_properties) {
151             node.style[k] = this.invalid_style_properties[k];
152         }
153     }
154 };
155 TimestampRange.prototype.paint_valid_node = function(node) {
156     if (node) {
157         for (var k in this.saved_style_properties) {
158             node.style[k] = this.saved_style_properties[k];
159         }
160     }
161 };
162 TimestampRange.prototype.is_valid = function() {
163     return (this.validity.start && this.validity.end);
164 };
165 var reserve_timestamp_range = new TimestampRange();
166
167 function SelectorMemory(selector) {
168     this.selector = selector;
169     this.memory = {};
170 }
171 SelectorMemory.prototype.save = function() {
172     for (var i = 0; i < this.selector.options.length; i++) {
173         if (this.selector.options[i].selected) {
174             this.memory[this.selector.options[i].value] = true;
175         }
176     }
177 };
178 SelectorMemory.prototype.restore = function() {
179     for (var i = 0; i < this.selector.options.length; i++) {
180         if (this.memory[this.selector.options[i].value]) {
181             if (!this.selector.options[i].disabled)
182                 this.selector.options[i].selected = true;
183         }
184     }
185 };
186
187 /*
188  * These functions communicate with the middle layer.
189  */
190 function get_all_noncat_brt() {
191     return pcrud.search("brt",
192         {"id": {"!=": null}, "catalog_item": "f"},
193         {"order_by": {"brt":"name"}}
194     );
195 }
196
197 function get_brt_by_id(id) {
198     return pcrud.retrieve("brt", id);
199 }
200
201 function get_brsrc_id_list() {
202     var options = {"type": our_brt.id(), "pickup_lib": pickup_lib_selected};
203
204     /* This mechanism for avoiding the passing of an empty 'attribute_values'
205      * option is essential because if you pass such an option to the
206      * middle layer API at all, it won't return any IDs for brsrcs that
207      * don't have at least one attribute of some kind.
208      */
209     var attribute_values = attr_value_table.get_all_values();
210     if (attribute_values.length > 0)
211         options.attribute_values = attribute_values;
212
213     options.available = reserve_timestamp_range.get_range();
214
215     return fieldmapper.standardRequest(
216         ["open-ils.booking", "open-ils.booking.resources.filtered_id_list"],
217         [xulG.auth.session.key, options]
218     );
219 }
220
221 /* FIXME: We need failure checking after pcrud.retrieve() */
222 function add_brsrc_to_index_if_needed(list, further) {
223     for (var i in list) {
224         if (!brsrc_index[list[i]]) {
225             brsrc_index[list[i]] = pcrud.retrieve("brsrc", list[i]);
226         }
227         if (further)
228             further(brsrc_index[list[i]]);
229     }
230 }
231
232 function sync_brsrc_index_from_ids(available_list, additional_list) {
233     /* Default states for everything in the index. Read the further comments. */
234     for (var i in brsrc_index) {
235         brsrc_index[i].isdeleted(true);
236         brsrc_index[i].ischanged(false);
237     }
238
239     /* Populate the cache with anything that's missing and tag everything
240      * in the "available" list as *not* deleted, and tag everything in the
241      * additional list as "changed." See below. */
242     add_brsrc_to_index_if_needed(
243         available_list, function(o) { o.isdeleted(false); }
244     );
245     add_brsrc_to_index_if_needed(
246         additional_list,
247         function(o) {
248             if (!(o.id() in just_reserved_now)) o.ischanged(true);
249         }
250     );
251     /* NOTE: We lightly abuse the isdeleted() and ischanged() magic fieldmapper
252      * attributes of the brsrcs in our cache.  Because we're not going to
253      * pass back any brsrcs to the middle layer, it doesn't really matter
254      * what we set this attribute to. What we're using it for is to indicate
255      * in our little brsrc cache how a given brsrc should be displayed in this
256      * UI's current state (based on whether the brsrc matches timestamp range
257      * availability (isdeleted(false)) and whether the brsrc has been forced
258      * into the list because it was selected in a previous interface (like
259      * the catalog) (ischanged(true))).
260      */
261 }
262
263 function check_bresv_targeting(results) {
264     var missing = 0;
265     for (var i in results) {
266         if (!(results[i].targeting && results[i].targeting.current_resource)) {
267             missing++;
268         } else {
269             just_reserved_now[results[i].targeting.current_resource] = true;
270         }
271     }
272     return missing;
273 }
274
275 function create_bresv(resource_list) {
276     var barcode = document.getElementById("patron_barcode").value;
277     if (barcode == "") {
278         alert(localeStrings.WHERES_THE_BARCODE);
279         return;
280     } else if (!reserve_timestamp_range.is_valid()) {
281         alert(localeStrings.INVALID_TS_RANGE);
282         return;
283     }
284     var results;
285     try {
286         results = fieldmapper.standardRequest(
287             ["open-ils.booking", "open-ils.booking.reservations.create"],
288             [
289                 xulG.auth.session.key,
290                 barcode,
291                 reserve_timestamp_range.get_range(),
292                 pickup_lib_selected,
293                 our_brt.id(),
294                 resource_list,
295                 attr_value_table.get_all_values()
296             ]
297         );
298     } catch (E) {
299         alert(localeStrings.CREATE_BRESV_LOCAL_ERROR + E);
300     }
301     if (results) {
302         if (is_ils_event(results)) {
303             if (is_ils_actor_card_error(results)) {
304                 alert(localeStrings.ACTOR_CARD_NOT_FOUND);
305             } else {
306                 alert(my_ils_error(
307                     localeStrings.CREATE_BRESV_SERVER_ERROR, results
308                 ));
309             }
310         } else {
311             var missing;
312             alert((missing = check_bresv_targeting(results)) ?
313                 localeStrings.CREATE_BRESV_OK_MISSING_TARGET(
314                     results.length, missing
315                 ) :
316                 localeStrings.CREATE_BRESV_OK(results.length)
317             );
318             update_brsrc_list();
319             update_bresv_grid();
320         }
321     } else {
322         alert(localeStrings.CREATE_BRESV_SERVER_NO_RESPONSE);
323     }
324 }
325
326 function flatten_to_dojo_data(obj_list) {
327     return {
328         "label": "id",
329         "identifier": "id",
330         "items": obj_list.map(function(o) {
331             var new_obj = {
332                 "id": o.id(),
333                 "type": o.target_resource_type().name(),
334                 "start_time": humanize_timestamp_string(o.start_time()),
335                 "end_time": humanize_timestamp_string(o.end_time()),
336             };
337
338             if (o.current_resource())
339                 new_obj["resource"] = o.current_resource().barcode();
340             else if (o.target_resource())
341                 new_obj["resource"] = "* " + o.target_resource().barcode();
342             else
343                 new_obj["resource"] = "* " + localeStrings.UNTARGETED + " *";
344             return new_obj;
345         })
346     };
347 }
348
349 function create_bresv_on_brsrc() {
350     var selector = document.getElementById("brsrc_list");
351     var selected_values = [];
352     for (var i in selector.options) {
353         if (selector.options[i] && selector.options[i].selected)
354             selected_values.push(selector.options[i].value);
355     }
356     if (selected_values.length > 0)
357         create_bresv(selected_values);
358     else
359         alert(localeStrings.SELECT_A_BRSRC_THEN);
360 }
361
362 function create_bresv_on_brt() {
363     if (any_usable_brsrc())
364         create_bresv();
365     else
366         alert(localeStrings.NO_USABLE_BRSRC);
367 }
368
369 function get_actor_by_barcode(barcode) {
370     var usr = fieldmapper.standardRequest(
371         ["open-ils.actor", "open-ils.actor.user.fleshed.retrieve_by_barcode"],
372         [xulG.auth.session.key, barcode]
373     );
374     if (usr == null) {
375         alert(localeStrings.GET_PATRON_NO_RESULT);
376     } else if (is_ils_event(usr)) {
377         return null; /* XXX inelegant: this function is quiet about errors
378                         here because to report them would be redundant with
379                         another function that gets called right after this one.
380                       */
381     } else {
382         return usr;
383     }
384 }
385
386 function init_bresv_grid(barcode) {
387     var result = fieldmapper.standardRequest(
388         ["open-ils.booking",
389             "open-ils.booking.reservations.filtered_id_list"
390         ],
391         [xulG.auth.session.key, {
392             "user_barcode": barcode,
393             "fields": {
394                 "pickup_time": null,
395                 "cancel_time": null,
396                 "return_time": null
397             }
398         }, /* whole_obj */ true]
399     );
400     if (result == null) {
401         set_datagrid_empty_store(bresvGrid, flatten_to_dojo_data);
402         alert(localeStrings.GET_BRESV_LIST_NO_RESULT);
403     } else if (is_ils_event(result)) {
404         set_datagrid_empty_store(bresvGrid, flatten_to_dojo_data);
405         if (is_ils_actor_card_error(result)) {
406             alert(localeStrings.ACTOR_CARD_NOT_FOUND);
407         } else {
408             alert(my_ils_error(localeStrings.GET_BRESV_LIST_ERR, result));
409         }
410     } else {
411         if (result.length < 1) {
412             document.getElementById("bresv_grid_alt_explanation").innerHTML =
413                 localeStrings.NO_EXISTING_BRESV;
414             hide_dom_element(document.getElementById("bresv_grid"));
415             reveal_dom_element(document.getElementById("reserve_under"));
416         } else {
417             document.getElementById("bresv_grid_alt_explanation").innerHTML =
418                 "";
419             reveal_dom_element(document.getElementById("bresv_grid"));
420             reveal_dom_element(document.getElementById("reserve_under"));
421         }
422         /* May as well do the following in either case... */
423         bresvGrid.setStore(
424             new dojo.data.ItemFileReadStore(
425                 {"data": flatten_to_dojo_data(result)}
426             )
427         );
428         bresv_index = {};
429         for (var i in result) {
430             bresv_index[result[i].id()] = result[i];
431         }
432     }
433 }
434
435 function cancel_reservations(bresv_id_list) {
436     try {
437         var result = fieldmapper.standardRequest(
438             ["open-ils.booking", "open-ils.booking.reservations.cancel"],
439             [xulG.auth.session.key, bresv_id_list]
440         );
441     } catch (E) {
442         alert(localeStrings.CXL_BRESV_FAILURE2 + E);
443         return;
444     }
445     setTimeout(update_bresv_grid, 0);
446     if (!result) {
447         alert(localeStrings.CXL_BRESV_FAILURE);
448     } else if (is_ils_event(result)) {
449         alert(my_ils_error(localeStrings.CXL_BRESV_FAILURE2, result));
450     } else {
451         alert(localeStrings.CXL_BRESV_SUCCESS(result.length));
452     }
453 }
454
455 function munge_specific_resource(barcode) {
456     try {
457         var copy_list = pcrud.search(
458             "acp", {"barcode": barcode, "deleted": "f"}
459         );
460         if (copy_list && copy_list.length > 0) {
461             var r = fieldmapper.standardRequest(
462                 ["open-ils.booking",
463                     "open-ils.booking.resources.create_from_copies"],
464                 [xulG.auth.session.key,
465                     copy_list.map(function(o) { return o.id(); })]
466             );
467
468             if (!r) {
469                 alert(localeStrings.ON_FLY_NO_RESPONSE);
470             } else if (is_ils_event(r)) {
471                 alert(my_ils_error(localeStrings.ON_FLY_ERROR, r));
472             } else {
473                 if (!(our_brt = get_brt_by_id(r.brt[0][0]))) {
474                     alert(localeStrings.COULD_NOT_RETRIEVE_BRT_PASSED_IN);
475                 } else {
476                     opts.booking_results = r;
477                     init_reservation_interface();
478                 }
479             }
480         } else {
481             alert(localeStrings.BRSRC_NOT_FOUND);
482         }
483     } catch (E) {
484         alert(localeStrings.BRSRC_RETRIEVE_ERROR + E);
485     }
486 }
487
488 /*
489  * These functions deal with interface tricks (populating widgets,
490  * changing the page, etc.).
491  */
492 function init_pickup_lib_selector() {
493     var User = new openils.User();
494     User.buildPermOrgSelector(
495         "ADMIN_BOOKING_RESERVATION", pickup_lib_selector, null,
496         function() {
497             pickup_lib_selected = pickup_lib_selector.getValue();
498             dojo.connect(pickup_lib_selector, "onChange",
499                 function() {
500                     pickup_lib_selected = this.getValue();
501                     update_brsrc_list();
502                 }
503             )
504         }
505     );
506 }
507
508 function provide_brt_selector(targ_div) {
509     if (!targ_div) {
510         alert(localeStrings.NO_TARG_DIV);
511     } else {
512         brt_list = get_all_noncat_brt();
513         if (!brt_list || brt_list.length < 1) {
514             document.getElementById("select_noncat_brt_block").
515                 style.display = "none";
516         } else {
517             var selector = document.createElement("select");
518             selector.setAttribute("id", "brt_selector");
519             selector.setAttribute("name", "brt_selector");
520             /* I'm reluctantly hardcoding this "size" attribute as 8
521              * because you can't accomplish this with CSS anyway.
522              */
523             selector.setAttribute("size", 8);
524             for (var i in brt_list) {
525                 var option = document.createElement("option");
526                 option.setAttribute("value", brt_list[i].id());
527                 option.appendChild(document.createTextNode(brt_list[i].name()));
528                 selector.appendChild(option);
529             }
530             targ_div.innerHTML = "";
531             targ_div.appendChild(selector);
532         }
533     }
534 }
535
536 function init_resv_iface_arb() {
537     init_reservation_interface(document.getElementById("arbitrary_resource"));
538 }
539
540 function init_resv_iface_sel() {
541     init_reservation_interface(document.getElementById("brt_selector"));
542 }
543
544 function init_reservation_interface(widget) {
545     /* Save a global reference to the brt we're going to reserve */
546     if (widget && (widget.selectedIndex != undefined)) {
547         our_brt = brt_list[widget.selectedIndex];
548     } else if (widget != undefined) {
549         if (!munge_specific_resource(widget.value))
550             return;
551     }
552
553     /* Hide and reveal relevant divs. */
554     var search_block = document.getElementById("brt_search_block");
555     var reserve_block = document.getElementById("brt_reserve_block");
556     hide_dom_element(search_block);
557     reveal_dom_element(reserve_block);
558
559     /* Get a list of attributes that can apply to that brt. */
560     var bra_list = pcrud.search("bra", {"resource_type": our_brt.id()});
561     if (!bra_list) {
562         alert(localeString.NO_BRA_LIST);
563         return;
564     }
565
566     /* Get a table of values that can apply to the above attributes. */
567     var brav_by_bra = {};
568     bra_list.map(function(o) {
569         brav_by_bra[o.id()] = pcrud.search("brav", {"attr": o.id()});
570     });
571
572     /* Hide the label over the attributes widgets if we have nothing to show. */
573     var domf = (bra_list.length < 1) ? hide_dom_element : reveal_dom_element;
574     domf(document.getElementById("bra_and_brav_header"));
575
576     /* Create DOM widgets to represent each attribute/values set. */
577     for (var i in bra_list) {
578         var bra_div = document.createElement("div");
579         bra_div.setAttribute("class", "nice_vertical_padding");
580
581         var bra_select = document.createElement("select");
582         bra_select.setAttribute("name", "bra_" + bra_list[i].id());
583         bra_select.setAttribute(
584             "onchange",
585             "attr_value_table.update_from_selector(this); update_brsrc_list();"
586         );
587
588         var bra_opt_any = document.createElement("option");
589         bra_opt_any.appendChild(document.createTextNode(localeStrings.ANY));
590         bra_opt_any.setAttribute("value", "");
591
592         bra_select.appendChild(bra_opt_any);
593
594         var bra_label = document.createElement("label");
595         bra_label.setAttribute("class", "bra");
596         bra_label.appendChild(document.createTextNode(bra_list[i].name()));
597
598         var j = bra_list[i].id();
599         for (var k in brav_by_bra[j]) {
600             var bra_opt = document.createElement("option");
601             bra_opt.setAttribute("value", brav_by_bra[j][k].id());
602             bra_opt.appendChild(
603                 document.createTextNode(brav_by_bra[j][k].valid_value())
604             );
605             bra_select.appendChild(bra_opt);
606         }
607
608         bra_div.appendChild(bra_label);
609         bra_div.appendChild(bra_select);
610         document.getElementById("bra_and_brav").appendChild(bra_div);
611     }
612     /* Add a prominent label reminding the user what resource type they're
613      * asking about. */
614     document.getElementById("brsrc_list_header").innerHTML = our_brt.name();
615     init_pickup_lib_selector();
616     update_brsrc_list();
617 }
618
619 function update_brsrc_list() {
620     var brsrc_id_list = get_brsrc_id_list();
621     var force_list = (opts.booking_results && opts.booking_results.brsrc) ?
622         opts.booking_results.brsrc.map(function(o) { return o[0]; }) : [];
623
624     sync_brsrc_index_from_ids(brsrc_id_list, force_list);
625
626     var target_selector = document.getElementById("brsrc_list");
627     var selector_memory = new SelectorMemory(target_selector);
628     selector_memory.save();
629     target_selector.innerHTML = "";
630
631     for (var i in brsrc_index) {
632         if (brsrc_index[i].isdeleted() && (!brsrc_index[i].ischanged()))
633             continue;
634
635         var opt = document.createElement("option");
636         opt.setAttribute("value", brsrc_index[i].id());
637         opt.appendChild(document.createTextNode(brsrc_index[i].barcode()));
638
639         if (brsrc_index[i].isdeleted() && (brsrc_index[i].ischanged())) {
640             opt.setAttribute("class", "forced_unavailable");
641             opt.setAttribute("disabled", "disabled");
642         }
643
644         target_selector.appendChild(opt);
645     }
646
647     selector_memory.restore();
648 }
649
650 function any_usable_brsrc() {
651     for (var i in brsrc_index) {
652         if (!brsrc_index[i].isdeleted())
653             return true;
654     }
655     return false;
656 }
657
658 function update_bresv_grid() {
659     var widg = document.getElementById("patron_barcode");
660     if (widg.value != "") {
661         setTimeout(function() {
662             var target = document.getElementById(
663                 "existing_reservation_patron_line"
664             );
665             var patron = get_actor_by_barcode(widg.value);
666             if (patron) {
667                 target.innerHTML = (
668                     localeStrings.HERE_ARE_EXISTING_BRESV + " " +
669                     formal_name(patron) + ": "
670                 );
671             } else {
672                 target.innerHTML = "";
673             }
674         }, 0);
675         setTimeout(function() { init_bresv_grid(widg.value); }, 0);
676     }
677 }
678
679 function init_timestamp_widgets() {
680     var when = ["start", "end"];
681     for (var i in when) {
682         reserve_timestamp_range.update_from_widget(
683             new dijit.form.TimeTextBox({
684                 name: "reserve_time_" + when[i],
685                 value: new Date(),
686                 constraints: {
687                     timePattern: "HH:mm",
688                     clickableIncrement: "T00:15:00",
689                     visibleIncrement: "T00:15:00",
690                     visibleRange: "T01:30:00",
691                 },
692                 onChange: function() {
693                     reserve_timestamp_range.update_from_widget(this);
694                     update_brsrc_list();
695                 }
696             }, "reserve_time_" + when[i])
697         );
698         reserve_timestamp_range.update_from_widget(
699             new dijit.form.DateTextBox({
700                 name: "reserve_date_" + when[i],
701                 value: new Date(),
702                 onChange: function() {
703                     reserve_timestamp_range.update_from_widget(this);
704                     update_brsrc_list();
705                 }
706             }, "reserve_date_" + when[i])
707         );
708     }
709 }
710
711 function cancel_selected_bresv(bresv_dojo_items) {
712     if (bresv_dojo_items && bresv_dojo_items.length > 0 &&
713         (bresv_dojo_items[0].length == undefined ||
714             bresv_dojo_items[0].length > 0)) {
715         cancel_reservations(
716             bresv_dojo_items.map(function(o) { return o.id[0]; })
717         );
718         /* After some delay to allow the cancellations a chance to get
719          * committed, refresh the brsrc list as it might reflect newly
720          * available resources now. */
721         setTimeout(update_brsrc_list, 2000);
722     } else {
723         alert(localeStrings.CXL_BRESV_SELECT_SOMETHING);
724     }
725 }
726
727 /* The following function should return true if the reservation interface
728  * should start normally (show a list of brt to choose from) or false if
729  * it should not (because we've "started" it some other way by setting up
730  * and displaying other widgets).
731  */
732 function early_action_passthru() {
733     if (opts.booking_results) {
734         if (opts.booking_results.brt.length != 1) {
735             alert(localeStrings.NEED_EXACTLY_ONE_BRT_PASSED_IN);
736             return true;
737         } else if (!(our_brt = get_brt_by_id(opts.booking_results.brt[0][0]))) {
738             alert(localeStrings.COULD_NOT_RETRIEVE_BRT_PASSED_IN);
739             return true;
740         }
741
742         init_reservation_interface();
743         return false;
744     }
745
746     if (opts.patron_barcode) {
747         document.getElementById("contain_patron_barcode").style.display="none";
748         document.getElementById("patron_barcode").value = opts.patron_barcode;
749         update_bresv_grid();
750     }
751
752     return true;
753 }
754
755 /*
756  * my_init
757  */
758 function my_init() {
759     hide_dom_element(document.getElementById("brt_reserve_block"));
760     reveal_dom_element(document.getElementById("brt_search_block"));
761     hide_dom_element(document.getElementById("reserve_under"));
762     init_auto_l10n(document.getElementById("auto_l10n_start_here"));
763     init_timestamp_widgets();
764
765     if (!(opts = xulG.bresv_interface_opts)) opts = {};
766     if (early_action_passthru())
767         provide_brt_selector(document.getElementById("brt_selector_here"));
768 }