Fix LP949249, Edit, then Create button creates a new item in all cases
[transitory.git] / Open-ILS / xul / staff_client / server / cat / copy_editor.js
index 68fb253..e430b8e 100644 (file)
@@ -1,17 +1,15 @@
-// vim:noet:sw=4:ts=4
-var g = {};
+// vim:et:sw=4:ts=4
+var g = { 'disabled' : false };
 g.map_acn = {};
 
-var xulG = {};
-
 function $(id) { return document.getElementById(id); }
+function $_(x) { return $('catStrings').getString(x); }
 
 function my_init() {
     try {
         /******************************************************************************************************/
         /* setup JSAN and some initial libraries */
 
-        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
         if (typeof JSAN == 'undefined') {
             throw( $('commonStrings').getString('common.jsan.missing') );
         }
@@ -24,13 +22,17 @@ function my_init() {
         JSAN.use('OpenILS.data'); g.data = new OpenILS.data(); g.data.init({'via':'stash'});
         JSAN.use('util.network'); g.network = new util.network();
 
-        g.docid = xul_param('docid',{'modal_xulG':true});
-        g.handle_update = xul_param('handle_update',{'modal_xulG':true});
+        if (xulG.unified_interface) {
+            $('non_unified_buttons').hidden = true;
+        }
+
+        g.docid = xul_param('docid');
+        g.handle_update = xul_param('handle_update');
 
         /******************************************************************************************************/
         /* Get the copy ids from various sources and flesh them */
 
-        var copy_ids = xul_param('copy_ids',{'concat':true,'JSON2js_if_cgi':true,'JSON2js_if_xulG':true,'JSON2js_if_xpcom':true,'stash_name':'temp_copy_ids','clear_xpcom':true,'modal_xulG':true});
+        var copy_ids = xul_param('copy_ids',{'concat':true,'JSON2js_if_cgi':true,'JSON2js_if_xulG':true,'JSON2js_if_xpcom':true,'stash_name':'temp_copy_ids','clear_xpcom':true});
         if (!copy_ids) copy_ids = [];
 
         if (copy_ids.length > 0) g.copies = g.network.simple_request(
@@ -42,14 +44,23 @@ function my_init() {
         /* And other fleshed copies if any */
 
         if (!g.copies) g.copies = [];
-        var c = xul_param('copies',{'concat':true,'JSON2js_if_cgi':true,'JSON2js_if_xpcom':true,'stash_name':'temp_copies','clear_xpcom':true,'modal_xulG':true})
+        var c = xul_param('copies',{'concat':true,'JSON2js_if_cgi':true,'JSON2js_if_xpcom':true,'stash_name':'temp_copies','clear_xpcom':true})
         if (c) g.copies = g.copies.concat(c);
 
         /******************************************************************************************************/
         /* We try to retrieve callnumbers for existing copies, but for new copies, we rely on this */
 
-        g.callnumbers = xul_param('callnumbers',{'concat':true,'JSON2js_if_cgi':true,'JSON2js_if_xpcom':true,'stash_name':'temp_callnumbers','clear_xpcom':true,'modal_xulG':true});
+        g.callnumbers = xul_param('callnumbers',{'concat':true,'JSON2js_if_cgi':true,'JSON2js_if_xpcom':true,'stash_name':'temp_callnumbers','clear_xpcom':true});
+
+        /******************************************************************************************************/
+        /* Get preference (if it exists) for copy location label order */
 
+        g.cl_first = false; // Default to legacy OU first mode
+        var prefs = Components.classes['@mozilla.org/preferences-service;1']
+            .getService(Components.interfaces['nsIPrefBranch']);
+        try {
+            g.cl_first = prefs.getBoolPref('oils.copy_editor.copy_location_name_first');
+        } catch(E) { }
 
         /******************************************************************************************************/
         /* Quick fix, this was defined inline in the global scope but now needs g.error and g.copies from my_init */
@@ -61,80 +72,78 @@ function my_init() {
         /******************************************************************************************************/
         /* Is the interface an editor or a viewer, single or multi copy, existing copies or new copies? */
 
-        if (xul_param('edit',{'modal_xulG':true}) == '1') { 
+        if (xul_param('edit') == '1') { 
 
-            // Editor desired, but let's check permissions
             g.edit = false;
 
-            try {
-                var check = g.network.simple_request(
-                    'PERM_MULTI_ORG_CHECK',
-                    [ 
-                        ses(), 
-                        g.data.list.au[0].id(), 
-                        util.functional.map_list(
-                            g.copies,
-                            function (o) {
-                                var lib;
-                                var cn_id = o.call_number();
-                                if (cn_id == -1) {
-                                    lib = o.circ_lib(); // base perms on circ_lib instead of owning_lib if pre-cat
-                                } else {
-                                    if (! g.map_acn[ cn_id ]) {
-                                        var req = g.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ cn_id ]);
-                                        if (typeof req.ilsevent == 'undefined') {
-                                            g.map_acn[ cn_id ] = req;
-                                            lib = g.map_acn[ cn_id ].owning_lib();
+            if (g.copies.length > 0) { // When loaded in the unified interface, there may be no copies yet (from the volum/item creator) 
+
+                // Editor desired, but let's check permissions
+
+                try {
+                    var check = g.network.simple_request(
+                        'PERM_MULTI_ORG_CHECK',
+                        [ 
+                            ses(), 
+                            g.data.list.au[0].id(), 
+                            util.functional.map_list(
+                                g.copies,
+                                function (o) {
+                                    var lib;
+                                    var cn_id = o.call_number();
+                                    if (cn_id == -1) {
+                                        lib = o.circ_lib(); // base perms on circ_lib instead of owning_lib if pre-cat
+                                    } else {
+                                        if (! g.map_acn[ cn_id ]) {
+                                            var req = g.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ cn_id ]);
+                                            if (typeof req.ilsevent == 'undefined') {
+                                                g.map_acn[ cn_id ] = req;
+                                                lib = g.map_acn[ cn_id ].owning_lib();
+                                            } else {
+                                                lib = o.circ_lib();
+                                            }
                                         } else {
-                                            lib = o.circ_lib();
+                                            lib = g.map_acn[ cn_id ].owning_lib();
                                         }
-                                    } else {
-                                        lib = g.map_acn[ cn_id ].owning_lib();
                                     }
+                                    return typeof lib == 'object' ? lib.id() : lib;
                                 }
-                                return typeof lib == 'object' ? lib.id() : lib;
-                            }
-                        ),
-                        g.copies.length == 1 ? [ 'UPDATE_COPY' ] : [ 'UPDATE_COPY', 'UPDATE_BATCH_COPY' ]
-                    ]
-                );
-                g.edit = check.length == 0;
-            } catch(E) {
-                g.error.standard_unexpected_error_alert('batch permission check',E);
+                            ),
+                            g.copies.length == 1 ? [ 'UPDATE_COPY' ] : [ 'UPDATE_COPY', 'UPDATE_BATCH_COPY' ]
+                        ]
+                    );
+                    g.edit = check.length == 0;
+                } catch(E) {
+                    g.error.standard_unexpected_error_alert('batch permission check',E);
+                }
+
             }
 
             if (g.edit) {
                 $('caption').setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.caption')); 
                 $('save').setAttribute('hidden','false'); 
-                g.retrieve_templates();
             } else {
                 $('top_nav').setAttribute('hidden','true');
             }
+
+            g.retrieve_templates();
+
         } else {
             $('top_nav').setAttribute('hidden','true');
         }
 
-        if (g.copies.length > 0 && g.copies[0].id() < 0) {
-            document.getElementById('copy_notes').setAttribute('hidden','true');
-            g.apply("status",5 /* In Process */);
-            $('save').setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.create_copies'));
-        } else {
-            g.panes_and_field_names.left_pane = 
+        g.panes_and_field_names.left_pane = 
+            [
                 [
-                    [
-                        $('catStrings').getString('staff.cat.copy_editor.status'),
-                        { 
-                            render: 'typeof fm.status() == "object" ? fm.status().name() : g.data.hash.ccs[ fm.status() ].name()', 
-                            input: g.safe_to_edit_copy_status() ? 'c = function(v){ g.apply("status",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( g.data.list.ccs, function(obj) { return [ obj.name(), obj.id(), typeof my_constants.magical_statuses[obj.id()] != "undefined" ? true : false ]; } ).sort() ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);' : undefined,
-                            //input: 'c = function(v){ g.apply("status",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( util.functional.filter_list( g.data.list.ccs, function(obj) { return typeof my_constants.magical_statuses[obj.id()] == "undefined"; } ), function(obj) { return [ obj.name(), obj.id() ]; } ).sort() ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
-                        }
-                    ]
-                ].concat(g.panes_and_field_names.left_pane);
-        }
+                    $('catStrings').getString('staff.cat.copy_editor.status'),
+                    { 
+                        render: 'typeof fm.status() == "object" ? fm.status().name() : g.data.hash.ccs[ fm.status() ].name()', 
+                        input: g.safe_to_edit_copy_status() ? 'c = function(v){ g.apply("status",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( g.data.list.ccs, function(obj) { return [ obj.name(), obj.id(), typeof my_constants.magical_statuses[obj.id()] != "undefined" ? true : false ]; } ).sort() ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);' : undefined,
+                        //input: 'c = function(v){ g.apply("status",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( util.functional.filter_list( g.data.list.ccs, function(obj) { return typeof my_constants.magical_statuses[obj.id()] == "undefined"; } ), function(obj) { return [ obj.name(), obj.id() ]; } ).sort() ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
+                    }
+                ]
+            ].concat(g.panes_and_field_names.left_pane);
 
-        if (g.copies.length != 1) {
-            document.getElementById('copy_notes').setAttribute('hidden','true');
-        }
 
         /******************************************************************************************************/
         /* Show the Record Details? */
@@ -161,6 +170,62 @@ function my_init() {
 
         g.summarize( g.copies );
         g.render();
+        g.check_for_unmet_required_fields();
+
+        if (xulG.unified_interface) {
+            xulG.disable_copy_editor = function(c) {
+                addCSSClass(document.documentElement,'disabled_copy_editor');
+                g.disabled = true;
+            }
+            xulG.enable_copy_editor = function(c) {
+                removeCSSClass(document.documentElement,'disabled_copy_editor');
+                g.disabled = false;
+                xulG.refresh_copy_editor();
+            }
+            xulG.refresh_copy_editor = function() {
+                dump('refresh_copy_editor\n');
+                addCSSClass(document.documentElement,'enabling_copy_editor');
+                try {
+                    xulG.clear_update_copy_editor_timeout();
+                    g.copies = xulG.copies;
+                    g.edit = g.copies.length > 0;
+                    if (g.edit) {
+                        $('caption').setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.caption'));
+                    }
+                    g.original_copies = js2JSON( g.copies );
+                    g.hide_copy_notes_button();
+                    for (var i = 0; i < g.applied_templates.length; i++) {
+                        g._apply_template( g.applied_templates[i], false);
+                    }
+                    if (g.copies.length > 0) {
+                        // Stop tracking these templates once they're applied
+                        // to actual copies
+                        g.applied_templates = [];
+                    }
+                    g.summarize( g.copies );
+                    g.render();
+                    g.check_for_unmet_required_fields();
+                    setTimeout(
+                        function() {
+                            removeCSSClass(document.documentElement,'enabling_copy_editor');
+                        }, 1000
+                    );
+                } catch(E) {
+                    alert('Error in copy_editor.js, xulG.refresh_copy_editor(): ' + E);
+                }
+            };
+            xulG.unlock_copy_editor = function() {
+                oils_unlock_page();
+            };
+            xulG.notify_of_templatable_field_change = function(id,v) {
+                g.changed[ 'volume_copy_creator.'+id ] = { 'type' : 'volume_copy_creator', 'field' : id, 'value' : v };
+            }
+        } else {
+            g.hide_copy_notes_button();
+        }
+
+        JSAN.use('util.hide');
+        util.hide.generate_css('ui.hide_copy_editor_fields');
 
     } catch(E) {
         var err_msg = $("commonStrings").getFormattedString('common.exception', ['cat/copy_editor.js', E]);
@@ -170,6 +235,19 @@ function my_init() {
 }
 
 /******************************************************************************************************/
+/* Show copy notes button */
+g.hide_copy_notes_button = function() {
+    if (g.copies.length > 0 && g.copies[0].id() < 0) {
+        document.getElementById('copy_notes').setAttribute('hidden','true');
+        $('save').setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.create_copies'));
+        $('save').setAttribute('accesskey', $('catStrings').getString('staff.cat.copy_editor.create_copies.accesskey'));
+    }
+    if (g.copies.length != 1) {
+        document.getElementById('copy_notes').setAttribute('hidden','true');
+    }
+}
+
+/******************************************************************************************************/
 /* Retrieve Templates */
 
 g.retrieve_templates = function() {
@@ -191,6 +269,26 @@ g.retrieve_templates = function() {
             function() { g.copy_editor_prefs[ 'template_menu' ] = { 'value' : g.template_menu.value }; g.save_attributes(); },
             false
         );
+
+        if (xulG.unified_interface) {
+            if (typeof xulG.update_unified_template_list == 'function') {
+                xulG.update_unified_template_list(list);
+                // functions the unified wrapper should use to let the item attribute editor do the heavy lifting for templates
+                xulG.update_item_editor_template_selection = function(new_value) {
+                    g.template_menu.setAttribute('value', new_value);
+                    g.template_menu.value = new_value;
+                    g.copy_editor_prefs[ 'template_menu' ] = { 'value' : g.template_menu.value };
+                    g.save_attributes();
+                }
+                xulG.item_editor_apply_template = function() { g.apply_template(true); };
+                xulG.item_editor_delete_template = function() { g.delete_template(); };
+                xulG.item_editor_save_template = function() { g.save_template(); };
+                xulG.item_editor_import_templates = function() { g.import_templates(); };
+                xulG.item_editor_export_templates = function() { g.export_templates(); };
+                xulG.item_editor_reset = function() { g.reset(); };
+            }
+        }
+
     } catch(E) {
         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.retrieve_templates.error'), E);
     }
@@ -199,12 +297,46 @@ g.retrieve_templates = function() {
 /******************************************************************************************************/
 /* Apply Template */
 
-g.apply_template = function() {
+g.applied_templates = [];
+
+g.apply_template = function(apply_volume_editor_template_changes) {
     try {
         var name = g.template_menu.value;
         if (g.templates[ name ] != 'undefined') {
+            if (g.copies == 0) {
+                // We're only tracking these applied templates temporarily,
+                // specifically when they're used prior to copies being
+                // created in the unified interface.
+                g.applied_templates.push( name );
+            }
+            g._apply_template(name,apply_volume_editor_template_changes);
+            g.summarize( g.copies );
+            g.render();
+            g.check_for_unmet_required_fields();
+        }
+    } catch(E) {
+        g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.apply_templates.error'), E);
+    }
+}
+
+g._apply_template = function(name,apply_volume_editor_template_changes) {
+    try {
+        if (g.templates[ name ] != 'undefined') {
             var template = g.templates[ name ];
             for (var i in template) {
+                if (g.is_field_hidden(i)) {
+                    alert($('catStrings').getFormattedString(
+                        'staff.cat.copy_editor.apply_unsafe_field',
+                        [i]
+                    ));
+                    continue;
+                }
+                if (template[i].field == 'status') {
+                    if (!g.safe_to_edit_copy_status()) {
+                        alert($('catStrings').getFormattedString('staff.cat.copy_editor.apply_unsafe_field',[i]));
+                        continue;
+                    }
+                }
                 g.changed[ i ] = template[ i ];
                 switch( template[i].type ) {
                     case 'attribute' :
@@ -216,13 +348,16 @@ g.apply_template = function() {
                     case 'owning_lib' :
                         g.apply_owning_lib(template[i].value);
                     break;
+                    case 'volume_copy_creator' :
+                        if (xulG.unified_interface && apply_volume_editor_template_changes) {
+                            xulG.apply_template_to_batch(template[i].field,template[i].value);
+                        }
+                    break;
                 }
             }
-            g.summarize( g.copies );
-            g.render();
         }
     } catch(E) {
-        g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.apply_templates.error'), E);
+        alert('Error in copy_editor.js, g._apply_template('+name+'): ' + E);
     }
 }
 
@@ -296,7 +431,6 @@ g.delete_template = function() {
 
 g.export_templates = function() {
     try {
-        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
         JSAN.use('util.file'); var f = new util.file('');
         f.export_file( { 'title' : $('catStrings').getString('staff.cat.copy_editor.export_templates.title'), 'data' : g.templates } );
     } catch(E) {
@@ -309,7 +443,6 @@ g.export_templates = function() {
 
 g.import_templates = function() {
     try {
-        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
         JSAN.use('util.file'); var f = new util.file('');
         var temp = f.import_file( { 'title' : $('catStrings').getString('staff.cat.copy_editor.import_templates.title') } );
         if (temp) {
@@ -382,10 +515,16 @@ g.import_templates = function() {
 /* Restore backup copies */
 
 g.reset = function() {
+    g.applied_templates = [];
     g.changed = {};
     g.copies = JSON2js( g.original_copies );
     g.summarize( g.copies );
     g.render();
+    g.check_for_unmet_required_fields();
+    oils_unlock_page();
+    if (xulG.unified_interface) {
+        xulG.reset_batch_menus();
+    }
 }
 
 /******************************************************************************************************/
@@ -393,10 +532,16 @@ g.reset = function() {
 
 g.apply = function(field,value) {
     g.error.sdump('D_TRACE','applying field = <' + field + '>  value = <' + value + '>\n');
-    if (value == '<HACK:KLUDGE:NULL>') value = null;
+    if (value == '<HACK:KLUDGE:NULL>') {
+        value = null;
+    }
     if (field == 'alert_message') { value = value.replace(/^\W+$/g,''); }
     if (field == 'price' || field == 'deposit_amount') {
-        if (value == '') { value = null; } else { JSAN.use('util.money'); value = util.money.sanitize( value ); }
+        if (value == '') {
+            value = null;
+        } else {
+            JSAN.use('util.money'); value = util.money.sanitize( value );
+        }
     }
     for (var i = 0; i < g.copies.length; i++) {
         var copy = g.copies[i];
@@ -406,6 +551,8 @@ g.apply = function(field,value) {
             alert(E);
         }
     }
+
+    oils_lock_page();
 }
 
 /******************************************************************************************************/
@@ -425,18 +572,22 @@ g.apply_stat_cat = function(sc_id,entry_id) {
                     return (obj.stat_cat() != sc_id);
                 }
             );
-            if (entry_id > -1) temp.push( 
-                util.functional.find_id_object_in_list( 
-                    g.data.hash.asc[sc_id].entries(), 
-                    entry_id
-                )
-            );
+            if (entry_id > -1) {
+                temp.push( 
+                    util.functional.find_id_object_in_list( 
+                        g.data.hash.asc[sc_id].entries(), 
+                        entry_id
+                    )
+                );
+            }
             copy.stat_cat_entries( temp );
 
         } catch(E) {
             g.error.standard_unexpected_error_alert('apply_stat_cat',E);
         }
     }
+
+    oils_lock_page();
 }
 
 /******************************************************************************************************/
@@ -444,6 +595,8 @@ g.apply_stat_cat = function(sc_id,entry_id) {
 
 g.apply_owning_lib = function(ou_id) {
     g.error.sdump('D_TRACE','ou_id = ' + ou_id + '\n');
+    // but don't allow this when bundled with the volume/copy creator UI, or if we're editing pre-cats
+    if (! g.safe_to_change_owning_lib() ) { return; }
     for (var i = 0; i < g.copies.length; i++) {
         var copy = g.copies[i];
         try {
@@ -456,27 +609,30 @@ g.apply_owning_lib = function(ou_id) {
                 g.map_acn[copy.call_number()] = volume;
             }
             var old_volume = g.map_acn[copy.call_number()];
-            var acn_id = g.network.simple_request(
+            var acn_blob = g.network.simple_request(
                 'FM_ACN_FIND_OR_CREATE',
-                [ses(),old_volume.label(),old_volume.record(),ou_id]
+                [ses(),old_volume.label(),old_volume.record(),ou_id,old_volume.prefix().id(),old_volume.suffix().id(),old_volume.label_class().id()]
             );
-            if (typeof acn_id.ilsevent != 'undefined') {
-                g.error.standard_unexpected_error_alert($('catStrings').getFormattedString('staff.cat.copy_editor.apply_owning_lib.call_number.error', [copy.barcode()]), acn_id);
+            if (typeof acn_blob.ilsevent != 'undefined') {
+                g.error.standard_unexpected_error_alert($('catStrings').getFormattedString('staff.cat.copy_editor.apply_owning_lib.call_number.error', [copy.barcode()]), acn_blob);
                 continue;
             }
-            copy.call_number(acn_id);
+            copy.call_number(acn_blob.acn_id);
             copy.ischanged('1');
         } catch(E) {
             g.error.standard_unexpected_error_alert('apply_stat_cat',E);
         }
     }
+
+    oils_lock_page();
 }
 
 /******************************************************************************************************/
-/* This returns true if none of the copies being edited are pre-cats */
+/* This returns false if any of the copies being edited are pre-cats, or if we're embedded in the unified volume/copy UI */
 
 g.safe_to_change_owning_lib = function() {
     try {
+        if (xulG.unified_interface) { return false; }
         var safe = true;
         for (var i = 0; i < g.copies.length; i++) {
             var cn = g.copies[i].call_number();
@@ -508,6 +664,23 @@ g.safe_to_edit_copy_status = function() {
 }
 
 /******************************************************************************************************/
+/* This returns true if the field has been hidden via util.hide */
+
+g.is_field_hidden = function(field) {
+    try {
+        g.data.stash_retrieve();
+        if (g.data.hash.aous['ui.hide_copy_editor_fields']
+            && g.data.hash.aous['ui.hide_copy_editor_fields'].indexOf(field) > -1) {
+            return true;
+        }
+    } catch(E) {
+        g.error.standard_unexpected_error_alert('is_field_hidden?',E);
+        return false;
+    }
+}
+
+
+/******************************************************************************************************/
 /* This concats and uniques all the alert messages for use as the default value for a new alert message */
 
 g.populate_alert_message_input = function(tb) {
@@ -664,11 +837,17 @@ g.get_acpl_list = function() {
 
 
 /******************************************************************************************************/
-/* This keeps track of what fields have been edited for styling purposes */
+/* This keeps track of which fields have been edited for styling purposes */
 
 g.changed = {};
 
 /******************************************************************************************************/
+/* This keeps track of which fields are required, and which fields have been populated */
+
+g.required = {};
+g.populated = {};
+
+/******************************************************************************************************/
 /* These need data from the middle layer to render */
 
 function init_panes0() {
@@ -750,6 +929,13 @@ g.panes_and_field_names = {
         $('catStrings').getString('staff.cat.copy_editor.field.barcode.label'),
         {
             render: 'fm.barcode();',
+            input:
+                  'c = function (v) {'
+                +     'g.apply("barcode", v);'
+                +     'if (typeof post_c === "function") post_c(v);'
+                + '};'
+                + 'x = document.createElement("textbox");',
+            attr: { 'class': 'disabled' },
         }
     ], 
     [
@@ -759,6 +945,12 @@ g.panes_and_field_names = {
         }
     ],
     [
+        $('catStrings').getString('staff.cat.copy_editor.field.active_date.label'),
+        { 
+            render: 'util.date.formatted_date( fm.active_date(), "%F");',
+        }
+    ],
+    [
         $('catStrings').getString('staff.cat.copy_editor.field.creator.label'),
         { 
             render: 'fm.creator();',
@@ -785,7 +977,7 @@ g.panes_and_field_names = {
         $('catStrings').getString('staff.cat.copy_editor.field.location.label'),
         { 
             render: 'typeof fm.location() == "object" ? fm.location().name() : g.data.lookup("acpl",fm.location()).name()', 
-            input: 'c = function(v){ g.apply("location",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( g.get_acpl_list(), function(obj) { return [ g.data.hash.aou[ obj.owning_lib() ].shortname() + " : " + obj.name(), obj.id() ]; }).sort()); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
+            input: 'c = function(v){ g.apply("location",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( g.get_acpl_list(), function(obj) { return [ ' + (g.cl_first ? 'obj.name() + " : " + g.data.hash.aou[ obj.owning_lib() ].shortname()' : 'g.data.hash.aou[ obj.owning_lib() ].shortname() + " : " + obj.name()') + ', obj.id() ]; }).sort()); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
 
         }
     ],
@@ -865,15 +1057,15 @@ g.panes_and_field_names = {
      [
         $('catStrings').getString('staff.cat.copy_editor.field.circulate_as_type.label'),
         {     
-            render: 'fm.circ_as_type() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : g.data.hash.citm[ fm.circ_as_type() ].value()',
-            input: 'c = function(v){ g.apply("circ_as_type",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.remove_circulate_as_type"), "<HACK:KLUDGE:NULL>" ] ].concat( util.functional.map_list( g.data.list.citm, function(n){return [ n.code() + " - " + n.value(), n.code()];} ).sort() ) ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
+            render: 'fm.circ_as_type() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : g.data.hash.citm[ fm.circ_as_type() ].value',
+            input: 'c = function(v){ g.apply("circ_as_type",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.remove_circulate_as_type"), "<HACK:KLUDGE:NULL>" ] ].concat( util.functional.map_list( g.data.list.citm, function(n){return [ n.code + " - " + n.value, n.code];} ).sort() ) ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
         } 
     ],
     [
         $('catStrings').getString('staff.cat.copy_editor.field.circulation_modifier.label'),
         {    
-            render: 'fm.circ_modifier() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : fm.circ_modifier()',
-            input: 'c = function(v){ g.apply("circ_modifier",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null"), "<HACK:KLUDGE:NULL>" ] ].concat( util.functional.map_list( g.data.list.circ_modifier, function(obj) { return [ obj, obj ]; } ).sort() ) ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
+            render: 'fm.circ_modifier() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : $("commonStrings").getFormattedString("staff.circ_modifier.display",[fm.circ_modifier(),g.data.hash.ccm[fm.circ_modifier()].name(),g.data.hash.ccm[fm.circ_modifier()].description()])',
+            input: 'c = function(v){ g.apply("circ_modifier",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null"), "<HACK:KLUDGE:NULL>" ] ].concat( util.functional.map_list( g.data.list.ccm, function(obj) { return [ $("commonStrings").getFormattedString("staff.circ_modifier.display",[obj.code(),obj.name(),obj.description()]), obj.code() ]; } ).sort() ) ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
         }
     ],
 ],
@@ -964,6 +1156,7 @@ g.summarize = function( copies ) {
         var render = g.field_names[i][1].render;
         var attr = g.field_names[i][1].attr;
         g.summary[ field_name ] = {};
+        g.populated[ field_name ] = 1; // delete later if we encounter a copy with the field unset
 
         /******************************************************************************************************/
         /* Loop through the copies */
@@ -972,7 +1165,7 @@ g.summarize = function( copies ) {
 
             var fm = copies[j];
             var cmd = render || ('fm.' + field_name + '();');
-            var value = '???';
+            var value = $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null");
 
             /**********************************************************************************************/
             /* Try to retrieve the value for this field for this copy */
@@ -985,6 +1178,9 @@ g.summarize = function( copies ) {
             if (typeof value == 'object' && value != null) {
                 alert('FIXME: field_name = <' + field_name + '>  value = <' + js2JSON(value) + '>\n');
             }
+            if (value == $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null")) {
+                delete g.populated[field_name];
+            }
 
             /**********************************************************************************************/
             /* Tally the count */
@@ -1051,15 +1247,19 @@ g.render = function() {
             try {
                 var f = g.panes_and_field_names[h][i]; var fn = f[0]; var attr = f[1].attr;
                 groupbox = document.createElement('groupbox'); document.getElementById(h).appendChild(groupbox);
+                groupbox.setAttribute('hideable',fn);
                 if (attr) {
                     for (var a in attr) {
                         groupbox.setAttribute(a,attr[a]);
                     }
                 }
-                if (typeof g.changed[fn] != 'undefined') groupbox.setAttribute('class','copy_editor_field_changed');
                 caption = document.createElement('caption'); groupbox.appendChild(caption);
-                caption.setAttribute('label',fn); caption.setAttribute('id','caption_'+fn);
-                vbox = document.createElement('vbox'); groupbox.appendChild(vbox);
+                caption.setAttribute('label',fn);
+                caption.setAttribute('id','caption_'+fn); // used for focus/keyboard navigation
+                caption.setAttribute('hideable',fn);
+                vbox = document.createElement('vbox'); groupbox.appendChild(vbox); // main display widget goes here
+                if (typeof g.changed[fn] != 'undefined') { addCSSClass(vbox,'copy_editor_field_changed'); }
+                if (typeof g.required[fn] != 'undefined') { addCSSClass(vbox,'copy_editor_field_required'); }
                 grid = util.widgets.make_grid( [ { 'flex' : 1 }, {}, {} ] ); vbox.appendChild(grid);
                 grid.setAttribute('flex','1');
                 rows = grid.lastChild;
@@ -1086,10 +1286,11 @@ g.render = function() {
                     }
                     label2.appendChild( document.createTextNode(copy_count) );
                 }
-                var hbox = document.createElement('hbox'); 
-                hbox.setAttribute('id',fn);
+                groupbox.setAttribute('id','groupbox_'+fn); // this id is meant to be referenced by cat_custom.css for hiding fields
+                var hbox = document.createElement('hbox'); // main input controls go here
+                hbox.setAttribute('id',fn); // this id gets used to color areas green, etc.
                 groupbox.appendChild(hbox);
-                var hbox2 = document.createElement('hbox');
+                var hbox2 = document.createElement('hbox'); // cancel/apply buttons go here
                 groupbox.appendChild(hbox2);
 
                 /**************************************************************************************/
@@ -1118,8 +1319,16 @@ g.render = function() {
             } catch(E) { alert(E); }
         }
     }
-    if (g.template_menu) g.template_menu.value = g.template_menu.getAttribute('value');
+    if (g.template_menu) {
+        g.template_menu.value = g.template_menu.getAttribute('value');
+        if (xulG.unified_interface) {
+            if (typeof xulG.update_unified_template_selection == 'function') {
+                xulG.update_unified_template_selection(g.template_menu.value);
+            }
+        }
+    }
 
+    util.hide.generate_css('ui.hide_copy_editor_fields');
 }
 
 /******************************************************************************************************/
@@ -1148,16 +1357,17 @@ g.render_input = function(node,blob) {
             groupbox.setAttribute('style','');
         }
 
-        vbox.addEventListener('mouseover',on_mouseover,false);
-        vbox.addEventListener('mouseout',on_mouseout,false);
         groupbox.addEventListener('mouseover',on_mouseover,false);
         groupbox.addEventListener('mouseout',on_mouseout,false);
-        groupbox.firstChild.addEventListener('mouseover',on_mouseover,false);
-        groupbox.firstChild.addEventListener('mouseout',on_mouseout,false);
 
         function on_click(ev){
             try {
-                if (block) return; block = true;
+                if (block || g.disabled || !g.edit || ev.currentTarget.classList.contains('disabled')) {
+                    return;
+                }
+                block = true;
+
+                oils_lock_page();
 
                 function post_c(v) {
                     try {
@@ -1181,6 +1391,7 @@ g.render_input = function(node,blob) {
                             function() {
                                 g.summarize( g.copies );
                                 g.render();
+                                g.check_for_unmet_required_fields();
                                 document.getElementById(caption.id).focus();
                             }, 0
                         );
@@ -1201,7 +1412,16 @@ g.render_input = function(node,blob) {
                     apply.addEventListener('command',function() { c(x.value); },false);
                     var cancel = document.createElement('button');
                     cancel.setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.cancel.label'));
-                    cancel.addEventListener('command',function() { setTimeout( function() { g.summarize( g.copies ); g.render(); document.getElementById(caption.id).focus(); }, 0); }, false);
+                    cancel.addEventListener('command',function() {
+                            setTimeout( function() {
+                                    g.summarize( g.copies );
+                                    g.render();
+                                    g.check_for_unmet_required_fields();
+                                    document.getElementById(caption.id).focus(); 
+                                }, 0
+                            );
+                        }, false
+                    );
                     hbox2.appendChild(cancel);
                     setTimeout( function() { x.focus(); }, 0 );
                 }
@@ -1209,10 +1429,8 @@ g.render_input = function(node,blob) {
                 g.error.standard_unexpected_error_alert('render_input',E);
             }
         }
-        vbox.addEventListener('click',on_click, false);
-        hbox.addEventListener('click',on_click, false);
-        caption.addEventListener('click',on_click, false);
-        caption.addEventListener('keypress',function(ev) {
+        groupbox.addEventListener('click',on_click, false);
+        groupbox.addEventListener('keypress',function(ev) {
             if (ev.keyCode == 13 /* enter */ || ev.keyCode == 77 /* mac enter */) on_click();
         }, false);
         caption.setAttribute('style','-moz-user-focus: normal');
@@ -1228,18 +1446,25 @@ g.render_input = function(node,blob) {
 /* store the copies in the global xpcom stash */
 
 g.stash_and_close = function() {
+    var r = {textcode: ''};
     try {
+        oils_unlock_page();
+
         if (g.handle_update) {
             try {
-                var r = g.network.request(
+                r = g.network.request(
                     api.FM_ACP_FLESHED_BATCH_UPDATE.app,
                     api.FM_ACP_FLESHED_BATCH_UPDATE.method,
                     [ ses(), g.copies, true ]
                 );
-                if (typeof r.ilsevent != 'undefined') {
+                if (r.textcode === 'ITEM_BARCODE_EXISTS') {
+                    alert('error with item update: ' + r.desc);
+                    var barcode = $($_('staff.cat.copy_editor.field.barcode.label'));
+                    barcode.parentNode.classList.remove('disabled');
+                    barcode.click();
+                }
+                else if (typeof r.ilsevent !== 'undefined') {
                     g.error.standard_unexpected_error_alert('copy update',r);
-                } else {
-                    alert($('catStrings').getString('staff.cat.copy_editor.handle_update.success'));
                 }
                 /* FIXME -- revisit the return value here */
             } catch(E) {
@@ -1249,10 +1474,12 @@ g.stash_and_close = function() {
         //g.data.temp_copies = js2JSON( g.copies );
         //g.data.stash('temp_copies');
         xulG.copies = g.copies;
-        update_modal_xulG(xulG);
-        window.close();
+        if (r.textcode !== 'ITEM_BARCODE_EXISTS') {
+            JSAN.use('util.widgets');
+            util.widgets.dispatch('close',window);
+        }
     } catch(E) {
-        g.error.standard_unexpected_error_alert('stash and close',E);
+        alert('Error in copy_editor.js, g.stash_and_close(): '+E);
     }
 }
 
@@ -1328,6 +1555,10 @@ g.add_stat_cat = function(sc) {
 
         var label_name = g.data.hash.aou[ sc.owner() ].shortname() + " : " + sc.name();
 
+        if (get_bool( sc.required() )) {
+            g.required[ label_name ] = 1;
+        }
+
         var temp_array = [
             label_name,
             {
@@ -1353,7 +1584,7 @@ g.add_stat_cat = function(sc) {
 g.populate_stat_cats = function() {
     try {
         g.data.stash_retrieve();
-        g.stat_cat_seen = {};
+        g.stat_cat_seen = {}; // used for determining whether a stat cat is displayed (and is eligible to be manipulated via a template)
 
         function get(lib_id,only_these) {
             g.data.stash_retrieve();
@@ -1449,4 +1680,30 @@ g.populate_stat_cats = function() {
     }
 }
 
+g.check_for_unmet_required_fields = function() {
+    var abort = [];
+    for (var fn in g.required) {
+        if (typeof g.populated[fn] == 'undefined') {
+            abort.push(fn);
+        }
+    }
+    if (xulG.unified_interface) {
+        if (abort.length > 0) {
+            if (typeof xulG.lock_save_button == 'function') {
+                xulG.lock_save_button();
+            }
+        } else {
+            if (typeof xulG.unlock_save_button == 'function') {
+                xulG.unlock_save_button();
+            }
+        }
+    } else {
+        if (abort.length > 0) {
+            $('save').setAttribute('disabled','true');
+        } else {
+            $('save').setAttribute('disabled','false');
+        }
+    }
+}
+