]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/server/serial/pattern_wizard.js
6874719d83e1976d2fe9ec5f4b32d978bdc5fe97
[working/Evergreen.git] / Open-ILS / xul / staff_client / server / serial / pattern_wizard.js
1 /* The code in this file relies on common.js */
2
3 dojo.require("openils.Util");
4
5 var wizard;
6
7 function S(k) {
8     return dojo.byId("serialStrings").getString("pattern_wizard." + k).
9         replace("\\n", "\n");
10 }
11
12 var _chronstants = {    /* snicker */
13     "month": {
14         "values": [
15             "01", "02", "03", "04", "05", "06",
16             "07", "08", "09", "10", "11", "12"
17         ]
18     },
19     "weekday": {
20         "values": ["mo", "tu", "we", "th", "fr", "sa", "su"]
21     },
22     "week": {
23         "values": ["00", "01", "02", "03", "04", "05", "97", "98", "99"]
24     },
25     "season": {
26         "values": ["21", "22", "23", "24"]
27     }
28 };
29
30 function _menulist(values, labels, items_only) {
31     var menuitems = [];
32     for (var i = 0; i < values.length; i++) {
33         menuitems.push(
34             dojo.create(
35                 "menuitem", {"value": values[i], "label": labels[i]}
36             )
37         );
38     }
39     if (items_only) {
40         return menuitems;
41     } else {
42         var menupopup = dojo.create("menupopup");
43         menuitems.forEach(
44             function(menuitem) { dojo.place(menuitem, menupopup, "last"); }
45         );
46         var menulist = dojo.create("menulist");
47         dojo.place(menupopup, menulist, "only");
48         return menulist;
49     }
50 }
51
52 function _cap_number_textbox_value(node, max) {
53     if (node.value > max) node.value = max;
54     node.max = max;
55 }
56
57 function _cap_to_month(month, date_box) {
58     if (!date_box)
59         return;
60
61     if (month == "02") {
62         _cap_number_textbox_value(date_box, 29);
63     } else if (
64         ["09", "04", "06", "11"].indexOf(month) != -1
65     ) {
66         _cap_number_textbox_value(date_box, 30);
67     } else {
68         _cap_number_textbox_value(date_box, 31);
69     }
70 }
71
72 function CalendarChangeRow() {
73     var self = this;
74
75     this._init = function(template, id, manager) {
76         this.element = dojo.clone(template);
77
78         this.point_widgets = ["month", "season", "date"].map(
79             function(type) { return node_by_name(type, self.element); }
80         );
81
82         dojo.attr(
83             node_by_name("type", this.element), "oncommand", function(ev) {
84                 self.point_widgets.forEach(
85                     function(w) {
86                         var active = (dojo.attr(w, "name") == ev.target.value);
87                         (active ? show : hide)(w);
88                     }
89                 );
90             }
91         );
92
93         dojo.attr(
94             node_by_name("date_month", this.element),
95             "oncommand",
96             function(ev){
97                 _cap_to_month(
98                     ev.target.value, node_by_name("date_day", self.element)
99                 );
100             }
101         );
102
103         this.remover = node_by_name("remover", this.element);
104         dojo.attr(
105             this.remover, "onclick", function() { manager.remove_row(id); }
106         );
107     };
108
109     this._compile_month = function() {
110         return node_by_name("month", this.element).value;
111     };
112
113     this._compile_season = function() {
114         return node_by_name("season", this.element).value;
115     };
116
117     this._compile_date = function() {
118         var n = Number(node_by_name("date_day", this.element).value);
119         if (n < 10)
120             n = "0" + String(n);
121         else
122             n = String(n);
123
124         return node_by_name("date_month", this.element).value + n;
125     };
126
127     this.compile = function() {
128         var type = node_by_name("type", this.element).value;
129
130         return type ? this["_compile_" + type]() : [];
131     };
132
133     this._init.apply(this, arguments);
134 };
135
136 function RegularityRow() {
137     var self = this;
138
139     this._init = function(template, id, manager) {
140         this.id = id;
141         this.manager = manager;
142         this.element = dojo.clone(template);
143
144         this.publication_code = null;
145         this.type_and_code_pattern = null;
146
147         this._prepare_event_handlers(id);
148     };
149
150     this._prepare_event_handlers = function(id) {
151         dojo.attr(
152             node_by_name("remove", this.element),
153             "oncommand",
154             function() { self.manager.remove_row(id); }
155         );
156         dojo.attr(
157             node_by_name("poc", this.element),
158             "oncommand",
159             function(ev) {
160                 self.publication_code = ev.target.value;
161                 self.update_chron_code_controls();
162             }
163         );
164         dojo.attr(
165             node_by_name("type_and_code_pattern", this.element),
166             "oncommand",
167             function(ev) {
168                 self.type_and_code_pattern = ev.target.value;
169                 self.update_chron_code_controls();
170             }
171         );
172
173         this.add_sub_row_btn = node_by_name("add_sub_row", this.element);
174         dojo.attr(
175             this.add_sub_row_btn, "oncommand", function(ev) {
176                 self.add_sub_row();
177             }
178         );
179     };
180
181     this.add_sub_row = function() {
182         var container = dojo.create(
183             "hbox", null, node_by_name("sub_rows_here", this.element), "last"
184         );
185
186         /* Break up our type and code pattern into parts that can be
187          * mapped to widgets. */
188         this.get_code_pattern().map(
189             function(pattern) {
190                 return self.manufacture_chron_code_control(pattern);
191             }
192         ).forEach(
193             function(control) {
194                 dojo.place(control, container, "last");
195             }
196         );
197
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");
201
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;
205         } else {
206             this.add_sub_row_btn.disabled = false;
207
208             dojo.create(
209                 "button", {
210                     "style": {
211                         "fontWeight": "bold", "color": "red"
212                     },
213                     "label": "X",
214                     "tooltiptext": S("remove_sub_row"),
215                     "oncommand": function() {
216                         hard_empty(container); dojo.destroy(container);
217                     }
218                 }, container, "last"
219             );
220         }
221     };
222
223     this.get_code_pattern = function() {
224         var code_pattern_str = this.type_and_code_pattern.split(":")[1];
225
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
231          *
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.
235          */
236         if (code_pattern_str[0] == "Y")
237             return [code_pattern_str];
238
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));
242
243         return code_pattern;
244     };
245
246     this.allow_year_split = function(yes) {
247         dojo.attr(
248             dojo.query("[name='type_and_code_pattern'] [value='y:YYYY']")[0],
249             "disabled",
250             !yes
251         );
252     };
253
254     this.update_chron_code_controls = function() {
255         if (!this.type_and_code_pattern || !this.publication_code)
256             return;
257
258         this.allow_year_split(this.publication_code != "c");
259
260         var container = node_by_name("sub_rows_here", this.element);
261         /* for some reason, this repeitition is necessary with XUL documents
262          * and dojo */
263         for (var i = 0 ; i < 2; i++) {
264             hard_empty(container);
265             dojo.forEach(container.childNodes, dojo.destroy);
266         }
267
268         this.add_sub_row();
269     };
270
271     this.manufacture_chron_code_control = function(pattern) {
272         return {
273             "dd": function() {
274                 return _menulist(
275                     _chronstants.weekday.values, _chronstants.weekday.names
276                 );
277             },
278             "DD": function() {
279                 return dojo.create( /* XXX TODO change min/max based on month */
280                     "textbox", {
281                         "size": 3,
282                         "type": "number",
283                         "min": 1,
284                         "max": 31
285                     }
286                 );
287             },
288             "MM": function() {
289                 var mm = _menulist(
290                     _chronstants.month.values, _chronstants.month.names
291                 );
292                 dojo.attr(
293                     mm, "oncommand", function(ev) {
294                         _cap_to_month(
295                             dojo.attr(ev.target, "value"),
296                             dojo.query(
297                                 'textbox[type="number"]',
298                                 ev.target.parentNode.parentNode.parentNode
299                                 /* ev.target is the menuITEM node */
300                             )[0]
301                         );
302                     }
303                 );
304                 return mm;
305             },
306             "SS": function() {
307                 return _menulist(
308                     _chronstants.season.values, _chronstants.season.names
309                 );
310             },
311             "WW": function() {
312                 return _menulist(
313                     _chronstants.week.values, _chronstants.week.names
314                 );
315             },
316             "YYYY": function() {
317                 return dojo.create(
318                     "textbox", {
319                         "disabled": "true", "value": "yyy1/yyy2", "size": 9
320                     }
321                 );
322             }
323         }[pattern]();
324     };
325
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(
330                 function(sub_row) {
331                     var t = "";
332                     dojo.filter(
333                         sub_row.childNodes, function(n) {
334                             return (
335                                 n.nodeName == "menulist" ||
336                                 n.nodeName == "textbox"
337                             );
338                         }
339                     ).forEach(
340                         function(control) {
341                             if (control.value.match(/^\d$/))
342                                 t += "0" + control.value;
343                             else
344                                 t += control.value;
345                         }
346                     );
347                     return t;
348                 }
349             ).join(this.publication_code == "c" ? "/" : ",");
350     };
351
352     this._init.apply(this, arguments);
353 }
354
355 function RegularityEditor() {
356     var self = this;
357
358     this._init = function() {
359         this.rows = {};
360         this.row_count = 0;
361
362         this._prepare_template();
363         this.add_row();
364     };
365
366     this._prepare_template = function() {
367         var tmpl = dojo.byId("regularity_template_y");
368         tmpl.parentNode.removeChild(tmpl);
369
370         this.template = tmpl;
371     };
372
373     this.toggle = function(ev) {
374         this.active = ev.target.checked;
375         (this.active ? show : hide)("regularity_editor_here");
376     };
377
378     this.add_row = function() {
379         var id = this.row_count++;
380
381         this.rows[id] = new RegularityRow(this.template, id, this);
382
383         dojo.place(this.rows[id].element, "y_rows_here", "last");
384     };
385
386     this.remove_row = function(id) {
387         var row = this.rows[id];
388         hard_empty(row.element);
389         dojo.destroy(row.element);
390
391         delete this.rows[id];
392     };
393
394     this.compile = function() {
395         if (!this.active) {
396             return [];
397         } else {
398             return openils.Util.objectProperties(this.rows).sort().reduce(
399                 function(a, b){return a.concat(["y",self.rows[b].compile()]);},
400                 []
401             );
402         }
403     };
404
405     this._init.apply(this, arguments);
406 }
407
408 function CalendarChangeEditor() {
409     var self = this;
410
411     this._init = function() {
412         this.rows = {};
413         this.row_count = 0;
414
415         this._get_template();
416         this.add_row();
417     };
418
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");
424
425         [
426             dojo.query("[name='month'] menupopup", this.template)[0],
427             dojo.query("[name='date_month'] menupopup", this.template)[0]
428         ].forEach(
429             function(menupopup) {
430                 _menulist(
431                     _chronstants.month.values,
432                     _chronstants.month.names,
433                     /* items_only */ true
434                 ).forEach(
435                     function(menuitem) {
436                         dojo.place(menuitem, menupopup, "last");
437                     }
438                 );
439             }
440         );
441     };
442
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];
447
448         dojo.byId("calendar_change_add_row").disabled = false;
449     };
450
451     this.add_row = function() {
452         var id = this.row_count++;
453
454         this.rows[id] =
455             new CalendarChangeRow(dojo.clone(this.template), id, this);
456         dojo.place(this.rows[id].element, this.grid, "last");
457     };
458
459     this.toggle = function(ev) {
460         this.active = ev.target.checked;
461         (this.active ? show : hide)("calendar_change_editor_here");
462     };
463
464     this.compile = function() {
465         if (!this.active) return [];
466
467         return [
468             "x",
469             openils.Util.objectProperties(this.rows).sort(num_sort).map(
470                 function(key) { return self.rows[key].compile(); }
471             ).join(",")
472         ];
473     };
474
475     this._init.apply(this, arguments);
476 }
477
478 function ChronRow() {
479     var self = this;
480
481     this._init = function(template, subfield, manager) {
482         this.subfield = subfield;
483         this.element = dojo.clone(template);
484
485         dojo.attr(
486             node_by_name("caption_label", this.element),
487             "value", S("chronology." + subfield) + ":"
488         );
489
490         this.fields = {};
491         ["caption", "display_in_holding"].forEach(
492             function(o) { self.fields[o] = node_by_name(o, self.element); }
493         );
494
495         this.remover = node_by_name("remover", this.element);
496         dojo.attr(
497             this.remover, "onclick", function(){ manager.remove_row(subfield); }
498         );
499     };
500
501     this._init.apply(this, arguments);
502 };
503
504 function ChronEditor() {
505     /* TODO make this enforce unique caption values for each row? */
506     var self = this;
507
508     this._init = function() {
509         this.rows = {};
510
511         this.subfields = ["i", "j", "k", "l", "m"];
512
513         this._get_template();
514         this.add_row();
515     };
516
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");
522     };
523
524     this._test_removability = function(subfield) {
525         var start = this.subfields.indexOf(subfield);
526
527         if (start < 0) {
528             /* no such field, not OK to remove */
529             return false;
530         } else if (!this.subfields[start]) {
531             /* field row not present, not OK to remove */
532             return false;
533         }
534
535         var next = this.subfields[start + 1];
536         if (typeof(next) == "undefined") { /* last in set, ok to remove */
537             return true;
538         } else {
539             if (this.rows[next]) { /* NOT last in set, not ok to remove */
540                 return false;
541             } else { /* last in set actually present, ok to remove */
542                 return true;
543             }
544         }
545     };
546
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];
552
553             dojo.byId("chron_add_row").disabled = false;
554         } else {
555             alert(S("not_removable_row"));
556         }
557     };
558
559     this.add_row = function() {
560         var available = this.subfields.filter(
561             function(subfield) { return !Boolean(self.rows[subfield]); }
562         );
563
564         if (available.length) {
565             var subfield = available.shift();
566             if (!available.length)
567                 dojo.byId("chron_add_row").disabled = true;
568         } else {
569             /* We shouldn't really be able to get here. */
570             return;
571         }
572
573         this.rows[subfield] =
574             new ChronRow(dojo.clone(this.template), subfield, this);
575
576         dojo.place(this.rows[subfield].element, this.grid, "last");
577     };
578
579     this.toggle = function(ev) {
580         this.active = ev.target.checked;
581         (this.active ? show : hide)("chron_editor_here");
582     };
583
584     this.compile = function() {
585         if (!this.active) return [];
586
587         return this.subfields.filter(
588             function(subfield) { return Boolean(self.rows[subfield]); }
589         ).reduce(
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]);
595             }, []
596         );
597     };
598
599     this._init.apply(this, arguments);
600 }
601
602 function EnumRow() {
603     var self = this;
604
605     this._init = function(template, subfield, manager) {
606         this.subfield = subfield;
607         this.element = dojo.clone(template);
608
609         this.fields = {};
610         ["caption","units_per","units_per_number","continuity","remover"].
611             forEach(
612                 function(o) { self.fields[o] = node_by_name(o, self.element); }
613             );
614
615         if (subfield == "a" || subfield == "g") {
616             ["units_per", "continuity"].forEach(
617                 function(o) { soft_hide(node_by_name(o, self.element)); }
618             );
619         }
620
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) + ":");
626
627         this.remover = this.fields.remover;
628         dojo.attr(
629             this.remover, "onclick", function(){manager.remove_row(subfield);}
630         );
631     };
632
633     this._init.apply(this, arguments);
634 };
635
636 function EnumEditor() {
637     var self = this;
638
639     this._init = function() {
640         this.normal_rows = {};
641         this.alt_rows = {};
642
643         this.normal_subfields = ["a","b","c","d","e","f"];
644         this.alt_subfields = ["g","h"];
645
646         this._get_template();
647         this.add_normal_row();
648     };
649
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");
655     };
656
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]) {
662                 set = this.alt_rows;
663                 add_button = "enum_add_alt_row";
664             }
665
666             hard_empty(set[subfield].element);
667             dojo.destroy(set[subfield].element);
668             delete set[subfield];
669             dojo.byId(add_button).disabled = false;
670         } else {
671             alert(S("not_removable_row"));
672         }
673     };
674
675     this._test_removability = function(id) {
676         var set = this.normal_subfields;
677         var rows = this.normal_rows;
678         var start = set.indexOf(id);
679
680         if (start == -1) {
681             set = this.alt_subfields;
682             rows = this.alt_rows;
683             start = set.indexOf(id);
684         }
685
686         if (start < 0) {
687             /* no such field, not OK to remove */
688             return false;
689         } else if (!set[start]) {
690             /* field row not present, not OK to remove */
691             return false;
692         }
693
694         var next = set[start + 1];
695         if (typeof(next) == "undefined") { /* last in set, ok to remove */
696             return true;
697         } else {
698             if (rows[next]) { /* NOT last in set, not ok to remove */
699                 return false;
700             } else { /* last in set actually present, ok to remove */
701                 return true;
702             }
703         }
704     };
705
706     this.add_normal_row = function() {
707         var available = this.normal_subfields.filter(
708             function(subfield) { return !Boolean(self.normal_rows[subfield]); }
709         );
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;
716             }
717         } else {
718             /* We shouldn't really be able to get here. */
719             return;
720         }
721
722         this.normal_rows[subfield] =
723             new EnumRow(dojo.clone(this.template), subfield, this);
724
725         dojo.place(this.normal_rows[subfield].element, this.grid, "last");
726     };
727
728     this.add_alt_row = function() {
729         var available = this.alt_subfields.filter(
730             function(subfield) { return !Boolean(self.alt_rows[subfield]); }
731         );
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;
738             }
739         } else {
740             /* We shouldn't really be able to get here. */
741             return;
742         }
743
744         this.alt_rows[subfield] =
745             new EnumRow(dojo.clone(this.template), subfield, this);
746
747         dojo.place(this.alt_rows[subfield].element, this.grid, "last");
748     };
749
750     this.toggle = function(ev) {
751         var func;
752         var use_calendar_change = dojo.byId("use_calendar_change");
753
754         this.active = ev.target.checked;
755
756         if (this.active) {
757             func = show;
758             use_calendar_change.disabled = false;
759         } else {
760             use_calendar_change.checked = false;
761             use_calendar_change.doCommand();
762             use_calendar_change.disabled = true;
763             func = hide;
764         }
765
766         func("enum_editor_here");
767     };
768
769     this.compile = function() {
770         if (!this.active) return [];
771
772         var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
773         var subfields = [].concat(this.normal_subfields, this.alt_subfields);
774
775         return subfields.filter(
776             function(subfield) { return Boolean(rows[subfield]); }
777         ).reduce(
778             function(result, subfield) {
779                 var fields = rows[subfield].fields;
780                 var pairs = [subfield, fields.caption.value];
781
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
788                             ]);
789                         }
790                     } else {
791                         pairs = pairs.concat([
792                             "u", fields.units_per.value,
793                             "v", fields.continuity.value
794                         ]);
795                     }
796                 }
797
798                 return result.concat(pairs);
799             }, []
800         );
801     };
802
803     this._init.apply(this, arguments);
804 }
805
806 function Wizard() {
807     var self = this;
808
809     var _step_prefix = "wizard_step_";
810     var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
811
812     this._init = function(onsubmit) {
813         this._onsubmit = onsubmit;
814
815         this.load();
816         this.reset();
817     };
818
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).
824          */
825         this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
826             function(o) {
827                 return dojo.attr(o, "id").match(_step_regex)[1];
828             }
829         );
830
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();
835
836         this.field_w = dojo.byId("hard_w");
837         dojo.attr(
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();
843                 }
844             }
845         );
846     };
847
848     this.reset = function() {
849         this.step = 0;
850         this.show_only_step(this.steps[0]);
851         this.step_bounds_check();
852     };
853
854     this.show_step = function(step) { show(_step_prefix + step); }
855     this.hide_step = function(step) { hide(_step_prefix + step); }
856
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;
861     };
862
863     this.show_only_step = function(to_keep) {
864         this.steps.forEach(
865             function(step) { if (step != to_keep) self.hide_step(step); }
866         );
867         this.show_step(to_keep);
868     };
869
870     this.previous_step = function() {
871         this.show_only_step(this.steps[--(this.step)]);
872         this.step_bounds_check();
873     };
874
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();
879     };
880
881     this.frequency_type_toggle = function(which) {
882         var other = which == "soft_w" ? "hard_w" : "soft_w";
883
884         dojo.byId(other).disabled = true;
885         dojo.byId(which).disabled = false;
886         dojo.byId(which).focus();
887
888         this.field_w = dojo.byId(which);
889     };
890
891     this.compile = function() {
892         var code = [
893             dojo.byId("ind1").value, dojo.byId("ind2").value,
894             "8", "1" /* TODO find out how to best deal with $8 */
895         ];
896
897         code = code.concat(this.enum_editor.compile());
898         code = code.concat(this.chron_editor.compile());
899
900         code = code.concat("w", this.field_w.value);
901
902         code = code.concat(this.calendar_change_editor.compile());
903         code = code.concat(this.regularity_editor.compile());
904
905         return code;
906     };
907
908     this.submit = function() {
909         this._onsubmit(js2JSON(this.compile()));
910         window.close();
911     };
912
913     this._init.apply(this, arguments);
914 }
915
916 function my_init() {
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(" ");
921
922     wizard = new Wizard(window.arguments[0]);
923 }