]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/web/js/ui/default/booking/reservation.js
Stab at matching up reservation.js with the pluralized, JSON-compliant Dojo NLS file
[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             if (missing = check_bresv_targeting(results)) {
313                 alert(dojo.string.substitute(
314                     localeStrings.CREATE_BRESV_OK_MISSING_TARGET,
315                     [results.length, missing]
316                 ));
317             } else {
318                 if (results.length == 1) {
319                     alert(dojo.string.substitute(
320                         localeStrings.CREATE_BRESV_OK(results.length)
321                     ));
322                 } else {
323                     alert(dojo.string.substitute(
324                         localeStrings.CREATE_BRESV_OK_PLURAL(results.length)
325                     ));
326                 }
327             );
328             }
329             update_brsrc_list();
330             update_bresv_grid();
331         }
332     } else {
333         alert(localeStrings.CREATE_BRESV_SERVER_NO_RESPONSE);
334     }
335 }
336
337 function flatten_to_dojo_data(obj_list) {
338     return {
339         "label": "id",
340         "identifier": "id",
341         "items": obj_list.map(function(o) {
342             var new_obj = {
343                 "id": o.id(),
344                 "type": o.target_resource_type().name(),
345                 "start_time": humanize_timestamp_string(o.start_time()),
346                 "end_time": humanize_timestamp_string(o.end_time()),
347             };
348
349             if (o.current_resource())
350                 new_obj["resource"] = o.current_resource().barcode();
351             else if (o.target_resource())
352                 new_obj["resource"] = "* " + o.target_resource().barcode();
353             else
354                 new_obj["resource"] = "* " + localeStrings.UNTARGETED + " *";
355             return new_obj;
356         })
357     };
358 }
359
360 function create_bresv_on_brsrc() {
361     var selector = document.getElementById("brsrc_list");
362     var selected_values = [];
363     for (var i in selector.options) {
364         if (selector.options[i] && selector.options[i].selected)
365             selected_values.push(selector.options[i].value);
366     }
367     if (selected_values.length > 0)
368         create_bresv(selected_values);
369     else
370         alert(localeStrings.SELECT_A_BRSRC_THEN);
371 }
372
373 function create_bresv_on_brt() {
374     if (any_usable_brsrc())
375         create_bresv();
376     else
377         alert(localeStrings.NO_USABLE_BRSRC);
378 }
379
380 function get_actor_by_barcode(barcode) {
381     var usr = fieldmapper.standardRequest(
382         ["open-ils.actor", "open-ils.actor.user.fleshed.retrieve_by_barcode"],
383         [xulG.auth.session.key, barcode]
384     );
385     if (usr == null) {
386         alert(localeStrings.GET_PATRON_NO_RESULT);
387     } else if (is_ils_event(usr)) {
388         return null; /* XXX inelegant: this function is quiet about errors
389                         here because to report them would be redundant with
390                         another function that gets called right after this one.
391                       */
392     } else {
393         return usr;
394     }
395 }
396
397 function init_bresv_grid(barcode) {
398     var result = fieldmapper.standardRequest(
399         ["open-ils.booking",
400             "open-ils.booking.reservations.filtered_id_list"
401         ],
402         [xulG.auth.session.key, {
403             "user_barcode": barcode,
404             "fields": {
405                 "pickup_time": null,
406                 "cancel_time": null,
407                 "return_time": null
408             }
409         }, /* whole_obj */ true]
410     );
411     if (result == null) {
412         set_datagrid_empty_store(bresvGrid, flatten_to_dojo_data);
413         alert(localeStrings.GET_BRESV_LIST_NO_RESULT);
414     } else if (is_ils_event(result)) {
415         set_datagrid_empty_store(bresvGrid, flatten_to_dojo_data);
416         if (is_ils_actor_card_error(result)) {
417             alert(localeStrings.ACTOR_CARD_NOT_FOUND);
418         } else {
419             alert(my_ils_error(localeStrings.GET_BRESV_LIST_ERR, result));
420         }
421     } else {
422         if (result.length < 1) {
423             document.getElementById("bresv_grid_alt_explanation").innerHTML =
424                 localeStrings.NO_EXISTING_BRESV;
425             hide_dom_element(document.getElementById("bresv_grid"));
426             reveal_dom_element(document.getElementById("reserve_under"));
427         } else {
428             document.getElementById("bresv_grid_alt_explanation").innerHTML =
429                 "";
430             reveal_dom_element(document.getElementById("bresv_grid"));
431             reveal_dom_element(document.getElementById("reserve_under"));
432         }
433         /* May as well do the following in either case... */
434         bresvGrid.setStore(
435             new dojo.data.ItemFileReadStore(
436                 {"data": flatten_to_dojo_data(result)}
437             )
438         );
439         bresv_index = {};
440         for (var i in result) {
441             bresv_index[result[i].id()] = result[i];
442         }
443     }
444 }
445
446 function cancel_reservations(bresv_id_list) {
447     try {
448         var result = fieldmapper.standardRequest(
449             ["open-ils.booking", "open-ils.booking.reservations.cancel"],
450             [xulG.auth.session.key, bresv_id_list]
451         );
452     } catch (E) {
453         alert(localeStrings.CXL_BRESV_FAILURE2 + E);
454         return;
455     }
456     setTimeout(update_bresv_grid, 0);
457     if (!result) {
458         alert(localeStrings.CXL_BRESV_FAILURE);
459     } else if (is_ils_event(result)) {
460         alert(my_ils_error(localeStrings.CXL_BRESV_FAILURE2, result));
461     } else {
462         if (result.length == 1) {
463             alert(dojo.string.substitute(
464                 localeStrings.CXL_BRESV_SUCCESS, [result.length]
465             ));
466         } else {
467             alert(dojo.string.substitute(
468                 localeStrings.CXL_BRESV_SUCCESS_PLURAL, [result.length]
469             ));
470         }
471     }
472 }
473
474 function munge_specific_resource(barcode) {
475     try {
476         var copy_list = pcrud.search(
477             "acp", {"barcode": barcode, "deleted": "f"}
478         );
479         if (copy_list && copy_list.length > 0) {
480             var r = fieldmapper.standardRequest(
481                 ["open-ils.booking",
482                     "open-ils.booking.resources.create_from_copies"],
483                 [xulG.auth.session.key,
484                     copy_list.map(function(o) { return o.id(); })]
485             );
486
487             if (!r) {
488                 alert(localeStrings.ON_FLY_NO_RESPONSE);
489             } else if (is_ils_event(r)) {
490                 alert(my_ils_error(localeStrings.ON_FLY_ERROR, r));
491             } else {
492                 if (!(our_brt = get_brt_by_id(r.brt[0][0]))) {
493                     alert(localeStrings.COULD_NOT_RETRIEVE_BRT_PASSED_IN);
494                 } else {
495                     opts.booking_results = r;
496                     init_reservation_interface();
497                 }
498             }
499         } else {
500             alert(localeStrings.BRSRC_NOT_FOUND);
501         }
502     } catch (E) {
503         alert(localeStrings.BRSRC_RETRIEVE_ERROR + E);
504     }
505 }
506
507 /*
508  * These functions deal with interface tricks (populating widgets,
509  * changing the page, etc.).
510  */
511 function init_pickup_lib_selector() {
512     var User = new openils.User();
513     User.buildPermOrgSelector(
514         "ADMIN_BOOKING_RESERVATION", pickup_lib_selector, null,
515         function() {
516             pickup_lib_selected = pickup_lib_selector.getValue();
517             dojo.connect(pickup_lib_selector, "onChange",
518                 function() {
519                     pickup_lib_selected = this.getValue();
520                     update_brsrc_list();
521                 }
522             )
523         }
524     );
525 }
526
527 function provide_brt_selector(targ_div) {
528     if (!targ_div) {
529         alert(localeStrings.NO_TARG_DIV);
530     } else {
531         brt_list = get_all_noncat_brt();
532         if (!brt_list || brt_list.length < 1) {
533             document.getElementById("select_noncat_brt_block").
534                 style.display = "none";
535         } else {
536             var selector = document.createElement("select");
537             selector.setAttribute("id", "brt_selector");
538             selector.setAttribute("name", "brt_selector");
539             /* I'm reluctantly hardcoding this "size" attribute as 8
540              * because you can't accomplish this with CSS anyway.
541              */
542             selector.setAttribute("size", 8);
543             for (var i in brt_list) {
544                 var option = document.createElement("option");
545                 option.setAttribute("value", brt_list[i].id());
546                 option.appendChild(document.createTextNode(brt_list[i].name()));
547                 selector.appendChild(option);
548             }
549             targ_div.innerHTML = "";
550             targ_div.appendChild(selector);
551         }
552     }
553 }
554
555 function init_resv_iface_arb() {
556     init_reservation_interface(document.getElementById("arbitrary_resource"));
557 }
558
559 function init_resv_iface_sel() {
560     init_reservation_interface(document.getElementById("brt_selector"));
561 }
562
563 function init_reservation_interface(widget) {
564     /* Save a global reference to the brt we're going to reserve */
565     if (widget && (widget.selectedIndex != undefined)) {
566         our_brt = brt_list[widget.selectedIndex];
567     } else if (widget != undefined) {
568         if (!munge_specific_resource(widget.value))
569             return;
570     }
571
572     /* Hide and reveal relevant divs. */
573     var search_block = document.getElementById("brt_search_block");
574     var reserve_block = document.getElementById("brt_reserve_block");
575     hide_dom_element(search_block);
576     reveal_dom_element(reserve_block);
577
578     /* Get a list of attributes that can apply to that brt. */
579     var bra_list = pcrud.search("bra", {"resource_type": our_brt.id()});
580     if (!bra_list) {
581         alert(localeString.NO_BRA_LIST);
582         return;
583     }
584
585     /* Get a table of values that can apply to the above attributes. */
586     var brav_by_bra = {};
587     bra_list.map(function(o) {
588         brav_by_bra[o.id()] = pcrud.search("brav", {"attr": o.id()});
589     });
590
591     /* Hide the label over the attributes widgets if we have nothing to show. */
592     var domf = (bra_list.length < 1) ? hide_dom_element : reveal_dom_element;
593     domf(document.getElementById("bra_and_brav_header"));
594
595     /* Create DOM widgets to represent each attribute/values set. */
596     for (var i in bra_list) {
597         var bra_div = document.createElement("div");
598         bra_div.setAttribute("class", "nice_vertical_padding");
599
600         var bra_select = document.createElement("select");
601         bra_select.setAttribute("name", "bra_" + bra_list[i].id());
602         bra_select.setAttribute(
603             "onchange",
604             "attr_value_table.update_from_selector(this); update_brsrc_list();"
605         );
606
607         var bra_opt_any = document.createElement("option");
608         bra_opt_any.appendChild(document.createTextNode(localeStrings.ANY));
609         bra_opt_any.setAttribute("value", "");
610
611         bra_select.appendChild(bra_opt_any);
612
613         var bra_label = document.createElement("label");
614         bra_label.setAttribute("class", "bra");
615         bra_label.appendChild(document.createTextNode(bra_list[i].name()));
616
617         var j = bra_list[i].id();
618         for (var k in brav_by_bra[j]) {
619             var bra_opt = document.createElement("option");
620             bra_opt.setAttribute("value", brav_by_bra[j][k].id());
621             bra_opt.appendChild(
622                 document.createTextNode(brav_by_bra[j][k].valid_value())
623             );
624             bra_select.appendChild(bra_opt);
625         }
626
627         bra_div.appendChild(bra_label);
628         bra_div.appendChild(bra_select);
629         document.getElementById("bra_and_brav").appendChild(bra_div);
630     }
631     /* Add a prominent label reminding the user what resource type they're
632      * asking about. */
633     document.getElementById("brsrc_list_header").innerHTML = our_brt.name();
634     init_pickup_lib_selector();
635     update_brsrc_list();
636 }
637
638 function update_brsrc_list() {
639     var brsrc_id_list = get_brsrc_id_list();
640     var force_list = (opts.booking_results && opts.booking_results.brsrc) ?
641         opts.booking_results.brsrc.map(function(o) { return o[0]; }) : [];
642
643     sync_brsrc_index_from_ids(brsrc_id_list, force_list);
644
645     var target_selector = document.getElementById("brsrc_list");
646     var selector_memory = new SelectorMemory(target_selector);
647     selector_memory.save();
648     target_selector.innerHTML = "";
649
650     for (var i in brsrc_index) {
651         if (brsrc_index[i].isdeleted() && (!brsrc_index[i].ischanged()))
652             continue;
653
654         var opt = document.createElement("option");
655         opt.setAttribute("value", brsrc_index[i].id());
656         opt.appendChild(document.createTextNode(brsrc_index[i].barcode()));
657
658         if (brsrc_index[i].isdeleted() && (brsrc_index[i].ischanged())) {
659             opt.setAttribute("class", "forced_unavailable");
660             opt.setAttribute("disabled", "disabled");
661         }
662
663         target_selector.appendChild(opt);
664     }
665
666     selector_memory.restore();
667 }
668
669 function any_usable_brsrc() {
670     for (var i in brsrc_index) {
671         if (!brsrc_index[i].isdeleted())
672             return true;
673     }
674     return false;
675 }
676
677 function update_bresv_grid() {
678     var widg = document.getElementById("patron_barcode");
679     if (widg.value != "") {
680         setTimeout(function() {
681             var target = document.getElementById(
682                 "existing_reservation_patron_line"
683             );
684             var patron = get_actor_by_barcode(widg.value);
685             if (patron) {
686                 target.innerHTML = (
687                     localeStrings.HERE_ARE_EXISTING_BRESV + " " +
688                     formal_name(patron) + ": "
689                 );
690             } else {
691                 target.innerHTML = "";
692             }
693         }, 0);
694         setTimeout(function() { init_bresv_grid(widg.value); }, 0);
695     }
696 }
697
698 function init_timestamp_widgets() {
699     var when = ["start", "end"];
700     for (var i in when) {
701         reserve_timestamp_range.update_from_widget(
702             new dijit.form.TimeTextBox({
703                 name: "reserve_time_" + when[i],
704                 value: new Date(),
705                 constraints: {
706                     timePattern: "HH:mm",
707                     clickableIncrement: "T00:15:00",
708                     visibleIncrement: "T00:15:00",
709                     visibleRange: "T01:30:00",
710                 },
711                 onChange: function() {
712                     reserve_timestamp_range.update_from_widget(this);
713                     update_brsrc_list();
714                 }
715             }, "reserve_time_" + when[i])
716         );
717         reserve_timestamp_range.update_from_widget(
718             new dijit.form.DateTextBox({
719                 name: "reserve_date_" + when[i],
720                 value: new Date(),
721                 onChange: function() {
722                     reserve_timestamp_range.update_from_widget(this);
723                     update_brsrc_list();
724                 }
725             }, "reserve_date_" + when[i])
726         );
727     }
728 }
729
730 function cancel_selected_bresv(bresv_dojo_items) {
731     if (bresv_dojo_items && bresv_dojo_items.length > 0 &&
732         (bresv_dojo_items[0].length == undefined ||
733             bresv_dojo_items[0].length > 0)) {
734         cancel_reservations(
735             bresv_dojo_items.map(function(o) { return o.id[0]; })
736         );
737         /* After some delay to allow the cancellations a chance to get
738          * committed, refresh the brsrc list as it might reflect newly
739          * available resources now. */
740         setTimeout(update_brsrc_list, 2000);
741     } else {
742         alert(localeStrings.CXL_BRESV_SELECT_SOMETHING);
743     }
744 }
745
746 /* The following function should return true if the reservation interface
747  * should start normally (show a list of brt to choose from) or false if
748  * it should not (because we've "started" it some other way by setting up
749  * and displaying other widgets).
750  */
751 function early_action_passthru() {
752     if (opts.booking_results) {
753         if (opts.booking_results.brt.length != 1) {
754             alert(localeStrings.NEED_EXACTLY_ONE_BRT_PASSED_IN);
755             return true;
756         } else if (!(our_brt = get_brt_by_id(opts.booking_results.brt[0][0]))) {
757             alert(localeStrings.COULD_NOT_RETRIEVE_BRT_PASSED_IN);
758             return true;
759         }
760
761         init_reservation_interface();
762         return false;
763     }
764
765     if (opts.patron_barcode) {
766         document.getElementById("contain_patron_barcode").style.display="none";
767         document.getElementById("patron_barcode").value = opts.patron_barcode;
768         update_bresv_grid();
769     }
770
771     return true;
772 }
773
774 /*
775  * my_init
776  */
777 function my_init() {
778     hide_dom_element(document.getElementById("brt_reserve_block"));
779     reveal_dom_element(document.getElementById("brt_search_block"));
780     hide_dom_element(document.getElementById("reserve_under"));
781     init_auto_l10n(document.getElementById("auto_l10n_start_here"));
782     init_timestamp_widgets();
783
784     if (!(opts = xulG.bresv_interface_opts)) opts = {};
785     if (early_action_passthru())
786         provide_brt_selector(document.getElementById("brt_selector_here"));
787 }