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("[value='y:YYYY']",dojo.query("[name='type_and_code_pattern']")[0])[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']", this.template)[0],
427 dojo.query("[name='date_month']", this.template)[0]
429 function(menupopup) {
430 menupopup = dojo.query("menupopup", menupopup)[0];
432 _chronstants.month.values,
433 _chronstants.month.names,
434 /* items_only */ true
437 dojo.place(menuitem, menupopup, "last");
444 this.remove_row = function(id) {
445 hard_empty(this.rows[id].element);
446 dojo.destroy(this.rows[id].element);
447 delete this.rows[id];
449 dojo.byId("calendar_change_add_row").disabled = false;
452 this.add_row = function() {
453 var id = this.row_count++;
456 new CalendarChangeRow(dojo.clone(this.template), id, this);
457 dojo.place(this.rows[id].element, this.grid, "last");
460 this.toggle = function(ev) {
461 this.active = ev.target.checked;
462 (this.active ? show : hide)("calendar_change_editor_here");
465 this.compile = function() {
466 if (!this.active) return [];
470 openils.Util.objectProperties(this.rows).sort(num_sort).map(
471 function(key) { return self.rows[key].compile(); }
476 this._init.apply(this, arguments);
479 function ChronRow() {
482 this._init = function(template, subfield, manager) {
483 this.subfield = subfield;
484 this.element = dojo.clone(template);
487 node_by_name("caption_label", this.element),
488 "value", S("chronology." + subfield) + ":"
492 ["caption", "display_in_holding"].forEach(
493 function(o) { self.fields[o] = node_by_name(o, self.element); }
496 this.remover = node_by_name("remover", this.element);
498 this.remover, "onclick", function(){ manager.remove_row(subfield); }
502 this._init.apply(this, arguments);
505 function ChronEditor() {
506 /* TODO make this enforce unique caption values for each row? */
509 this._init = function() {
512 this.subfields = ["i", "j", "k", "l", "m"];
514 this._get_template();
518 this._get_template = function() {
519 var temp_template = dojo.byId("chron_row_template");
520 this.grid = temp_template.parentNode;
521 this.template = this.grid.removeChild(temp_template);
522 this.template.removeAttribute("id");
525 this._test_removability = function(subfield) {
526 var start = this.subfields.indexOf(subfield);
529 /* no such field, not OK to remove */
531 } else if (!this.subfields[start]) {
532 /* field row not present, not OK to remove */
536 var next = this.subfields[start + 1];
537 if (typeof(next) == "undefined") { /* last in set, ok to remove */
540 if (this.rows[next]) { /* NOT last in set, not ok to remove */
542 } else { /* last in set actually present, ok to remove */
548 this.remove_row = function(subfield) {
549 if (this._test_removability(subfield)) {
550 hard_empty(this.rows[subfield].element);
551 dojo.destroy(this.rows[subfield].element);
552 delete this.rows[subfield];
554 dojo.byId("chron_add_row").disabled = false;
556 alert(S("not_removable_row"));
560 this.add_row = function() {
561 var available = this.subfields.filter(
562 function(subfield) { return !Boolean(self.rows[subfield]); }
565 if (available.length) {
566 var subfield = available.shift();
567 if (!available.length)
568 dojo.byId("chron_add_row").disabled = true;
570 /* We shouldn't really be able to get here. */
574 this.rows[subfield] =
575 new ChronRow(dojo.clone(this.template), subfield, this);
577 dojo.place(this.rows[subfield].element, this.grid, "last");
580 this.toggle = function(ev) {
581 this.active = ev.target.checked;
582 (this.active ? show : hide)("chron_editor_here");
585 this.compile = function() {
586 if (!this.active) return [];
588 return this.subfields.filter(
589 function(subfield) { return Boolean(self.rows[subfield]); }
591 function(result, subfield) {
592 var caption = self.rows[subfield].fields.caption.value;
593 if (!self.rows[subfield].fields.display_in_holding.checked)
594 caption = "(" + caption + ")";
595 return result.concat([subfield, caption]);
600 this._init.apply(this, arguments);
606 this._init = function(template, subfield, manager) {
607 this.subfield = subfield;
608 this.element = dojo.clone(template);
611 ["caption","units_per","units_per_number","continuity","remover"].
613 function(o) { self.fields[o] = node_by_name(o, self.element); }
616 if (subfield == "a" || subfield == "g") {
617 ["units_per", "continuity"].forEach(
618 function(o) { soft_hide(node_by_name(o, self.element)); }
622 var caption_id = "enum_caption_" + subfield;
623 var caption_label = node_by_name("caption_label", this.element);
624 dojo.attr(this.fields.caption, "id", caption_id);
625 dojo.attr(caption_label, "control", caption_id);
626 dojo.attr(caption_label, "value", S("enumeration." + subfield) + ":");
628 this.remover = this.fields.remover;
630 this.remover, "onclick", function(){manager.remove_row(subfield);}
634 this._init.apply(this, arguments);
637 function EnumEditor() {
640 this._init = function() {
641 this.normal_rows = {};
644 this.normal_subfields = ["a","b","c","d","e","f"];
645 this.alt_subfields = ["g","h"];
647 this._get_template();
648 this.add_normal_row();
651 this._get_template = function() {
652 var temp_template = dojo.byId("enum_row_template");
653 this.grid = temp_template.parentNode;
654 this.template = this.grid.removeChild(temp_template);
655 this.template.removeAttribute("id");
658 this.remove_row = function(subfield) {
659 if (this._test_removability(subfield)) {
660 var add_button = "enum_add_normal_row";
661 var set = this.normal_rows;
662 if (!set[subfield]) {
664 add_button = "enum_add_alt_row";
667 hard_empty(set[subfield].element);
668 dojo.destroy(set[subfield].element);
669 delete set[subfield];
670 dojo.byId(add_button).disabled = false;
672 alert(S("not_removable_row"));
676 this._test_removability = function(id) {
677 var set = this.normal_subfields;
678 var rows = this.normal_rows;
679 var start = set.indexOf(id);
682 set = this.alt_subfields;
683 rows = this.alt_rows;
684 start = set.indexOf(id);
688 /* no such field, not OK to remove */
690 } else if (!set[start]) {
691 /* field row not present, not OK to remove */
695 var next = set[start + 1];
696 if (typeof(next) == "undefined") { /* last in set, ok to remove */
699 if (rows[next]) { /* NOT last in set, not ok to remove */
701 } else { /* last in set actually present, ok to remove */
707 this.add_normal_row = function() {
708 var available = this.normal_subfields.filter(
709 function(subfield) { return !Boolean(self.normal_rows[subfield]); }
711 if (available.length) {
712 var subfield = available.shift();
713 if (!available.length) {
714 /* If that was the last available normal row, disable the
715 * add rows button. */
716 dojo.byId("enum_add_normal_row").disabled = true;
719 /* We shouldn't really be able to get here. */
723 this.normal_rows[subfield] =
724 new EnumRow(dojo.clone(this.template), subfield, this);
726 dojo.place(this.normal_rows[subfield].element, this.grid, "last");
729 this.add_alt_row = function() {
730 var available = this.alt_subfields.filter(
731 function(subfield) { return !Boolean(self.alt_rows[subfield]); }
733 if (available.length) {
734 var subfield = available.shift();
735 if (!available.length) {
736 /* If that was the last available normal row, disable the
737 * add rows button. */
738 dojo.byId("enum_add_alt_row").disabled = true;
741 /* We shouldn't really be able to get here. */
745 this.alt_rows[subfield] =
746 new EnumRow(dojo.clone(this.template), subfield, this);
748 dojo.place(this.alt_rows[subfield].element, this.grid, "last");
751 this.toggle = function(ev) {
753 var use_calendar_change = dojo.byId("use_calendar_change");
755 this.active = ev.target.checked;
759 use_calendar_change.disabled = false;
761 use_calendar_change.checked = false;
762 use_calendar_change.doCommand();
763 use_calendar_change.disabled = true;
767 func("enum_editor_here");
770 this.compile = function() {
771 if (!this.active) return [];
773 var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
774 var subfields = [].concat(this.normal_subfields, this.alt_subfields);
776 return subfields.filter(
777 function(subfield) { return Boolean(rows[subfield]); }
779 function(result, subfield) {
780 var fields = rows[subfield].fields;
781 var pairs = [subfield, fields.caption.value];
783 if (subfield != "a" && subfield != "g") {
784 if (fields.units_per.value == "number") {
785 if (fields.units_per_number.value) {
786 pairs = pairs.concat([
787 "u", fields.units_per_number.value,
788 "v", fields.continuity.value
792 pairs = pairs.concat([
793 "u", fields.units_per.value,
794 "v", fields.continuity.value
799 return result.concat(pairs);
804 this._init.apply(this, arguments);
810 var _step_prefix = "wizard_step_";
811 var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
813 this._init = function(onsubmit) {
814 this._onsubmit = onsubmit;
820 this.load = function() {
821 /* The Wizard object will handle simpler parts of the wizard (those
822 * parts with more-or-less static controls) itself, and will
823 * instantiate more specific objects to deal with more dynamic
824 * super-widgets (like the enum and chron editors).
826 this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
828 return dojo.attr(o, "id").match(_step_regex)[1];
832 this.enum_editor = new EnumEditor();
833 this.chron_editor = new ChronEditor();
834 this.calendar_change_editor = new CalendarChangeEditor();
835 this.regularity_editor = new RegularityEditor();
837 this.field_w = dojo.byId("hard_w");
839 dojo.byId("soft_w"), "onchange", function(ev) {
840 var use_regularity = dojo.byId("use_regularity");
841 if (ev.target.value && !use_regularity.checked) {
842 use_regularity.checked = true;
843 use_regularity.doCommand();
849 this.reset = function() {
851 this.show_only_step(this.steps[0]);
852 this.step_bounds_check();
855 this.show_step = function(step) { show(_step_prefix + step); }
856 this.hide_step = function(step) { hide(_step_prefix + step); }
858 this.step_bounds_check = function() {
859 dojo.byId("wizard_previous_step").disabled = this.step < 1;
860 dojo.byId("wizard_next_step").disabled =
861 this.step >= this.steps.length -1;
864 this.show_only_step = function(to_keep) {
866 function(step) { if (step != to_keep) self.hide_step(step); }
868 this.show_step(to_keep);
871 this.previous_step = function() {
872 this.show_only_step(this.steps[--(this.step)]);
873 this.step_bounds_check();
876 /* Figure out the what step we're in, and proceed to the next */
877 this.next_step = function() {
878 this.show_only_step(this.steps[++(this.step)]);
879 this.step_bounds_check();
882 this.frequency_type_toggle = function(which) {
883 var other = which == "soft_w" ? "hard_w" : "soft_w";
885 dojo.byId(other).disabled = true;
886 dojo.byId(which).disabled = false;
887 dojo.byId(which).focus();
889 this.field_w = dojo.byId(which);
892 this.compile = function() {
894 dojo.byId("ind1").value, dojo.byId("ind2").value,
895 "8", "1" /* TODO find out how to best deal with $8 */
898 code = code.concat(this.enum_editor.compile());
899 code = code.concat(this.chron_editor.compile());
901 code = code.concat("w", this.field_w.value);
903 code = code.concat(this.calendar_change_editor.compile());
904 code = code.concat(this.regularity_editor.compile());
909 this.submit = function() {
910 this._onsubmit(js2JSON(this.compile()));
914 this._init.apply(this, arguments);
918 _chronstants.week.names = S("weeks").split("."); /* ., sic */
919 _chronstants.weekday.names = S("weekdays").split(" ");
920 _chronstants.month.names = S("months").split(" ");
921 _chronstants.season.names = S("seasons").split(" ");
923 wizard = new Wizard(window.arguments[0]);