1 /* The code in this file relies on common.js */
3 dojo.require("openils.Util");
8 return dojo.byId("serialStrings").getString("pattern_wizard." + k).
12 var _chronstants = { /* snicker */
15 "01", "02", "03", "04", "05", "06",
16 "07", "08", "09", "10", "11", "12"
20 "values": ["mo", "tu", "we", "th", "fr", "sa", "su"]
23 "values": ["00", "01", "02", "03", "04", "05", "97", "98", "99"]
26 "values": ["21", "22", "23", "24"]
30 function _menulist(values, labels, items_only) {
32 for (var i = 0; i < values.length; i++) {
35 "menuitem", {"value": values[i], "label": labels[i]}
42 var menupopup = dojo.create("menupopup");
44 function(menuitem) { dojo.place(menuitem, menupopup, "last"); }
46 var menulist = dojo.create("menulist");
47 dojo.place(menupopup, menulist, "only");
52 function _cap_number_textbox_value(node, max) {
53 if (node.value > max) node.value = max;
57 function _cap_to_month(month, date_box) {
62 _cap_number_textbox_value(date_box, 29);
64 ["09", "04", "06", "11"].indexOf(month) != -1
66 _cap_number_textbox_value(date_box, 30);
68 _cap_number_textbox_value(date_box, 31);
72 function CalendarChangeRow() {
75 this._init = function(template, id, manager) {
76 this.element = dojo.clone(template);
78 this.point_widgets = ["month", "season", "date"].map(
79 function(type) { return node_by_name(type, self.element); }
83 node_by_name("type", this.element), "oncommand", function(ev) {
84 self.point_widgets.forEach(
86 var active = (dojo.attr(w, "name") == ev.target.value);
87 (active ? show : hide)(w);
94 node_by_name("date_month", this.element),
98 ev.target.value, node_by_name("date_day", self.element)
103 this.remover = node_by_name("remover", this.element);
105 this.remover, "onclick", function() { manager.remove_row(id); }
109 this._compile_month = function() {
110 return node_by_name("month", this.element).value;
113 this._compile_season = function() {
114 return node_by_name("season", this.element).value;
117 this._compile_date = function() {
118 var n = Number(node_by_name("date_day", this.element).value);
124 return node_by_name("date_month", this.element).value + n;
127 this.compile = function() {
128 var type = node_by_name("type", this.element).value;
130 return type ? this["_compile_" + type]() : [];
133 this._init.apply(this, arguments);
136 function RegularityRow() {
139 this._init = function(template, id, manager) {
141 this.manager = manager;
142 this.element = dojo.clone(template);
144 this.publication_code = null;
145 this.type_and_code_pattern = null;
147 this._prepare_event_handlers(id);
150 this._prepare_event_handlers = function(id) {
152 node_by_name("remove", this.element),
154 function() { self.manager.remove_row(id); }
157 node_by_name("poc", this.element),
160 self.publication_code = ev.target.value;
161 self.update_chron_code_controls();
165 node_by_name("type_and_code_pattern", this.element),
168 self.type_and_code_pattern = ev.target.value;
169 self.update_chron_code_controls();
173 this.add_sub_row_btn = node_by_name("add_sub_row", this.element);
175 this.add_sub_row_btn, "oncommand", function(ev) {
181 this.add_sub_row = function() {
182 var container = dojo.create(
183 "hbox", null, node_by_name("sub_rows_here", this.element), "last"
186 /* Break up our type and code pattern into parts that can be
187 * mapped to widgets. */
188 this.get_code_pattern().map(
190 return self.manufacture_chron_code_control(pattern);
194 dojo.place(control, container, "last");
198 /* special case: add a label for clarity for MMWW */
199 if (this.type_and_code_pattern == "w:MMWW")
200 dojo.create("description", {"value": S("week")}, container, "last");
202 /* another special case: YYYY needs no add/remove subrow buttons */
203 if (this.type_and_code_pattern == "y:YYYY") {
204 this.add_sub_row_btn.disabled = true;
206 this.add_sub_row_btn.disabled = false;
211 "fontWeight": "bold", "color": "red"
214 "tooltiptext": S("remove_sub_row"),
215 "oncommand": function() {
216 hard_empty(container); dojo.destroy(container);
223 this.get_code_pattern = function() {
224 var code_pattern_str = this.type_and_code_pattern.split(":")[1];
226 /* A special case: if the strings is YYYY, return it whole. Otherwise
227 * break it up into two-char parts. These parts come from the
228 * "Possible Code Pattern" column of "Chronology Type and Code
229 * Patterns" of the subfield $y section of the document at
230 * http://www.loc.gov/marc/holdings/hd853855.html
232 * In retrospect, there was no reason to adopt these multi-char
233 * code patterns for this purpose. Something single-char and
234 * quicker to decode would have sufficed, but meh, this works.
236 if (code_pattern_str[0] == "Y")
237 return [code_pattern_str];
239 var code_pattern = [];
240 for (var i=0; code_pattern_str[i]; i+=2)
241 code_pattern.push(code_pattern_str.slice(i, i + 2));
246 this.allow_year_split = function(yes) {
248 dojo.query("[name='type_and_code_pattern'] [value='y:YYYY']")[0],
254 this.update_chron_code_controls = function() {
255 if (!this.type_and_code_pattern || !this.publication_code)
258 this.allow_year_split(this.publication_code != "c");
260 var container = node_by_name("sub_rows_here", this.element);
261 /* for some reason, this repeitition is necessary with XUL documents
263 for (var i = 0 ; i < 2; i++) {
264 hard_empty(container);
265 dojo.forEach(container.childNodes, dojo.destroy);
271 this.manufacture_chron_code_control = function(pattern) {
275 _chronstants.weekday.values, _chronstants.weekday.names
279 return dojo.create( /* XXX TODO change min/max based on month */
290 _chronstants.month.values, _chronstants.month.names
293 mm, "oncommand", function(ev) {
295 dojo.attr(ev.target, "value"),
297 'textbox[type="number"]',
298 ev.target.parentNode.parentNode.parentNode
299 /* ev.target is the menuITEM node */
308 _chronstants.season.values, _chronstants.season.names
313 _chronstants.week.values, _chronstants.week.names
319 "disabled": "true", "value": "yyy1/yyy2", "size": 9
326 this.compile = function() {
327 return this.publication_code +
328 this.type_and_code_pattern[0] +
329 dojo.query("hbox", node_by_name("sub_rows_here", this.element)).map(
333 sub_row.childNodes, function(n) {
335 n.nodeName == "menulist" ||
336 n.nodeName == "textbox"
341 if (control.value.match(/^\d$/))
342 t += "0" + control.value;
349 ).join(this.publication_code == "c" ? "/" : ",");
352 this._init.apply(this, arguments);
355 function RegularityEditor() {
358 this._init = function() {
362 this._prepare_template();
366 this._prepare_template = function() {
367 var tmpl = dojo.byId("regularity_template_y");
368 tmpl.parentNode.removeChild(tmpl);
370 this.template = tmpl;
373 this.toggle = function(ev) {
374 this.active = ev.target.checked;
375 (this.active ? show : hide)("regularity_editor_here");
378 this.add_row = function() {
379 var id = this.row_count++;
381 this.rows[id] = new RegularityRow(this.template, id, this);
383 dojo.place(this.rows[id].element, "y_rows_here", "last");
386 this.remove_row = function(id) {
387 var row = this.rows[id];
388 hard_empty(row.element);
389 dojo.destroy(row.element);
391 delete this.rows[id];
394 this.compile = function() {
398 return openils.Util.objectProperties(this.rows).sort().reduce(
399 function(a, b){return a.concat(["y",self.rows[b].compile()]);},
405 this._init.apply(this, arguments);
408 function CalendarChangeEditor() {
411 this._init = function() {
415 this._get_template();
419 this._get_template = function() {
420 var temp_template = dojo.byId("calendar_row_template");
421 this.grid = temp_template.parentNode;
422 this.template = this.grid.removeChild(temp_template);
423 this.template.removeAttribute("id");
426 dojo.query("[name='month'] menupopup", this.template)[0],
427 dojo.query("[name='date_month'] menupopup", this.template)[0]
429 function(menupopup) {
431 _chronstants.month.values,
432 _chronstants.month.names,
433 /* items_only */ true
436 dojo.place(menuitem, menupopup, "last");
443 this.remove_row = function(id) {
444 hard_empty(this.rows[id].element);
445 dojo.destroy(this.rows[id].element);
446 delete this.rows[id];
448 dojo.byId("calendar_change_add_row").disabled = false;
451 this.add_row = function() {
452 var id = this.row_count++;
455 new CalendarChangeRow(dojo.clone(this.template), id, this);
456 dojo.place(this.rows[id].element, this.grid, "last");
459 this.toggle = function(ev) {
460 this.active = ev.target.checked;
461 (this.active ? show : hide)("calendar_change_editor_here");
464 this.compile = function() {
465 if (!this.active) return [];
469 openils.Util.objectProperties(this.rows).sort(num_sort).map(
470 function(key) { return self.rows[key].compile(); }
475 this._init.apply(this, arguments);
478 function ChronRow() {
481 this._init = function(template, subfield, manager) {
482 this.subfield = subfield;
483 this.element = dojo.clone(template);
486 node_by_name("caption_label", this.element),
487 "value", S("chronology." + subfield) + ":"
491 ["caption", "display_in_holding"].forEach(
492 function(o) { self.fields[o] = node_by_name(o, self.element); }
495 this.remover = node_by_name("remover", this.element);
497 this.remover, "onclick", function(){ manager.remove_row(subfield); }
501 this._init.apply(this, arguments);
504 function ChronEditor() {
505 /* TODO make this enforce unique caption values for each row? */
508 this._init = function() {
511 this.subfields = ["i", "j", "k", "l", "m"];
513 this._get_template();
517 this._get_template = function() {
518 var temp_template = dojo.byId("chron_row_template");
519 this.grid = temp_template.parentNode;
520 this.template = this.grid.removeChild(temp_template);
521 this.template.removeAttribute("id");
524 this._test_removability = function(subfield) {
525 var start = this.subfields.indexOf(subfield);
528 /* no such field, not OK to remove */
530 } else if (!this.subfields[start]) {
531 /* field row not present, not OK to remove */
535 var next = this.subfields[start + 1];
536 if (typeof(next) == "undefined") { /* last in set, ok to remove */
539 if (this.rows[next]) { /* NOT last in set, not ok to remove */
541 } else { /* last in set actually present, ok to remove */
547 this.remove_row = function(subfield) {
548 if (this._test_removability(subfield)) {
549 hard_empty(this.rows[subfield].element);
550 dojo.destroy(this.rows[subfield].element);
551 delete this.rows[subfield];
553 dojo.byId("chron_add_row").disabled = false;
555 alert(S("not_removable_row"));
559 this.add_row = function() {
560 var available = this.subfields.filter(
561 function(subfield) { return !Boolean(self.rows[subfield]); }
564 if (available.length) {
565 var subfield = available.shift();
566 if (!available.length)
567 dojo.byId("chron_add_row").disabled = true;
569 /* We shouldn't really be able to get here. */
573 this.rows[subfield] =
574 new ChronRow(dojo.clone(this.template), subfield, this);
576 dojo.place(this.rows[subfield].element, this.grid, "last");
579 this.toggle = function(ev) {
580 this.active = ev.target.checked;
581 (this.active ? show : hide)("chron_editor_here");
584 this.compile = function() {
585 if (!this.active) return [];
587 return this.subfields.filter(
588 function(subfield) { return Boolean(self.rows[subfield]); }
590 function(result, subfield) {
591 var caption = self.rows[subfield].fields.caption.value;
592 if (!self.rows[subfield].fields.display_in_holding.checked)
593 caption = "(" + caption + ")";
594 return result.concat([subfield, caption]);
599 this._init.apply(this, arguments);
605 this._init = function(template, subfield, manager) {
606 this.subfield = subfield;
607 this.element = dojo.clone(template);
610 ["caption","units_per","units_per_number","continuity","remover"].
612 function(o) { self.fields[o] = node_by_name(o, self.element); }
615 if (subfield == "a" || subfield == "g") {
616 ["units_per", "continuity"].forEach(
617 function(o) { soft_hide(node_by_name(o, self.element)); }
621 var caption_id = "enum_caption_" + subfield;
622 var caption_label = node_by_name("caption_label", this.element);
623 dojo.attr(this.fields.caption, "id", caption_id);
624 dojo.attr(caption_label, "control", caption_id);
625 dojo.attr(caption_label, "value", S("enumeration." + subfield) + ":");
627 this.remover = this.fields.remover;
629 this.remover, "onclick", function(){manager.remove_row(subfield);}
633 this._init.apply(this, arguments);
636 function EnumEditor() {
639 this._init = function() {
640 this.normal_rows = {};
643 this.normal_subfields = ["a","b","c","d","e","f"];
644 this.alt_subfields = ["g","h"];
646 this._get_template();
647 this.add_normal_row();
650 this._get_template = function() {
651 var temp_template = dojo.byId("enum_row_template");
652 this.grid = temp_template.parentNode;
653 this.template = this.grid.removeChild(temp_template);
654 this.template.removeAttribute("id");
657 this.remove_row = function(subfield) {
658 if (this._test_removability(subfield)) {
659 var add_button = "enum_add_normal_row";
660 var set = this.normal_rows;
661 if (!set[subfield]) {
663 add_button = "enum_add_alt_row";
666 hard_empty(set[subfield].element);
667 dojo.destroy(set[subfield].element);
668 delete set[subfield];
669 dojo.byId(add_button).disabled = false;
671 alert(S("not_removable_row"));
675 this._test_removability = function(id) {
676 var set = this.normal_subfields;
677 var rows = this.normal_rows;
678 var start = set.indexOf(id);
681 set = this.alt_subfields;
682 rows = this.alt_rows;
683 start = set.indexOf(id);
687 /* no such field, not OK to remove */
689 } else if (!set[start]) {
690 /* field row not present, not OK to remove */
694 var next = set[start + 1];
695 if (typeof(next) == "undefined") { /* last in set, ok to remove */
698 if (rows[next]) { /* NOT last in set, not ok to remove */
700 } else { /* last in set actually present, ok to remove */
706 this.add_normal_row = function() {
707 var available = this.normal_subfields.filter(
708 function(subfield) { return !Boolean(self.normal_rows[subfield]); }
710 if (available.length) {
711 var subfield = available.shift();
712 if (!available.length) {
713 /* If that was the last available normal row, disable the
714 * add rows button. */
715 dojo.byId("enum_add_normal_row").disabled = true;
718 /* We shouldn't really be able to get here. */
722 this.normal_rows[subfield] =
723 new EnumRow(dojo.clone(this.template), subfield, this);
725 dojo.place(this.normal_rows[subfield].element, this.grid, "last");
728 this.add_alt_row = function() {
729 var available = this.alt_subfields.filter(
730 function(subfield) { return !Boolean(self.alt_rows[subfield]); }
732 if (available.length) {
733 var subfield = available.shift();
734 if (!available.length) {
735 /* If that was the last available normal row, disable the
736 * add rows button. */
737 dojo.byId("enum_add_alt_row").disabled = true;
740 /* We shouldn't really be able to get here. */
744 this.alt_rows[subfield] =
745 new EnumRow(dojo.clone(this.template), subfield, this);
747 dojo.place(this.alt_rows[subfield].element, this.grid, "last");
750 this.toggle = function(ev) {
752 var use_calendar_change = dojo.byId("use_calendar_change");
754 this.active = ev.target.checked;
758 use_calendar_change.disabled = false;
760 use_calendar_change.checked = false;
761 use_calendar_change.doCommand();
762 use_calendar_change.disabled = true;
766 func("enum_editor_here");
769 this.compile = function() {
770 if (!this.active) return [];
772 var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
773 var subfields = [].concat(this.normal_subfields, this.alt_subfields);
775 return subfields.filter(
776 function(subfield) { return Boolean(rows[subfield]); }
778 function(result, subfield) {
779 var fields = rows[subfield].fields;
780 var pairs = [subfield, fields.caption.value];
782 if (subfield != "a" && subfield != "g") {
783 if (fields.units_per.value == "number") {
784 if (fields.units_per_number.value) {
785 pairs = pairs.concat([
786 "u", fields.units_per_number.value,
787 "v", fields.continuity.value
791 pairs = pairs.concat([
792 "u", fields.units_per.value,
793 "v", fields.continuity.value
798 return result.concat(pairs);
803 this._init.apply(this, arguments);
809 var _step_prefix = "wizard_step_";
810 var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
812 this._init = function(onsubmit) {
813 this._onsubmit = onsubmit;
819 this.load = function() {
820 /* The Wizard object will handle simpler parts of the wizard (those
821 * parts with more-or-less static controls) itself, and will
822 * instantiate more specific objects to deal with more dynamic
823 * super-widgets (like the enum and chron editors).
825 this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
827 return dojo.attr(o, "id").match(_step_regex)[1];
831 this.enum_editor = new EnumEditor();
832 this.chron_editor = new ChronEditor();
833 this.calendar_change_editor = new CalendarChangeEditor();
834 this.regularity_editor = new RegularityEditor();
836 this.field_w = dojo.byId("hard_w");
838 dojo.byId("soft_w"), "onchange", function(ev) {
839 var use_regularity = dojo.byId("use_regularity");
840 if (ev.target.value && !use_regularity.checked) {
841 use_regularity.checked = true;
842 use_regularity.doCommand();
848 this.reset = function() {
850 this.show_only_step(this.steps[0]);
851 this.step_bounds_check();
854 this.show_step = function(step) { show(_step_prefix + step); }
855 this.hide_step = function(step) { hide(_step_prefix + step); }
857 this.step_bounds_check = function() {
858 dojo.byId("wizard_previous_step").disabled = this.step < 1;
859 dojo.byId("wizard_next_step").disabled =
860 this.step >= this.steps.length -1;
863 this.show_only_step = function(to_keep) {
865 function(step) { if (step != to_keep) self.hide_step(step); }
867 this.show_step(to_keep);
870 this.previous_step = function() {
871 this.show_only_step(this.steps[--(this.step)]);
872 this.step_bounds_check();
875 /* Figure out the what step we're in, and proceed to the next */
876 this.next_step = function() {
877 this.show_only_step(this.steps[++(this.step)]);
878 this.step_bounds_check();
881 this.frequency_type_toggle = function(which) {
882 var other = which == "soft_w" ? "hard_w" : "soft_w";
884 dojo.byId(other).disabled = true;
885 dojo.byId(which).disabled = false;
886 dojo.byId(which).focus();
888 this.field_w = dojo.byId(which);
891 this.compile = function() {
893 dojo.byId("ind1").value, dojo.byId("ind2").value,
894 "8", "1" /* TODO find out how to best deal with $8 */
897 code = code.concat(this.enum_editor.compile());
898 code = code.concat(this.chron_editor.compile());
900 code = code.concat("w", this.field_w.value);
902 code = code.concat(this.calendar_change_editor.compile());
903 code = code.concat(this.regularity_editor.compile());
908 this.submit = function() {
909 this._onsubmit(js2JSON(this.compile()));
913 this._init.apply(this, arguments);
917 _chronstants.week.names = S("weeks").split("."); /* ., sic */
918 _chronstants.weekday.names = S("weekdays").split(" ");
919 _chronstants.month.names = S("months").split(" ");
920 _chronstants.season.names = S("seasons").split(" ");
922 wizard = new Wizard(window.arguments[0]);