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