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