]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/server/serial/pattern_wizard.js
Serials: a wizard for the pattern code field of the caption_and_pattern object
[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 function _month_menuitems() {
13     /* XXX i18n, and also this is just pathetic in general, but a datepicker
14      * seemed wrong since we don't want a year. */
15     return [
16         ["01", "January"],
17         ["02", "February"],
18         ["03", "March"],
19         ["04", "April"],
20         ["05", "May"],
21         ["06", "June"],
22         ["07", "July"],
23         ["08", "August"],
24         ["09", "September"],
25         ["10", "October"],
26         ["11", "November"],
27         ["12", "December"]
28     ].map(
29         function(t) {
30             return dojo.create("menuitem", {"value": t[0], "label": t[1]});
31         }
32     );
33 }
34
35 function _date_validate(date_val, month_val) {
36     /* general purpose date validation irrespective of year */
37     date_val = date_val.trim();
38
39     if (!date_val.match(/^[0123]?\d$/))
40         return false;
41
42     date_val = Number(date_val); /* do NOT use parseInt */
43     month_val = Number(month_val);
44
45     if (date_val < 1) {
46         return false;
47     } else if (month_val == 2) {
48         return date_val <= 29;
49     } else if ([1,3,5,7,8,10,12].indexOf(month_val) != -1) {
50         return date_val <= 31;
51     } else {
52         return date_val <= 30;
53     }
54 }
55
56 function CalendarChangeRow() {
57     var self = this;
58
59     this._init = function(template, id, manager) {
60         this.element = dojo.clone(template);
61
62         this.point_widgets = ["month", "season", "date"].map(
63             function(type) { return node_by_name(type, self.element); }
64         );
65
66         dojo.attr(
67             node_by_name("type", this.element), "oncommand", function(ev) {
68                 self.point_widgets.forEach(
69                     function(w) {
70                         var active = (dojo.attr(w, "name") == ev.target.value);
71                         (active ? show : hide)(w);
72                     }
73                 );
74             }
75         );
76
77         var date_month_selector = node_by_name("date_month", this.element);
78
79         dojo.attr(
80             node_by_name("date_day", this.element), "onchange", function(ev) {
81                 if (_date_validate(ev.target.value,date_month_selector.value)){
82                     return true;
83                 } else {
84                     alert(S("bad_date_value"));
85                     ev.target.focus();
86                     return false;
87                 }
88             }
89         );
90
91         this.remover = node_by_name("remover", this.element);
92         dojo.attr(
93             this.remover, "onclick", function() { manager.remove_row(id); }
94         );
95     };
96
97     this._compile_month = function() {
98         return node_by_name("month", this.element).value;
99     };
100
101     this._compile_season = function() {
102         return node_by_name("season", this.element).value;
103     };
104
105     this._compile_date = function() {
106         var n = Number(node_by_name("date_day", this.element).value);
107         if (n < 10)
108             n = "0" + String(n);
109         else
110             n = String(n);
111
112         return node_by_name("date_month", this.element).value + n;
113     };
114
115     this.compile = function() {
116         var type = node_by_name("type", this.element).value;
117
118         return this["_compile_" + type]();
119     };
120
121     this._init.apply(this, arguments);
122 };
123
124 function CalendarChangeEditor() {
125     var self = this;
126
127     this._init = function() {
128         this.rows = {};
129         this.row_count = 0;
130
131         this._get_template();
132         this.add_row();
133     };
134
135     this._get_template = function() {
136         var temp_template = dojo.byId("calendar_row_template");
137         this.grid = temp_template.parentNode;
138         this.template = this.grid.removeChild(temp_template);
139         this.template.removeAttribute("id");
140
141         [
142             dojo.query("[name='month'] menupopup", this.template)[0],
143             dojo.query("[name='date_month'] menupopup", this.template)[0]
144         ].forEach(
145             function(menupopup) {
146                 _month_menuitems().forEach(
147                     function(menuitem) {
148                         dojo.place(menuitem, menupopup, "last");
149                     }
150                 );
151             }
152         );
153     };
154
155     this.remove_row = function(id) {
156         hard_empty(this.rows[id].element);
157         dojo.destroy(this.rows[id].element);
158         delete this.rows[id];
159
160         dojo.byId("calendar_change_add_row").disabled = false;
161     };
162
163     this.add_row = function() {
164         var id = this.row_count++;
165
166         this.rows[id] =
167             new CalendarChangeRow(dojo.clone(this.template), id, this);
168         dojo.place(this.rows[id].element, this.grid, "last");
169     };
170
171     this.toggle = function(ev) {
172         (ev.target.checked ? show : hide)("calendar_change_editor_here");
173     };
174
175     this.compile = function() {
176         return [
177             "x",
178             openils.Util.objectProperties(this.rows).sort(num_sort).map(
179                 function(key) { return self.rows[key].compile(); }
180             ).join(",")
181         ];
182     };
183
184     this._init.apply(this, arguments);
185 }
186
187 function ChronRow() {
188     var self = this;
189
190     this._init = function(template, subfield, manager) {
191         this.subfield = subfield;
192         this.element = dojo.clone(template);
193
194         dojo.attr(
195             node_by_name("caption_label", this.element),
196             "value", S("chronology." + subfield) + ":"
197         );
198
199         this.fields = {};
200         ["caption", "display_in_holding"].forEach(
201             function(o) { self.fields[o] = node_by_name(o, self.element); }
202         );
203
204         this.remover = node_by_name("remover", this.element);
205         dojo.attr(
206             this.remover, "onclick", function(){ manager.remove_row(subfield); }
207         );
208     };
209
210     this._init.apply(this, arguments);
211 };
212
213 function ChronEditor() {
214     /* TODO make this enforce unique caption values for each row? */
215     var self = this;
216
217     this._init = function() {
218         this.rows = {};
219
220         this.subfields = ["i", "j", "k", "l", "m"];
221
222         this._get_template();
223         this.add_row();
224     };
225
226     this._get_template = function() {
227         var temp_template = dojo.byId("chron_row_template");
228         this.grid = temp_template.parentNode;
229         this.template = this.grid.removeChild(temp_template);
230         this.template.removeAttribute("id");
231     };
232
233     this._test_removability = function(subfield) {
234         var start = this.subfields.indexOf(subfield);
235
236         if (start < 0) {
237             /* no such field, not OK to remove */
238             return false;
239         } else if (!this.subfields[start]) {
240             /* field row not present, not OK to remove */
241             return false;
242         }
243
244         var next = this.subfields[start + 1];
245         if (typeof(next) == "undefined") { /* last in set, ok to remove */
246             return true;
247         } else {
248             if (this.rows[next]) { /* NOT last in set, not ok to remove */
249                 return false;
250             } else { /* last in set actually present, ok to remove */
251                 return true;
252             }
253         }
254     };
255
256     this.remove_row = function(subfield) {
257         if (this._test_removability(subfield)) {
258             hard_empty(this.rows[subfield].element);
259             dojo.destroy(this.rows[subfield].element);
260             delete this.rows[subfield];
261
262             dojo.byId("chron_add_row").disabled = false;
263         } else {
264             alert(S("not_removable_row"));
265         }
266     };
267
268     this.add_row = function() {
269         var available = this.subfields.filter(
270             function(subfield) { return !Boolean(self.rows[subfield]); }
271         );
272
273         if (available.length) {
274             var subfield = available.shift();
275             if (!available.length)
276                 dojo.byId("chron_add_row").disabled = true;
277         } else {
278             /* We shouldn't really be able to get here. */
279             return;
280         }
281
282         this.rows[subfield] =
283             new ChronRow(dojo.clone(this.template), subfield, this);
284
285         dojo.place(this.rows[subfield].element, this.grid, "last");
286     };
287
288     this.toggle = function(ev) {
289         (ev.target.checked ? show : hide)("chron_editor_here");
290     };
291
292     this.compile = function() {
293         return this.subfields.filter(
294             function(subfield) { return Boolean(self.rows[subfield]); }
295         ).reduce(
296             function(result, subfield) {
297                 var caption = self.rows[subfield].fields.caption.value;
298                 if (!self.rows[subfield].fields.display_in_holding.checked)
299                     caption = "(" + caption + ")";
300                 return result.concat([subfield, caption]);
301             }, []
302         );
303     };
304
305     this._init.apply(this, arguments);
306 }
307
308 function EnumRow() {
309     var self = this;
310
311     this._init = function(template, subfield, manager) {
312         this.subfield = subfield;
313         this.element = dojo.clone(template);
314
315         this.fields = {};
316         ["caption","units_per","units_per_number","continuity","remover"].
317             forEach(
318                 function(o) { self.fields[o] = node_by_name(o, self.element); }
319             );
320
321         if (subfield == "a" || subfield == "g") {
322             ["units_per", "continuity"].forEach(
323                 function(o) { soft_hide(node_by_name(o, self.element)); }
324             );
325         }
326
327         var caption_id = "enum_caption_" + subfield;
328         var caption_label = node_by_name("caption_label", this.element);
329         dojo.attr(this.fields.caption, "id", caption_id);
330         dojo.attr(caption_label, "control", caption_id);
331         dojo.attr(caption_label, "value", S("enumeration." + subfield) + ":");
332
333         this.remover = this.fields.remover;
334         dojo.attr(
335             this.remover, "onclick", function(){manager.remove_row(subfield);}
336         );
337     };
338
339     this._init.apply(this, arguments);
340 };
341
342 function EnumEditor() {
343     var self = this;
344
345     this._init = function() {
346         this.normal_rows = {};
347         this.alt_rows = {};
348
349         this.normal_subfields = ["a","b","c","d","e","f"];
350         this.alt_subfields = ["g","h"];
351
352         this._get_template();
353         this.add_normal_row();
354     };
355
356     this._get_template = function() {
357         var temp_template = dojo.byId("enum_row_template");
358         this.grid = temp_template.parentNode;
359         this.template = this.grid.removeChild(temp_template);
360         this.template.removeAttribute("id");
361     };
362
363     this.remove_row = function(subfield) {
364         if (this._test_removability(subfield)) {
365             var add_button = "enum_add_normal_row";
366             var set = this.normal_rows;
367             if (!set[subfield]) {
368                 set = this.alt_rows;
369                 add_button = "enum_add_alt_row";
370             }
371
372             hard_empty(set[subfield].element);
373             dojo.destroy(set[subfield].element);
374             delete set[subfield];
375             dojo.byId(add_button).disabled = false;
376         } else {
377             alert(S("not_removable_row"));
378         }
379     };
380
381     this._test_removability = function(id) {
382         var set = this.normal_subfields;
383         var rows = this.normal_rows;
384         var start = set.indexOf(id);
385
386         if (start == -1) {
387             set = this.alt_subfields;
388             rows = this.alt_rows;
389             start = set.indexOf(id);
390         }
391
392         if (start < 0) {
393             /* no such field, not OK to remove */
394             return false;
395         } else if (!set[start]) {
396             /* field row not present, not OK to remove */
397             return false;
398         }
399
400         var next = set[start + 1];
401         if (typeof(next) == "undefined") { /* last in set, ok to remove */
402             return true;
403         } else {
404             if (rows[next]) { /* NOT last in set, not ok to remove */
405                 return false;
406             } else { /* last in set actually present, ok to remove */
407                 return true;
408             }
409         }
410     };
411
412     this.add_normal_row = function() {
413         var available = this.normal_subfields.filter(
414             function(subfield) { return !Boolean(self.normal_rows[subfield]); }
415         );
416         if (available.length) {
417             var subfield = available.shift();
418             if (!available.length) {
419                 /* If that was the last available normal row, disable the
420                  * add rows button. */
421                 dojo.byId("enum_add_normal_row").disabled = true;
422             }
423         } else {
424             /* We shouldn't really be able to get here. */
425             return;
426         }
427
428         this.normal_rows[subfield] =
429             new EnumRow(dojo.clone(this.template), subfield, this);
430
431         dojo.place(this.normal_rows[subfield].element, this.grid, "last");
432     };
433
434     this.add_alt_row = function() {
435         var available = this.alt_subfields.filter(
436             function(subfield) { return !Boolean(self.alt_rows[subfield]); }
437         );
438         if (available.length) {
439             var subfield = available.shift();
440             if (!available.length) {
441                 /* If that was the last available normal row, disable the
442                  * add rows button. */
443                 dojo.byId("enum_add_alt_row").disabled = true;
444             }
445         } else {
446             /* We shouldn't really be able to get here. */
447             return;
448         }
449
450         this.alt_rows[subfield] =
451             new EnumRow(dojo.clone(this.template), subfield, this);
452
453         dojo.place(this.alt_rows[subfield].element, this.grid, "last");
454     };
455
456     this.toggle = function(ev) {
457         var func;
458         var use_calendar_change = dojo.byId("use_calendar_change");
459
460         if (ev.target.checked) {
461             func = show;
462             use_calendar_change.disabled = false;
463         } else {
464             use_calendar_change.checked = false;
465             use_calendar_change.doCommand();
466             use_calendar_change.disabled = true;
467             func = hide;
468         }
469
470         func("enum_editor_here");
471     };
472
473     this.compile = function() {
474         var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
475         var subfields = [].concat(this.normal_subfields, this.alt_subfields);
476
477         return subfields.filter(
478             function(subfield) { return Boolean(rows[subfield]); }
479         ).reduce(
480             function(result, subfield) {
481                 var fields = rows[subfield].fields;
482                 var pairs = [subfield, fields.caption.value];
483
484                 if (subfield != "a" && subfield != "g") {
485                     if (fields.units_per.value == "number") {
486                         if (fields.units_per_number.value) {
487                             pairs = pairs.concat([
488                                 "u", fields.units_per_number.value,
489                                 "v", fields.continuity.value
490                             ]);
491                         }
492                     } else {
493                         pairs = pairs.concat([
494                             "u", fields.units_per.value,
495                             "v", fields.continuity.value
496                         ]);
497                     }
498                 }
499
500                 return result.concat(pairs);
501             }, []
502         );
503     };
504
505     this._init.apply(this, arguments);
506 }
507
508 function Wizard() {
509     var self = this;
510
511     var _step_prefix = "wizard_step_";
512     var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
513
514     this._init = function(onsubmit) {
515         this._onsubmit = onsubmit;
516
517         this.load();
518         this.reset();
519     };
520
521     this.load = function() {
522         /* The Wizard object will handle simpler parts of the wizard (those
523          * parts with more-or-less static controls) itself, and will
524          * instantiate more specific objects to deal with more dynamic
525          * super-widgets (like the enum and chron editors).
526          */
527         this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
528             function(o) {
529                 return dojo.attr(o, "id").match(_step_regex)[1];
530             }
531         );
532
533         this.enum_editor = new EnumEditor();
534         this.chron_editor = new ChronEditor();
535         this.calendar_change_editor = new CalendarChangeEditor();
536
537         this.field_w = dojo.byId("hard_w");
538     };
539
540     this.reset = function() {
541         this.step = 0;
542         this.show_only_step(this.steps[0]);
543         this.step_bounds_check();
544     };
545
546     this.show_step = function(step) { show(_step_prefix + step); }
547     this.hide_step = function(step) { hide(_step_prefix + step); }
548
549     this.step_bounds_check = function() {
550         dojo.byId("wizard_previous_step").disabled = this.step < 1;
551         dojo.byId("wizard_next_step").disabled =
552             this.step >= this.steps.length -1;
553     };
554
555     this.show_only_step = function(to_keep) {
556         this.steps.forEach(
557             function(step) { if (step != to_keep) self.hide_step(step); }
558         );
559         this.show_step(to_keep);
560     };
561
562     this.previous_step = function() {
563         this.show_only_step(this.steps[--(this.step)]);
564         this.step_bounds_check();
565     };
566
567     /* Figure out the what step we're in, and proceed to the next */
568     this.next_step = function() {
569         this.show_only_step(this.steps[++(this.step)]);
570         this.step_bounds_check();
571     };
572
573     this.frequency_type_toggle = function(which) {
574         var other = which == "soft_w" ? "hard_w" : "soft_w";
575
576         dojo.byId(other).disabled = true;
577         dojo.byId(which).disabled = false;
578         dojo.byId(which).focus();
579
580         this.field_w = dojo.byId(which);
581     };
582
583     this.compile = function() {
584         var code = [
585             dojo.byId("ind1").value, dojo.byId("ind2").value,
586             "8", "1" /* TODO find out how to best deal with $8 */
587         ];
588
589         code = code.concat(this.enum_editor.compile());
590         code = code.concat(this.chron_editor.compile());
591
592         code = code.concat("w", this.field_w.value);
593
594         code = code.concat(this.calendar_change_editor.compile());
595
596         return code;
597     };
598
599     this.submit = function() {
600         this._onsubmit(js2JSON(this.compile()));
601         window.close();
602     };
603
604     this._init.apply(this, arguments);
605 }
606
607 function my_init() {
608     wizard = new Wizard(window.arguments[0]);
609 }