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