]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/xul/staff_client/server/cat/copy_editor.js
robustify (and klunkify) unified volume copy creator
[Evergreen.git] / Open-ILS / xul / staff_client / server / cat / copy_editor.js
1 // vim:et:sw=4:ts=4
2 var g = { 'disabled' : false };
3 g.map_acn = {};
4
5 function $(id) { return document.getElementById(id); }
6
7 function my_init() {
8     try {
9         /******************************************************************************************************/
10         /* setup JSAN and some initial libraries */
11
12         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
13         if (typeof JSAN == 'undefined') {
14             throw( $('commonStrings').getString('common.jsan.missing') );
15         }
16         JSAN.errorLevel = "die"; // none, warn, or die
17         JSAN.addRepository('/xul/server/');
18         JSAN.use('util.error'); g.error = new util.error();
19         g.error.sdump('D_TRACE','my_init() for cat/copy_editor.xul');
20
21         JSAN.use('util.functional');
22         JSAN.use('OpenILS.data'); g.data = new OpenILS.data(); g.data.init({'via':'stash'});
23         JSAN.use('util.network'); g.network = new util.network();
24
25         if (xulG.unified_interface) {
26             $('non_unified_buttons').hidden = true;
27         }
28
29         g.docid = xul_param('docid',{'modal_xulG':true});
30         g.handle_update = xul_param('handle_update',{'modal_xulG':true});
31
32         /******************************************************************************************************/
33         /* Get the copy ids from various sources and flesh them */
34
35         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});
36         if (!copy_ids) copy_ids = [];
37
38         if (copy_ids.length > 0) g.copies = g.network.simple_request(
39             'FM_ACP_FLESHED_BATCH_RETRIEVE.authoritative',
40             [ copy_ids ]
41         );
42
43         /******************************************************************************************************/
44         /* And other fleshed copies if any */
45
46         if (!g.copies) g.copies = [];
47         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})
48         if (c) g.copies = g.copies.concat(c);
49
50         /******************************************************************************************************/
51         /* We try to retrieve callnumbers for existing copies, but for new copies, we rely on this */
52
53         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});
54
55
56         /******************************************************************************************************/
57         /* Quick fix, this was defined inline in the global scope but now needs g.error and g.copies from my_init */
58         /* Quick fix, messagecatalog only usable during/after onload */
59
60         init_panes0();
61         init_panes();
62
63         /******************************************************************************************************/
64         /* Is the interface an editor or a viewer, single or multi copy, existing copies or new copies? */
65
66         if (xul_param('edit',{'modal_xulG':true}) == '1') { 
67
68             g.edit = false;
69
70             if (g.copies.length > 0) { // When loaded in the unified interface, there may be no copies yet (from the volum/item creator) 
71
72                 // Editor desired, but let's check permissions
73
74                 try {
75                     var check = g.network.simple_request(
76                         'PERM_MULTI_ORG_CHECK',
77                         [ 
78                             ses(), 
79                             g.data.list.au[0].id(), 
80                             util.functional.map_list(
81                                 g.copies,
82                                 function (o) {
83                                     var lib;
84                                     var cn_id = o.call_number();
85                                     if (cn_id == -1) {
86                                         lib = o.circ_lib(); // base perms on circ_lib instead of owning_lib if pre-cat
87                                     } else {
88                                         if (! g.map_acn[ cn_id ]) {
89                                             var req = g.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ cn_id ]);
90                                             if (typeof req.ilsevent == 'undefined') {
91                                                 g.map_acn[ cn_id ] = req;
92                                                 lib = g.map_acn[ cn_id ].owning_lib();
93                                             } else {
94                                                 lib = o.circ_lib();
95                                             }
96                                         } else {
97                                             lib = g.map_acn[ cn_id ].owning_lib();
98                                         }
99                                     }
100                                     return typeof lib == 'object' ? lib.id() : lib;
101                                 }
102                             ),
103                             g.copies.length == 1 ? [ 'UPDATE_COPY' ] : [ 'UPDATE_COPY', 'UPDATE_BATCH_COPY' ]
104                         ]
105                     );
106                     g.edit = check.length == 0;
107                 } catch(E) {
108                     g.error.standard_unexpected_error_alert('batch permission check',E);
109                 }
110
111             }
112
113             if (g.edit) {
114                 $('caption').setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.caption')); 
115                 $('save').setAttribute('hidden','false'); 
116             } else {
117                 $('top_nav').setAttribute('hidden','true');
118             }
119
120             g.retrieve_templates();
121
122         } else {
123             $('top_nav').setAttribute('hidden','true');
124         }
125
126         g.panes_and_field_names.left_pane = 
127             [
128                 [
129                     $('catStrings').getString('staff.cat.copy_editor.status'),
130                     { 
131                         render: 'typeof fm.status() == "object" ? fm.status().name() : g.data.hash.ccs[ fm.status() ].name()', 
132                         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,
133                         //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);',
134                     }
135                 ]
136             ].concat(g.panes_and_field_names.left_pane);
137
138
139         /******************************************************************************************************/
140         /* Show the Record Details? */
141
142         var bdb = document.getElementById('brief_display_box'); while(bdb.firstChild) bdb.removeChild(bdb.lastChild);
143         if (g.docid) {
144             var brief_display = document.createElement('iframe'); bdb.appendChild(brief_display); 
145             brief_display.setAttribute( 'src', urls.XUL_BIB_BRIEF + '?docid=' + g.docid); // this is a modal window, so can't push in xulG
146             brief_display.setAttribute( 'flex','1' );
147         }
148
149         /******************************************************************************************************/
150         /* Add stat cats to the panes_and_field_names.right_pane4 */
151
152         g.populate_stat_cats();
153
154         /******************************************************************************************************/
155         /* Backup copies :) */
156
157         g.original_copies = js2JSON( g.copies );
158
159         /******************************************************************************************************/
160         /* Do it */
161
162         g.summarize( g.copies );
163         g.render();
164         g.check_for_unmet_required_fields();
165
166         if (xulG.unified_interface) {
167             xulG.disable_copy_editor = function(c) {
168                 addCSSClass(document.documentElement,'red_bg');
169                 g.disabled = true;
170             }
171             xulG.enable_copy_editor = function(c) {
172                 removeCSSClass(document.documentElement,'red_bg');
173                 g.disabled = false;
174                 xulG.refresh_copy_editor();
175             }
176             xulG.refresh_copy_editor = function() {
177                 dump('refresh_copy_editor\n');
178                 addCSSClass(document.documentElement,'blue_bg');
179                 try {
180                     xulG.clear_update_copy_editor_timeout();
181                     g.copies = xulG.copies;
182                     g.edit = g.copies.length > 0;
183                     if (g.edit) {
184                         $('caption').setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.caption'));
185                     }
186                     g.original_copies = js2JSON( g.copies );
187                     g.hide_copy_notes_button();
188                     for (var i = 0; i < g.applied_templates.length; i++) {
189                         g._apply_template( g.applied_templates[i], false);
190                     }
191                     g.summarize( g.copies );
192                     g.render();
193                     g.check_for_unmet_required_fields();
194                     setTimeout(
195                         function() {
196                             removeCSSClass(document.documentElement,'blue_bg');
197                         }, 1000
198                     );
199                 } catch(E) {
200                     alert('Error in copy_editor.js, xulG.refresh_copy_editor(): ' + E);
201                 }
202             };
203             xulG.unlock_copy_editor = function() {
204                 oils_unlock_page();
205             };
206             xulG.notify_of_templatable_field_change = function(id,v) {
207                 g.changed[ 'volume_copy_creator.'+id ] = { 'type' : 'volume_copy_creator', 'field' : id, 'value' : v };
208             }
209         } else {
210             g.hide_copy_notes_button();
211         }
212
213     } catch(E) {
214         var err_msg = $("commonStrings").getFormattedString('common.exception', ['cat/copy_editor.js', E]);
215         try { g.error.sdump('D_ERROR',err_msg); } catch(E) { dump(err_msg); dump(js2JSON(E)); }
216         alert(err_msg);
217     }
218 }
219
220 /******************************************************************************************************/
221 /* Show copy notes button */
222 g.hide_copy_notes_button = function() {
223     if (g.copies.length > 0 && g.copies[0].id() < 0) {
224         document.getElementById('copy_notes').setAttribute('hidden','true');
225         $('save').setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.create_copies'));
226         $('save').setAttribute('accesskey', $('catStrings').getString('staff.cat.copy_editor.create_copies.accesskey'));
227     }
228     if (g.copies.length != 1) {
229         document.getElementById('copy_notes').setAttribute('hidden','true');
230     }
231 }
232
233 /******************************************************************************************************/
234 /* Retrieve Templates */
235
236 g.retrieve_templates = function() {
237     try {
238         JSAN.use('util.widgets'); JSAN.use('util.functional');
239         g.templates = {};
240         var robj = g.network.simple_request('FM_AUS_RETRIEVE',[ses(),g.data.list.au[0].id()]);
241         if (typeof robj['staff_client.copy_editor.templates'] != 'undefined') {
242             g.templates = robj['staff_client.copy_editor.templates'];
243         }
244         util.widgets.remove_children('template_placeholder');
245         var list = util.functional.map_object_to_list( g.templates, function(obj,i) { return [i, i]; } ).sort();
246
247         g.template_menu = util.widgets.make_menulist( list );
248         g.template_menu.setAttribute('id','template_menu');
249         $('template_placeholder').appendChild(g.template_menu);
250         g.template_menu.addEventListener(
251             'command',
252             function() { g.copy_editor_prefs[ 'template_menu' ] = { 'value' : g.template_menu.value }; g.save_attributes(); },
253             false
254         );
255
256         if (xulG.unified_interface) {
257             if (typeof xulG.update_unified_template_list == 'function') {
258                 xulG.update_unified_template_list(list);
259                 // functions the unified wrapper should use to let the item attribute editor do the heavy lifting for templates
260                 xulG.update_item_editor_template_selection = function(new_value) {
261                     g.template_menu.value = new_value;
262                     g.copy_editor_prefs[ 'template_menu' ] = { 'value' : g.template_menu.value };
263                     g.save_attributes();
264                 }
265                 xulG.item_editor_apply_template = function() { g.apply_template(true); };
266                 xulG.item_editor_delete_template = function() { g.delete_template(); };
267                 xulG.item_editor_save_template = function() { g.save_template(); };
268                 xulG.item_editor_import_templates = function() { g.import_templates(); };
269                 xulG.item_editor_export_templates = function() { g.export_templates(); };
270                 xulG.item_editor_reset = function() { g.reset(); };
271             }
272         }
273
274     } catch(E) {
275         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.retrieve_templates.error'), E);
276     }
277 }
278
279 /******************************************************************************************************/
280 /* Apply Template */
281
282 g.applied_templates = [];
283
284 g.apply_template = function(apply_volume_editor_template_changes) {
285     try {
286         var name = g.template_menu.value;
287         if (g.templates[ name ] != 'undefined') {
288             g.applied_templates.push( name );
289             g._apply_template(name,apply_volume_editor_template_changes);
290             g.summarize( g.copies );
291             g.render();
292             g.check_for_unmet_required_fields();
293         }
294     } catch(E) {
295         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.apply_templates.error'), E);
296     }
297 }
298
299 g._apply_template = function(name,apply_volume_editor_template_changes) {
300     try {
301         if (g.templates[ name ] != 'undefined') {
302             var template = g.templates[ name ];
303             for (var i in template) {
304                 g.changed[ i ] = template[ i ];
305                 switch( template[i].type ) {
306                     case 'attribute' :
307                         g.apply(template[i].field,template[i].value);
308                     break;
309                     case 'stat_cat' :
310                         if (g.stat_cat_seen[ template[i].field ]) g.apply_stat_cat(template[i].field,template[i].value);
311                     break;
312                     case 'owning_lib' :
313                         g.apply_owning_lib(template[i].value);
314                     break;
315                     case 'volume_copy_creator' :
316                         if (xulG.unified_interface && apply_volume_editor_template_changes) {
317                             xulG.apply_template_to_batch(template[i].field,template[i].value);
318                         }
319                     break;
320                 }
321             }
322         }
323     } catch(E) {
324         alert('Error in copy_editor.js, g._apply_template('+name+'): ' + E);
325     }
326 }
327
328 /******************************************************************************************************/
329 /* Save as Template */
330
331 g.save_template = function() {
332     try {
333         var name = window.prompt(
334             $('catStrings').getString('staff.cat.copy_editor.save_as_template.prompt'),
335             '',
336             $('catStrings').getString('staff.cat.copy_editor.save_as_template.title')
337         );
338         if (!name) return;
339         g.templates[name] = g.changed;
340         var robj = g.network.simple_request(
341             'FM_AUS_UPDATE',[ses(),g.data.list.au[0].id(), { 'staff_client.copy_editor.templates' : g.templates }]
342         );
343         if (typeof robj.ilsevent != 'undefined') {
344             throw(robj);
345         } else {
346             alert($('catStrings').getFormattedString('staff.cat.copy_editor.save_as_template.success', [name]));
347             setTimeout(
348                 function() {
349                     try {
350                         g.retrieve_templates();
351                     } catch(E) {
352                         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.save_as_template.error'), E);
353                     }
354                 },0
355             );
356         }
357     } catch(E) {
358         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.save_as_template.error'), E);
359     }
360 }
361
362 /******************************************************************************************************/
363 /* Delete Template */
364
365 g.delete_template = function() {
366     try {
367         var name = g.template_menu.value;
368         if (!name) return;
369         if (! window.confirm($('catStrings').getFormattedString('staff.cat.copy_editor.delete_template.confirm', [name]))) return;
370         delete(g.templates[name]);
371         var robj = g.network.simple_request(
372             'FM_AUS_UPDATE',[ses(),g.data.list.au[0].id(), { 'staff_client.copy_editor.templates' : g.templates }]
373         );
374         if (typeof robj.ilsevent != 'undefined') {
375             throw(robj);
376         } else {
377             alert($('catStrings').getFormattedString('staff.cat.copy_editor.delete_template.confirm', [name]));
378             setTimeout(
379                 function() {
380                     try {
381                         g.retrieve_templates();
382                     } catch(E) {
383                         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.delete_template.error'), E);
384                     }
385                 },0
386             );
387         }
388     } catch(E) {
389         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.delete_template.error'), E);
390     }
391 }
392
393 /******************************************************************************************************/
394 /* Export Templates */
395
396 g.export_templates = function() {
397     try {
398         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
399         JSAN.use('util.file'); var f = new util.file('');
400         f.export_file( { 'title' : $('catStrings').getString('staff.cat.copy_editor.export_templates.title'), 'data' : g.templates } );
401     } catch(E) {
402         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.export_templates.error'), E);
403     }
404 }
405
406 /******************************************************************************************************/
407 /* Import Templates */
408
409 g.import_templates = function() {
410     try {
411         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
412         JSAN.use('util.file'); var f = new util.file('');
413         var temp = f.import_file( { 'title' : $('catStrings').getString('staff.cat.copy_editor.import_templates.title') } );
414         if (temp) {
415             for (var i in temp) {
416
417                 if (g.templates[i]) {
418
419                     var r = g.error.yns_alert(
420                         $('catStrings').getString('staff.cat.copy_editor.import_templates.replace.prompt') + '\n' + g.error.pretty_print( js2JSON( temp[i] ) ),
421                         $('catStrings').getFormattedString('staff.cat.copy_editor.import_templates.replace.title', [i]),
422                         $('catStrings').getString('staff.cat.copy_editor.import_templates.replace.yes'),
423                         $('catStrings').getString('staff.cat.copy_editor.import_templates.replace.no'),
424                         null,
425                         $('catStrings').getString('staff.cat.copy_editor.import_templates.replace.click_here')
426                     );
427
428                     if (r == 0 /* Yes */) g.templates[i] = temp[i];
429
430                 } else {
431
432                     g.templates[i] = temp[i];
433
434                 }
435
436             }
437
438             var r = g.error.yns_alert(
439                 $('catStrings').getString('staff.cat.copy_editor.import_templates.save.prompt'),
440                 $('catStrings').getFormattedString('staff.cat.copy_editor.import_templates.save.title'),
441                 $('catStrings').getString('staff.cat.copy_editor.import_templates.save.yes'),
442                 $('catStrings').getString('staff.cat.copy_editor.import_templates.save.no'),
443                 null,
444                 $('catStrings').getString('staff.cat.copy_editor.import_templates.save.click_here')
445             );
446
447             if (r == 0 /* Yes */) {
448                 var robj = g.network.simple_request(
449                     'FM_AUS_UPDATE',[ses(),g.data.list.au[0].id(), { 'staff_client.copy_editor.templates' : g.templates }]
450                 );
451                 if (typeof robj.ilsevent != 'undefined') {
452                     throw(robj);
453                 } else {
454                     alert($('catStrings').getString('staff.cat.copy_editor.import_templates.save.success'));
455                     setTimeout(
456                         function() {
457                             try {
458                                 g.retrieve_templates();
459                             } catch(E) {
460                                 g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.import_templates.save.error'), E);
461                             }
462                         },0
463                     );
464                 }
465             } else {
466                 util.widgets.remove_children('template_placeholder');
467                 var list = util.functional.map_object_to_list( g.templates, function(obj,i) { return [i, i]; } );
468                 g.template_menu = util.widgets.make_menulist( list );
469                 $('template_placeholder').appendChild(g.template_menu);
470                 alert($('catStrings').getString('staff.cat.copy_editor.import_templates.note'));
471             }
472
473         }
474     } catch(E) {
475         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.import_templates.error'), E);
476     }
477 }
478
479
480 /******************************************************************************************************/
481 /* Restore backup copies */
482
483 g.reset = function() {
484     g.applied_templates = [];
485     g.changed = {};
486     g.copies = JSON2js( g.original_copies );
487     g.summarize( g.copies );
488     g.render();
489     g.check_for_unmet_required_fields();
490     oils_unlock_page();
491     if (xulG.unified_interface) {
492         xulG.reset_batch_menus();
493     }
494 }
495
496 /******************************************************************************************************/
497 /* Apply a value to a specific field on all the copies being edited */
498
499 g.apply = function(field,value) {
500     g.error.sdump('D_TRACE','applying field = <' + field + '>  value = <' + value + '>\n');
501     if (value == '<HACK:KLUDGE:NULL>') {
502         value = null;
503     }
504     if (field == 'alert_message') { value = value.replace(/^\W+$/g,''); }
505     if (field == 'price' || field == 'deposit_amount') {
506         if (value == '') {
507             value = null;
508         } else {
509             JSAN.use('util.money'); value = util.money.sanitize( value );
510         }
511     }
512     for (var i = 0; i < g.copies.length; i++) {
513         var copy = g.copies[i];
514         try {
515             copy[field]( value ); copy.ischanged('1');
516         } catch(E) {
517             alert(E);
518         }
519     }
520
521     oils_lock_page();
522 }
523
524 /******************************************************************************************************/
525 /* Apply a stat cat entry to all the copies being edited.  An entry_id of < 0 signifies the stat cat is being removed. */
526
527 g.apply_stat_cat = function(sc_id,entry_id) {
528     g.error.sdump('D_TRACE','sc_id = ' + sc_id + '  entry_id = ' + entry_id + '\n');
529     for (var i = 0; i < g.copies.length; i++) {
530         var copy = g.copies[i];
531         try {
532             copy.ischanged('1');
533             var temp = copy.stat_cat_entries();
534             if (!temp) temp = [];
535             temp = util.functional.filter_list(
536                 temp,
537                 function (obj) {
538                     return (obj.stat_cat() != sc_id);
539                 }
540             );
541             if (entry_id > -1) {
542                 temp.push( 
543                     util.functional.find_id_object_in_list( 
544                         g.data.hash.asc[sc_id].entries(), 
545                         entry_id
546                     )
547                 );
548             }
549             copy.stat_cat_entries( temp );
550
551         } catch(E) {
552             g.error.standard_unexpected_error_alert('apply_stat_cat',E);
553         }
554     }
555
556     oils_lock_page();
557 }
558
559 /******************************************************************************************************/
560 /* Apply an "owning lib" to all the copies being edited.  That is, change and auto-vivicating volumes */
561
562 g.apply_owning_lib = function(ou_id) {
563     g.error.sdump('D_TRACE','ou_id = ' + ou_id + '\n');
564     // but don't allow this when bundled with the volume/copy creator UI, or if we're editing pre-cats
565     if (! g.safe_to_change_owning_lib() ) { return; }
566     for (var i = 0; i < g.copies.length; i++) {
567         var copy = g.copies[i];
568         try {
569             if (!g.map_acn[copy.call_number()]) {
570                 var volume = g.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ copy.call_number() ]);
571                 if (typeof volume.ilsevent != 'undefined') {
572                     g.error.standard_unexpected_error_alert($('catStrings').getFormattedString('staff.cat.copy_editor.apply_owning_lib.undefined_volume.error', [copy.barcode()]), volume);
573                     continue;
574                 }
575                 g.map_acn[copy.call_number()] = volume;
576             }
577             var old_volume = g.map_acn[copy.call_number()];
578             var acn_blob = g.network.simple_request(
579                 'FM_ACN_FIND_OR_CREATE',
580                 [ses(),old_volume.label(),old_volume.record(),ou_id,old_volume.prefix().id(),old_volume.suffix().id(),old_volume.label_class().id()]
581             );
582             if (typeof acn_blob.ilsevent != 'undefined') {
583                 g.error.standard_unexpected_error_alert($('catStrings').getFormattedString('staff.cat.copy_editor.apply_owning_lib.call_number.error', [copy.barcode()]), acn_blob);
584                 continue;
585             }
586             copy.call_number(acn_blob.acn_id);
587             copy.ischanged('1');
588         } catch(E) {
589             g.error.standard_unexpected_error_alert('apply_stat_cat',E);
590         }
591     }
592
593     oils_lock_page();
594 }
595
596 /******************************************************************************************************/
597 /* This returns false if any of the copies being edited are pre-cats, or if we're embedded in the unified volume/copy UI */
598
599 g.safe_to_change_owning_lib = function() {
600     try {
601         if (xulG.unified_interface) { return false; }
602         var safe = true;
603         for (var i = 0; i < g.copies.length; i++) {
604             var cn = g.copies[i].call_number();
605             if (typeof cn == 'object') { cn = cn.id(); }
606             if (cn == -1) { safe = false; }
607         }
608         return safe;
609     } catch(E) {
610         g.error.standard_unexpected_error_alert('safe_to_change_owning_lib?',E);
611         return false;
612     }
613 }
614
615 /******************************************************************************************************/
616 /* This returns true if none of the copies being edited have a magical status found in my_constants.magical_statuses */
617
618 g.safe_to_edit_copy_status = function() {
619     try {
620         var safe = true;
621         for (var i = 0; i < g.copies.length; i++) {
622             var status = g.copies[i].status(); if (typeof status == 'object') status = status.id();
623             if (typeof my_constants.magical_statuses[ status ] != 'undefined') safe = false;
624         }
625         return safe;
626     } catch(E) {
627         g.error.standard_unexpected_error_alert('safe_to_edit_copy_status?',E);
628         return false;
629     }
630 }
631
632 /******************************************************************************************************/
633 /* This concats and uniques all the alert messages for use as the default value for a new alert message */
634
635 g.populate_alert_message_input = function(tb) {
636     try {
637         var seen = {}; var s = '';
638         for (var i = 0; i < g.copies.length; i++) {
639             var msg = g.copies[i].alert_message(); 
640             if (msg) {
641                 if (typeof seen[msg] == 'undefined') {
642                     s += msg + '\n';
643                     seen[msg] = true;
644                 }
645             }
646         }
647         tb.setAttribute('value',s);
648     } catch(E) {
649         g.error.standard_unexpected_error_alert('populate_alert_message_input',E);
650     }
651 }
652
653 /***************************************************************************************************************/
654 /* This returns a list of acpl's appropriate for the copies being edited (and caches them in the global stash) */
655
656 g.get_acpl_list_for_lib = function(lib_id,but_only_these) {
657     g.data.stash_retrieve();
658     var label = 'acpl_list_for_lib_'+lib_id;
659     if (typeof g.data[label] == 'undefined') {
660         var robj = g.network.simple_request('FM_ACPL_RETRIEVE', [ lib_id ]); // This returns acpl's for all ancestors and descendants as well as the lib
661         if (typeof robj.ilsevent != 'undefined') throw(robj);
662         var temp_list = [];
663         for (var j = 0; j < robj.length; j++) {
664             var my_acpl = robj[j];
665             if (typeof g.data.hash.acpl[ my_acpl.id() ] == 'undefined') {
666                 g.data.hash.acpl[ my_acpl.id() ] = my_acpl;
667                 g.data.list.acpl.push( my_acpl );
668             }
669             var only_this_lib = my_acpl.owning_lib(); if (!only_this_lib) continue;
670             if (typeof only_this_lib == 'object') only_this_lib = only_this_lib.id();
671             if (but_only_these.indexOf( String( only_this_lib ) ) != -1) { // This filters out some of the libraries (usually the descendants)
672                 temp_list.push( my_acpl );
673             }
674         }
675         g.data[label] = temp_list; g.data.stash(label,'hash','list');
676     }
677     return g.data[label];
678 }
679
680 /******************************************************************************************************/
681 /* This returns a list of acpl's appropriate for the copies being edited */
682
683 g.get_acpl_list = function() {
684     try {
685
686         JSAN.use('util.functional');
687
688         var my_acpls = {};
689
690         /**************************************/
691         /* get owning libs from call numbers */
692
693         var owning_libs = {}; 
694         for (var i = 0; i < g.copies.length; i++) {
695             var callnumber = g.copies[i].call_number();
696             if (!callnumber) continue;
697             var cn_id = typeof callnumber == 'object' ? callnumber.id() : callnumber;
698             if (cn_id > 0) {
699                 if (! g.map_acn[ cn_id ]) {
700                     var req = g.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ cn_id ]);
701                     if (typeof req.ilsevent == 'undefined') {
702                         g.map_acn[ cn_id ] = req;
703                     } else {
704                         continue;
705                     }
706                 }
707                 var consider_lib = g.map_acn[ cn_id ].owning_lib();
708                 if (!consider_lib) continue;
709                 owning_libs[ typeof consider_lib == 'object' ? consider_lib.id() : consider_lib ] = true;
710             }
711         }
712         if (g.callnumbers) {
713             for (var i in g.callnumbers) {
714                 var consider_lib = g.callnumbers[i].owning_lib;
715                 if (!consider_lib) continue;
716                 owning_libs[ typeof consider_lib == 'object' ? consider_lib.id() : consider_lib ] = true;
717             }
718         }
719
720         /***************************************************************************************************/
721         /* now find the first ancestor they all have in common, get the acpl's for it and higher ancestors */
722
723         JSAN.use('util.fm_utils');
724         var libs = []; for (var i in owning_libs) libs.push(i);
725         if (libs.length > 0) {
726             var ancestor = util.fm_utils.find_common_aou_ancestor( libs );
727             if (typeof ancestor == 'object' && ancestor != null) ancestor = ancestor.id();
728
729             if (ancestor) {
730                 var ancestors = util.fm_utils.find_common_aou_ancestors( libs );
731                 var acpl_list = g.get_acpl_list_for_lib(ancestor, ancestors);
732                 if (acpl_list) for (var i = 0; i < acpl_list.length; i++) {
733                     if (acpl_list[i] != null) {
734                         my_acpls[ typeof acpl_list[i] == 'object' ? acpl_list[i].id() : acpl_list[i] ] = true;
735                     }
736                 }
737             }
738         }
739         
740         /*****************/
741         /* get circ libs */
742
743         var circ_libs = {};
744
745         for (var i = 0; i < g.copies.length; i++) {
746             var consider_lib = g.copies[i].circ_lib();
747             if (!consider_lib) continue;
748             circ_libs[ typeof consider_lib == 'object' ? consider_lib.id() : consider_lib ] = true;
749         }
750
751         /***************************************************************************************************/
752         /* now find the first ancestor they all have in common, get the acpl's for it and higher ancestors */
753
754         libs = []; for (var i in circ_libs) libs.push(i);
755         if (libs.length > 0) {
756             var ancestor = util.fm_utils.find_common_aou_ancestor( libs );
757             if (typeof ancestor == 'object' && ancestor != null) ancestor = ancestor.id();
758
759             if (ancestor) {
760                 var ancestors = util.fm_utils.find_common_aou_ancestors( libs );
761                 var acpl_list = g.get_acpl_list_for_lib(ancestor, ancestors);
762                 if (acpl_list) for (var i = 0; i < acpl_list.length; i++) {
763                     if (acpl_list[i] != null) {
764                         my_acpls[ typeof acpl_list[i] == 'object' ? acpl_list[i].id() : acpl_list[i] ] = true;
765                     }
766                 }
767             }
768         }
769
770         var acpl_list = []; for (var i in my_acpls) acpl_list.push( g.data.hash.acpl[ i ] );
771         return acpl_list.sort(
772             function(a,b) {
773                 var label_a = g.data.hash.aou[ a.owning_lib() ].shortname() + ' : ' + a.name();
774                 var label_b = g.data.hash.aou[ b.owning_lib() ].shortname() + ' : ' + b.name();
775                 if (label_a < label_b) return -1;
776                 if (label_a > label_b) return 1;
777                 return 0;
778             }
779         );
780     
781     } catch(E) {
782         g.error.standard_unexpected_error_alert('get_acpl_list',E);
783         return [];
784     }
785 }
786
787
788 /******************************************************************************************************/
789 /* This keeps track of which fields have been edited for styling purposes */
790
791 g.changed = {};
792
793 /******************************************************************************************************/
794 /* This keeps track of which fields are required, and which fields have been populated */
795
796 g.required = {};
797 g.populated = {};
798
799 /******************************************************************************************************/
800 /* These need data from the middle layer to render */
801
802 function init_panes0() {
803 g.special_exception = {};
804 g.special_exception[$('catStrings').getString('staff.cat.copy_editor.field.owning_library.label')] = function(label,value) {
805         JSAN.use('util.widgets');
806         if (value>0) { /* an existing call number */
807             g.network.simple_request(
808                 'FM_ACN_RETRIEVE.authoritative',
809                 [ value ],
810                 function(req) {
811                     var cn = '??? id = ' + value;
812                     try {
813                         cn = req.getResultObject();
814                     } catch(E) {
815                         g.error.sdump('D_ERROR','callnumber retrieve: ' + E);
816                     }
817                     util.widgets.set_text(label,g.data.hash.aou[ cn.owning_lib() ].shortname() + ' : ' + cn.label());
818                 }
819             );
820         } else { /* a yet to be created call number */
821             if (g.callnumbers) {
822                 util.widgets.set_text(label,g.data.hash.aou[ g.callnumbers[value].owning_lib ].shortname() + ' : ' + g.callnumbers[value].label);
823             }
824         }
825     };
826 g.special_exception[$('catStrings').getString('staff.cat.copy_editor.field.creator.label')] = function(label,value) {
827         if (!Number(value)) return;
828         g.network.simple_request(
829             'FM_AU_RETRIEVE_VIA_ID',
830             [ ses(), value ],
831             function(req) {
832                 var p = '??? id = ' + value;
833                 try {
834                     p = req.getResultObject();
835                     p = p.usrname();
836
837                 } catch(E) {
838                     g.error.sdump('D_ERROR','patron retrieve: ' + E);
839                 }
840                 JSAN.use('util.widgets');
841                 util.widgets.set_text(label,p);
842             }
843         );
844     };
845 g.special_exception[$('catStrings').getString('staff.cat.copy_editor.field.last_editor.label')] = function(label,value) {
846         if (!Number(value)) return;
847         g.network.simple_request(
848             'FM_AU_RETRIEVE_VIA_ID',
849             [ ses(), value ],
850             function(req) {
851                 var p = '??? id = ' + value;
852                 try {
853                     p = req.getResultObject();
854                     p = p.usrname();
855
856                 } catch(E) {
857                     g.error.sdump('D_ERROR','patron retrieve: ' + E);
858                 }
859                 util.widgets.set_text(label,p);
860             }
861         );
862     };
863 }
864
865 /******************************************************************************************************/
866 g.readonly_stat_cat_names = [];
867 g.editable_stat_cat_names = [];
868
869 /******************************************************************************************************/
870 /* These get show in the left panel */
871
872 function init_panes() {
873 g.panes_and_field_names = {
874
875     'left_pane' :
876 [
877     [
878         $('catStrings').getString('staff.cat.copy_editor.field.barcode.label'),
879         {
880             render: 'fm.barcode();',
881         }
882     ], 
883     [
884         $('catStrings').getString('staff.cat.copy_editor.field.creation_date.label'),
885         { 
886             render: 'util.date.formatted_date( fm.create_date(), "%F");',
887         }
888     ],
889     [
890         $('catStrings').getString('staff.cat.copy_editor.field.active_date.label'),
891         { 
892             render: 'util.date.formatted_date( fm.active_date(), "%F");',
893         }
894     ],
895     [
896         $('catStrings').getString('staff.cat.copy_editor.field.creator.label'),
897         { 
898             render: 'fm.creator();',
899         }
900     ],
901     [
902         $('catStrings').getString('staff.cat.copy_editor.field.last_edit_date.label'),
903         { 
904             render: 'util.date.formatted_date( fm.edit_date(), "%F");',
905         }
906     ],
907     [
908         $('catStrings').getString('staff.cat.copy_editor.field.last_editor.label'),
909         {
910             render: 'fm.editor();',
911         }
912     ],
913
914 ],
915
916 'right_pane' :
917 [
918     [
919         $('catStrings').getString('staff.cat.copy_editor.field.location.label'),
920         { 
921             render: 'typeof fm.location() == "object" ? fm.location().name() : g.data.lookup("acpl",fm.location()).name()', 
922             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);',
923
924         }
925     ],
926     [
927         $('catStrings').getString('staff.cat.copy_editor.field.circulation_library.label'),
928         {     
929             render: 'typeof fm.circ_lib() == "object" ? fm.circ_lib().shortname() : g.data.hash.aou[ fm.circ_lib() ].shortname()',
930             //input: 'c = function(v){ g.apply("circ_lib",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.my_aou, function(obj) { return g.data.hash.aout[ obj.ou_type() ].can_have_vols(); }), function(obj) { return [ obj.shortname(), obj.id() ]; }).sort() ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
931             input: 'c = function(v){ g.apply("circ_lib",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( g.data.list.aou, function(obj) { var sname = obj.shortname(); for (i = sname.length; i < 20; i++) sname += " "; return [ obj.name() ? sname + " " + obj.name() : obj.shortname(), obj.id(), ( ! get_bool( g.data.hash.aout[ obj.ou_type() ].can_have_vols() ) ), ( g.data.hash.aout[ obj.ou_type() ].depth() * 2), ]; }), g.data.list.au[0].ws_ou()); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
932         } 
933     ],
934     [
935         $('catStrings').getString('staff.cat.copy_editor.field.owning_library.label'),
936         {
937             render: 'fm.call_number();',
938             input: g.safe_to_change_owning_lib() ? 'c = function(v){ g.apply_owning_lib(v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( g.data.list.aou, function(obj) { var sname = obj.shortname(); for (i = sname.length; i < 20; i++) sname += " "; return [ obj.name() ? sname + " " + obj.name() : obj.shortname(), obj.id(), ( ! get_bool( g.data.hash.aout[ obj.ou_type() ].can_have_vols() ) ), ( g.data.hash.aout[ obj.ou_type() ].depth() * 2), ]; }), g.data.list.au[0].ws_ou()); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);' : undefined,
939         }
940     ],
941     [
942         $('catStrings').getString('staff.cat.copy_editor.field.copy_number.label'),
943         { 
944             render: 'fm.copy_number() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : fm.copy_number()',
945             input: 'c = function(v){ g.apply("copy_number",v); if (typeof post_c == "function") post_c(v); }; x = document.createElement("textbox"); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
946         }
947     ],
948
949
950 ],
951
952 'right_pane2' :
953 [
954     [
955         $('catStrings').getString('staff.cat.copy_editor.field.circulate.label'),
956         {     
957             render: 'fm.circulate() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( get_bool( fm.circulate() ) ? $("catStrings").getString("staff.cat.copy_editor.field.circulate.yes_or_true") : $("catStrings").getString("staff.cat.copy_editor.field.circulate.no_or_false") )',
958             input: 'c = function(v){ g.apply("circulate",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.circulate.yes_or_true"), get_db_true() ], [ $("catStrings").getString("staff.cat.copy_editor.field.circulate.no_or_false"), get_db_false() ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
959         }
960     ],
961     [
962         $('catStrings').getString('staff.cat.copy_editor.field.holdable.label'),
963         { 
964             render: 'fm.holdable() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( get_bool( fm.holdable() ) ? $("catStrings").getString("staff.cat.copy_editor.field.holdable.yes_or_true") : $("catStrings").getString("staff.cat.copy_editor.field.holdable.no_or_false") )',
965             input: 'c = function(v){ g.apply("holdable",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.holdable.yes_or_true"), get_db_true() ], [ $("catStrings").getString("staff.cat.copy_editor.field.holdable.no_or_false"), get_db_false() ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
966         }
967     ],
968     [
969         $('catStrings').getString('staff.cat.copy_editor.field.age_based_hold_protection.label'),
970         {
971             render: 'fm.age_protect() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( typeof fm.age_protect() == "object" ? fm.age_protect().name() : g.data.hash.crahp[ fm.age_protect() ].name() )', 
972             input: 'c = function(v){ g.apply("age_protect",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.remove_age_based_hold_protection"), "<HACK:KLUDGE:NULL>" ] ].concat( util.functional.map_list( g.data.list.crahp, function(obj) { return [ obj.name(), obj.id() ]; }).sort() ) ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
973         }
974
975     ],
976     [
977         $('catStrings').getString('staff.cat.copy_editor.field.floating.label'),
978         { 
979             render: 'fm.floating() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( get_bool( fm.floating() ) ? $("catStrings").getString("staff.cat.copy_editor.field.floating.yes_or_true") : $("catStrings").getString("staff.cat.copy_editor.field.floating.no_or_false") )',
980             input: 'c = function(v){ g.apply("floating",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.floating.yes_or_true"), get_db_true() ], [ $("catStrings").getString("staff.cat.copy_editor.field.floating.no_or_false"), get_db_false() ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
981         }
982     ],
983     [
984         $('catStrings').getString('staff.cat.copy_editor.field.loan_duration.label'),
985         { 
986             render: 'switch(Number(fm.loan_duration())){ case 1: $("catStrings").getString("staff.cat.copy_editor.field.loan_duration.short"); break; case 2: $("catStrings").getString("staff.cat.copy_editor.field.loan_duration.normal"); break; case 3: $("catStrings").getString("staff.cat.copy_editor.field.loan_duration.extended"); break; }',
987             input: 'c = function(v){ g.apply("loan_duration",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.loan_duration.short"), "1" ], [ $("catStrings").getString("staff.cat.copy_editor.field.loan_duration.normal"), "2" ], [ $("catStrings").getString("staff.cat.copy_editor.field.loan_duration.extended"), "3" ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
988
989         }
990     ],
991     [
992         $('catStrings').getString('staff.cat.copy_editor.field.fine_level.label'),
993         {
994             render: 'switch(Number(fm.fine_level())){ case 1: $("catStrings").getString("staff.cat.copy_editor.field.fine_level.low"); break; case 2: $("catStrings").getString("staff.cat.copy_editor.field.fine_level.normal"); break; case 3: $("catStrings").getString("staff.cat.copy_editor.field.fine_level.high"); break; }',
995             input: 'c = function(v){ g.apply("fine_level",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.fine_level.low"), "1" ], [ $("catStrings").getString("staff.cat.copy_editor.field.fine_level.normal"), "2" ], [ $("catStrings").getString("staff.cat.copy_editor.field.fine_level.high"), "3" ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
996         }
997     ],
998
999      [
1000         $('catStrings').getString('staff.cat.copy_editor.field.circulate_as_type.label'),
1001         {     
1002             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',
1003             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);',
1004         } 
1005     ],
1006     [
1007         $('catStrings').getString('staff.cat.copy_editor.field.circulation_modifier.label'),
1008         {    
1009             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()])',
1010             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);',
1011         }
1012     ],
1013 ],
1014
1015 'right_pane3' :
1016 [    [
1017         $('catStrings').getString('staff.cat.copy_editor.field.alert_message.label'),
1018         {
1019             render: 'fm.alert_message() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : fm.alert_message()',
1020             input: 'c = function(v){ g.apply("alert_message",v); if (typeof post_c == "function") post_c(v); }; x = document.createElement("textbox"); x.setAttribute("multiline",true); g.populate_alert_message_input(x); x.addEventListener("apply",function(f){ return function(ev) { f( ev.target.value ); } }(c), false);',
1021         }
1022     ],
1023
1024     [
1025         $('catStrings').getString('staff.cat.copy_editor.field.deposit.label'),
1026         { 
1027             render: 'fm.deposit() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( get_bool( fm.deposit() ) ? $("catStrings").getString("staff.cat.copy_editor.field.deposit.yes_or_true") : $("catStrings").getString("staff.cat.copy_editor.field.deposit.no_or_false") )',
1028             input: 'c = function(v){ g.apply("deposit",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.deposit.yes_or_true"), get_db_true() ], [ $("catStrings").getString("staff.cat.copy_editor.field.deposit.no_or_false"), get_db_false() ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
1029         }
1030     ],
1031     [
1032         $('catStrings').getString('staff.cat.copy_editor.field.deposit_amount.label'),
1033         { 
1034             render: 'if (fm.deposit_amount() == null) { $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null"); } else { util.money.sanitize( fm.deposit_amount() ); }',
1035             input: 'c = function(v){ g.apply("deposit_amount",v); if (typeof post_c == "function") post_c(v); }; x = document.createElement("textbox"); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
1036         }
1037     ],
1038     [
1039         $('catStrings').getString('staff.cat.copy_editor.field.price.label'),
1040         { 
1041             render: 'if (fm.price() == null) { $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null"); } else { util.money.sanitize( fm.price() ); }', 
1042             input: 'c = function(v){ g.apply("price",v); if (typeof post_c == "function") post_c(v); }; x = document.createElement("textbox"); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
1043         }
1044     ],
1045
1046     [
1047         $('catStrings').getString('staff.cat.copy_editor.field.opac_visible.label'),
1048         { 
1049             render: 'fm.opac_visible() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( get_bool( fm.opac_visible() ) ? $("catStrings").getString("staff.cat.copy_editor.field.opac_visible.yes_or_true") : $("catStrings").getString("staff.cat.copy_editor.field.opac_visible.no_or_false") )', 
1050             input: 'c = function(v){ g.apply("opac_visible",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.opac_visible.yes_or_true"), get_db_true() ], [ $("catStrings").getString("staff.cat.copy_editor.field.opac_visible.no_or_false"), get_db_false() ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
1051         }
1052     ],
1053     [
1054         $('catStrings').getString('staff.cat.copy_editor.field.reference.label'),
1055         { 
1056             render: 'fm.ref() == null ? $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null") : ( get_bool( fm.ref() ) ? $("catStrings").getString("staff.cat.copy_editor.field.reference.yes_or_true") : $("catStrings").getString("staff.cat.copy_editor.field.reference.no_or_false") )', 
1057             input: 'c = function(v){ g.apply("ref",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.reference.yes_or_true"), get_db_true() ], [ $("catStrings").getString("staff.cat.copy_editor.field.reference.no_or_false"), get_db_false() ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
1058         }
1059     ],
1060     [
1061         $('catStrings').getString('staff.cat.copy_editor.field.mint_condition.label'),
1062         { 
1063             render: 'get_bool( fm.mint_condition() ) ? $("catStrings").getString("staff.cat.copy_editor.field.mint_condition.yes_or_true") : $("catStrings").getString("staff.cat.copy_editor.field.mint_condition.no_or_false")', 
1064             input: 'c = function(v){ g.apply("mint_condition",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.field.mint_condition.yes_or_true"), get_db_true() ], [ $("catStrings").getString("staff.cat.copy_editor.field.mint_condition.no_or_false"), get_db_false() ] ] ); x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c), false);',
1065         }
1066     ]
1067 ],
1068
1069 'right_pane4' : 
1070 [
1071 ]
1072
1073 };
1074 }
1075
1076 /******************************************************************************************************/
1077 /* This loops through all our fieldnames and all the copies, tallying up counts for the different values */
1078
1079 g.summarize = function( copies ) {
1080     /******************************************************************************************************/
1081     /* Setup */
1082
1083     JSAN.use('util.date'); JSAN.use('util.money');
1084     g.summary = {};
1085     g.field_names = [];
1086     for (var i in g.panes_and_field_names) {
1087         g.field_names = g.field_names.concat( g.panes_and_field_names[i] );
1088     }
1089     g.field_names = g.field_names.concat( g.editable_stat_cat_names );
1090     g.field_names = g.field_names.concat( g.readonly_stat_cat_names );
1091
1092     /******************************************************************************************************/
1093     /* Loop through the field names */
1094
1095     for (var i = 0; i < g.field_names.length; i++) {
1096
1097         var field_name = g.field_names[i][0];
1098         var render = g.field_names[i][1].render;
1099         var attr = g.field_names[i][1].attr;
1100         g.summary[ field_name ] = {};
1101         g.populated[ field_name ] = 1; // delete later if we encounter a copy with the field unset
1102
1103         /******************************************************************************************************/
1104         /* Loop through the copies */
1105
1106         for (var j = 0; j < copies.length; j++) {
1107
1108             var fm = copies[j];
1109             var cmd = render || ('fm.' + field_name + '();');
1110             var value = $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null");
1111
1112             /**********************************************************************************************/
1113             /* Try to retrieve the value for this field for this copy */
1114
1115             try { 
1116                 value = eval( cmd ); 
1117             } catch(E) { 
1118                 g.error.sdump('D_ERROR','Attempted ' + cmd + '\n' +  E + '\n'); 
1119             }
1120             if (typeof value == 'object' && value != null) {
1121                 alert('FIXME: field_name = <' + field_name + '>  value = <' + js2JSON(value) + '>\n');
1122             }
1123             if (value == $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null")) {
1124                 delete g.populated[field_name];
1125             }
1126
1127             /**********************************************************************************************/
1128             /* Tally the count */
1129
1130             if (g.summary[ field_name ][ value ]) {
1131                 g.summary[ field_name ][ value ]++;
1132             } else {
1133                 g.summary[ field_name ][ value ] = 1;
1134             }
1135         }
1136     }
1137     g.error.sdump('D_TRACE','summary = ' + js2JSON(g.summary) + '\n');
1138 }
1139
1140 /******************************************************************************************************/
1141 /* Display the summarized data and inputs for editing */
1142
1143 g.render = function() {
1144
1145     /******************************************************************************************************/
1146     /* Library setup and clear any existing interface */
1147
1148     JSAN.use('util.widgets'); JSAN.use('util.date'); JSAN.use('util.money'); JSAN.use('util.functional');
1149
1150     for (var i in g.panes_and_field_names) {
1151         var p = document.getElementById(i);
1152         if (p) util.widgets.remove_children(p);
1153     }
1154
1155     /******************************************************************************************************/
1156     /* Populate the library filter menu for stat cats */
1157
1158     var sc_libs = {};
1159     for (var i = 0; i < g.panes_and_field_names.right_pane4.length; i++) {
1160         sc_libs[ g.panes_and_field_names.right_pane4[i][1].attr.sc_lib ] = true;
1161     }
1162     var sc_libs2 = [];
1163     for (var i in sc_libs) { sc_libs2.push( [ g.data.hash.aou[ i ].shortname(), i ] ); }
1164     sc_libs2.sort();
1165     var x = document.getElementById("stat_cat_lib_filter_menu").firstChild;
1166     JSAN.use('util.widgets'); util.widgets.remove_children(x);
1167     for (var i = 0; i < sc_libs2.length; i++) {
1168         var menuitem = document.createElement('menuitem');
1169         menuitem.setAttribute('id','filter_'+sc_libs2[i][1]);
1170         menuitem.setAttribute('type','checkbox');
1171         menuitem.setAttribute('checked','true');
1172         menuitem.setAttribute('label',sc_libs2[i][0]);
1173         menuitem.setAttribute('value',sc_libs2[i][1]);
1174         menuitem.setAttribute('oncommand','try{g.toggle_stat_cat_display(this);}catch(E){alert(E);}');
1175         x.appendChild(menuitem);
1176     }
1177
1178     /******************************************************************************************************/
1179     /* Prepare the panes */
1180
1181     var groupbox; var caption; var vbox; var grid; var rows;
1182     
1183     /******************************************************************************************************/
1184     /* Loop through the field names */
1185
1186     for (h in g.panes_and_field_names) {
1187         if (!document.getElementById(h)) continue;
1188         for (var i = 0; i < g.panes_and_field_names[h].length; i++) {
1189             try {
1190                 var f = g.panes_and_field_names[h][i]; var fn = f[0]; var attr = f[1].attr;
1191                 groupbox = document.createElement('groupbox'); document.getElementById(h).appendChild(groupbox);
1192                 if (attr) {
1193                     for (var a in attr) {
1194                         groupbox.setAttribute(a,attr[a]);
1195                     }
1196                 }
1197                 caption = document.createElement('caption'); groupbox.appendChild(caption);
1198                 caption.setAttribute('label',fn);
1199                 caption.setAttribute('id','caption_'+fn); // used for focus/keyboard navigation
1200                 vbox = document.createElement('vbox'); groupbox.appendChild(vbox); // main display widget goes here
1201                 if (typeof g.changed[fn] != 'undefined') { addCSSClass(vbox,'copy_editor_field_changed'); }
1202                 if (typeof g.required[fn] != 'undefined') { addCSSClass(vbox,'copy_editor_field_required'); }
1203                 grid = util.widgets.make_grid( [ { 'flex' : 1 }, {}, {} ] ); vbox.appendChild(grid);
1204                 grid.setAttribute('flex','1');
1205                 rows = grid.lastChild;
1206                 var row;
1207                 
1208                 /**************************************************************************************/
1209                 /* Loop through each value for the field */
1210
1211                 for (var j in g.summary[fn]) {
1212                     var value = j; var count = g.summary[fn][j];
1213                     row = document.createElement('row'); rows.appendChild(row);
1214                     var label1 = document.createElement('description'); row.appendChild(label1);
1215                     if (g.special_exception[ fn ]) {
1216                         g.special_exception[ fn ]( label1, value );
1217                     } else {
1218                         label1.appendChild( document.createTextNode(value) );
1219                     }
1220                     var label2 = document.createElement('description'); row.appendChild(label2);
1221                     var copy_count;
1222                     if (count == 1) {
1223                         copy_count = $('catStrings').getString('staff.cat.copy_editor.copy_count');
1224                     } else {
1225                         copy_count = $('catStrings').getFormattedString('staff.cat.copy_editor.copy_count.plural', [count]);
1226                     }
1227                     label2.appendChild( document.createTextNode(copy_count) );
1228                 }
1229                 groupbox.setAttribute('id','groupbox_'+fn); // this id is meant to be referenced by cat_custom.css for hiding fields
1230                 var hbox = document.createElement('hbox'); // main input controls go here
1231                 hbox.setAttribute('id',fn); // this id gets used to color areas green, etc.
1232                 groupbox.appendChild(hbox);
1233                 var hbox2 = document.createElement('hbox'); // cancel/apply buttons go here
1234                 groupbox.appendChild(hbox2);
1235
1236                 /**************************************************************************************/
1237                 /* Render the input widget */
1238
1239                 if (f[1].input && g.edit) {
1240                     g.render_input(hbox,f[1]);
1241                 }
1242
1243             } catch(E) {
1244                 g.error.sdump('D_ERROR','copy editor: ' + E + '\n');
1245             }
1246         }
1247     }
1248     
1249     
1250     /******************************************************************************************************/
1251     /* Synchronize stat cat visibility with library filter menu, and default template selection */
1252     JSAN.use('util.file'); 
1253     var file = new util.file('copy_editor_prefs.'+g.data.server_unadorned);
1254     g.copy_editor_prefs = util.widgets.load_attributes(file);
1255     for (var i in g.copy_editor_prefs) {
1256         if (i.match(/filter_/) && g.copy_editor_prefs[i].checked == '') {
1257             try { 
1258                 g.toggle_stat_cat_display( document.getElementById(i) ); 
1259             } catch(E) { alert(E); }
1260         }
1261     }
1262     if (g.template_menu) g.template_menu.value = g.template_menu.getAttribute('value');
1263
1264 }
1265
1266 /******************************************************************************************************/
1267 /* This actually draws the change button and input widget for a given field */
1268 g.render_input = function(node,blob) {
1269     try {
1270         // node = hbox ;    groupbox ->  hbox, hbox
1271
1272         var groupbox = node.parentNode;
1273         var caption = groupbox.firstChild;
1274         var vbox = node.previousSibling;
1275         var hbox = node;
1276         var hbox2 = node.nextSibling;
1277
1278         var input_cmd = blob.input;
1279         var render_cmd = blob.render;
1280         var attr = blob.attr;
1281
1282         var block = false; var first = true;
1283
1284         function on_mouseover(ev) {
1285             groupbox.setAttribute('style','background: white');
1286         }
1287
1288         function on_mouseout(ev) {
1289             groupbox.setAttribute('style','');
1290         }
1291
1292         vbox.addEventListener('mouseover',on_mouseover,false);
1293         vbox.addEventListener('mouseout',on_mouseout,false);
1294         groupbox.addEventListener('mouseover',on_mouseover,false);
1295         groupbox.addEventListener('mouseout',on_mouseout,false);
1296         groupbox.firstChild.addEventListener('mouseover',on_mouseover,false);
1297         groupbox.firstChild.addEventListener('mouseout',on_mouseout,false);
1298
1299         function on_click(ev){
1300             try {
1301                 if (block || g.disabled || !g.edit) {
1302                     return;
1303                 }
1304                 block = true;
1305
1306                 oils_lock_page();
1307
1308                 function post_c(v) {
1309                     try {
1310                         /* FIXME - kludgy */
1311                         var t = input_cmd.match('apply_stat_cat') ? 'stat_cat' : ( input_cmd.match('apply_owning_lib') ? 'owning_lib' : 'attribute' );
1312                         var f;
1313                         switch(t) {
1314                             case 'attribute' :
1315                                 f = input_cmd.match(/apply\("(.+?)",/)[1];
1316                             break;
1317                             case 'stat_cat' :
1318                                 f = input_cmd.match(/apply_stat_cat\((.+?),/)[1];
1319                             break;
1320                             case 'owning_lib' :
1321                                 f = null;
1322                             break;
1323                         }
1324                         g.changed[ hbox.id ] = { 'type' : t, 'field' : f, 'value' : v };
1325                         block = false;
1326                         setTimeout(
1327                             function() {
1328                                 g.summarize( g.copies );
1329                                 g.render();
1330                                 g.check_for_unmet_required_fields();
1331                                 document.getElementById(caption.id).focus();
1332                             }, 0
1333                         );
1334                     } catch(E) {
1335                         g.error.standard_unexpected_error_alert('post_c',E);
1336                     }
1337                 }
1338                 var x; var c; eval( input_cmd );
1339                 if (x) {
1340                     util.widgets.remove_children(vbox);
1341                     util.widgets.remove_children(hbox);
1342                     util.widgets.remove_children(hbox2);
1343                     hbox.appendChild(x);
1344                     var apply = document.createElement('button');
1345                     apply.setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.apply.label'));
1346                     apply.setAttribute('accesskey', $('catStrings').getString('staff.cat.copy_editor.apply.accesskey'));
1347                     hbox2.appendChild(apply);
1348                     apply.addEventListener('command',function() { c(x.value); },false);
1349                     var cancel = document.createElement('button');
1350                     cancel.setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.cancel.label'));
1351                     cancel.addEventListener('command',function() {
1352                             setTimeout( function() {
1353                                     g.summarize( g.copies );
1354                                     g.render();
1355                                     g.check_for_unmet_required_fields();
1356                                     document.getElementById(caption.id).focus(); 
1357                                 }, 0
1358                             );
1359                         }, false
1360                     );
1361                     hbox2.appendChild(cancel);
1362                     setTimeout( function() { x.focus(); }, 0 );
1363                 }
1364             } catch(E) {
1365                 g.error.standard_unexpected_error_alert('render_input',E);
1366             }
1367         }
1368         vbox.addEventListener('click',on_click, false);
1369         hbox.addEventListener('click',on_click, false);
1370         caption.addEventListener('click',on_click, false);
1371         caption.addEventListener('keypress',function(ev) {
1372             if (ev.keyCode == 13 /* enter */ || ev.keyCode == 77 /* mac enter */) on_click();
1373         }, false);
1374         caption.setAttribute('style','-moz-user-focus: normal');
1375         caption.setAttribute('onfocus','this.setAttribute("class","outline_me")');
1376         caption.setAttribute('onblur','this.setAttribute("class","")');
1377
1378     } catch(E) {
1379         g.error.sdump('D_ERROR',E + '\n');
1380     }
1381 }
1382
1383 /******************************************************************************************************/
1384 /* store the copies in the global xpcom stash */
1385
1386 g.stash_and_close = function() {
1387     try {
1388         oils_unlock_page();
1389
1390         if (g.handle_update) {
1391             try {
1392                 var r = g.network.request(
1393                     api.FM_ACP_FLESHED_BATCH_UPDATE.app,
1394                     api.FM_ACP_FLESHED_BATCH_UPDATE.method,
1395                     [ ses(), g.copies, true ]
1396                 );
1397                 if (typeof r.ilsevent != 'undefined') {
1398                     g.error.standard_unexpected_error_alert('copy update',r);
1399                 }
1400                 /* FIXME -- revisit the return value here */
1401             } catch(E) {
1402                 alert($('catStrings').getString('staff.cat.copy_editor.handle_update.error') + ' ' + js2JSON(E));
1403             }
1404         }
1405         //g.data.temp_copies = js2JSON( g.copies );
1406         //g.data.stash('temp_copies');
1407         xulG.copies = g.copies;
1408         update_modal_xulG(xulG);
1409         JSAN.use('util.widgets');
1410         util.widgets.dispatch('close',window);
1411     } catch(E) {
1412         alert('Error in copy_editor.js, g.stash_and_close(): '+E);
1413     }
1414 }
1415
1416 /******************************************************************************************************/
1417 /* spawn copy notes interface */
1418
1419 g.copy_notes = function() {
1420     JSAN.use('util.window'); var win = new util.window();
1421     win.open(
1422         urls.XUL_COPY_NOTES, 
1423         //+ '?copy_id=' + window.escape(g.copies[0].id()),
1424         $("catStrings").getString("staff.cat.copy_editor.copy_notes"),'chrome,resizable,modal',
1425         { 'copy_id' : g.copies[0].id() }
1426     );
1427 }
1428
1429 /******************************************************************************************************/
1430 /* hides or unhides stat cats based on library stat cat filter menu */
1431 g.toggle_stat_cat_display = function(el) {
1432     if (!el) return;
1433     var visible = el.getAttribute('checked');
1434     var nl = document.getElementsByAttribute('sc_lib',el.getAttribute('value'));
1435     for (var n = 0; n < nl.length; n++) {
1436         if (visible) {
1437             nl[n].setAttribute('hidden','false');
1438         } else {
1439             nl[n].setAttribute('hidden','true');
1440         }
1441     }
1442     g.copy_editor_prefs[ el.getAttribute('id') ] = { 'checked' : visible };
1443     g.save_attributes();
1444 }
1445
1446 /******************************************************************************************************/
1447 /* This adds a stat cat definition to the stat cat pane for rendering */
1448 g.save_attributes = function() {
1449     JSAN.use('util.widgets'); JSAN.use('util.file'); var file = new util.file('copy_editor_prefs.'+g.data.server_unadorned);
1450     var what_to_save = {};
1451     for (var i in g.copy_editor_prefs) {
1452         what_to_save[i] = [];
1453         for (var j in g.copy_editor_prefs[i]) what_to_save[i].push(j);
1454     }
1455     util.widgets.save_attributes(file, what_to_save );
1456 }
1457
1458 /******************************************************************************************************/
1459 /* This adds a stat cat definition to the stat cat pane for rendering */
1460 g.add_stat_cat = function(sc) {
1461     try {
1462         if (typeof g.data.hash.asc == 'undefined') { g.data.hash.asc = {}; g.data.stash('hash'); }
1463
1464         var sc_id = sc;
1465
1466         if (typeof sc == 'object') {
1467
1468             sc_id = sc.id();
1469         }
1470
1471         if (typeof g.stat_cat_seen[sc_id] != 'undefined') { return; }
1472
1473         g.stat_cat_seen[ sc_id ] = 1;
1474
1475         if (typeof sc != 'object') {
1476
1477             sc = g.network.simple_request(
1478                 'FM_ASC_BATCH_RETRIEVE',
1479                 [ ses(), [ sc_id ] ]
1480             )[0];
1481
1482         }
1483
1484         g.data.hash.asc[ sc.id() ] = sc; g.data.stash('hash');
1485
1486         var label_name = g.data.hash.aou[ sc.owner() ].shortname() + " : " + sc.name();
1487
1488         if (get_bool( sc.required() )) {
1489             g.required[ label_name ] = 1;
1490         }
1491
1492         var temp_array = [
1493             label_name,
1494             {
1495                 render: 'var l = util.functional.find_list( fm.stat_cat_entries(), function(e){ return e.stat_cat() == ' 
1496                     + sc.id() + '; } ); l ? l.value() : $("catStrings").getString("staff.cat.copy_editor.field.unset_or_null");',
1497                 input: 'c = function(v){ g.apply_stat_cat(' + sc.id() + ',v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( [ [ $("catStrings").getString("staff.cat.copy_editor.remove_stat_cat_entry"), -1 ] ].concat( util.functional.map_list( g.data.hash.asc[' + sc.id() 
1498                     + '].entries(), function(obj){ return [ obj.value(), obj.id() ]; } ) ).sort() ); '
1499                     + 'x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c),false);',
1500                 attr: {
1501                     sc_lib: sc.owner(),
1502                 }
1503             }
1504         ];
1505
1506         g.panes_and_field_names.right_pane4.push( temp_array );
1507     } catch(E) {
1508         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.add_stat_cat.error'), E);
1509     }
1510 }
1511
1512 /******************************************************************************************************/
1513 /* Add stat cats to the panes_and_field_names.right_pane4 */
1514 g.populate_stat_cats = function() {
1515     try {
1516         g.data.stash_retrieve();
1517         g.stat_cat_seen = {}; // used for determining whether a stat cat is displayed (and is eligible to be manipulated via a template)
1518
1519         function get(lib_id,only_these) {
1520             g.data.stash_retrieve();
1521             var label = 'asc_list_for_lib_'+lib_id;
1522             if (typeof g.data[label] == 'undefined') {
1523                 var robj = g.network.simple_request('FM_ASC_RETRIEVE_VIA_AOU', [ ses(), lib_id ]);
1524                 if (typeof robj.ilsevent != 'undefined') throw(robj);
1525                 var temp_list = [];
1526                 for (var j = 0; j < robj.length; j++) {
1527                     var my_asc = robj[j];
1528                     if (typeof g.data.hash.asc == 'undefined') { g.data.hash.asc = {}; }
1529                     if (typeof g.data.hash.asc[ my_asc.id() ] == 'undefined') {
1530                         g.data.hash.asc[ my_asc.id() ] = my_asc;
1531                     }
1532                     var only_this_lib = my_asc.owner(); if (typeof only_this_lib == 'object') only_this_lib = only_this_lib.id();
1533                     if (only_these.indexOf( String( only_this_lib ) ) != -1) {
1534                         temp_list.push( my_asc );
1535                     }
1536                 }
1537                 g.data[label] = temp_list; g.data.stash(label,'hash','list');
1538             }
1539             return g.data[label];
1540         }
1541
1542         /* The stat cats for the pertinent library -- this is based on workstation ou */
1543         var label = 'asc_list_for_' + typeof g.data.ws_ou == 'object' ? g.data.ws_ou.id() : g.data.ws_ou;
1544         g.data[ label ] = g.data.list.my_asc; g.data.stash('label');
1545         for (var i = 0; i < g.data.list.my_asc.length; i++) {
1546             g.add_stat_cat( g.data.list.my_asc[i] );
1547         }
1548
1549         /* For the others, we want to consider the owning libs, circ libs, and any libs that have stat cats already on the copies,
1550             however, if batch editing, we only want to show the ones they have in common.  So let's compile the libs  */
1551
1552         function add_common_ancestors(sc_libs) {
1553             JSAN.use('util.fm_utils'); 
1554             var libs = []; for (var i in sc_libs) libs.push(i);
1555             var ancestor = util.fm_utils.find_common_aou_ancestor( libs );
1556             if (typeof ancestor == 'object' && ancestor != null) ancestor = ancestor.id();
1557             if (ancestor) {
1558                 var ancestors = util.fm_utils.find_common_aou_ancestors( libs );
1559                 var asc_list = get(ancestor, ancestors);
1560                 for (var i = 0; i < asc_list.length; i++) {
1561                     g.add_stat_cat( asc_list[i] );
1562                 }
1563             }
1564         }
1565
1566         /* stat cats based on stat cat entries present on these copies */
1567         var sc_libs = {};
1568         for (var i = 0; i < g.copies.length; i++) {
1569             var entries = g.copies[i].stat_cat_entries();
1570             if (!entries) entries = [];
1571             for (var j = 0; j < entries.length; j++) {
1572                 var lib = entries[j].owner(); if (typeof lib == 'object') lib = lib.id();
1573                 sc_libs[ lib ] = true;
1574             }
1575         }
1576         add_common_ancestors(sc_libs); // CAVEAT - if a copy has no stat_cat_entries, it basically gets no vote here
1577
1578         /* stat cats based on Circ Lib */
1579         sc_libs = {};
1580         for (var i = 0; i < g.copies.length; i++) {
1581             var circ_lib = g.copies[i].circ_lib(); if (typeof circ_lib == 'object') circ_lib = circ_lib.id();
1582             sc_libs[ circ_lib ] = true;
1583         }
1584         add_common_ancestors(sc_libs);
1585
1586         /* stat cats based on Owning Lib */
1587         sc_libs = {};
1588         for (var i = 0; i < g.copies.length; i++) {
1589             var cn_id = g.copies[i].call_number();
1590             if (cn_id > 0) {
1591                 if (! g.map_acn[ cn_id ]) {
1592                     var req = g.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ cn_id ]);
1593                     if (typeof req.ilsevent == 'undefined') {
1594                         g.map_acn[ cn_id ] = req;
1595                     } else {
1596                         continue;
1597                     }
1598                 }
1599                 var owning_lib = g.map_acn[ cn_id ].owning_lib(); if (typeof owning_lib == 'object') owning_lib = owning_lib.id();
1600                 sc_libs[ owning_lib ] = true;
1601             }
1602         }
1603         add_common_ancestors(sc_libs); // CAVEAT - if a copy is a pre-cat, it basically gets no vote here
1604
1605         g.panes_and_field_names.right_pane4.sort();
1606
1607     } catch(E) {
1608         alert(E);
1609         g.error.standard_unexpected_error_alert($('catStrings').getString('staff.cat.copy_editor.populate_stat_cat.error'),E);
1610     }
1611 }
1612
1613 g.check_for_unmet_required_fields = function() {
1614     var abort = [];
1615     for (var fn in g.required) {
1616         if (typeof g.populated[fn] == 'undefined') {
1617             abort.push(fn);
1618         }
1619     }
1620     if (xulG.unified_interface) {
1621         if (abort.length > 0) {
1622             if (typeof xulG.lock_save_button == 'function') {
1623                 xulG.lock_save_button();
1624             }
1625         } else {
1626             if (typeof xulG.unlock_save_button == 'function') {
1627                 xulG.unlock_save_button();
1628             }
1629         }
1630     } else {
1631         if (abort.length > 0) {
1632             $('save').setAttribute('disabled','true');
1633         } else {
1634             $('save').setAttribute('disabled','false');
1635         }
1636     }
1637 }
1638
1639