[open-ils-commits] r17472 - in trunk/Open-ILS: web/opac/locale/en-US xul/staff_client/server/locale/en-US xul/staff_client/server/serial xul/staff_client/server/skin (senator)

svn at svn.open-ils.org svn at svn.open-ils.org
Fri Sep 3 12:30:51 EDT 2010


Author: senator
Date: 2010-09-03 12:30:47 -0400 (Fri, 03 Sep 2010)
New Revision: 17472

Added:
   trunk/Open-ILS/xul/staff_client/server/serial/common.js
   trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard.js
   trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard.xul
   trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard_overlay.xul
Modified:
   trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
   trunk/Open-ILS/xul/staff_client/server/locale/en-US/serial.properties
   trunk/Open-ILS/xul/staff_client/server/serial/batch_receive.js
   trunk/Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul
   trunk/Open-ILS/xul/staff_client/server/serial/scap_editor.js
   trunk/Open-ILS/xul/staff_client/server/serial/scap_editor.xul
   trunk/Open-ILS/xul/staff_client/server/skin/serial.css
Log:
Serials: a wizard for the pattern code field of the caption_and_pattern object

This field holds the same data you'd find in the contents of an 853, 854, or
855 tag.  This wizard aims to make it a little easier for mere mortals to
compose this information, since the correctness of this field is fairly
central to reasonably accurate serials issuance prediction.

Find a button to launch this wizard from the Caption/Pattern interface of the
Serial Control View.

Big thanks to Galen Charlton, Dan Wells and Mike Rylander for all the help in
getting this started and understanding the MARC.

The wizard does still have a way to go before it's everything it can be. It
still needs (in no particular order):
    - scrollbars on that dialog window
    - support for subfield $y and probably $z, possibly others
    - i18n fixes, accessibility improvements
    - more control over subfield $8
    - more input validation and user guidance; sane defaults for some fields?
    - reconsideration of the order of the parts of the wizard
    - finding out if it makes sense to allow $m without $j,$k and $l present



Modified: trunk/Open-ILS/web/opac/locale/en-US/lang.dtd
===================================================================
--- trunk/Open-ILS/web/opac/locale/en-US/lang.dtd	2010-09-03 15:41:05 UTC (rev 17471)
+++ trunk/Open-ILS/web/opac/locale/en-US/lang.dtd	2010-09-03 16:30:47 UTC (rev 17472)
@@ -1571,6 +1571,9 @@
 <!ENTITY staff.serial.mfhd_menu.add.label "Add MFHD Record">
 <!ENTITY staff.serial.mfhd_menu.edit.label "Edit MFHD Record">
 <!ENTITY staff.serial.mfhd_menu.delete.label "Delete MFHD Record">
+<!ENTITY staff.serial.scap_editor.pattern_wizard "Pattern Code Wizard">
+<!ENTITY staff.serial.scap_editor.pattern_wizard.accesskey "Z">
+<!ENTITY staff.serial.scap_editor.modify.accesskey "M">
 <!ENTITY staff.serial.scap_editor.modify "Modify Caption and Pattern(s)">
 <!ENTITY staff.serial.scap_editor.modify.accesskey "M">
 <!ENTITY staff.serial.scap_editor.create "Create Caption and Pattern(s)">

Modified: trunk/Open-ILS/xul/staff_client/server/locale/en-US/serial.properties
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/locale/en-US/serial.properties	2010-09-03 15:41:05 UTC (rev 17471)
+++ trunk/Open-ILS/xul/staff_client/server/locale/en-US/serial.properties	2010-09-03 16:30:47 UTC (rev 17472)
@@ -67,3 +67,18 @@
 batch_receive.cn_for_lib=Do you want to use this call number at %1$s?\nIt doesn't exist there, and it will have to be created.
 batch_receive.missing_units=You have not provided barcodes and call numbers for all of the selected items.  Choose OK to receive those items anyway, or choose Cancel to supply the missing information.
 batch_receive.missing_cn=You cannot assign a barcode without selecting a call number. Please correct the non-conforming units.
+pattern_wizard.enumeration.a=First level
+pattern_wizard.enumeration.b=Second level
+pattern_wizard.enumeration.c=Third level
+pattern_wizard.enumeration.d=Fourth level
+pattern_wizard.enumeration.e=Fifth level
+pattern_wizard.enumeration.f=Sixth level
+pattern_wizard.enumeration.g=First alternate
+pattern_wizard.enumeration.h=Second alternate
+pattern_wizard.chronology.i=First level
+pattern_wizard.chronology.j=Second level
+pattern_wizard.chronology.k=Third level
+pattern_wizard.chronology.l=Fourth level
+pattern_wizard.chronology.m=Alternative numbering scheme
+pattern_wizard.not_removable_row=You cannot remove this row because it's not at the end of the sequence.  Remove later rows first.
+pattern_wizard.bad_date_value=That is not a valid day for that month.

Modified: trunk/Open-ILS/xul/staff_client/server/serial/batch_receive.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/serial/batch_receive.js	2010-09-03 15:41:05 UTC (rev 17471)
+++ trunk/Open-ILS/xul/staff_client/server/serial/batch_receive.js	2010-09-03 16:30:47 UTC (rev 17472)
@@ -1,7 +1,6 @@
+/* The code in this file relies on common.js */
+
 dojo.require("dojo.cookie");
-dojo.require("dojo.date.locale");
-dojo.require("dojo.date.stamp");
-dojo.require("dojo.string");
 dojo.require("openils.Util");
 dojo.require("openils.User");
 dojo.require("openils.CGI");
@@ -9,61 +8,6 @@
 
 var batch_receiver;
 
-String.prototype.trim = function() {return this.replace(/^\s*(.+)\s*$/,"$1");}
-
-/**
- * hard_empty() is needed because dojo.empty() doesn't seem to work on
- * XUL nodes. This also means that dojo.place() with a position argument of
- * "only" doesn't do what it should, but calling hard_empty() on the refnode
- * first will do the trick.
- */
-function hard_empty(node) {
-    if (typeof(node) == "string")
-        node = dojo.byId(node);
-
-    if (node && node.childNodes.length > 0) {
-        dojo.forEach(
-            node.childNodes,
-            function(c) {
-                if (c) {
-                    if (c.childNodes.length > 0)
-                        dojo.forEach(c.childNodes, hard_empty);
-                    dojo.destroy(c);
-                }
-            }
-        );
-    }
-}
-
-function hide(e) {
-    if (typeof(e) == "string") e = dojo.byId(e);
-    openils.Util.addCSSClass(e, "hideme");
-}
-
-function show(e) {
-    if (typeof(e) == "string") e = dojo.byId(e);
-    openils.Util.removeCSSClass(e, "hideme");
-}
-
-function hide_table_cell(e) {
-    if (typeof(e) == "string") e = dojo.byId(e);
-
-    e.style.display = "none";
-    e.style.visibility = "hidden";
-}
-
-function show_table_cell(e) {
-    if (typeof(e) == "string") e = dojo.byId(e);
-    e.style.display = "table-cell";
-    e.style.visibility = "visible";
-}
-
-function busy(on) {
-    if (typeof(busy._window) == "undefined")
-        busy._window = dojo.query("window")[0];
-    busy._window.style.cursor = on ? "wait" : "auto";
-}
-
 function S(k) {
     return dojo.byId("serialStrings").getString("batch_receive." + k).
         replace("\\n", "\n");
@@ -74,15 +18,6 @@
         getFormattedString("batch_receive." + k, args).replace("\\n", "\n");
 }
 
-function T(s) { return document.createTextNode(s); }
-function D(s) {return s ? openils.Util.timeStamp(s,{"selector":"date"}) : "";}
-function node_by_name(s, ctx) {return dojo.query("[name='"+ s +"']",ctx)[0];}
-
-function num_sort(a, b) {
-    [a, b] = [Number(a), Number(b)];
-    return a > b ? 1 : (a < b ? -1 : 0);
-}
-
 function BatchReceiver() {
     var self = this;
 

Modified: trunk/Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul	2010-09-03 15:41:05 UTC (rev 17471)
+++ trunk/Open-ILS/xul/staff_client/server/serial/batch_receive_overlay.xul	2010-09-03 16:30:47 UTC (rev 17472)
@@ -6,10 +6,11 @@
     xmlns:h="http://www.w3.org/1999/xhtml"
     xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
+    <script type="text/javascript" src="/xul/server/serial/common.js" />
     <script type="text/javascript" src="/xul/server/serial/batch_receive.js" />
 
     <box id="batch_receive_main" flex="1" orient="vertical" class="my_overflow">
-        <caption label="&staff.serial.batch_receive;" />
+        <caption class="top" label="&staff.serial.batch_receive;" />
 
         <vbox flex="1" id="batch_receve_main_action">
             <vbox id="batch_receive_bib" class="hideme">

Added: trunk/Open-ILS/xul/staff_client/server/serial/common.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/serial/common.js	                        (rev 0)
+++ trunk/Open-ILS/xul/staff_client/server/serial/common.js	2010-09-03 16:30:47 UTC (rev 17472)
@@ -0,0 +1,62 @@
+String.prototype.trim = function() {return this.replace(/^\s*(.+)\s*$/,"$1");}
+
+/**
+ * hard_empty() is needed because dojo.empty() doesn't seem to work on
+ * XUL nodes. This also means that dojo.place() with a position argument of
+ * "only" doesn't do what it should, but calling hard_empty() on the refnode
+ * first will do the trick.
+ */
+function hard_empty(node) {
+    if (typeof(node) == "string")
+        node = dojo.byId(node);
+    if (node)
+        dojo.forEach(node.childNodes, dojo.destroy);
+}
+
+function hide(e) {
+    if (typeof(e) == "string") e = dojo.byId(e);
+    openils.Util.addCSSClass(e, "hideme");
+}
+
+function show(e) {
+    if (typeof(e) == "string") e = dojo.byId(e);
+    openils.Util.removeCSSClass(e, "hideme");
+}
+
+function hide_table_cell(e) {
+    if (typeof(e) == "string") e = dojo.byId(e);
+
+    e.style.display = "none";
+    e.style.visibility = "hidden";
+}
+
+function show_table_cell(e) {
+    if (typeof(e) == "string") e = dojo.byId(e);
+    e.style.display = "table-cell";
+    e.style.visibility = "visible";
+}
+
+function soft_hide(e) { /* doesn't disrupt XUL grid alignment */
+    if (typeof(e) == "string") e = dojo.byId(e);
+    e.style.visibility = "hidden";
+}
+
+function soft_show(e) {
+    if (typeof(e) == "string") e = dojo.byId(e);
+    e.style.visibility = "visible";
+}
+
+function busy(on) {
+    if (typeof(busy._window) == "undefined")
+        busy._window = dojo.query("window")[0];
+    busy._window.style.cursor = on ? "wait" : "auto";
+}
+
+function T(s) { return document.createTextNode(s); }
+function D(s) {return s ? openils.Util.timeStamp(s, {"selector":"date"}) : "";}
+function node_by_name(s, ctx) {return dojo.query("[name='" + s + "']", ctx)[0];}
+
+function num_sort(a, b) {
+    [a, b] = [Number(a), Number(b)];
+    return a > b ? 1 : (a < b ? -1 : 0);
+}

Added: trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard.js	                        (rev 0)
+++ trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard.js	2010-09-03 16:30:47 UTC (rev 17472)
@@ -0,0 +1,609 @@
+/* The code in this file relies on common.js */
+
+dojo.require("openils.Util");
+
+var wizard;
+
+function S(k) {
+    return dojo.byId("serialStrings").getString("pattern_wizard." + k).
+        replace("\\n", "\n");
+}
+
+function _month_menuitems() {
+    /* XXX i18n, and also this is just pathetic in general, but a datepicker
+     * seemed wrong since we don't want a year. */
+    return [
+        ["01", "January"],
+        ["02", "February"],
+        ["03", "March"],
+        ["04", "April"],
+        ["05", "May"],
+        ["06", "June"],
+        ["07", "July"],
+        ["08", "August"],
+        ["09", "September"],
+        ["10", "October"],
+        ["11", "November"],
+        ["12", "December"]
+    ].map(
+        function(t) {
+            return dojo.create("menuitem", {"value": t[0], "label": t[1]});
+        }
+    );
+}
+
+function _date_validate(date_val, month_val) {
+    /* general purpose date validation irrespective of year */
+    date_val = date_val.trim();
+
+    if (!date_val.match(/^[0123]?\d$/))
+        return false;
+
+    date_val = Number(date_val); /* do NOT use parseInt */
+    month_val = Number(month_val);
+
+    if (date_val < 1) {
+        return false;
+    } else if (month_val == 2) {
+        return date_val <= 29;
+    } else if ([1,3,5,7,8,10,12].indexOf(month_val) != -1) {
+        return date_val <= 31;
+    } else {
+        return date_val <= 30;
+    }
+}
+
+function CalendarChangeRow() {
+    var self = this;
+
+    this._init = function(template, id, manager) {
+        this.element = dojo.clone(template);
+
+        this.point_widgets = ["month", "season", "date"].map(
+            function(type) { return node_by_name(type, self.element); }
+        );
+
+        dojo.attr(
+            node_by_name("type", this.element), "oncommand", function(ev) {
+                self.point_widgets.forEach(
+                    function(w) {
+                        var active = (dojo.attr(w, "name") == ev.target.value);
+                        (active ? show : hide)(w);
+                    }
+                );
+            }
+        );
+
+        var date_month_selector = node_by_name("date_month", this.element);
+
+        dojo.attr(
+            node_by_name("date_day", this.element), "onchange", function(ev) {
+                if (_date_validate(ev.target.value,date_month_selector.value)){
+                    return true;
+                } else {
+                    alert(S("bad_date_value"));
+                    ev.target.focus();
+                    return false;
+                }
+            }
+        );
+
+        this.remover = node_by_name("remover", this.element);
+        dojo.attr(
+            this.remover, "onclick", function() { manager.remove_row(id); }
+        );
+    };
+
+    this._compile_month = function() {
+        return node_by_name("month", this.element).value;
+    };
+
+    this._compile_season = function() {
+        return node_by_name("season", this.element).value;
+    };
+
+    this._compile_date = function() {
+        var n = Number(node_by_name("date_day", this.element).value);
+        if (n < 10)
+            n = "0" + String(n);
+        else
+            n = String(n);
+
+        return node_by_name("date_month", this.element).value + n;
+    };
+
+    this.compile = function() {
+        var type = node_by_name("type", this.element).value;
+
+        return this["_compile_" + type]();
+    };
+
+    this._init.apply(this, arguments);
+};
+
+function CalendarChangeEditor() {
+    var self = this;
+
+    this._init = function() {
+        this.rows = {};
+        this.row_count = 0;
+
+        this._get_template();
+        this.add_row();
+    };
+
+    this._get_template = function() {
+        var temp_template = dojo.byId("calendar_row_template");
+        this.grid = temp_template.parentNode;
+        this.template = this.grid.removeChild(temp_template);
+        this.template.removeAttribute("id");
+
+        [
+            dojo.query("[name='month'] menupopup", this.template)[0],
+            dojo.query("[name='date_month'] menupopup", this.template)[0]
+        ].forEach(
+            function(menupopup) {
+                _month_menuitems().forEach(
+                    function(menuitem) {
+                        dojo.place(menuitem, menupopup, "last");
+                    }
+                );
+            }
+        );
+    };
+
+    this.remove_row = function(id) {
+        hard_empty(this.rows[id].element);
+        dojo.destroy(this.rows[id].element);
+        delete this.rows[id];
+
+        dojo.byId("calendar_change_add_row").disabled = false;
+    };
+
+    this.add_row = function() {
+        var id = this.row_count++;
+
+        this.rows[id] =
+            new CalendarChangeRow(dojo.clone(this.template), id, this);
+        dojo.place(this.rows[id].element, this.grid, "last");
+    };
+
+    this.toggle = function(ev) {
+        (ev.target.checked ? show : hide)("calendar_change_editor_here");
+    };
+
+    this.compile = function() {
+        return [
+            "x",
+            openils.Util.objectProperties(this.rows).sort(num_sort).map(
+                function(key) { return self.rows[key].compile(); }
+            ).join(",")
+        ];
+    };
+
+    this._init.apply(this, arguments);
+}
+
+function ChronRow() {
+    var self = this;
+
+    this._init = function(template, subfield, manager) {
+        this.subfield = subfield;
+        this.element = dojo.clone(template);
+
+        dojo.attr(
+            node_by_name("caption_label", this.element),
+            "value", S("chronology." + subfield) + ":"
+        );
+
+        this.fields = {};
+        ["caption", "display_in_holding"].forEach(
+            function(o) { self.fields[o] = node_by_name(o, self.element); }
+        );
+
+        this.remover = node_by_name("remover", this.element);
+        dojo.attr(
+            this.remover, "onclick", function(){ manager.remove_row(subfield); }
+        );
+    };
+
+    this._init.apply(this, arguments);
+};
+
+function ChronEditor() {
+    /* TODO make this enforce unique caption values for each row? */
+    var self = this;
+
+    this._init = function() {
+        this.rows = {};
+
+        this.subfields = ["i", "j", "k", "l", "m"];
+
+        this._get_template();
+        this.add_row();
+    };
+
+    this._get_template = function() {
+        var temp_template = dojo.byId("chron_row_template");
+        this.grid = temp_template.parentNode;
+        this.template = this.grid.removeChild(temp_template);
+        this.template.removeAttribute("id");
+    };
+
+    this._test_removability = function(subfield) {
+        var start = this.subfields.indexOf(subfield);
+
+        if (start < 0) {
+            /* no such field, not OK to remove */
+            return false;
+        } else if (!this.subfields[start]) {
+            /* field row not present, not OK to remove */
+            return false;
+        }
+
+        var next = this.subfields[start + 1];
+        if (typeof(next) == "undefined") { /* last in set, ok to remove */
+            return true;
+        } else {
+            if (this.rows[next]) { /* NOT last in set, not ok to remove */
+                return false;
+            } else { /* last in set actually present, ok to remove */
+                return true;
+            }
+        }
+    };
+
+    this.remove_row = function(subfield) {
+        if (this._test_removability(subfield)) {
+            hard_empty(this.rows[subfield].element);
+            dojo.destroy(this.rows[subfield].element);
+            delete this.rows[subfield];
+
+            dojo.byId("chron_add_row").disabled = false;
+        } else {
+            alert(S("not_removable_row"));
+        }
+    };
+
+    this.add_row = function() {
+        var available = this.subfields.filter(
+            function(subfield) { return !Boolean(self.rows[subfield]); }
+        );
+
+        if (available.length) {
+            var subfield = available.shift();
+            if (!available.length)
+                dojo.byId("chron_add_row").disabled = true;
+        } else {
+            /* We shouldn't really be able to get here. */
+            return;
+        }
+
+        this.rows[subfield] =
+            new ChronRow(dojo.clone(this.template), subfield, this);
+
+        dojo.place(this.rows[subfield].element, this.grid, "last");
+    };
+
+    this.toggle = function(ev) {
+        (ev.target.checked ? show : hide)("chron_editor_here");
+    };
+
+    this.compile = function() {
+        return this.subfields.filter(
+            function(subfield) { return Boolean(self.rows[subfield]); }
+        ).reduce(
+            function(result, subfield) {
+                var caption = self.rows[subfield].fields.caption.value;
+                if (!self.rows[subfield].fields.display_in_holding.checked)
+                    caption = "(" + caption + ")";
+                return result.concat([subfield, caption]);
+            }, []
+        );
+    };
+
+    this._init.apply(this, arguments);
+}
+
+function EnumRow() {
+    var self = this;
+
+    this._init = function(template, subfield, manager) {
+        this.subfield = subfield;
+        this.element = dojo.clone(template);
+
+        this.fields = {};
+        ["caption","units_per","units_per_number","continuity","remover"].
+            forEach(
+                function(o) { self.fields[o] = node_by_name(o, self.element); }
+            );
+
+        if (subfield == "a" || subfield == "g") {
+            ["units_per", "continuity"].forEach(
+                function(o) { soft_hide(node_by_name(o, self.element)); }
+            );
+        }
+
+        var caption_id = "enum_caption_" + subfield;
+        var caption_label = node_by_name("caption_label", this.element);
+        dojo.attr(this.fields.caption, "id", caption_id);
+        dojo.attr(caption_label, "control", caption_id);
+        dojo.attr(caption_label, "value", S("enumeration." + subfield) + ":");
+
+        this.remover = this.fields.remover;
+        dojo.attr(
+            this.remover, "onclick", function(){manager.remove_row(subfield);}
+        );
+    };
+
+    this._init.apply(this, arguments);
+};
+
+function EnumEditor() {
+    var self = this;
+
+    this._init = function() {
+        this.normal_rows = {};
+        this.alt_rows = {};
+
+        this.normal_subfields = ["a","b","c","d","e","f"];
+        this.alt_subfields = ["g","h"];
+
+        this._get_template();
+        this.add_normal_row();
+    };
+
+    this._get_template = function() {
+        var temp_template = dojo.byId("enum_row_template");
+        this.grid = temp_template.parentNode;
+        this.template = this.grid.removeChild(temp_template);
+        this.template.removeAttribute("id");
+    };
+
+    this.remove_row = function(subfield) {
+        if (this._test_removability(subfield)) {
+            var add_button = "enum_add_normal_row";
+            var set = this.normal_rows;
+            if (!set[subfield]) {
+                set = this.alt_rows;
+                add_button = "enum_add_alt_row";
+            }
+
+            hard_empty(set[subfield].element);
+            dojo.destroy(set[subfield].element);
+            delete set[subfield];
+            dojo.byId(add_button).disabled = false;
+        } else {
+            alert(S("not_removable_row"));
+        }
+    };
+
+    this._test_removability = function(id) {
+        var set = this.normal_subfields;
+        var rows = this.normal_rows;
+        var start = set.indexOf(id);
+
+        if (start == -1) {
+            set = this.alt_subfields;
+            rows = this.alt_rows;
+            start = set.indexOf(id);
+        }
+
+        if (start < 0) {
+            /* no such field, not OK to remove */
+            return false;
+        } else if (!set[start]) {
+            /* field row not present, not OK to remove */
+            return false;
+        }
+
+        var next = set[start + 1];
+        if (typeof(next) == "undefined") { /* last in set, ok to remove */
+            return true;
+        } else {
+            if (rows[next]) { /* NOT last in set, not ok to remove */
+                return false;
+            } else { /* last in set actually present, ok to remove */
+                return true;
+            }
+        }
+    };
+
+    this.add_normal_row = function() {
+        var available = this.normal_subfields.filter(
+            function(subfield) { return !Boolean(self.normal_rows[subfield]); }
+        );
+        if (available.length) {
+            var subfield = available.shift();
+            if (!available.length) {
+                /* If that was the last available normal row, disable the
+                 * add rows button. */
+                dojo.byId("enum_add_normal_row").disabled = true;
+            }
+        } else {
+            /* We shouldn't really be able to get here. */
+            return;
+        }
+
+        this.normal_rows[subfield] =
+            new EnumRow(dojo.clone(this.template), subfield, this);
+
+        dojo.place(this.normal_rows[subfield].element, this.grid, "last");
+    };
+
+    this.add_alt_row = function() {
+        var available = this.alt_subfields.filter(
+            function(subfield) { return !Boolean(self.alt_rows[subfield]); }
+        );
+        if (available.length) {
+            var subfield = available.shift();
+            if (!available.length) {
+                /* If that was the last available normal row, disable the
+                 * add rows button. */
+                dojo.byId("enum_add_alt_row").disabled = true;
+            }
+        } else {
+            /* We shouldn't really be able to get here. */
+            return;
+        }
+
+        this.alt_rows[subfield] =
+            new EnumRow(dojo.clone(this.template), subfield, this);
+
+        dojo.place(this.alt_rows[subfield].element, this.grid, "last");
+    };
+
+    this.toggle = function(ev) {
+        var func;
+        var use_calendar_change = dojo.byId("use_calendar_change");
+
+        if (ev.target.checked) {
+            func = show;
+            use_calendar_change.disabled = false;
+        } else {
+            use_calendar_change.checked = false;
+            use_calendar_change.doCommand();
+            use_calendar_change.disabled = true;
+            func = hide;
+        }
+
+        func("enum_editor_here");
+    };
+
+    this.compile = function() {
+        var rows = dojo.mixin({}, this.normal_rows, this.alt_rows);
+        var subfields = [].concat(this.normal_subfields, this.alt_subfields);
+
+        return subfields.filter(
+            function(subfield) { return Boolean(rows[subfield]); }
+        ).reduce(
+            function(result, subfield) {
+                var fields = rows[subfield].fields;
+                var pairs = [subfield, fields.caption.value];
+
+                if (subfield != "a" && subfield != "g") {
+                    if (fields.units_per.value == "number") {
+                        if (fields.units_per_number.value) {
+                            pairs = pairs.concat([
+                                "u", fields.units_per_number.value,
+                                "v", fields.continuity.value
+                            ]);
+                        }
+                    } else {
+                        pairs = pairs.concat([
+                            "u", fields.units_per.value,
+                            "v", fields.continuity.value
+                        ]);
+                    }
+                }
+
+                return result.concat(pairs);
+            }, []
+        );
+    };
+
+    this._init.apply(this, arguments);
+}
+
+function Wizard() {
+    var self = this;
+
+    var _step_prefix = "wizard_step_";
+    var _step_regex = new RegExp("^" + _step_prefix + "(.+)$");
+
+    this._init = function(onsubmit) {
+        this._onsubmit = onsubmit;
+
+        this.load();
+        this.reset();
+    };
+
+    this.load = function() {
+        /* The Wizard object will handle simpler parts of the wizard (those
+         * parts with more-or-less static controls) itself, and will
+         * instantiate more specific objects to deal with more dynamic
+         * super-widgets (like the enum and chron editors).
+         */
+        this.steps = dojo.query("[id^='" + _step_prefix + "']").map(
+            function(o) {
+                return dojo.attr(o, "id").match(_step_regex)[1];
+            }
+        );
+
+        this.enum_editor = new EnumEditor();
+        this.chron_editor = new ChronEditor();
+        this.calendar_change_editor = new CalendarChangeEditor();
+
+        this.field_w = dojo.byId("hard_w");
+    };
+
+    this.reset = function() {
+        this.step = 0;
+        this.show_only_step(this.steps[0]);
+        this.step_bounds_check();
+    };
+
+    this.show_step = function(step) { show(_step_prefix + step); }
+    this.hide_step = function(step) { hide(_step_prefix + step); }
+
+    this.step_bounds_check = function() {
+        dojo.byId("wizard_previous_step").disabled = this.step < 1;
+        dojo.byId("wizard_next_step").disabled =
+            this.step >= this.steps.length -1;
+    };
+
+    this.show_only_step = function(to_keep) {
+        this.steps.forEach(
+            function(step) { if (step != to_keep) self.hide_step(step); }
+        );
+        this.show_step(to_keep);
+    };
+
+    this.previous_step = function() {
+        this.show_only_step(this.steps[--(this.step)]);
+        this.step_bounds_check();
+    };
+
+    /* Figure out the what step we're in, and proceed to the next */
+    this.next_step = function() {
+        this.show_only_step(this.steps[++(this.step)]);
+        this.step_bounds_check();
+    };
+
+    this.frequency_type_toggle = function(which) {
+        var other = which == "soft_w" ? "hard_w" : "soft_w";
+
+        dojo.byId(other).disabled = true;
+        dojo.byId(which).disabled = false;
+        dojo.byId(which).focus();
+
+        this.field_w = dojo.byId(which);
+    };
+
+    this.compile = function() {
+        var code = [
+            dojo.byId("ind1").value, dojo.byId("ind2").value,
+            "8", "1" /* TODO find out how to best deal with $8 */
+        ];
+
+        code = code.concat(this.enum_editor.compile());
+        code = code.concat(this.chron_editor.compile());
+
+        code = code.concat("w", this.field_w.value);
+
+        code = code.concat(this.calendar_change_editor.compile());
+
+        return code;
+    };
+
+    this.submit = function() {
+        this._onsubmit(js2JSON(this.compile()));
+        window.close();
+    };
+
+    this._init.apply(this, arguments);
+}
+
+function my_init() {
+    wizard = new Wizard(window.arguments[0]);
+}

Added: trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard.xul	                        (rev 0)
+++ trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard.xul	2010-09-03 16:30:47 UTC (rev 17472)
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/serial.css" type="text/css"?>
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+<?xul-overlay href="/xul/server/serial/pattern_wizard_overlay.xul"?>
+
+<window id="pattern_wizard_win"
+    onload="try{my_init();font_helper();persist_helper();}catch(E){alert(E);}"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <script type="text/javascript">
+        var myPackageDir = "open_ils_staff_client";
+        var IAMXUL = true;
+        var g = {};
+    </script>
+
+    <scripts id="openils_util_scripts" />
+
+    <!-- JSAN is still needed for font_helper stuff, but I'm going to try
+        not to use it otherwise.  -->
+    <script type="text/javascript" src="/xul/server/main/JSAN.js" />
+
+    <messagecatalog id="serialStrings"
+        src="/xul/server/locale/<!--#echo var='locale'-->/serial.properties" />
+
+    <commandset />
+    <box id="pattern_wizard_main" />
+</window>

Added: trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard_overlay.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard_overlay.xul	                        (rev 0)
+++ trunk/Open-ILS/xul/staff_client/server/serial/pattern_wizard_overlay.xul	2010-09-03 16:30:47 UTC (rev 17472)
@@ -0,0 +1,289 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE overlay PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+<overlay id="pattern_wizard_overlay"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <script type="text/javascript" src="/xul/server/serial/common.js" />
+    <script type="text/javascript" src="/xul/server/serial/pattern_wizard.js" />
+
+    <box orient="vertical" id="pattern_wizard_main" flex="1">
+        <caption class="top" label="Pattern Code Wizard" />
+        <vbox flex="1">
+            <hbox align="center" class="padded_bottom">
+                <button id="wizard_previous_step" disabled="true"
+                    icon="go-back" label="Previous"
+                    oncommand="wizard.previous_step();" />
+                <spacer flex="1" />
+                <button id="wizard_next_step" disabled="true"
+                    icon="go-forward" label="Next"
+                    oncommand="wizard.next_step();" />
+            </hbox>
+            <vbox id="wizard_step_captions" class="hideme">
+                <checkbox id="use_enum"
+                    oncommand="wizard.enum_editor.toggle(event);"
+                    label="Use enumerations?" />
+                <vbox id="enum_editor_here" class="hideme">
+                    <description class="step">
+                        "v." and "no." are common first and second level
+                        enumeration captions.
+                    </description>
+                    <grid flex="1">
+                        <columns>
+                            <column />
+                            <column />
+                            <column />
+                            <column />
+                        </columns>
+                        <rows>
+                            <row id="enum_row_headings">
+                                <label value="Enumeration Caption" />
+                                <label value="Units Per Higher Level" />
+                                <label value="Numbering Continuity" />
+                                <label />
+                            </row>
+                            <row id="enum_row_template" align="top">
+                                <vbox>
+                                    <label name="caption_label" />
+                                    <hbox>
+                                        <spacer flex="1" />
+                                        <textbox size="6" name="caption" />
+                                    </hbox>
+                                </vbox>
+                                <radiogroup name="units_per">
+                                    <hbox align="center">
+                                        <radio label="Number" value="number" />
+                                        <textbox name="units_per_number"
+                                            size="6" />
+                                    </hbox>
+                                    <radio label="Varies" value="var" />
+                                    <radio label="Undetermined" value="und" />
+                                </radiogroup>
+                                <menulist name="continuity">
+                                    <menupopup>
+                                        <menuitem value="c" label="Increments continuously" />
+                                        <menuitem value="r" label="Restarts at unit completion" />
+                                    </menupopup>
+                                </menulist>
+                                <button icon="remove" name="remover" label="Remove" />
+                            </row>
+                        </rows>
+                    </grid>
+                    <hbox>
+                        <spacer flex="1" />
+                        <button id="enum_add_normal_row"
+                            icon="add"
+                            label="Add Enumeration" accesskey="E"
+                            oncommand="wizard.enum_editor.add_normal_row();"
+                            />
+                        <spacer flex="1" />
+                        <button id="enum_add_alt_row"
+                            icon="add"
+                            label="Add Alternate Enumeration" accesskey="A"
+                            oncommand="wizard.enum_editor.add_alt_row();"
+                            />
+                        <spacer flex="1" />
+                    </hbox>
+                </vbox>
+            </vbox>
+            <vbox id="wizard_step_calendar_change" class="hideme">
+                <checkbox id="use_calendar_change"
+                    disabled="true"
+                    oncommand="wizard.calendar_change_editor.toggle(event);"
+                    label="Use calendar changes?" />
+                <vbox id="calendar_change_editor_here" class="hideme">
+                    <description class="step">
+                        Identify any points during the year at which the
+                        highest level enumeration caption changes.
+                    </description>
+                    <grid>
+                        <columns>
+                            <column />
+                            <column flex="1" />
+                            <column />
+                        </columns>
+                        <rows>
+                            <row id="calendar_change_row_headings">
+                                <label value="Type" />
+                                <label value="Point" />
+                                <label />
+                            </row>
+                            <row id="calendar_row_template">
+                                <menulist name="type">
+                                    <menupopup>
+                                        <menuitem value="month" label="At start of a month" />
+                                        <menuitem value="season" label="At start of a season" />
+                                        <menuitem value="date" label="On a date" />
+                                    </menupopup>
+                                </menulist>
+                                <hbox align="center">
+                                    <menulist name="month">
+                                        <menupopup>
+                                        </menupopup>
+                                    </menulist>
+                                    <menulist name="season" class="hideme">
+                                        <menupopup>
+                                            <menuitem value="21" label="Spring" />
+                                            <menuitem value="22" label="Summer" />
+                                            <menuitem value="23" label="Autumn" />
+                                            <menuitem value="24" label="Winter" />
+                                        </menupopup>
+                                    </menulist>
+                                    <hbox name="date" class="hideme">
+                                        <menulist name="date_month">
+                                            <menupopup>
+                                            </menupopup>
+                                        </menulist>
+                                        <textbox name="date_day" size="3" />
+                                    </hbox>
+                                </hbox>
+                                <button icon="remove" name="remover" label="Remove" />
+                            </row>
+                        </rows>
+                    </grid>
+                    <hbox pack="center">
+                        <button
+                            id="calendar_change_add_row"
+                            label="Add Calendar Change"
+                            accesskey="C"
+                            oncommand="wizard.calendar_change_editor.add_row();" />
+                    </hbox>
+                </vbox>
+            </vbox>
+            <vbox id="wizard_step_chronology" class="hideme">
+                <checkbox id="use_chron"
+                    oncommand="wizard.chron_editor.toggle(event);"
+                    label="Use chronology captions?" />
+                <vbox id="chron_editor_here" class="hideme">
+                    <description class="step">
+                        Generally, each caption should be a smaller unit of
+                        time than the preceding caption.
+                    </description>
+                    <grid>
+                        <columns>
+                            <column />
+                            <column />
+                            <column flex="1" />
+                            <column />
+                        </columns>
+                        <rows>
+                            <row id="chron_row_headings">
+                                <label />
+                                <label value="Caption" />
+                                <label value="Display in holding field?" />
+                                <label />
+                            </row>
+                            <row id="chron_row_template">
+                                <label name="caption_label" />
+                                <menulist name="caption">
+                                    <menupopup>
+                                        <menuitem label="Year" value="year" />
+                                        <menuitem label="Season" value="season" />
+                                        <menuitem label="Month" value="month" />
+                                        <menuitem label="Week" value="week" />
+                                        <menuitem label="Day" value="day" />
+                                        <menuitem label="Hour" value="hour" />
+                                    </menupopup>
+                                </menulist>
+                                <checkbox name="display_in_holding" />
+                                <button icon="remove" name="remover" label="Remove" />
+                            </row>
+                        </rows>
+                    </grid>
+                    <hbox pack="center">
+                        <button
+                            id="chron_add_row"
+                            label="Add Chronology Caption"
+                            accesskey="C"
+                            oncommand="wizard.chron_editor.add_row();" />
+                    </hbox>
+                </vbox>
+            </vbox>
+            <vbox id="wizard_step_basics" class="hideme">
+                <grid class="padded_bottom">
+                    <columns>
+                        <column />
+                        <column />
+                    </columns>
+                    <rows><!-- TODO hide these inputs if we're doing an 855 -->
+                        <row align="center">
+                            <label align="right" value="Compressibility and Expandability:" />
+                            <menulist id="ind1">
+                                <menupopup>
+                                    <menuitem value="0" label="Cannot compress or expand" />
+                                    <menuitem value="1" label="Can compress but not expand" />
+                                    <menuitem value="2" label="Can compress or expand" />
+                                    <menuitem value="3" label="Unknown" />
+                                </menupopup>
+                            </menulist>
+                        </row>
+                        <row align="center">
+                            <label align="right" value="Caption Evaluation:" />
+                            <menulist id="ind2">
+                                <menupopup>
+                                    <menuitem value="0" label="Captions verified; all levels present" />
+                                    <menuitem value="1" label="Captions verified; all levels may not be present" />
+                                    <menuitem value="2" label="Captions unverified; all levels present" />
+                                    <menuitem value="3" label="Captions unverified; all levels may not be present" />
+                                </menupopup>
+                            </menulist>
+                        </row>
+                    </rows>
+                </grid>
+                <radiogroup
+                    oncommand="wizard.frequency_type_toggle(this.value);">
+                    <grid>
+                        <columns><column /><column /></columns>
+                        <rows>
+                            <row>
+                                <radio
+                                    label="Select fundamental periodicity:"
+                                    accesskey="F" selected="true"
+                                    value="hard_w" />
+                                <menulist id="hard_w">
+                                    <menupopup>
+                                        <menuitem label="Annual" value="a" />
+                                        <menuitem label="Bimonthly" value="b" />
+                                        <menuitem label="Semiweekly" value="c" />
+                                        <menuitem label="Daily" value="d" />
+                                        <menuitem label="Biweekly" value="e" />
+                                        <menuitem label="Semiannual" value="f" />
+                                        <menuitem label="Biennial" value="g" />
+                                        <menuitem label="Triennial" value="h" />
+                                        <menuitem label="Three times a week" value="i" />
+                                        <menuitem label="Three times a month" value="j" />
+                                        <menuitem label="Continuously updated" value="k" />
+                                        <menuitem label="Monthly" value="m" />
+                                        <menuitem label="Quarterly" value="q" />
+                                        <menuitem label="Semimonthly" value="s" />
+                                        <menuitem label="Three times a year" value="t" />
+                                        <menuitem label="Weekly" value="w" />
+                                        <menuitem label="Completely irregular" value="x" />
+                                    </menupopup>
+                                </menulist>
+                            </row>
+                            <row>
+                                <radio
+                                    label="Use number of issues per year:"
+                                    value="soft_w"
+                                    selected="false" accesskey="I" />
+                                <textbox id="soft_w" disabled="true" />
+                            </row>
+                        </rows>
+                    </grid>
+                </radiogroup>
+            </vbox>
+            <vbox id="wizard_step_submit" class="hideme">
+                <description class="step">
+                    Are you ready to create a pattern code from your
+                    selections in this wizard?
+                </description>
+                <hbox pack="center">
+                    <button oncommand="wizard.submit();" icon="accept"
+                        accesskey="P" label="Create Pattern Code" />
+                </hbox>
+            </vbox>
+        </vbox>
+    </box>
+</overlay>

Modified: trunk/Open-ILS/xul/staff_client/server/serial/scap_editor.js
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/serial/scap_editor.js	2010-09-03 15:41:05 UTC (rev 17471)
+++ trunk/Open-ILS/xul/staff_client/server/serial/scap_editor.js	2010-09-03 16:30:47 UTC (rev 17472)
@@ -2,6 +2,7 @@
 // vim:noet:sw=4:ts=4:
 
 JSAN.use('serial.editor_base');
+var pattern_code_key = 'Pattern Code (temporary)';
 
 if (typeof serial == 'undefined') serial = {};
 serial.scap_editor = function (params) {
@@ -98,7 +99,7 @@
                 }
             ],
             [
-                'Pattern Code (temporary)',
+                pattern_code_key,
                 { 
                     render: 'fm.pattern_code() == null ? "" : fm.pattern_code();',
                     input: 'c = function(v){ obj.apply("pattern_code",v); if (typeof post_c == "function") post_c(v); }; x = document.createElement("textbox"); x.setAttribute("value",obj.editor_values.pattern_code); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
@@ -132,6 +133,24 @@
         obj.editor_base_save('open-ils.serial.caption_and_pattern.batch.update');
     },
 
+    /* Pattern/caption wizard */
+    "pattern_wizard": function() {
+        var obj = this;
+
+        var onsubmit = function(value) {
+            obj.apply("pattern_code", value);
+            obj.summarize(obj.scaps);
+            obj.render();
+        };
+        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+        window.openDialog(
+            xulG.url_prefix("/xul/server/serial/pattern_wizard.xul"),
+            "pattern_wizard",
+            "scrollbars=yes", /* XXX FIXME: scrollbars aren't working. what to do? */
+            onsubmit
+        );
+    },
+
     /******************************************************************************************************/
     'save_attributes' : serial.editor_base.editor_base_save_attributes
 };

Modified: trunk/Open-ILS/xul/staff_client/server/serial/scap_editor.xul
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/serial/scap_editor.xul	2010-09-03 15:41:05 UTC (rev 17471)
+++ trunk/Open-ILS/xul/staff_client/server/serial/scap_editor.xul	2010-09-03 16:30:47 UTC (rev 17472)
@@ -22,6 +22,7 @@
 
 		<hbox id="scap_editor_nav">
 			<spacer flex="1"/>
+			<button id="scap_pattern_wizard" label="&staff.serial.scap_editor.pattern_wizard;" accesskey="&staff.serial.scap_editor.pattern_wizard.accesskey;" oncommand="g.manage_subs.scap_editor.pattern_wizard();" />
 			<button id="scap_save" label="&staff.serial.scap_editor.modify;" hidden="true" accesskey="&staff.serial.scap_editor.modify.accesskey;" oncommand="g.manage_subs.scap_editor.save()" />
 			<!--<button id="cancel" label="&staff.cat.copy_editor.cancel.label;" accesskey="&staff.cat.copy_editor.cancel.accesskey;" oncommand="window.close();"/>-->
 		</hbox>

Modified: trunk/Open-ILS/xul/staff_client/server/skin/serial.css
===================================================================
--- trunk/Open-ILS/xul/staff_client/server/skin/serial.css	2010-09-03 15:41:05 UTC (rev 17471)
+++ trunk/Open-ILS/xul/staff_client/server/skin/serial.css	2010-09-03 16:30:47 UTC (rev 17472)
@@ -1,5 +1,5 @@
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-caption {
+caption.top {
     background-color: #00246b;
     color: #ff6308;
     font-size: 200%;
@@ -10,3 +10,13 @@
 #batch_receive_entry { padding-top: 10px; }
 #entry_submitter { padding: 20px 0; }
 menulist.cn { width: 12em; }
+button[icon="remove"] { min-width: 2.5em; }
+#enum_row_headings label { font-weight: bold; }
+#chron_row_headings label { font-weight: bold; }
+#calendar_change_row_headings label { font-weight: bold; }
+description.step { font-style: italic; font-size: 110%; }
+checkbox:focus:not([label]) .checkbox-label-box {
+    -moz-appearance: none;
+    border: none;
+}
+.padded_bottom { padding-bottom: 10px; }



More information about the open-ils-commits mailing list