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 _date_validate(date_val, month_val) {
53 /* general purpose date validation irrespective of year */
54 date_val = date_val.trim();
56 if (!date_val.match(/^[0123]?\d$/))
59 date_val = Number(date_val); /* do NOT use parseInt */
60 month_val = Number(month_val);
64 } else if (month_val == 2) {
65 return date_val <= 29;
66 } else if ([1,3,5,7,8,10,12].indexOf(month_val) != -1) {
67 return date_val <= 31;
69 return date_val <= 30;
73 function CalendarChangeRow() {
76 this._init = function(template, id, manager) {
77 this.element = dojo.clone(template);
79 this.point_widgets = ["month", "season", "date"].map(
80 function(type) { return node_by_name(type, self.element); }
84 node_by_name("type", this.element), "oncommand", function(ev) {
85 self.point_widgets.forEach(
87 var active = (dojo.attr(w, "name") == ev.target.value);
88 (active ? show : hide)(w);
94 var date_month_selector = node_by_name("date_month", this.element);
97 node_by_name("date_day", this.element), "onchange", function(ev) {
98 if (_date_validate(ev.target.value,date_month_selector.value)){
101 alert(S("bad_date_value"));
108 this.remover = node_by_name("remover", this.element);
110 this.remover, "onclick", function() { manager.remove_row(id); }
114 this._compile_month = function() {
115 return node_by_name("month", this.element).value;
118 this._compile_season = function() {
119 return node_by_name("season", this.element).value;
122 this._compile_date = function() {
123 var n = Number(node_by_name("date_day", this.element).value);
129 return node_by_name("date_month", this.element).value + n;
132 this.compile = function() {
133 var type = node_by_name("type", this.element).value;
135 return type ? this["_compile_" + type]() : [];
138 this._init.apply(this, arguments);
141 function RegularityRow() {
144 this._init = function(template, id, manager) {
146 this.manager = manager;
147 this.element = dojo.clone(template);
149 this.publication_code = null;
150 this.type_and_code_pattern = null;
152 this._prepare_event_handlers(id);
155 this._prepare_event_handlers = function(id) {
157 node_by_name("remove", this.element),
159 function() { self.manager.remove_row(id); }
162 node_by_name("poc", this.element),
165 self.publication_code = ev.target.value;
166 self.update_chron_code_controls();
170 node_by_name("type_and_code_pattern", this.element),
173 self.type_and_code_pattern = ev.target.value;
174 self.update_chron_code_controls();
178 this.add_sub_row_btn = node_by_name("add_sub_row", this.element);
180 this.add_sub_row_btn, "oncommand", function(ev) {
186 this.add_sub_row = function() {
187 var container = dojo.create(
188 "hbox", null, node_by_name("sub_rows_here", this.element), "last"
191 /* Break up our type and code pattern into parts that can be
192 * mapped to widgets. */
193 this.get_code_pattern().map(
195 return self.manufacture_chron_code_control(pattern);
199 dojo.place(control, container, "last");
203 /* special case: add a label for clarity for MMWW */
204 if (this.type_and_code_pattern == "w:MMWW")
205 dojo.create("description", {"value": S("week")}, container, "last");
207 /* another special case: YYYY needs no add/remove subrow buttons */
208 if (this.type_and_code_pattern == "y:YYYY") {
209 this.add_sub_row_btn.disabled = true;
211 this.add_sub_row_btn.disabled = false;
216 "fontWeight": "bold", "color": "red"
219 "tooltiptext": S("remove_sub_row"),
220 "oncommand": function() {
221 hard_empty(container); dojo.destroy(container);
228 this.get_code_pattern = function() {
229 var code_pattern_str = this.type_and_code_pattern.split(":")[1];
231 /* A special case: if the strings is YYYY, return it whole. Otherwise
232 * break it up into two-char parts. These parts come from the
233 * "Possible Code Pattern" column of "Chronology Type and Code
234 * Patterns" of the subfield $y section of the document at
235 * http://www.loc.gov/marc/holdings/hd853855.html
237 * In retrospect, there was no reason to adopt these multi-char
238 * code patterns for this purpose. Something single-char and
239 * quicker to decode would have sufficed, but meh, this works.
241 if (code_pattern_str[0] == "Y")
242 return [code_pattern_str];
244 var code_pattern = [];
245 for (var i=0; code_pattern_str[i]; i+=2)
246 code_pattern.push(code_pattern_str.slice(i, i + 2));
251 this.allow_year_split = function(yes) {
253 dojo.query("[name='type_and_code_pattern'] [value='y:YYYY']")[0],
259 this.update_chron_code_controls = function() {
260 if (!this.type_and_code_pattern || !this.publication_code)
263 this.allow_year_split(this.publication_code != "c");
265 var container = node_by_name("sub_rows_here", this.element);
266 /* for some reason, this repeitition is necessary with XUL documents
268 for (var i = 0 ; i < 2; i++) {
269 hard_empty(container);
270 dojo.forEach(container.childNodes, dojo.destroy);
276 this.manufacture_chron_code_control = function(pattern) {
280 _chronstants.weekday.values, _chronstants.weekday.names
284 return dojo.create( /* XXX TODO change min/max based on month */
295 _chronstants.month.values, _chronstants.month.names
300 _chronstants.season.values, _chronstants.season.names
305 _chronstants.week.values, _chronstants.week.names
311 "disabled": "true", "value": "yyy1/yyy2", "size": 9
318 this.compile = function() {
319 return this.publication_code +
320 this.type_and_code_pattern[0] +
321 dojo.query("hbox", node_by_name("sub_rows_here", this.element)).map(
325 sub_row.childNodes, function(n) {
327 n.nodeName == "menulist" ||
328 n.nodeName == "textbox"
333 if (control.value.match(/^\d$/))
334 t += "0" + control.value;
341 ).join(this.publication_code == "c" ? "/" : ",");
344 this._init.apply(this, arguments);
347 function RegularityEditor() {
350 this._init = function() {
354 this._prepare_template();
358 this._prepare_template = function() {
359 var tmpl = dojo.byId("regularity_template_y");
360 tmpl.parentNode.removeChild(tmpl);
362 this.template = tmpl;
365 this.toggle = function(ev) {
366 this.active = ev.target.checked;
367 (this.active ? show : hide)("regularity_editor_here");
370 this.add_row = function() {
371 var id = this.row_count++;
373 this.rows[id] = new RegularityRow(this.template, id, this);
375 dojo.place(this.rows[id].element, "y_row_before_this", "before");
378 this.remove_row = function(id) {
379 var row = this.rows[id];
380 hard_empty(row.element);
381 dojo.destroy(row.element);
383 delete this.rows[id];
386 this.compile = function() {
387 return openils.Util.objectProperties(this.rows).sort().reduce(
388 function(a, b) { return a.concat(["y", self.rows[b].compile()]); },
393 this._init.apply(this, arguments);
396 function CalendarChangeEditor() {
399 this._init = function() {
403 this._get_template();
407 this._get_template = function() {
408 var temp_template = dojo.byId("calendar_row_template");
409 this.grid = temp_template.parentNode;
410 this.template = this.grid.removeChild(temp_template);
411 this.template.removeAttribute("id");
414 dojo.query("[name='month'] menupopup", this.template)[0],
415 dojo.query("[name='date_month'] menupopup", this.template)[0]
417 function(menupopup) {
419 _chronstants.month.values,
420 _chronstants.month.names,
421 /* items_only */ true
424 dojo.place(menuitem, menupopup, "last");
431 this.remove_row = function(id) {
432 hard_empty(this.rows[id].element);
433 dojo.destroy(this.rows[id].element);
434 delete this.rows[id];
436 dojo.byId("calendar_change_add_row").disabled = false;
439 this.add_row = function() {
440 var id = this.row_count++;
443 new CalendarChangeRow(dojo.clone(this.template), id, this);
444 dojo.place(this.rows[id].element, this.grid, "last");
447 this.toggle = function(ev) {
448 this.active = ev.target.checked;
449 (this.active ? show : hide)("calendar_change_editor_here");
452 this.compile = function() {
453 if (!this.active) return [];
457 openils.Util.objectProperties(this.rows).sort(num_sort).map(
458 function(key) { return self.rows[key].compile(); }
463 this._init.apply(this, arguments);
466 function ChronRow() {
469 this._init = function(template, subfield, manager) {
470 this.subfield = subfield;
471 this.element = dojo.clone(template);
474 node_by_name("caption_label", this.element),
475 "value", S("chronology." + subfield) + ":"
479 ["caption", "display_in_holding"].forEach(
480 function(o) { self.fields[o] = node_by_name(o, self.element); }
483 this.remover = node_by_name("remover", this.element);
485 this.remover, "onclick", function(){ manager.remove_row(subfield); }
489 this._init.apply(this, arguments);
492 function ChronEditor() {
493 /* TODO make this enforce unique caption values for each row? */
496 this._init = function() {
499 this.subfields = ["i", "j", "k", "l", "m"];
501 this._get_template();
505 this._get_template = function() {
506 var temp_template = dojo.byId("chron_row_template");
507 this.grid = temp_template.parentNode;
508 this.template = this.grid.removeChild(temp_template);
509 this.template.removeAttribute("id");
512 this._test_removability = function(subfield) {
513 var start = this.subfields.indexOf(subfield);
516 /* no such field, not OK to remove */
518 } else if (!this.subfields[start]) {
519 /* field row not present, not OK to remove */
523 var next = this.subfields[start + 1];
524 if (typeof(next) == "undefined") { /* last in set, ok to remove */
527 if (this.rows[next]) { /* NOT last in set, not ok to remove */
529 } else { /* last in set actually present, ok to remove */
535 this.remove_row = function(subfield) {
536 if (this._test_removability(subfield)) {
537 hard_empty(this.rows[subfield].element);
538 dojo.destroy(this.rows[subfield].element);
539 delete this.rows[subfield];
541 dojo.byId("chron_add_row").disabled = false;
543 alert(S("not_removable_row"));
547 this.add_row = function() {
548 var available = this.subfields.filter(
549 function(subfield) { return !Boolean(self.rows[subfield]); }
552 if (available.length) {
553 var subfield = available.shift();
554 if (!available.length)
555 dojo.byId("chron_add_row").disabled = true;
557 /* We shouldn't really be able to get here. */
561 this.rows[subfield] =
562 new ChronRow(dojo.clone(this.template), subfield, this);
564 dojo.place(this.rows[subfield].element, this.grid, "last");
567 this.toggle = function(ev) {
568 this.active = ev.target.checked;
569 (this.active ? show : hide)("chron_editor_here");
572 this.compile = function() {
573 if (!this.active) return [];
575 return this.subfields.filter(
576 function(subfield) { return Boolean(self.rows[subfield]); }
578 function(result, subfield) {
579 var caption = self.rows[subfield].fields.caption.value;
580 if (!self.rows[subfield].fields.display_in_holding.checked)
581 caption = "(" + caption + ")";
582 return result.concat([subfield, caption]);
587 this._init.apply(this, arguments);
593 this._init = function(template, subfield, manager) {
594 this.subfield = subfield;
595 this.element = dojo.clone(template);
598 ["caption","units_per","units_per_number","continuity","remover"].
600 function(o) { self.fields[o] = node_by_name(o, self.element); }
603 if (subfield == "a" || subfield == "g") {
604 ["units_per", "continuity"].forEach(
605 function(o) { soft_hide(node_by_name(o, self.element)); }
609 var caption_id = "enum_caption_" + subfield;
610 var caption_label = node_by_name("caption_label", this.element);
611 dojo.attr(this.fields.caption, "id", caption_id);
612 dojo.attr(caption_label, "control", caption_id);
613 dojo.attr(caption_label, "value", S("enumeration." + subfield) + ":");
615 this.remover = this.fields.remover;
617 this.remover, "onclick", function(){manager.remove_row(subfield);}
621 this._init.apply(this, arguments);
624 function EnumEditor() {
627 this._init = function() {
628 this.normal_rows = {};
631 this.normal_subfields = ["a","b","c","d","e","f"];
632 this.alt_subfields = ["g","h"];
634 this._get_template();
635 this.add_normal_row();
638 this._get_template = function() {
639 var temp_template = dojo.byId("enum_row_template");
640 this.grid = temp_template.parentNode;
641 this.template = this.grid.removeChild(temp_template);
642 this.template.removeAttribute("id");
645 this.remove_row = function(subfield) {
646 if (this._test_removability(subfield)) {
647 var add_button = "enum_add_normal_row";
648 var set = this.normal_rows;
649 if (!set[subfield]) {
651 add_button = "enum_add_alt_row";
654 hard_empty(set[subfield].element);
655 dojo.destroy(set[subfield].element);
656 delete set[subfield];
657 dojo.byId(add_button).disabled = false;
659 alert(S("not_removable_row"));
663 this._test_removability = function(id) {
664 var set = this.normal_subfields;
665 var rows = this.normal_rows;
666 var start = set.indexOf(id);
669 set = this.alt_subfields;
670 rows = this.alt_rows;
671 start = set.indexOf(id);
675 /* no such field, not OK to remove */
677 } else if (!set[start]) {
678 /* field row not present, not OK to remove */
682 var next = set[start + 1];
683 if (typeof(next) == "undefined") { /* last in set, ok to remove */
686 if (rows[next]) { /* NOT last in set, not ok to remove */
688 } else { /* last in set actually present, ok to remove */
694 this.add_normal_row = function() {
695 var available = this.normal_subfields.filter(
696 function(subfield) { return !Boolean(self.normal_rows[subfield]); }
698 if (available.length) {
699 var subfield = available.shift();
700 if (!available.length) {
701 /* If that was the last available normal row, disable the
702 * add rows button. */
703 dojo.byId("enum_add_normal_row").disabled = true;
706 /* We shouldn't really be able to get here. */
710 this.normal_rows[subfield] =
711 new EnumRow(dojo.clone(this.template), subfield, this);
713 dojo.place(this.normal_rows[subfield].element, this.grid, "last");
716 this.add_alt_row = function() {
717 var available = this.alt_subfields.filter(
718 function(subfield) { return !Boolean(self.alt_rows[subfield]); }
720 if (available.length) {
721 var subfield = available.shift();
722 if (!available.length) {
723 /* If that was the last available normal row, disable the
724 * add rows button. */
725 dojo.byId("enum_add_alt_row").disabled = true;
728 /* We shouldn't really be able to get here. */
732 this.alt_rows[subfield] =
733 new EnumRow(dojo.clone(this.template), subfield, this);
735 dojo.place(this.alt_rows[subfield].element, this.grid, "last");
738 this.toggle = function(ev) {
740 var use_calendar_change = dojo.byId("use_calendar_change");
742 this.active = ev.target.checked;
746 use_calendar_change.disabled = false;
748 use_calendar_change.checked = false;
749 use_calendar_change.doCommand();
750 use_calendar_change.disabled = true;
754 func("enum_editor_here");
757 this.compile = function() {
758 if (!this.active) return [];
760 var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
761 var subfields = [].concat(this.normal_subfields, this.alt_subfields);
763 return subfields.filter(
764 function(subfield) { return Boolean(rows[subfield]); }
766 function(result, subfield) {
767 var fields = rows[subfield].fields;
768 var pairs = [subfield, fields.caption.value];
770 if (subfield != "a" && subfield != "g") {
771 if (fields.units_per.value == "number") {
772 if (fields.units_per_number.value) {
773 pairs = pairs.concat([
774 "u", fields.units_per_number.value,
775 "v", fields.continuity.value
779 pairs = pairs.concat([
780 "u", fields.units_per.value,
781 "v", fields.continuity.value
786 return result.concat(pairs);
791 this._init.apply(this, arguments);
797 var _step_prefix = "wizard_step_";
798 var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
800 this._init = function(onsubmit) {
801 this._onsubmit = onsubmit;
807 this.load = function() {
808 /* The Wizard object will handle simpler parts of the wizard (those
809 * parts with more-or-less static controls) itself, and will
810 * instantiate more specific objects to deal with more dynamic
811 * super-widgets (like the enum and chron editors).
813 this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
815 return dojo.attr(o, "id").match(_step_regex)[1];
819 this.enum_editor = new EnumEditor();
820 this.chron_editor = new ChronEditor();
821 this.calendar_change_editor = new CalendarChangeEditor();
822 this.regularity_editor = new RegularityEditor();
824 this.field_w = dojo.byId("hard_w");
827 this.reset = function() {
829 this.show_only_step(this.steps[0]);
830 this.step_bounds_check();
833 this.show_step = function(step) { show(_step_prefix + step); }
834 this.hide_step = function(step) { hide(_step_prefix + step); }
836 this.step_bounds_check = function() {
837 dojo.byId("wizard_previous_step").disabled = this.step < 1;
838 dojo.byId("wizard_next_step").disabled =
839 this.step >= this.steps.length -1;
842 this.show_only_step = function(to_keep) {
844 function(step) { if (step != to_keep) self.hide_step(step); }
846 this.show_step(to_keep);
849 this.previous_step = function() {
850 this.show_only_step(this.steps[--(this.step)]);
851 this.step_bounds_check();
854 /* Figure out the what step we're in, and proceed to the next */
855 this.next_step = function() {
856 this.show_only_step(this.steps[++(this.step)]);
857 this.step_bounds_check();
860 this.frequency_type_toggle = function(which) {
861 var other = which == "soft_w" ? "hard_w" : "soft_w";
863 dojo.byId(other).disabled = true;
864 dojo.byId(which).disabled = false;
865 dojo.byId(which).focus();
867 this.field_w = dojo.byId(which);
870 this.compile = function() {
872 dojo.byId("ind1").value, dojo.byId("ind2").value,
873 "8", "1" /* TODO find out how to best deal with $8 */
876 code = code.concat(this.enum_editor.compile());
877 code = code.concat(this.chron_editor.compile());
879 code = code.concat("w", this.field_w.value);
881 code = code.concat(this.calendar_change_editor.compile());
882 code = code.concat(this.regularity_editor.compile());
887 this.submit = function() {
888 this._onsubmit(js2JSON(this.compile()));
892 this._init.apply(this, arguments);
896 _chronstants.week.names = S("weeks").split("."); /* ., sic */
897 _chronstants.weekday.names = S("weekdays").split(" ");
898 _chronstants.month.names = S("months").split(" ");
899 _chronstants.season.names = S("seasons").split(" ");
901 wizard = new Wizard(window.arguments[0]);