]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/server/serial/pattern_wizard.js
68c58d3e09ea86bfb330ba09596a9e654880a1b9
[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("[value='y:YYYY']",dojo.query("[name='type_and_code_pattern']")[0])[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']", this.template)[0],
427             dojo.query("[name='date_month']", this.template)[0]
428         ].forEach(
429             function(menupopup) {
430                 menupopup = dojo.query("menupopup", menupopup)[0];
431                 _menulist(
432                     _chronstants.month.values,
433                     _chronstants.month.names,
434                     /* items_only */ true
435                 ).forEach(
436                     function(menuitem) {
437                         dojo.place(menuitem, menupopup, "last");
438                     }
439                 );
440             }
441         );
442     };
443
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];
448
449         dojo.byId("calendar_change_add_row").disabled = false;
450     };
451
452     this.add_row = function() {
453         var id = this.row_count++;
454
455         this.rows[id] =
456             new CalendarChangeRow(dojo.clone(this.template), id, this);
457         dojo.place(this.rows[id].element, this.grid, "last");
458     };
459
460     this.toggle = function(ev) {
461         this.active = ev.target.checked;
462         (this.active ? show : hide)("calendar_change_editor_here");
463     };
464
465     this.compile = function() {
466         if (!this.active) return [];
467
468         return [
469             "x",
470             openils.Util.objectProperties(this.rows).sort(num_sort).map(
471                 function(key) { return self.rows[key].compile(); }
472             ).join(",")
473         ];
474     };
475
476     this._init.apply(this, arguments);
477 }
478
479 function ChronRow() {
480     var self = this;
481
482     this._init = function(template, subfield, manager) {
483         this.subfield = subfield;
484         this.element = dojo.clone(template);
485
486         dojo.attr(
487             node_by_name("caption_label", this.element),
488             "value", S("chronology." + subfield) + ":"
489         );
490
491         this.fields = {};
492         ["caption", "display_in_holding"].forEach(
493             function(o) { self.fields[o] = node_by_name(o, self.element); }
494         );
495
496         this.remover = node_by_name("remover", this.element);
497         dojo.attr(
498             this.remover, "onclick", function(){ manager.remove_row(subfield); }
499         );
500     };
501
502     this._init.apply(this, arguments);
503 };
504
505 function ChronEditor() {
506     /* TODO make this enforce unique caption values for each row? */
507     var self = this;
508
509     this._init = function() {
510         this.rows = {};
511
512         this.subfields = ["i", "j", "k", "l", "m"];
513
514         this._get_template();
515         this.add_row();
516     };
517
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");
523     };
524
525     this._test_removability = function(subfield) {
526         var start = this.subfields.indexOf(subfield);
527
528         if (start < 0) {
529             /* no such field, not OK to remove */
530             return false;
531         } else if (!this.subfields[start]) {
532             /* field row not present, not OK to remove */
533             return false;
534         }
535
536         var next = this.subfields[start + 1];
537         if (typeof(next) == "undefined") { /* last in set, ok to remove */
538             return true;
539         } else {
540             if (this.rows[next]) { /* NOT last in set, not ok to remove */
541                 return false;
542             } else { /* last in set actually present, ok to remove */
543                 return true;
544             }
545         }
546     };
547
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];
553
554             dojo.byId("chron_add_row").disabled = false;
555         } else {
556             alert(S("not_removable_row"));
557         }
558     };
559
560     this.add_row = function() {
561         var available = this.subfields.filter(
562             function(subfield) { return !Boolean(self.rows[subfield]); }
563         );
564
565         if (available.length) {
566             var subfield = available.shift();
567             if (!available.length)
568                 dojo.byId("chron_add_row").disabled = true;
569         } else {
570             /* We shouldn't really be able to get here. */
571             return;
572         }
573
574         this.rows[subfield] =
575             new ChronRow(dojo.clone(this.template), subfield, this);
576
577         dojo.place(this.rows[subfield].element, this.grid, "last");
578     };
579
580     this.toggle = function(ev) {
581         this.active = ev.target.checked;
582         (this.active ? show : hide)("chron_editor_here");
583     };
584
585     this.compile = function() {
586         if (!this.active) return [];
587
588         return this.subfields.filter(
589             function(subfield) { return Boolean(self.rows[subfield]); }
590         ).reduce(
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]);
596             }, []
597         );
598     };
599
600     this._init.apply(this, arguments);
601 }
602
603 function EnumRow() {
604     var self = this;
605
606     this._init = function(template, subfield, manager) {
607         this.subfield = subfield;
608         this.element = dojo.clone(template);
609
610         this.fields = {};
611         ["caption","units_per","units_per_number","continuity","remover"].
612             forEach(
613                 function(o) { self.fields[o] = node_by_name(o, self.element); }
614             );
615
616         if (subfield == "a" || subfield == "g") {
617             ["units_per", "continuity"].forEach(
618                 function(o) { soft_hide(node_by_name(o, self.element)); }
619             );
620         }
621
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) + ":");
627
628         this.remover = this.fields.remover;
629         dojo.attr(
630             this.remover, "onclick", function(){manager.remove_row(subfield);}
631         );
632     };
633
634     this._init.apply(this, arguments);
635 };
636
637 function EnumEditor() {
638     var self = this;
639
640     this._init = function() {
641         this.normal_rows = {};
642         this.alt_rows = {};
643
644         this.normal_subfields = ["a","b","c","d","e","f"];
645         this.alt_subfields = ["g","h"];
646
647         this._get_template();
648         this.add_normal_row();
649     };
650
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");
656     };
657
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]) {
663                 set = this.alt_rows;
664                 add_button = "enum_add_alt_row";
665             }
666
667             hard_empty(set[subfield].element);
668             dojo.destroy(set[subfield].element);
669             delete set[subfield];
670             dojo.byId(add_button).disabled = false;
671         } else {
672             alert(S("not_removable_row"));
673         }
674     };
675
676     this._test_removability = function(id) {
677         var set = this.normal_subfields;
678         var rows = this.normal_rows;
679         var start = set.indexOf(id);
680
681         if (start == -1) {
682             set = this.alt_subfields;
683             rows = this.alt_rows;
684             start = set.indexOf(id);
685         }
686
687         if (start < 0) {
688             /* no such field, not OK to remove */
689             return false;
690         } else if (!set[start]) {
691             /* field row not present, not OK to remove */
692             return false;
693         }
694
695         var next = set[start + 1];
696         if (typeof(next) == "undefined") { /* last in set, ok to remove */
697             return true;
698         } else {
699             if (rows[next]) { /* NOT last in set, not ok to remove */
700                 return false;
701             } else { /* last in set actually present, ok to remove */
702                 return true;
703             }
704         }
705     };
706
707     this.add_normal_row = function() {
708         var available = this.normal_subfields.filter(
709             function(subfield) { return !Boolean(self.normal_rows[subfield]); }
710         );
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;
717             }
718         } else {
719             /* We shouldn't really be able to get here. */
720             return;
721         }
722
723         this.normal_rows[subfield] =
724             new EnumRow(dojo.clone(this.template), subfield, this);
725
726         dojo.place(this.normal_rows[subfield].element, this.grid, "last");
727     };
728
729     this.add_alt_row = function() {
730         var available = this.alt_subfields.filter(
731             function(subfield) { return !Boolean(self.alt_rows[subfield]); }
732         );
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;
739             }
740         } else {
741             /* We shouldn't really be able to get here. */
742             return;
743         }
744
745         this.alt_rows[subfield] =
746             new EnumRow(dojo.clone(this.template), subfield, this);
747
748         dojo.place(this.alt_rows[subfield].element, this.grid, "last");
749     };
750
751     this.toggle = function(ev) {
752         var func;
753         var use_calendar_change = dojo.byId("use_calendar_change");
754
755         this.active = ev.target.checked;
756
757         if (this.active) {
758             func = show;
759             use_calendar_change.disabled = false;
760         } else {
761             use_calendar_change.checked = false;
762             use_calendar_change.doCommand();
763             use_calendar_change.disabled = true;
764             func = hide;
765         }
766
767         func("enum_editor_here");
768     };
769
770     this.compile = function() {
771         if (!this.active) return [];
772
773         var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
774         var subfields = [].concat(this.normal_subfields, this.alt_subfields);
775
776         return subfields.filter(
777             function(subfield) { return Boolean(rows[subfield]); }
778         ).reduce(
779             function(result, subfield) {
780                 var fields = rows[subfield].fields;
781                 var pairs = [subfield, fields.caption.value];
782
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
789                             ]);
790                         }
791                     } else {
792                         pairs = pairs.concat([
793                             "u", fields.units_per.value,
794                             "v", fields.continuity.value
795                         ]);
796                     }
797                 }
798
799                 return result.concat(pairs);
800             }, []
801         );
802     };
803
804     this._init.apply(this, arguments);
805 }
806
807 function Wizard() {
808     var self = this;
809
810     var _step_prefix = "wizard_step_";
811     var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
812
813     this._init = function(onsubmit) {
814         this._onsubmit = onsubmit;
815
816         this.load();
817         this.reset();
818     };
819
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).
825          */
826         this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
827             function(o) {
828                 return dojo.attr(o, "id").match(_step_regex)[1];
829             }
830         );
831
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();
836
837         this.field_w = dojo.byId("hard_w");
838         dojo.attr(
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();
844                 }
845             }
846         );
847     };
848
849     this.reset = function() {
850         this.step = 0;
851         this.show_only_step(this.steps[0]);
852         this.step_bounds_check();
853     };
854
855     this.show_step = function(step) { show(_step_prefix + step); }
856     this.hide_step = function(step) { hide(_step_prefix + step); }
857
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;
862     };
863
864     this.show_only_step = function(to_keep) {
865         this.steps.forEach(
866             function(step) { if (step != to_keep) self.hide_step(step); }
867         );
868         this.show_step(to_keep);
869     };
870
871     this.previous_step = function() {
872         this.show_only_step(this.steps[--(this.step)]);
873         this.step_bounds_check();
874     };
875
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();
880     };
881
882     this.frequency_type_toggle = function(which) {
883         var other = which == "soft_w" ? "hard_w" : "soft_w";
884
885         dojo.byId(other).disabled = true;
886         dojo.byId(which).disabled = false;
887         dojo.byId(which).focus();
888
889         this.field_w = dojo.byId(which);
890     };
891
892     this.compile = function() {
893         var code = [
894             dojo.byId("ind1").value, dojo.byId("ind2").value,
895             "8", "1" /* TODO find out how to best deal with $8 */
896         ];
897
898         code = code.concat(this.enum_editor.compile());
899         code = code.concat(this.chron_editor.compile());
900
901         code = code.concat("w", this.field_w.value);
902
903         code = code.concat(this.calendar_change_editor.compile());
904         code = code.concat(this.regularity_editor.compile());
905
906         return code;
907     };
908
909     this.submit = function() {
910         this._onsubmit(js2JSON(this.compile()));
911         window.close();
912     };
913
914     this._init.apply(this, arguments);
915 }
916
917 function my_init() {
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(" ");
922
923     wizard = new Wizard(window.arguments[0]);
924 }