]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/server/serial/pattern_wizard.js
Serials: pattern wizard bugfix
[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 type ? 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         this.active = ev.target.checked;
173         (this.active ? show : hide)("calendar_change_editor_here");
174     };
175
176     this.compile = function() {
177         if (!this.active) return [];
178
179         return [
180             "x",
181             openils.Util.objectProperties(this.rows).sort(num_sort).map(
182                 function(key) { return self.rows[key].compile(); }
183             ).join(",")
184         ];
185     };
186
187     this._init.apply(this, arguments);
188 }
189
190 function ChronRow() {
191     var self = this;
192
193     this._init = function(template, subfield, manager) {
194         this.subfield = subfield;
195         this.element = dojo.clone(template);
196
197         dojo.attr(
198             node_by_name("caption_label", this.element),
199             "value", S("chronology." + subfield) + ":"
200         );
201
202         this.fields = {};
203         ["caption", "display_in_holding"].forEach(
204             function(o) { self.fields[o] = node_by_name(o, self.element); }
205         );
206
207         this.remover = node_by_name("remover", this.element);
208         dojo.attr(
209             this.remover, "onclick", function(){ manager.remove_row(subfield); }
210         );
211     };
212
213     this._init.apply(this, arguments);
214 };
215
216 function ChronEditor() {
217     /* TODO make this enforce unique caption values for each row? */
218     var self = this;
219
220     this._init = function() {
221         this.rows = {};
222
223         this.subfields = ["i", "j", "k", "l", "m"];
224
225         this._get_template();
226         this.add_row();
227     };
228
229     this._get_template = function() {
230         var temp_template = dojo.byId("chron_row_template");
231         this.grid = temp_template.parentNode;
232         this.template = this.grid.removeChild(temp_template);
233         this.template.removeAttribute("id");
234     };
235
236     this._test_removability = function(subfield) {
237         var start = this.subfields.indexOf(subfield);
238
239         if (start < 0) {
240             /* no such field, not OK to remove */
241             return false;
242         } else if (!this.subfields[start]) {
243             /* field row not present, not OK to remove */
244             return false;
245         }
246
247         var next = this.subfields[start + 1];
248         if (typeof(next) == "undefined") { /* last in set, ok to remove */
249             return true;
250         } else {
251             if (this.rows[next]) { /* NOT last in set, not ok to remove */
252                 return false;
253             } else { /* last in set actually present, ok to remove */
254                 return true;
255             }
256         }
257     };
258
259     this.remove_row = function(subfield) {
260         if (this._test_removability(subfield)) {
261             hard_empty(this.rows[subfield].element);
262             dojo.destroy(this.rows[subfield].element);
263             delete this.rows[subfield];
264
265             dojo.byId("chron_add_row").disabled = false;
266         } else {
267             alert(S("not_removable_row"));
268         }
269     };
270
271     this.add_row = function() {
272         var available = this.subfields.filter(
273             function(subfield) { return !Boolean(self.rows[subfield]); }
274         );
275
276         if (available.length) {
277             var subfield = available.shift();
278             if (!available.length)
279                 dojo.byId("chron_add_row").disabled = true;
280         } else {
281             /* We shouldn't really be able to get here. */
282             return;
283         }
284
285         this.rows[subfield] =
286             new ChronRow(dojo.clone(this.template), subfield, this);
287
288         dojo.place(this.rows[subfield].element, this.grid, "last");
289     };
290
291     this.toggle = function(ev) {
292         this.active = ev.target.checked;
293         (this.active ? show : hide)("chron_editor_here");
294     };
295
296     this.compile = function() {
297         if (!this.active) return [];
298
299         return this.subfields.filter(
300             function(subfield) { return Boolean(self.rows[subfield]); }
301         ).reduce(
302             function(result, subfield) {
303                 var caption = self.rows[subfield].fields.caption.value;
304                 if (!self.rows[subfield].fields.display_in_holding.checked)
305                     caption = "(" + caption + ")";
306                 return result.concat([subfield, caption]);
307             }, []
308         );
309     };
310
311     this._init.apply(this, arguments);
312 }
313
314 function EnumRow() {
315     var self = this;
316
317     this._init = function(template, subfield, manager) {
318         this.subfield = subfield;
319         this.element = dojo.clone(template);
320
321         this.fields = {};
322         ["caption","units_per","units_per_number","continuity","remover"].
323             forEach(
324                 function(o) { self.fields[o] = node_by_name(o, self.element); }
325             );
326
327         if (subfield == "a" || subfield == "g") {
328             ["units_per", "continuity"].forEach(
329                 function(o) { soft_hide(node_by_name(o, self.element)); }
330             );
331         }
332
333         var caption_id = "enum_caption_" + subfield;
334         var caption_label = node_by_name("caption_label", this.element);
335         dojo.attr(this.fields.caption, "id", caption_id);
336         dojo.attr(caption_label, "control", caption_id);
337         dojo.attr(caption_label, "value", S("enumeration." + subfield) + ":");
338
339         this.remover = this.fields.remover;
340         dojo.attr(
341             this.remover, "onclick", function(){manager.remove_row(subfield);}
342         );
343     };
344
345     this._init.apply(this, arguments);
346 };
347
348 function EnumEditor() {
349     var self = this;
350
351     this._init = function() {
352         this.normal_rows = {};
353         this.alt_rows = {};
354
355         this.normal_subfields = ["a","b","c","d","e","f"];
356         this.alt_subfields = ["g","h"];
357
358         this._get_template();
359         this.add_normal_row();
360     };
361
362     this._get_template = function() {
363         var temp_template = dojo.byId("enum_row_template");
364         this.grid = temp_template.parentNode;
365         this.template = this.grid.removeChild(temp_template);
366         this.template.removeAttribute("id");
367     };
368
369     this.remove_row = function(subfield) {
370         if (this._test_removability(subfield)) {
371             var add_button = "enum_add_normal_row";
372             var set = this.normal_rows;
373             if (!set[subfield]) {
374                 set = this.alt_rows;
375                 add_button = "enum_add_alt_row";
376             }
377
378             hard_empty(set[subfield].element);
379             dojo.destroy(set[subfield].element);
380             delete set[subfield];
381             dojo.byId(add_button).disabled = false;
382         } else {
383             alert(S("not_removable_row"));
384         }
385     };
386
387     this._test_removability = function(id) {
388         var set = this.normal_subfields;
389         var rows = this.normal_rows;
390         var start = set.indexOf(id);
391
392         if (start == -1) {
393             set = this.alt_subfields;
394             rows = this.alt_rows;
395             start = set.indexOf(id);
396         }
397
398         if (start < 0) {
399             /* no such field, not OK to remove */
400             return false;
401         } else if (!set[start]) {
402             /* field row not present, not OK to remove */
403             return false;
404         }
405
406         var next = set[start + 1];
407         if (typeof(next) == "undefined") { /* last in set, ok to remove */
408             return true;
409         } else {
410             if (rows[next]) { /* NOT last in set, not ok to remove */
411                 return false;
412             } else { /* last in set actually present, ok to remove */
413                 return true;
414             }
415         }
416     };
417
418     this.add_normal_row = function() {
419         var available = this.normal_subfields.filter(
420             function(subfield) { return !Boolean(self.normal_rows[subfield]); }
421         );
422         if (available.length) {
423             var subfield = available.shift();
424             if (!available.length) {
425                 /* If that was the last available normal row, disable the
426                  * add rows button. */
427                 dojo.byId("enum_add_normal_row").disabled = true;
428             }
429         } else {
430             /* We shouldn't really be able to get here. */
431             return;
432         }
433
434         this.normal_rows[subfield] =
435             new EnumRow(dojo.clone(this.template), subfield, this);
436
437         dojo.place(this.normal_rows[subfield].element, this.grid, "last");
438     };
439
440     this.add_alt_row = function() {
441         var available = this.alt_subfields.filter(
442             function(subfield) { return !Boolean(self.alt_rows[subfield]); }
443         );
444         if (available.length) {
445             var subfield = available.shift();
446             if (!available.length) {
447                 /* If that was the last available normal row, disable the
448                  * add rows button. */
449                 dojo.byId("enum_add_alt_row").disabled = true;
450             }
451         } else {
452             /* We shouldn't really be able to get here. */
453             return;
454         }
455
456         this.alt_rows[subfield] =
457             new EnumRow(dojo.clone(this.template), subfield, this);
458
459         dojo.place(this.alt_rows[subfield].element, this.grid, "last");
460     };
461
462     this.toggle = function(ev) {
463         var func;
464         var use_calendar_change = dojo.byId("use_calendar_change");
465
466         this.active = ev.target.checked;
467
468         if (this.active) {
469             func = show;
470             use_calendar_change.disabled = false;
471         } else {
472             use_calendar_change.checked = false;
473             use_calendar_change.doCommand();
474             use_calendar_change.disabled = true;
475             func = hide;
476         }
477
478         func("enum_editor_here");
479     };
480
481     this.compile = function() {
482         if (!this.active) return [];
483
484         var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
485         var subfields = [].concat(this.normal_subfields, this.alt_subfields);
486
487         return subfields.filter(
488             function(subfield) { return Boolean(rows[subfield]); }
489         ).reduce(
490             function(result, subfield) {
491                 var fields = rows[subfield].fields;
492                 var pairs = [subfield, fields.caption.value];
493
494                 if (subfield != "a" && subfield != "g") {
495                     if (fields.units_per.value == "number") {
496                         if (fields.units_per_number.value) {
497                             pairs = pairs.concat([
498                                 "u", fields.units_per_number.value,
499                                 "v", fields.continuity.value
500                             ]);
501                         }
502                     } else {
503                         pairs = pairs.concat([
504                             "u", fields.units_per.value,
505                             "v", fields.continuity.value
506                         ]);
507                     }
508                 }
509
510                 return result.concat(pairs);
511             }, []
512         );
513     };
514
515     this._init.apply(this, arguments);
516 }
517
518 function Wizard() {
519     var self = this;
520
521     var _step_prefix = "wizard_step_";
522     var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
523
524     this._init = function(onsubmit) {
525         this._onsubmit = onsubmit;
526
527         this.load();
528         this.reset();
529     };
530
531     this.load = function() {
532         /* The Wizard object will handle simpler parts of the wizard (those
533          * parts with more-or-less static controls) itself, and will
534          * instantiate more specific objects to deal with more dynamic
535          * super-widgets (like the enum and chron editors).
536          */
537         this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
538             function(o) {
539                 return dojo.attr(o, "id").match(_step_regex)[1];
540             }
541         );
542
543         this.enum_editor = new EnumEditor();
544         this.chron_editor = new ChronEditor();
545         this.calendar_change_editor = new CalendarChangeEditor();
546
547         this.field_w = dojo.byId("hard_w");
548     };
549
550     this.reset = function() {
551         this.step = 0;
552         this.show_only_step(this.steps[0]);
553         this.step_bounds_check();
554     };
555
556     this.show_step = function(step) { show(_step_prefix + step); }
557     this.hide_step = function(step) { hide(_step_prefix + step); }
558
559     this.step_bounds_check = function() {
560         dojo.byId("wizard_previous_step").disabled = this.step < 1;
561         dojo.byId("wizard_next_step").disabled =
562             this.step >= this.steps.length -1;
563     };
564
565     this.show_only_step = function(to_keep) {
566         this.steps.forEach(
567             function(step) { if (step != to_keep) self.hide_step(step); }
568         );
569         this.show_step(to_keep);
570     };
571
572     this.previous_step = function() {
573         this.show_only_step(this.steps[--(this.step)]);
574         this.step_bounds_check();
575     };
576
577     /* Figure out the what step we're in, and proceed to the next */
578     this.next_step = function() {
579         this.show_only_step(this.steps[++(this.step)]);
580         this.step_bounds_check();
581     };
582
583     this.frequency_type_toggle = function(which) {
584         var other = which == "soft_w" ? "hard_w" : "soft_w";
585
586         dojo.byId(other).disabled = true;
587         dojo.byId(which).disabled = false;
588         dojo.byId(which).focus();
589
590         this.field_w = dojo.byId(which);
591     };
592
593     this.compile = function() {
594         var code = [
595             dojo.byId("ind1").value, dojo.byId("ind2").value,
596             "8", "1" /* TODO find out how to best deal with $8 */
597         ];
598
599         code = code.concat(this.enum_editor.compile());
600         code = code.concat(this.chron_editor.compile());
601
602         code = code.concat("w", this.field_w.value);
603
604         code = code.concat(this.calendar_change_editor.compile());
605
606         return code;
607     };
608
609     this.submit = function() {
610         this._onsubmit(js2JSON(this.compile()));
611         window.close();
612     };
613
614     this._init.apply(this, arguments);
615 }
616
617 function my_init() {
618     wizard = new Wizard(window.arguments[0]);
619 }