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