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