Physical Characteristics Wizard for the MARC Editor
authorLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Tue, 12 Nov 2013 22:51:57 +0000 (17:51 -0500)
committerLebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Thu, 23 Jan 2014 20:33:30 +0000 (15:33 -0500)
Right-click in an 007 field to find a new entry in the context menu that
launches this wizard.  The prompts and suggested values come from data
already found in the database.

Signed-off-by: Lebbeous Fogle-Weekley <lebbeous@esilibrary.com>
Open-ILS/web/js/dojo/openils/widget/PhysCharWizard.js [new file with mode: 0644]
Open-ILS/web/js/dojo/openils/widget/nls/PhysCharWizard.js [new file with mode: 0644]
Open-ILS/web/opac/locale/en-US/lang.dtd
Open-ILS/xul/staff_client/chrome/content/cat/opac.js
Open-ILS/xul/staff_client/chrome/content/main/constants.js
Open-ILS/xul/staff_client/chrome/content/main/menu.js
Open-ILS/xul/staff_client/server/cat/marcedit.js
Open-ILS/xul/staff_client/server/cat/marcedit.xul

diff --git a/Open-ILS/web/js/dojo/openils/widget/PhysCharWizard.js b/Open-ILS/web/js/dojo/openils/widget/PhysCharWizard.js
new file mode 100644 (file)
index 0000000..3de2015
--- /dev/null
@@ -0,0 +1,538 @@
+if (!dojo._hasResource["openils.widget.PhysCharWizard"]) {
+    dojo._hasResource["openils.widget.PhysCharWizard"] = true;
+
+    dojo.provide("openils.widget.PhysCharWizard");
+    dojo.require("dojo.string");
+    dojo.require("openils.User");
+    dojo.require("openils.Util");
+    dojo.require("openils.PermaCrud");
+    dojo.requireLocalization("openils.widget", "PhysCharWizard");
+
+    (function() {   /* Namespace protection so we can still make little helpers
+                    within our own bubble */
+
+        var _xhtml_ns = "http://www.w3.org/1999/xhtml";
+        var _dump = dump ?
+            function(s) { dump(s + "\n"); } : (
+                console && console.log ?
+                    function(s) { console.log(s) } : alert
+            );
+
+        function _show_button(n, yes) { /* yet another hide/reveal thing */
+            /* This is a re-invented wheel, but I was having trouble
+             * getting my <button>s to react to the disabled property, and
+             * decided to hide/reveal them instead of disable/enable then.
+             * Then I had this need to do it in a consistent way. */
+            n.setAttribute("style", "visibility: " + (yes? "visible":"hidden"));
+        }
+
+        function _get_xul_combobox_value(menulist) {
+            /* XUL comboboxes (<menulist editable="true">) are funny. */
+
+            return menulist.selectedItem ?
+                menulist.selectedItem.value :
+                menulist.label;  /* sic! Even .getAttribute('label') is
+                                    wrong, and anything to do with 'value'
+                                    is also wrong. */
+        }
+
+        /* Within the openils.widget.PhysCharWizard class, methods and
+         * properties that start "_" should be considered private, and others
+         * public.
+         *
+         * The methods whose names end in "_XUL" could be replaced with HTML
+         * versions to make this wizard work as an HTML thing instead of as
+         * a XUL thing.
+         */
+        dojo.declare(
+            "openils.widget.PhysCharWizard", [], {
+                "active": true,
+                "constructor": function(args) {
+                    this._ = openils.widget.PhysCharWizard.localeStrings;
+                    this._cache = openils.widget.PhysCharWizard.cache;
+
+                    /* Reserve a little of the window namespace that we'll need
+                     * (under XUL anyway) */
+                    window._owPCWinstances = window._owPCWinstances || 0;
+                    window._owPCW = window._owPCW || {};
+                    this.instance_num = window._owPCWinstances++;
+                    window._owPCW[this.instance_num] = this;
+                    this.outside_ref =
+                        "window._owPCW[" + this.instance_num + "]";
+
+                    /* Initialize and save misc values, and call build() to
+                     * make and place widgets. */
+                    this.onapply = args.onapply ||
+                        function() { _dump("no onapply() given"); };
+
+                    this.step = 'a';
+                    this.more_back = false;
+                    this.more_forward = true;
+                    this.value = this.original_value = args.node.value;
+
+                    this.pcrud = new openils.PermaCrud(
+                        {"authtoken": ses ? ses() : openils.User.authtoken}
+                    );
+
+                    this.build(args.node);
+
+                    this._load_all_types(   /* and then: */
+                        dojo.hitch(this, function() { this.move(0); })
+                    );
+                },
+                "build": function(where) {
+                    this.original_node = where;
+                    var p = this.container_node = where.parentNode;
+                    p.removeChild(where);
+
+                    this._build_XUL();
+                },
+                "update_question": function(label, values) {
+                    this._update_question_XUL(label, values);
+                },
+                "update_value_label": function() {
+                    this._update_value_label_XUL(
+                        this._get_step_slot(), this.value
+                    );
+                },
+                "update_pagers": function() {
+                    _show_button(this.back_button, this.more_back);
+                    _show_button(this.forward_button, this.more_forward);
+                },
+                "apply": function(callback) {
+                    this.active = false;
+
+                    this.move(
+                        0, dojo.hitch(this, function() {
+                            this.onapply(this.value);
+                            if (typeof callback == "function")
+                                callback();
+                        })
+                    );
+                },
+                "cancel": function() {
+                    this.active = false;
+                    this.container_node.removeChild(this.wizard_root_node);
+                    this.container_node.appendChild(this.original_node);
+                },
+                "_default_after_00": function() {
+                    /* This method assumes that the things it looks for in
+                     * the cache are freshly put there. */
+                    var working_ptype = this.value.substr(0, 1);
+                    var sf_list = this._cache.subfields[working_ptype];
+                    if (!sf_list)
+                        throw new Error(this._.BAD_WORKING_PTYPE);
+
+                    this.value = working_ptype;
+                    for (var i = 0; i < sf_list.length; i++) {
+                        var s = sf_list[i];
+                        var gap = s.start_pos() - this.value.length;
+                        if (gap > 0) {
+                            for (var j = 0; j < gap; j++)
+                                this.value += " ";  /* XXX or '#' ? */
+                        } else if (gap < 0) {
+                            throw new Error(
+                                dojo.string.substitute(
+                                    this._.BACKWARDS_SUBFIELD_PROGRESSION,
+                                    [working_ptype]
+                                )
+                            );
+                        }
+
+                        for (var j = 0; j < s.length(); j++)
+                            this.value += "|";
+                    }
+                },
+                "move": function(offset, callback) {
+                    /* When we move the wizard, we need to accomplish five
+                     * things:
+                     *  1) Disable both pager buttons - sic
+                     *  2) Update the appopriate _slot of the working _value_
+                     *  with the value from the user input control.
+                     *  ---- sync above here ^ --------- async below here v ----
+                     *  3) Determine what the next _step_ will be and set it
+                     *  4) Replace the question and the dropdown with appro-
+                     *  priate data from the new _step_
+                     *  5) Reenable appropriate pager buttons
+                     *  6) (optional) fire any callback
+                     */
+
+                    /* Step 1 */
+                    _dump("move() step 1");
+                    _show_button(this.back_button, false);
+                    _show_button(this.forward_button, false);
+
+                    /* Step 2. No sweat so far. Skip if there is no
+                     * user control yet (initializing whole wizard still). */
+                    var a_changed = false;
+                    if (this.step_user_control) {
+                        _dump("move() step 2");
+                        a_changed = this.update_value_slot(
+                                this._get_step_slot(),
+                                this.get_step_value_from_control()
+                            ) && this.step == 'a';
+                    }
+
+                    /* Step 3 depends on knowing a) our working_ptype, which
+                     * may have just changed if step was 'a' and b) all the
+                     * subfields for that ptype, which we may have to
+                     * retrieve asynchronously. */
+                    _dump("move() step 3 (prep)");
+                    this._get_subfields_for_type(
+                        this.value.substr(0, 1), /* working_ptype */
+                        /* and then: */ dojo.hitch(this, function() {
+
+                            /* Step 2.9 (small) */
+                            if (a_changed) this._default_after_00();
+
+                            /* Step 3 proper: */
+                            _dump("move() step 3 (proper)");
+                            this._move_step(offset);
+
+                            /* Step 4: For the call to update_question, we had
+                             * better have values loaded for our current step.
+                             */
+                            _dump("move() step 4 (prep)");
+                            this._get_values_for_step(
+                                this.step,
+                                /* and then: */ dojo.hitch(this, function(l, v){
+                                    /* Step 4 proper: */
+                                    _dump("move() step 4 (proper)");
+                                    this.update_value_label();
+                                    _dump("move() step 4 (proper) - part 2");
+                                    this.update_question(l, v);
+
+                                    /* Step 5 */
+                                    _dump("move() step 5");
+                                    this.update_pagers();
+
+                                    if (typeof callback == "function") {
+                                        _dump("move() step 6");
+                                        callback();
+                                    }
+                                })
+                            );
+                        })
+                    );
+                },
+                "get_step_value_from_control": function() {
+                    return _get_xul_combobox_value(this.step_user_control);
+                },
+                "get_step_value": function() {
+                    return String.prototype.substr.apply(
+                        this.value, this._get_step_slot()
+                    );
+                },
+                "update_value_slot": function(slot, value) {
+                    /* Return true if this.value changes */
+
+                    if (!value.length) {
+                        /* Prevent erasing positions when backing up. */
+                        for (var i = 0; i < slot[1]; i++)
+                            value += '|';
+                    }
+
+                    var old_value = this.value;
+                    var before = this.value.substr(0, slot[0]);
+                    var after = this.value.substr(slot[0] + slot[1]);
+
+                    this.value = before + value.substr(0, slot[1]) + after;
+                    return (this.value != old_value);
+                },
+                "_load_all_types": function(callback) {
+                    /* It's easiest to have these always ready, and it's not
+                     * a large dataset. */
+
+                    if (this._cache.types.length)  /* maybe we already do */
+                        callback();
+
+                    this.pcrud.retrieveAll(
+                        "cmpctm", {
+                            "oncomplete": dojo.hitch(this, function(r) {
+                                if (r = openils.Util.readResponse(r)) {
+                                    this._cache.types = r.map(
+                                        function(o) {
+                                            return [o.ptype_key(), o.label()];
+                                        }
+                                    );
+                                    callback();
+                                } else {
+                                    throw new Error(this._.DATA_ERROR_007);
+                                }
+                            })
+                        }
+                    );
+                },
+                "_get_subfields_for_type": function(working_ptype, callback) {
+                    if (this._cache.subfields[working_ptype]) {
+                        callback(this._cache.subfields[working_ptype]);
+                    } else {
+                        this.pcrud.search(
+                            "cmpcsm", {"ptype_key": working_ptype}, {
+                                "order_by": {"cmpcsm": "subfield"},
+                                "oncomplete": dojo.hitch(this, function(r) {
+                                    if (r = openils.Util.readResponse(r)) {
+                                        this._cache.subfields[working_ptype]= r;
+                                        callback(r);
+                                    } else {
+                                        throw new Error(this._.DATA_ERROR_007);
+                                    }
+                                })
+                            }
+                        );
+                    }
+                },
+                "_get_values_for_step": function(step, callback) {
+                    /* Values are cached by subfield ID, so we find the
+                     * current subfield ID using the step and the
+                     * working_ptype. */
+
+                    if (this.step == 'a') {
+                        callback(this._.A_LABEL, this._cache.types);
+                        return;
+                    }
+
+                    var step = this.step;   /* for use w/in closure */
+                    var working_ptype = this.value.substr(0, 1);
+                    var subfields =
+                        this._cache.subfields[working_ptype].filter(
+                            function(s) { return s.subfield() == step; }
+                        );
+
+                    if (subfields.length != 1) {
+                        throw new Error(this._.BAD_SUBFIELD_DATA);
+                        return;
+                    }
+
+                    var subfield = subfields[0];
+                    if (this._cache.values[subfield.id()]) {
+                        callback(
+                            subfield.label(),
+                            this._cache.values[subfield.id()]
+                        );
+                    } else {
+                        this.pcrud.search(
+                            "cmpcvm", {"ptype_subfield": subfield.id()}, {
+                                "order_by": {"cmpcvm": "value"},
+                                "onresponse": dojo.hitch(this, function(r) {
+                                    if (r = openils.Util.readResponse(r)) {
+                                        this._cache.values[subfield.id()] =
+                                            r = r.map(
+                                                function(v) {
+                                                    return [v.value(),v.label()]
+                                                }
+                                            );
+                                        callback(subfield.label(), r);
+                                    } else {
+                                        throw new Error(this._.DATA_ERROR_007);
+                                    }
+                                })
+                            }
+                        );
+                    }
+                },
+                "_get_step_slot": function() {
+                    /* We should never need to know the slot for our step
+                     * until *after* we have the subfields for that step
+                     * loaded. That allows us to keep this function sync
+                     * (i.e., it returns instead of using a callback).  */
+
+                    if (this.step == 'a') {
+                        return [0, 1];
+                    } else {
+                        var step = this.step;   /* to use w/in closure */
+                        var working_ptype = this.value.substr(0, 1);
+                        var matches =
+                            this._cache.subfields[working_ptype].filter(
+                                function(s) { return s.subfield() == step; }
+                            );
+
+                        if (matches.length == 1)
+                            return [matches[0].start_pos(),matches[0].length()];
+                        else
+                            throw new Error(this._.BAD_SUBFIELD_DATA);
+                    }
+                },
+                "_move_step": function(offset) {
+                    /* This method is/should only be called when we know we
+                     * have the list of subfields for our working_ptype cached.
+                     *
+                     * We have two jobs in this method:
+                     *  1) Set this.step to something new.
+                     *  2) Update this.more_forward and this.more_back (bools)
+                     */
+                    var working_ptype = this.value.substr(0, 1);
+                    var found = -1;
+                    var sf_list = this._cache.subfields[working_ptype];
+
+                    for (var i = 0; i < sf_list.length; i++) {
+                        if (sf_list[i].subfield() == this.step) {
+                            found = i;
+                            break;
+                        }
+                    }
+
+                    var idx = found + offset;
+                    if (sf_list[idx]) {
+                        this.step = sf_list[idx].subfield();
+                        this.more_forward = Boolean(sf_list[idx + 1]);
+                        this.more_back = Boolean(idx >= 0);
+                    } else if (idx == -1) { /* 'a' */
+                        this.step = 'a';
+                        this.more_back = false;
+                        this.more_forward = true; /* or something's broke */
+                    } else {
+                        throw new Error(this._.FELL_OFF_STEPS);
+                    }
+                },
+                "_update_question_XUL": function(step_label, value_list) {
+                    var qh = this.question_holder;
+
+                    while (qh.firstChild) qh.removeChild(qh.firstChild);
+
+                    /* Add question label */
+                    var label = document.createElement("label");
+                    label.setAttribute("value", step_label + "?");
+                    label.setAttribute("style", "min-width: 16em;");
+                    qh.appendChild(label);
+
+                    /* Create combobox (in XUL this a <menulist editable="true">
+                     * with <menupopup> underneath and several <menuitem>s under
+                     * that). */
+                    var ml = this.step_user_control =
+                        document.createElement("menulist");
+                    ml.setAttribute("editable", "true");
+                    var mp = document.createElement("menupopup");
+                    ml.appendChild(mp);
+
+                    var starting_value = this.get_step_value();
+                    var found_starting_value = false;
+
+                    value_list.forEach(
+                        function(v) {
+                            var mi = document.createElement("menuitem");
+                            mi.setAttribute("label", v[0] + ": " + v[1]);
+                            mi.setAttribute("value", v[0]);
+
+                            if (v[0] == starting_value) {
+                                mi.setAttribute("selected", "true");
+                                found_starting_value = true;
+                            }
+
+                            mp.appendChild(mi);
+                        }
+                    );
+
+                    if (!found_starting_value) {
+                        /* Starting value wasn't one of the menuitems, but
+                         * we can force it: */
+                        ml.setAttribute("label", starting_value);
+                    }
+                    qh.appendChild(ml);
+                },
+                "_update_value_label_XUL": function(step_win, value) {
+                    var before = value.substr(0, step_win[0]);
+                    var within = value.substr(step_win[0], step_win[1]);
+                    var after = value.substr(step_win[0] + step_win[1]);
+
+                    var div = this.value_label;
+                    while (div.firstChild)
+                        div.removeChild(div.firstChild);
+
+                    div.appendChild(document.createTextNode(before));
+
+                    var el = document.createElementNS(_xhtml_ns,"xhtml:strong");
+                    el.appendChild(document.createTextNode(within));
+                    div.appendChild(el);
+
+                    div.appendChild(document.createTextNode(after));
+                },
+                "_gen_XUL_oncommand": function(methstr) {
+                    return "try { " + this.outside_ref +
+                        "." + methstr + " } catch (E) { alert('" +
+                        this.outside_ref + ": ' + E) }";
+                },
+                "_build_XUL": function() {
+                    var vbox = this.container_node.appendChild(
+                        document.createElement("vbox")
+                    );
+
+                    var top_hbox =
+                        vbox.appendChild(document.createElement("hbox"));
+
+                    this.question_holder =
+                        vbox.appendChild(document.createElement("hbox"));
+                    this.question_holder.setAttribute("align", "center");
+
+                    var bottom_hbox =
+                        vbox.appendChild(document.createElement("hbox"));
+
+                    this.value_label = top_hbox.appendChild(
+                        document.createElementNS(_xhtml_ns, "xhtml:div")
+                    );
+
+                    /* These em's must be measured in terms of the body
+                     * font-size, not the font-size local to these elements?
+                     * Or is that how em's always work? */
+                    this.value_label.setAttribute(
+                        "style", "min-width: 16em; white-space: pre;"
+                    );
+
+                    /* From here to the end of the method we're just building
+                     * and placing the wizard's four buttons. */
+                    var button;
+
+                    button = document.createElement("button");
+                    button.setAttribute("label", this._.OK);
+                    button.setAttribute("icon", "apply");
+                    button.setAttribute(
+                        "oncommand", this._gen_XUL_oncommand("apply()")
+                    );
+                    top_hbox.appendChild(button);
+
+                    button = document.createElement("button");
+                    button.setAttribute("label", this._.CANCEL);
+                    button.setAttribute("icon", "cancel");
+                    button.setAttribute(
+                        "oncommand", this._gen_XUL_oncommand("cancel()")
+                    );
+                    top_hbox.appendChild(button);
+
+                    this.back_button = button =
+                        document.createElement("button");
+                    button.setAttribute("label", this._.BACK);
+                    button.setAttribute("icon", "go-back");
+                    button.setAttribute(
+                        "oncommand", this._gen_XUL_oncommand("move(-1)")
+                    );
+                    button.disabled = true;
+                    bottom_hbox.appendChild(button);
+
+                    this.forward_button = button =
+                        document.createElement("button");
+                    button.setAttribute("label", this._.FORWARD);
+                    button.setAttribute("icon", "go-forward");
+                    button.setAttribute(
+                        "oncommand", this._gen_XUL_oncommand("move(1)")
+                    );
+                    button.disabled = true;
+                    bottom_hbox.appendChild(button);
+
+                    /* Save reference to root node of wizard for easy
+                     * removal when finished. */
+                    this.wizard_root_node = vbox;
+                }
+            }
+        );
+    })();
+
+    /* Class-wide cache; all instance objects share this */
+    openils.widget.PhysCharWizard.cache = {
+        "subfields": {},    /* by type */
+        "values": {},       /* by subfield ID */
+        "types": []
+    };
+
+    openils.widget.PhysCharWizard.localeStrings =
+        dojo.i18n.getLocalization("openils.widget", "PhysCharWizard");
+}
diff --git a/Open-ILS/web/js/dojo/openils/widget/nls/PhysCharWizard.js b/Open-ILS/web/js/dojo/openils/widget/nls/PhysCharWizard.js
new file mode 100644 (file)
index 0000000..c3462fe
--- /dev/null
@@ -0,0 +1,15 @@
+{
+    "OK": "OK",
+    "CANCEL": "Cancel",
+    "BACK": "Previous",
+    "FORWARD": "Next",
+    "A_LABEL": "Category of Material",
+    "ERROR_SUBFIELDS_NOT_CACHED": "Subfields not cached for type ${0}!",
+    "NO_WORKING_PTYPE": "no 00/a is set; no working_ptype",
+    "DATA_ERROR_007": "Error retrieving 007 data from system. Try logging out and back in?",
+    "BAD_SUBFIELD_DATA": "Failed to find exactly one row in the subfield map for the current type and step.",
+    "FELL_OFF_STEPS": "Can't go any further that way.",
+    "BAD_WORKING_PTYPE": "Invalid 00/a or corrupt cmpcsm table.",
+    "BACKWARDS_SUBFIELD_PROGRESSION": "Subfields in cmpcsm for ptype ${0} go backwards!? This is probably a corrupt cmpcsm table.",
+    "INACTIVE": "Attempted to apply an inactive wizard"
+}
index c3c6cf0..5521984 100644 (file)
 <!ENTITY staff.cat.marcedit.toggleFFE.label "Fixed Fields -- Record type: ">
 <!ENTITY staff.cat.marcedit.source.caption "Bibliographic source">
 <!ENTITY staff.cat.marcedit.source.submit.label "Update source">
+<!ENTITY staff.cat.marcedit.phys_char_wizard.label "Physical Characteristics Wizard">
+<!ENTITY staff.cat.marcedit.phys_char_wizard.accesskey "W">
 <!ENTITY staff.cat.marc_new.load.label "Load">
 <!ENTITY staff.cat.marc_new.load.accesskey "L">
 <!ENTITY staff.cat.marc_new.set_default.label "Set Workstation Default">
index 1d76bc2..ba54530 100644 (file)
@@ -277,7 +277,8 @@ function set_marc_edit() {
                 }
             },
             'lock_tab' : xulG.lock_tab,
-            'unlock_tab' : xulG.unlock_tab
+            'unlock_tab' : xulG.unlock_tab,
+            'url_prefix': xulG.url_prefix
         };
     if (marc_edit_reset) {
         bottom_pane.reset_iframe( a,b,c );
index 3856f72..189d1a4 100644 (file)
@@ -507,6 +507,7 @@ var urls = {
     'EG_ACQ_USER_REQUESTS' : 'oils://remote/eg/acq/picklist/user_request',
     'XUL_SERIAL_BATCH_RECEIVE': 'oils://remote/xul/server/serial/batch_receive.xul',
     'XUL_SERIAL_PATTERN_WIZARD' : 'oils://remote/xul/server/serial/pattern_wizard.xul',
+    'EG_MARCEDIT_PHYS_CHAR_WIZARD' : 'oils://remote/eg/cat/marcedit/phys_char_wizard',
     'CUSTOM_JS' : '/xul/server/skin/custom.js',
     'ACQ_LINEITEM' : 'oils://remote/eg/acq/lineitem/related/',
     'SERIAL_LIST_SUBSCRIPTION' : 'oils://remote/eg/serial/list_subscription',
index 3addc9c..786095e 100644 (file)
@@ -38,22 +38,35 @@ main.menu.prototype = {
     'toolbar_labelpos' : 'side',
 
     'url_prefix' : function(url,secure) {
+        dump("in this url_prefix()\n");
         // This allows urls to start with a urls key (or be only a urls key)
         // We stop at the first / or ? to allow extra paths and query strings.
+        dump("1\n");
         var base_url = url.match(/^[^?/|]+/);
+        dump("2\n");
         if(base_url) {
+            dump("3\n");
             base_url = base_url[0];
-            if(urls[base_url])
+            dump("4\n");
+            if(urls[base_url]) {
+                dump("5\n");
                 url = url.replace(/^[^?/|]+\|?/, urls[base_url]);
+            }
+            dump("6\n");
         }
+        dump("7\n");
         // if host unspecified URL with leading /, prefix the remote hostname
         if (url.match(/^\//)) url = urls.remote + url;
         // if it starts with http:// and we want secure, convert to https://
+        dump("8\n");
         if (secure && url.match(/^http:\/\//)) {
+            dump("9\n");
             url = url.replace(/^http:\/\//, 'https://');
         }
+        dump("10\n");
         // if it doesn't start with a known protocol, add http(s)://
         if (! url.match(/^(http|https|chrome|oils):\/\//) && ! url.match(/^data:/) ) {
+            dump("11\n");
             url = secure
                 ? 'https://' + url
                 : 'http://' + url;
index 6e78e22..93d5517 100644 (file)
@@ -257,12 +257,26 @@ function my_init() {
         }
 
         document.getElementById('save-button').setAttribute('label', window.xulG.save.label);
+        /* Ugh. Sorry about the spaghetti. */
         document.getElementById('save-button').setAttribute('oncommand',
+            'var to_save = function() { ' + /* begin to_save() */
             'if ($("xul-editor").hidden) set_flat_editor(false); ' +
             'mangle_005(); ' + 
             'var xml_string = xml_escape_unicode( xml_record.toXMLString() ); ' + 
             'save_attempt( xml_string ); ' +
-            'loadRecord();'
+            'loadRecord(); ' +
+            '}; ' + /* end to_save() */
+
+            'if (typeof _owPCW == "object") { ' +
+            ' for (var k in _owPCW) { ' +
+            '  if (_owPCW[k].active) { ' +
+            '    try { _owPCW[k].apply(to_save); to_save.ran = true; } ' +
+            '    catch (E) { alert("_ow_PCW[" + k + "]: " + E); } ' +
+            '    break; ' +
+            '  }' +
+            ' }' +
+            '} ' +
+            'if (!to_save.ran) to_save();'
         );
 
         if (window.xulG.record.url) {
@@ -430,7 +444,9 @@ function my_init() {
             buildBibSourceList(authtoken, xulG.record.id);
         }
 
+        preparePhysCharWizardContext();
         dojo.require('MARC.FixedFields');
+        dojo.require("openils.widget.PhysCharWizard");
 
     } catch(E) {
         alert('FIXME, MARC Editor, my_init: ' + E);
@@ -996,6 +1012,36 @@ function getFFContextMenu(type, name) {
     return context_menu_id;
 }
 
+/* This just sets up a special context menu for a 007 data field to use, so
+ * that users can right-click for a menu and get a choice to launch the
+ * Physical Characteristics Wizard.
+ */
+function preparePhysCharWizardContext() {
+    var menu = document.getElementById("physCharWizardContext");
+    menu.appendChild(document.createElement("menuseparator"));
+
+    var clipb_children = document.getElementById("clipboard").childNodes;
+    for (var i = 0; i < clipb_children.length; i++) /* collection not array */ {
+        var child = clipb_children[i];
+        if (child.nodeName == 'menuitem')
+            menu.appendChild(child.cloneNode(true));
+    }
+}
+
+function launchPhysCharWizard(popup_node) {
+    try {
+        new openils.widget.PhysCharWizard({
+            "node": popup_node,
+            "onapply": function(v) {
+                createControlField("007", v);
+                loadRecord();
+            }
+        });
+    } catch (E) {
+        alert("Exception raised by openils.widget.PhysCharWizard:\n" + E);
+    }
+}
+
 function fillFixedFields () {
     try {
             var grid = document.getElementById('leaderGrid');
@@ -1095,7 +1141,7 @@ function marcControlfield (field) {
                 { value : field.text(),
                   class : 'plain marcEditableControlfield',
                   name : 'CONTROL' + tagname,
-                  context : 'clipboard',
+                  context : tagname == 7 ? 'physCharWizardContext': 'clipboard',
                   size : 50,
                   maxlength : 50 } )
             );
index 6d86170..16505d9 100644 (file)
         <menuitem label="COM" oncommand="changeFFEditor('COM');"/>
         <menuitem label="MFHD" oncommand="changeFFEditor('MFHD');"/>
     </menupopup>
+    <menupopup id="physCharWizardContext" position="after_start">
+        <menuitem label="&staff.cat.marcedit.phys_char_wizard.label;" accesskey="&staff.cat.marcedit.phys_char_wizard.accesskey;" oncommand="launchPhysCharWizard(document.popupNode);" />
+    </menupopup>
 </popupset>
 
 </window>