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