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 /* When a caption and pattern has no enumeration, the chronology
549 * fields should go into the enumeration fields. */
550 this._shift_to_enum = function(subfield) {
551 /* i -> a, j -> b, ... */
552 return String.fromCharCode(subfield.charCodeAt(0) - 8);
555 this.remove_row = function(subfield) {
556 if (this._test_removability(subfield)) {
557 hard_empty(this.rows[subfield].element);
558 dojo.destroy(this.rows[subfield].element);
559 delete this.rows[subfield];
561 dojo.byId("chron_add_row").disabled = false;
563 alert(S("not_removable_row"));
567 this.add_row = function() {
568 var available = this.subfields.filter(
569 function(subfield) { return !Boolean(self.rows[subfield]); }
572 if (available.length) {
573 var subfield = available.shift();
574 if (!available.length)
575 dojo.byId("chron_add_row").disabled = true;
577 /* We shouldn't really be able to get here. */
581 this.rows[subfield] =
582 new ChronRow(dojo.clone(this.template), subfield, this);
584 dojo.place(this.rows[subfield].element, this.grid, "last");
587 this.toggle = function(ev) {
588 this.active = ev.target.checked;
589 (this.active ? show : hide)("chron_editor_here");
592 this.compile = function(enum_used) {
593 if (!this.active) return [];
595 return this.subfields.filter(
596 function(subfield) { return Boolean(self.rows[subfield]); }
598 function(result, subfield) {
599 var caption = self.rows[subfield].fields.caption.value;
600 if (!self.rows[subfield].fields.display_in_holding.checked)
601 caption = "(" + caption + ")";
602 return result.concat(
604 (enum_used ? subfield : self._shift_to_enum(subfield)),
612 this._init.apply(this, arguments);
618 this._init = function(template, subfield, manager) {
619 this.subfield = subfield;
620 this.element = dojo.clone(template);
623 ["caption","units_per","units_per_number","continuity","remover"].
625 function(o) { self.fields[o] = node_by_name(o, self.element); }
628 if (subfield == "a" || subfield == "g") {
629 ["units_per", "continuity"].forEach(
630 function(o) { soft_hide(node_by_name(o, self.element)); }
634 var caption_id = "enum_caption_" + subfield;
635 var caption_label = node_by_name("caption_label", this.element);
636 dojo.attr(this.fields.caption, "id", caption_id);
637 dojo.attr(caption_label, "control", caption_id);
638 dojo.attr(caption_label, "value", S("enumeration." + subfield) + ":");
640 this.remover = this.fields.remover;
642 this.remover, "onclick", function(){manager.remove_row(subfield);}
646 this._init.apply(this, arguments);
649 function EnumEditor() {
652 this._init = function() {
653 this.normal_rows = {};
656 this.normal_subfields = ["a","b","c","d","e","f"];
657 this.alt_subfields = ["g","h"];
659 this._get_template();
660 this.add_normal_row();
663 this._get_template = function() {
664 var temp_template = dojo.byId("enum_row_template");
665 this.grid = temp_template.parentNode;
666 this.template = this.grid.removeChild(temp_template);
667 this.template.removeAttribute("id");
670 this.remove_row = function(subfield) {
671 if (this._test_removability(subfield)) {
672 var add_button = "enum_add_normal_row";
673 var set = this.normal_rows;
674 if (!set[subfield]) {
676 add_button = "enum_add_alt_row";
679 hard_empty(set[subfield].element);
680 dojo.destroy(set[subfield].element);
681 delete set[subfield];
682 dojo.byId(add_button).disabled = false;
684 alert(S("not_removable_row"));
688 this._test_removability = function(id) {
689 var set = this.normal_subfields;
690 var rows = this.normal_rows;
691 var start = set.indexOf(id);
694 set = this.alt_subfields;
695 rows = this.alt_rows;
696 start = set.indexOf(id);
700 /* no such field, not OK to remove */
702 } else if (!set[start]) {
703 /* field row not present, not OK to remove */
707 var next = set[start + 1];
708 if (typeof(next) == "undefined") { /* last in set, ok to remove */
711 if (rows[next]) { /* NOT last in set, not ok to remove */
713 } else { /* last in set actually present, ok to remove */
719 this.add_normal_row = function() {
720 var available = this.normal_subfields.filter(
721 function(subfield) { return !Boolean(self.normal_rows[subfield]); }
723 if (available.length) {
724 var subfield = available.shift();
725 if (!available.length) {
726 /* If that was the last available normal row, disable the
727 * add rows button. */
728 dojo.byId("enum_add_normal_row").disabled = true;
731 /* We shouldn't really be able to get here. */
735 this.normal_rows[subfield] =
736 new EnumRow(dojo.clone(this.template), subfield, this);
738 dojo.place(this.normal_rows[subfield].element, this.grid, "last");
741 this.add_alt_row = function() {
742 var available = this.alt_subfields.filter(
743 function(subfield) { return !Boolean(self.alt_rows[subfield]); }
745 if (available.length) {
746 var subfield = available.shift();
747 if (!available.length) {
748 /* If that was the last available normal row, disable the
749 * add rows button. */
750 dojo.byId("enum_add_alt_row").disabled = true;
753 /* We shouldn't really be able to get here. */
757 this.alt_rows[subfield] =
758 new EnumRow(dojo.clone(this.template), subfield, this);
760 dojo.place(this.alt_rows[subfield].element, this.grid, "last");
763 this.toggle = function(ev) {
765 var use_calendar_change = dojo.byId("use_calendar_change");
767 this.active = ev.target.checked;
771 use_calendar_change.disabled = false;
773 use_calendar_change.checked = false;
774 use_calendar_change.doCommand();
775 use_calendar_change.disabled = true;
779 func("enum_editor_here");
782 this.compile = function() {
783 if (!this.active) return [];
785 var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
786 var subfields = [].concat(this.normal_subfields, this.alt_subfields);
788 return subfields.filter(
789 function(subfield) { return Boolean(rows[subfield]); }
791 function(result, subfield) {
792 var fields = rows[subfield].fields;
793 var pairs = [subfield, fields.caption.value];
795 if (subfield != "a" && subfield != "g") {
796 if (fields.units_per.value == "number") {
797 if (fields.units_per_number.value) {
798 pairs = pairs.concat([
799 "u", fields.units_per_number.value,
800 "v", fields.continuity.value
804 pairs = pairs.concat([
805 "u", fields.units_per.value,
806 "v", fields.continuity.value
811 return result.concat(pairs);
816 this._init.apply(this, arguments);
822 var _step_prefix = "wizard_step_";
823 var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
825 this._init = function(onsubmit) {
826 this._onsubmit = onsubmit;
832 this.load = function() {
833 /* The Wizard object will handle simpler parts of the wizard (those
834 * parts with more-or-less static controls) itself, and will
835 * instantiate more specific objects to deal with more dynamic
836 * super-widgets (like the enum and chron editors).
838 this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
840 return dojo.attr(o, "id").match(_step_regex)[1];
844 this.enum_editor = new EnumEditor();
845 this.chron_editor = new ChronEditor();
846 this.calendar_change_editor = new CalendarChangeEditor();
847 this.regularity_editor = new RegularityEditor();
849 this.field_w = dojo.byId("hard_w");
851 dojo.byId("soft_w"), "onchange", function(ev) {
852 var use_regularity = dojo.byId("use_regularity");
853 if (ev.target.value && !use_regularity.checked) {
854 use_regularity.checked = true;
855 use_regularity.doCommand();
861 this.reset = function() {
863 this.show_only_step(this.steps[0]);
864 this.step_bounds_check();
867 this.show_step = function(step) { show(_step_prefix + step); }
868 this.hide_step = function(step) { hide(_step_prefix + step); }
870 this.step_bounds_check = function() {
871 dojo.byId("wizard_previous_step").disabled = this.step < 1;
872 dojo.byId("wizard_next_step").disabled =
873 this.step >= this.steps.length -1;
876 this.show_only_step = function(to_keep) {
878 function(step) { if (step != to_keep) self.hide_step(step); }
880 this.show_step(to_keep);
883 this.previous_step = function() {
884 this.show_only_step(this.steps[--(this.step)]);
885 this.step_bounds_check();
888 /* Figure out the what step we're in, and proceed to the next */
889 this.next_step = function() {
890 this.show_only_step(this.steps[++(this.step)]);
891 this.step_bounds_check();
894 this.frequency_type_toggle = function(which) {
895 var other = which == "soft_w" ? "hard_w" : "soft_w";
897 dojo.byId(other).disabled = true;
898 dojo.byId(which).disabled = false;
899 dojo.byId(which).focus();
901 this.field_w = dojo.byId(which);
904 this.compile = function() {
906 dojo.byId("ind1").value, dojo.byId("ind2").value,
907 "8", "1" /* TODO find out how to best deal with $8 */
910 code = code.concat(this.enum_editor.compile());
911 code = code.concat(this.chron_editor.compile(this.enum_editor.active));
913 code = code.concat("w", this.field_w.value);
915 code = code.concat(this.calendar_change_editor.compile());
916 code = code.concat(this.regularity_editor.compile());
921 this.submit = function() {
922 this._onsubmit(js2JSON(this.compile()));
926 this._init.apply(this, arguments);
930 _chronstants.week.names = S("weeks").split("."); /* ., sic */
931 _chronstants.weekday.names = S("weekdays").split(" ");
932 _chronstants.month.names = S("months").split(" ");
933 _chronstants.season.names = S("seasons").split(" ");
935 wizard = new Wizard(window.arguments[0]);