]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/xul/staff_client/server/cat/copy_editor.js
xul_param and modal xulG conversion
[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                 /* Is the interface an editor or a viewer, single or multi copy, existing copies or new copies? */
49
50                 if (xul_param('edit',{'modal_xulG':true}) == '1') { 
51                         g.edit = true;
52                         document.getElementById('caption').setAttribute('label','Copy Editor'); 
53                         document.getElementById('save').setAttribute('hidden','false'); 
54                         g.retrieve_templates();
55                 } else {
56                         $('top_nav').setAttribute('hidden','true');
57                 }
58
59                 if (g.copies.length > 0 && g.copies[0].id() < 0) {
60                         document.getElementById('copy_notes').setAttribute('hidden','true');
61                         g.apply("status",5 /* In Process */);
62                         $('save').setAttribute('label','Create Copies');
63                 } else {
64                         g.panes_and_field_names.left_pane = 
65                                 [
66                                         [
67                                                 "Status",
68                                                 { 
69                                                         render: 'typeof fm.status() == "object" ? fm.status().name() : g.data.hash.ccs[ fm.status() ].name()', 
70                                                         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,
71                                                         //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);',
72                                                 }
73                                         ]
74                                 ].concat(g.panes_and_field_names.left_pane);
75                 }
76
77                 if (g.copies.length != 1) {
78                         document.getElementById('copy_notes').setAttribute('hidden','true');
79                 }
80
81                 /******************************************************************************************************/
82                 /* Show the Record Details? */
83
84                 if (g.docid) {
85                         document.getElementById('brief_display').setAttribute(
86                                 'src',
87                                 urls.XUL_BIB_BRIEF + '?docid=' + g.docid
88                         );
89                 } else {
90                         document.getElementById('brief_display').setAttribute('hidden','true');
91                 }
92
93                 /******************************************************************************************************/
94                 /* Add stat cats to the panes_and_field_names.right_pane4 */
95
96                 g.stat_cat_seen = {};
97
98                 function add_stat_cat(sc) {
99
100                         if (typeof g.data.hash.asc == 'undefined') { g.data.hash.asc = {}; g.data.stash('hash'); }
101
102                         var sc_id = sc;
103
104                         if (typeof sc == 'object') {
105
106                                 sc_id = sc.id();
107                         }
108
109                         if (typeof g.stat_cat_seen[sc_id] != 'undefined') { return; }
110
111                         g.stat_cat_seen[ sc_id ] = 1;
112
113                         if (typeof sc != 'object') {
114
115                                 sc = g.network.simple_request(
116                                         'FM_ASC_BATCH_RETRIEVE',
117                                         [ ses(), [ sc_id ] ]
118                                 )[0];
119
120                         }
121
122                         g.data.hash.asc[ sc.id() ] = sc; g.data.stash('hash');
123
124                         var label_name = g.data.hash.aou[ sc.owner() ].shortname() + " : " + sc.name();
125
126                         var temp_array = [
127                                 label_name,
128                                 {
129                                         render: 'var l = util.functional.find_list( fm.stat_cat_entries(), function(e){ return e.stat_cat() == ' 
130                                                 + sc.id() + '; } ); l ? l.value() : "<Unset>";',
131                                         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() 
132                                                 + '].entries(), function(obj){ return [ obj.value(), obj.id() ]; } ) ).sort() ); '
133                                         //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>", null ] ].concat( util.functional.map_list( g.data.hash.asc[' + sc.id() 
134                                         //      + '].entries(), function(obj){ return [ obj.value(), obj.id() ]; } ).sort() ) ); '
135                                                 + 'x.addEventListener("apply",function(f){ return function(ev) { f(ev.target.value); } }(c),false);',
136                                 }
137                         ];
138
139                         dump('temp_array = ' + js2JSON(temp_array) + '\n');
140
141                         g.panes_and_field_names.right_pane4.push( temp_array );
142                 }
143
144                 /* The stat cats for the pertinent library */
145                 for (var i = 0; i < g.data.list.my_asc.length; i++) {
146                         add_stat_cat( g.data.list.my_asc[i] );  
147                 }
148
149                 /* Other stat cats present on these copies */
150                 for (var i = 0; i < g.copies.length; i++) {
151                         var entries = g.copies[i].stat_cat_entries();
152                         if (!entries) entries = [];
153                         for (var j = 0; j < entries.length; j++) {
154                                 var sc_id = entries[j].stat_cat();
155                                 add_stat_cat( sc_id );
156                         }
157                 }
158
159                 /******************************************************************************************************/
160                 /* Backup copies :) */
161
162                 g.original_copies = js2JSON( g.copies );
163
164                 /******************************************************************************************************/
165                 /* Do it */
166
167                 g.summarize( g.copies );
168                 g.render();
169
170         } catch(E) {
171                 var err_msg = "!! This software has encountered an error.  Please tell your friendly " +
172                         "system administrator or software developer the following:\ncat/copy_editor.xul\n" + E + '\n';
173                 try { g.error.sdump('D_ERROR',err_msg); } catch(E) { dump(err_msg); dump(js2JSON(E)); }
174                 alert(err_msg);
175         }
176 }
177
178 /******************************************************************************************************/
179 /* File picker for template export/import */
180
181 function pick_file(mode) {
182         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
183         var nsIFilePicker = Components.interfaces.nsIFilePicker;
184         var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance( nsIFilePicker );
185         fp.init( 
186                 window, 
187                 mode == 'open' ? "Import Templates File" : "Save Templates File As", 
188                 mode == 'open' ? nsIFilePicker.modeOpen : nsIFilePicker.modeSave
189         );
190         fp.appendFilters( nsIFilePicker.filterAll );
191         if ( fp.show( ) == nsIFilePicker.returnOK && fp.file ) {
192                 return fp.file;
193         } else {
194                 return null;
195         }
196 }
197
198 /******************************************************************************************************/
199 /* Retrieve Templates */
200
201 g.retrieve_templates = function() {
202         try {
203                 JSAN.use('util.widgets'); JSAN.use('util.functional');
204                 g.templates = {};
205                 var robj = g.network.simple_request('FM_AUS_RETRIEVE',[ses(),g.data.list.au[0].id()]);
206                 if (typeof robj['staff_client.copy_editor.templates'] != 'undefined') {
207                         g.templates = robj['staff_client.copy_editor.templates'];
208                 }
209                 util.widgets.remove_children('template_placeholder');
210                 var list = util.functional.map_object_to_list( g.templates, function(obj,i) { return [i, i]; } );
211
212                 g.template_menu = util.widgets.make_menulist( list );
213                 $('template_placeholder').appendChild(g.template_menu);
214         } catch(E) {
215                 g.error.standard_unexpected_error_alert('Error retrieving templates',E);
216         }
217 }
218
219 /******************************************************************************************************/
220 /* Apply Template */
221
222 g.apply_template = function() {
223         try {
224                 var name = g.template_menu.value;
225                 if (g.templates[ name ] != 'undefined') {
226                         var template = g.templates[ name ];
227                         for (var i in template) {
228                                 g.changed[ i ] = template[ i ];
229                                 switch( template[i].type ) {
230                                         case 'attribute' :
231                                                 g.apply(template[i].field,template[i].value);
232                                         break;
233                                         case 'stat_cat' :
234                                                 if (g.stat_cat_seen[ template[i].field ]) g.apply_stat_cat(template[i].field,template[i].value);
235                                         break;
236                                         case 'owning_lib' :
237                                                 g.apply_owning_lib(template[i].value);
238                                         break;
239                                 }
240                         }
241                         g.summarize( g.copies );
242                         g.render();
243                 }
244         } catch(E) {
245                 g.error.standard_unexpected_error_alert('Error applying template',E);
246         }
247 }
248
249 /******************************************************************************************************/
250 /* Save as Template */
251
252 g.save_template = function() {
253         try {
254                 var name = window.prompt('Enter template name:','','Save As Template');
255                 if (!name) return;
256                 g.templates[name] = g.changed;
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 + '" saved.');
264                         setTimeout(
265                                 function() {
266                                         try {
267                                                 g.retrieve_templates();
268                                         } catch(E) {
269                                                 g.error.standard_unexpected_error_alert('Error saving template',E);
270                                         }
271                                 },0
272                         );
273                 }
274         } catch(E) {
275                 g.error.standard_unexpected_error_alert('Error saving template',E);
276         }
277 }
278
279 /******************************************************************************************************/
280 /* Delete Template */
281
282 g.delete_template = function() {
283         try {
284                 var name = g.template_menu.value;
285                 if (!name) return;
286                 if (! window.confirm('Delete template "' + name + '"?') ) return;
287                 delete(g.templates[name]);
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('Template "' + name + '" deleted.');
295                         setTimeout(
296                                 function() {
297                                         try {
298                                                 g.retrieve_templates();
299                                         } catch(E) {
300                                                 g.error.standard_unexpected_error_alert('Error deleting template',E);
301                                         }
302                                 },0
303                         );
304                 }
305         } catch(E) {
306                 g.error.standard_unexpected_error_alert('Error deleting template',E);
307         }
308 }
309
310 /******************************************************************************************************/
311 /* Export Templates */
312
313 g.export_templates = function() {
314         try {
315                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
316                 JSAN.use('util.file');
317                 var f = pick_file('save');
318                 if (f) {
319                         if (f.exists()) {
320                                 var r = G.error.yns_alert(
321                                         'Would you like to overwrite the existing file ' + f.leafName + '?',
322                                         'Templates Export Warning',
323                                         'Yes',
324                                         'No',
325                                         null,
326                                         'Check here to confirm this message'
327                                 );
328                                 if (r != 0) { file.close(); alert('Not overwriting file.'); return; }
329                         }
330                         var e_file = new util.file(''); e_file._file = f;
331                         e_file.write_content( 'truncate', js2JSON( g.templates ) );
332                         e_file.close();
333                         alert('Templates exported as file ' + f.leafName);
334                 } else {
335                         alert('File not chosen for export.');
336                 }
337
338         } catch(E) {
339                 g.error.standard_unexpected_error_alert('Error exporting templates',E);
340         }
341 }
342
343 /******************************************************************************************************/
344 /* Import Templates */
345
346 g.import_templates = function() {
347         try {
348                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
349                 JSAN.use('util.file');
350                 var f = pick_file('open');
351                 if (f && f.exists()) {
352                         var i_file = new util.file(''); i_file._file = f;
353                         var temp = JSON2js( i_file.get_content() );
354                         i_file.close();
355                         for (var i in temp) {
356
357                                 if (g.templates[i]) {
358
359                                         var r = g.error.yns_alert(
360                                                 'Replace the existing template with the imported template?\n' + g.error.pretty_print( js2JSON( temp[i] ) ),
361                                                 'Template ' + i + ' already exists.','Yes','No',null,'Click here'
362                                         );
363
364                                         if (r == 0 /* Yes */) g.templates[i] = temp[i];
365
366                                 } else {
367
368                                         g.templates[i] = temp[i];
369
370                                 }
371
372                         }
373
374                         var r = g.error.yns_alert(
375                                 'Save all of these imported templates permanently to this account?',
376                                 'Final Warning', 'Yes', 'No', null, 'Click here'
377                         );
378
379                         if (r == 0 /* Yes */) {
380                                 var robj = g.network.simple_request(
381                                         'FM_AUS_UPDATE',[ses(),g.data.list.au[0].id(), { 'staff_client.copy_editor.templates' : g.templates }]
382                                 );
383                                 if (typeof robj.ilsevent != 'undefined') {
384                                         throw(robj);
385                                 } else {
386                                         alert('All templates saved.');
387                                         setTimeout(
388                                                 function() {
389                                                         try {
390                                                                 g.retrieve_templates();
391                                                         } catch(E) {
392                                                                 g.error.standard_unexpected_error_alert('Error saving templates',E);
393                                                         }
394                                                 },0
395                                         );
396                                 }
397                         } else {
398                                 util.widgets.remove_children('template_placeholder');
399                                 var list = util.functional.map_object_to_list( g.templates, function(obj,i) { return [i, i]; } );
400                                 g.template_menu = util.widgets.make_menulist( list );
401                                 $('template_placeholder').appendChild(g.template_menu);
402                                 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 dissappear with the next invocation of the item attribute editor.");
403                         }
404
405                 } else {
406                         alert('File not chosen for import.');
407                 }
408         } catch(E) {
409                 g.error.standard_unexpected_error_alert('Error importing templates',E);
410         }
411 }
412
413
414 /******************************************************************************************************/
415 /* Restore backup copies */
416
417 g.reset = function() {
418         g.changed = {};
419         g.copies = JSON2js( g.original_copies );
420         g.summarize( g.copies );
421         g.render();
422 }
423
424 /******************************************************************************************************/
425 /* Apply a value to a specific field on all the copies being edited */
426
427 g.apply = function(field,value) {
428         g.error.sdump('D_TRACE','applying field = <' + field + '>  value = <' + value + '>\n');
429         if (value == '<HACK:KLUDGE:NULL>') value = null;
430         if (field == 'alert_message') { value = value.replace(/^\W+$/g,''); }
431         if (field == 'price' || field == 'deposit_amount') {
432                 if (value == '') { value = null; } else { JSAN.use('util.money'); value = util.money.sanitize( value ); }
433         }
434         for (var i = 0; i < g.copies.length; i++) {
435                 var copy = g.copies[i];
436                 try {
437                         copy[field]( value ); copy.ischanged('1');
438                 } catch(E) {
439                         alert(E);
440                 }
441         }
442 }
443
444 /******************************************************************************************************/
445 /* Apply a stat cat entry to all the copies being edited.  An entry_id of < 0 signifies the stat cat is being removed. */
446
447 g.apply_stat_cat = function(sc_id,entry_id) {
448         g.error.sdump('D_TRACE','sc_id = ' + sc_id + '  entry_id = ' + entry_id + '\n');
449         for (var i = 0; i < g.copies.length; i++) {
450                 var copy = g.copies[i];
451                 try {
452                         copy.ischanged('1');
453                         var temp = copy.stat_cat_entries();
454                         if (!temp) temp = [];
455                         temp = util.functional.filter_list(
456                                 temp,
457                                 function (obj) {
458                                         return (obj.stat_cat() != sc_id);
459                                 }
460                         );
461                         if (entry_id > -1) temp.push( 
462                                 util.functional.find_id_object_in_list( 
463                                         g.data.hash.asc[sc_id].entries(), 
464                                         entry_id
465                                 )
466                         );
467                         copy.stat_cat_entries( temp );
468
469                 } catch(E) {
470                         g.error.standard_unexpected_error_alert('apply_stat_cat',E);
471                 }
472         }
473 }
474
475 /******************************************************************************************************/
476 /* Apply an "owning lib" to all the copies being edited.  That is, change and auto-vivicating volumes */
477
478 g.apply_owning_lib = function(ou_id) {
479         g.error.sdump('D_TRACE','ou_id = ' + ou_id + '\n');
480         var map_acn = {};
481         for (var i = 0; i < g.copies.length; i++) {
482                 var copy = g.copies[i];
483                 try {
484                         if (!map_acn[copy.call_number()]) {
485                                 var volume = g.network.simple_request('FM_ACN_RETRIEVE',[ copy.call_number() ]);
486                                 if (typeof volume.ilsevent != 'undefined') {
487                                         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);
488                                         continue;
489                                 }
490                                 map_acn[copy.call_number()] = volume;
491                         }
492                         var old_volume = map_acn[copy.call_number()];
493                         var acn_id = g.network.simple_request(
494                                 'FM_ACN_FIND_OR_CREATE',
495                                 [ses(),old_volume.label(),old_volume.record(),ou_id]
496                         );
497                         if (typeof acn_id.ilsevent != 'undefined') {
498                                 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);
499                                 continue;
500                         }
501                         copy.call_number(acn_id);
502                         copy.ischanged('1');
503                 } catch(E) {
504                         g.error.standard_unexpected_error_alert('apply_stat_cat',E);
505                 }
506         }
507 }
508
509
510 /******************************************************************************************************/
511 /* This returns true if none of the copies being edited have a magical status found in my_constants.magical_statuses */
512
513 g.safe_to_edit_copy_status = function() {
514         try {
515                 var safe = true;
516                 for (var i = 0; i < g.copies.length; i++) {
517                         var status = g.copies[i].status(); if (typeof status == 'object') status = status.id();
518                         if (typeof my_constants.magical_statuses[ status ] != 'undefined') safe = false;
519                 }
520                 return safe;
521         } catch(E) {
522                 g.error.standard_unexpected_error_alert('safe_to_edit_copy_status?',E);
523                 return false;
524         }
525 }
526
527 /******************************************************************************************************/
528 /* This concats and uniques all the alert messages for use as the default value for a new alert message */
529
530 g.populate_alert_message_input = function(tb) {
531         try {
532                 var seen = {}; var s = '';
533                 for (var i = 0; i < g.copies.length; i++) {
534                         var msg = g.copies[i].alert_message(); 
535                         if (msg) {
536                                 if (typeof seen[msg] == 'undefined') {
537                                         s += msg + '\n';
538                                         seen[msg] = true;
539                                 }
540                         }
541                 }
542                 tb.setAttribute('value',s);
543         } catch(E) {
544                 g.error.standard_unexpected_error_alert('populate_alert_message_input',E);
545         }
546 }
547
548 /******************************************************************************************************/
549 /* This returns a list of acpl's appropriate for the copies being edited */
550
551 g.get_acpl_list = function() {
552         try {
553
554                 JSAN.use('util.functional');
555
556                 function get(lib_id,only_these) {
557                         g.data.stash_retrieve();
558                         var label = 'acpl_list_for_lib_'+lib_id;
559                         if (typeof g.data[label] == 'undefined') {
560                                 var robj = g.network.simple_request('FM_ACPL_RETRIEVE', [ lib_id ]);
561                                 if (typeof robj.ilsevent != 'undefined') throw(robj);
562                                 var temp_list = [];
563                                 for (var j = 0; j < robj.length; j++) {
564                                         var my_acpl = robj[j];
565                                         if (typeof g.data.hash.acpl[ my_acpl.id() ] == 'undefined') {
566                                                 g.data.hash.acpl[ my_acpl.id() ] = my_acpl;
567                                                 g.data.list.acpl.push( my_acpl );
568                                         }
569                                         if (only_these.indexOf( String( my_acpl.owning_lib() ) ) != -1) {
570                                                 temp_list.push( my_acpl );
571                                         }
572                                 }
573                                 g.data[label] = temp_list; g.data.stash(label,'hash','list');
574                         }
575                         return g.data[label];
576                 }
577
578                 var libs = []; var map_acn = {};
579                 for (var i = 0; i < g.copies.length; i++) {
580                         var cn_id = g.copies[i].call_number();
581                         if (cn_id > 0) {
582                                 if (! map_acn[ cn_id ]) {
583                                         map_acn[ cn_id ] = g.network.simple_request('FM_ACN_RETRIEVE',[ cn_id ]);
584                                         libs.push( map_acn[ cn_id ].owning_lib() );
585                                 }
586                         }
587                 }
588                 if (g.callnumbers) {
589                         for (var i in g.callnumbers) {
590                                 if ( ( libs.indexOf( g.callnumbers[i].owning_lib ) > -1 ) || ( libs.indexOf( String( g.callnumbers[i].owning_lib ) ) > -1 ) ) { /* already in list */ } else { libs.push( g.callnumbers[i].owning_lib ); }
591                         }
592                 }
593                 JSAN.use('util.fm_utils');
594                 var ancestor = util.fm_utils.find_common_aou_ancestor( libs );
595                 if (typeof ancestor == 'object' && ancestor != null) ancestor = ancestor.id();
596
597                 var ancestors = util.fm_utils.find_common_aou_ancestors( libs );
598
599                 if (ancestor) {
600                         return get(ancestor, ancestors);
601                 } else {
602                         return [];
603                 }
604
605         } catch(E) {
606                 g.error.standard_unexpected_error_alert('get_acpl_list',E);
607                 return [];
608         }
609 }
610
611
612 /******************************************************************************************************/
613 /* This keeps track of what fields have been edited for styling purposes */
614
615 g.changed = {};
616
617 /******************************************************************************************************/
618 /* These need data from the middle layer to render */
619
620 g.special_exception = {
621         'Owning Lib : Call Number' : function(label,value) {
622                 JSAN.use('util.widgets');
623                 if (value>0) { /* an existing call number */
624                         g.network.request(
625                                 api.FM_ACN_RETRIEVE.app,
626                                 api.FM_ACN_RETRIEVE.method,
627                                 [ value ],
628                                 function(req) {
629                                         var cn = '??? id = ' + value;
630                                         try {
631                                                 cn = req.getResultObject();
632                                         } catch(E) {
633                                                 g.error.sdump('D_ERROR','callnumber retrieve: ' + E);
634                                         }
635                                         util.widgets.set_text(label,g.data.hash.aou[ cn.owning_lib() ].shortname() + ' : ' + cn.label());
636                                 }
637                         );
638                 } else { /* a yet to be created call number */
639                         if (g.callnumbers) {
640                                 util.widgets.set_text(label,g.data.hash.aou[ g.callnumbers[value].owning_lib ].shortname() + ' : ' + g.callnumbers[value].label);
641                         }
642                 }
643         },
644         'Creator' : function(label,value) {
645                 if (value == null || value == '' || value == 'null') return;
646                 g.network.simple_request(
647                         'FM_AU_RETRIEVE_VIA_ID',
648                         [ ses(), value ],
649                         function(req) {
650                                 var p = '??? id = ' + value;
651                                 try {
652                                         p = req.getResultObject();
653                                         p = p.usrname();
654
655                                 } catch(E) {
656                                         g.error.sdump('D_ERROR','patron retrieve: ' + E);
657                                 }
658                                 JSAN.use('util.widgets');
659                                 util.widgets.set_text(label,p);
660                         }
661                 );
662         },
663         'Last Editor' : function(label,value) {
664                 if (value == null || value == '' || value == 'null') return;
665                 g.network.simple_request(
666                         'FM_AU_RETRIEVE_VIA_ID',
667                         [ ses(), value ],
668                         function(req) {
669                                 var p = '??? id = ' + value;
670                                 try {
671                                         p = req.getResultObject();
672                                         p = p.usrname();
673
674                                 } catch(E) {
675                                         g.error.sdump('D_ERROR','patron retrieve: ' + E);
676                                 }
677                                 util.widgets.set_text(label,p);
678                         }
679                 );
680         }
681
682 }
683
684 /******************************************************************************************************/
685 g.readonly_stat_cat_names = [];
686 g.editable_stat_cat_names = [];
687
688 /******************************************************************************************************/
689 /* These get show in the left panel */
690
691 g.panes_and_field_names = {
692
693         'left_pane' :
694 [
695         [
696                 "Barcode",               
697                 {
698                         render: 'fm.barcode();',
699                 }
700         ], 
701         [
702                 "Creation Date",
703                 { 
704                         render: 'util.date.formatted_date( fm.create_date(), "%F");',
705                 }
706         ],
707         [
708                 "Creator",
709                 { 
710                         render: 'fm.creator();',
711                 }
712         ],
713         [
714                 "Last Edit Date",
715                 { 
716                         render: 'util.date.formatted_date( fm.edit_date(), "%F");',
717                 }
718         ],
719         [
720                 "Last Editor",
721                 {
722                         render: 'fm.editor();',
723                 }
724         ],
725
726 ],
727
728 'right_pane' :
729 [
730         [
731                 "Shelving Location",
732                 { 
733                         render: 'typeof fm.location() == "object" ? fm.location().name() : g.data.lookup("acpl",fm.location()).name()', 
734                         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);',
735
736                 }
737         ],
738         [
739                 "Circulation Library",          
740                 {       
741                         render: 'typeof fm.circ_lib() == "object" ? fm.circ_lib().shortname() : g.data.hash.aou[ fm.circ_lib() ].shortname()',
742                         //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);',
743                         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);',
744                 } 
745         ],
746         [
747                 "Owning Lib : Call Number",     
748                 {
749                         render: 'fm.call_number();',
750                         input: '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);',
751                 }
752         ],
753         [
754                 "Copy Number",
755                 { 
756                         render: 'fm.copy_number() == null ? "<Unset>" : fm.copy_number()',
757                         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);',
758                 }
759         ],
760
761
762 ],
763
764 'right_pane2' :
765 [
766         [
767                 "Circulate?",
768                 {       
769                         render: 'fm.circulate() == null ? "<Unset>" : ( get_bool( fm.circulate() ) ? "Yes" : "No" )',
770                         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);',
771                 }
772         ],
773         [
774                 "Holdable?",
775                 { 
776                         render: 'fm.holdable() == null ? "<Unset>" : ( get_bool( fm.holdable() ) ? "Yes" : "No" )', 
777                         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);',
778                 }
779         ],
780         [
781                 "Age Protection",
782                 {
783                         render: 'fm.age_protect() == null ? "<Unset>" : ( typeof fm.age_protect() == "object" ? fm.age_protect().name() : g.data.hash.crahp[ fm.age_protect() ].name() )', 
784                         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);',
785                 }
786
787         ],
788         [
789                 "Loan Duration",
790                 { 
791                         render: 'switch(fm.loan_duration()){ case 1: case "1": "Short"; break; case 2: case "2": "Normal"; break; case 3:case "3": "Long"; break; }',
792                         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);',
793
794                 }
795         ],
796         [
797                 "Fine Level",
798                 {
799                         render: 'switch(fm.fine_level()){ case 1: case "1": "Low"; break; case 2: case "2": "Normal"; break; case 3: case "3": "High"; break; }',
800                         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);',
801                 }
802         ],
803
804          [
805                 "Circulate as Type",    
806                 {       
807                         render: 'fm.circ_as_type() == null ? "<Unset>" : g.data.hash.citm[ fm.circ_as_type() ].value()',
808                         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);',
809                 } 
810         ],
811         [
812                 "Circulation Modifier",
813                 {       
814                         render: 'fm.circ_modifier() == null ? "<Unset>" : fm.circ_modifier()',
815                         /*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);',*/
816                         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);',
817                 }
818         ],
819 ],
820
821 'right_pane3' :
822 [       [
823                 "Alert Message",
824                 {
825                         render: 'fm.alert_message() == null ? "<Unset>" : fm.alert_message()',
826                         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);',
827                 }
828         ],
829
830         [
831                 "Deposit?",
832                 { 
833                         render: 'fm.deposit() == null ? "<Unset>" : ( get_bool( fm.deposit() ) ? "Yes" : "No" )',
834                         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);',
835                 }
836         ],
837         [
838                 "Deposit Amount",
839                 { 
840                         render: 'if (fm.deposit_amount() == null) { "<Unset>"; } else { util.money.sanitize( fm.deposit_amount() ); }',
841                         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);',
842                 }
843         ],
844         [
845                 "Price",
846                 { 
847                         render: 'if (fm.price() == null) { "<Unset>"; } else { util.money.sanitize( fm.price() ); }', 
848                         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);',
849                 }
850         ],
851
852         [
853                 "OPAC Visible?",
854                 { 
855                         render: 'fm.opac_visible() == null ? "<Unset>" : ( get_bool( fm.opac_visible() ) ? "Yes" : "No" )', 
856                         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);',
857                 }
858         ],
859         [
860                 "Reference?",
861                 { 
862                         render: 'fm.ref() == null ? "<Unset>" : ( get_bool( fm.ref() ) ? "Yes" : "No" )', 
863                         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);',
864                 }
865         ],
866 ],
867
868 'right_pane4' : 
869 [
870 ]
871
872 };
873
874 /******************************************************************************************************/
875 /* This loops through all our fieldnames and all the copies, tallying up counts for the different values */
876
877 g.summarize = function( copies ) {
878         /******************************************************************************************************/
879         /* Setup */
880
881         JSAN.use('util.date'); JSAN.use('util.money');
882         g.summary = {};
883         g.field_names = [];
884         for (var i in g.panes_and_field_names) {
885                 g.field_names = g.field_names.concat( g.panes_and_field_names[i] );
886         }
887         g.field_names = g.field_names.concat( g.editable_stat_cat_names );
888         g.field_names = g.field_names.concat( g.readonly_stat_cat_names );
889
890         /******************************************************************************************************/
891         /* Loop through the field names */
892
893         for (var i = 0; i < g.field_names.length; i++) {
894
895                 var field_name = g.field_names[i][0];
896                 var render = g.field_names[i][1].render;
897                 g.summary[ field_name ] = {};
898
899                 /******************************************************************************************************/
900                 /* Loop through the copies */
901
902                 for (var j = 0; j < copies.length; j++) {
903
904                         var fm = copies[j];
905                         var cmd = render || ('fm.' + field_name + '();');
906                         var value = '???';
907
908                         /**********************************************************************************************/
909                         /* Try to retrieve the value for this field for this copy */
910
911                         try { 
912                                 value = eval( cmd ); 
913                         } catch(E) { 
914                                 g.error.sdump('D_ERROR','Attempted ' + cmd + '\n' +  E + '\n'); 
915                         }
916                         if (typeof value == 'object' && value != null) {
917                                 alert('FIXME: field_name = <' + field_name + '>  value = <' + js2JSON(value) + '>\n');
918                         }
919
920                         /**********************************************************************************************/
921                         /* Tally the count */
922
923                         if (g.summary[ field_name ][ value ]) {
924                                 g.summary[ field_name ][ value ]++;
925                         } else {
926                                 g.summary[ field_name ][ value ] = 1;
927                         }
928                 }
929         }
930         g.error.sdump('D_TRACE','summary = ' + js2JSON(g.summary) + '\n');
931 }
932
933 /******************************************************************************************************/
934 /* Display the summarized data and inputs for editing */
935
936 g.render = function() {
937
938         /******************************************************************************************************/
939         /* Library setup and clear any existing interface */
940
941         JSAN.use('util.widgets'); JSAN.use('util.date'); JSAN.use('util.money'); JSAN.use('util.functional');
942
943         for (var i in g.panes_and_field_names) {
944                 var p = document.getElementById(i);
945                 if (p) util.widgets.remove_children(p);
946         }
947
948         /******************************************************************************************************/
949         /* Prepare the panes */
950
951         var groupbox; var caption; var vbox; var grid; var rows;
952         
953         /******************************************************************************************************/
954         /* Loop through the field names */
955
956         for (h in g.panes_and_field_names) {
957                 if (!document.getElementById(h)) continue;
958                 for (var i = 0; i < g.panes_and_field_names[h].length; i++) {
959                         try {
960                                 var f = g.panes_and_field_names[h][i]; var fn = f[0];
961                                 groupbox = document.createElement('groupbox'); document.getElementById(h).appendChild(groupbox);
962                                 if (typeof g.changed[fn] != 'undefined') groupbox.setAttribute('class','copy_editor_field_changed');
963                                 caption = document.createElement('caption'); groupbox.appendChild(caption);
964                                 caption.setAttribute('label',fn); caption.setAttribute('id','caption_'+fn);
965                                 vbox = document.createElement('vbox'); groupbox.appendChild(vbox);
966                                 grid = util.widgets.make_grid( [ { 'flex' : 1 }, {}, {} ] ); vbox.appendChild(grid);
967                                 grid.setAttribute('flex','1');
968                                 rows = grid.lastChild;
969                                 var row;
970                                 
971                                 /**************************************************************************************/
972                                 /* Loop through each value for the field */
973
974                                 for (var j in g.summary[fn]) {
975                                         var value = j; var count = g.summary[fn][j];
976                                         row = document.createElement('row'); rows.appendChild(row);
977                                         var label1 = document.createElement('description'); row.appendChild(label1);
978                                         if (g.special_exception[ fn ]) {
979                                                 g.special_exception[ fn ]( label1, value );
980                                         } else {
981                                                 label1.appendChild( document.createTextNode(value) );
982                                         }
983                                         var label2 = document.createElement('description'); row.appendChild(label2);
984                                         var unit = count == 1 ? 'copy' : 'copies';
985                                         label2.appendChild( document.createTextNode(count + ' ' + unit) );
986                                 }
987                                 var hbox = document.createElement('hbox'); 
988                                 hbox.setAttribute('id',fn);
989                                 groupbox.appendChild(hbox);
990                                 var hbox2 = document.createElement('hbox');
991                                 groupbox.appendChild(hbox2);
992
993                                 /**************************************************************************************/
994                                 /* Render the input widget */
995
996                                 if (f[1].input && g.edit) {
997                                         g.render_input(hbox,f[1]);
998                                 }
999
1000                         } catch(E) {
1001                                 g.error.sdump('D_ERROR','copy editor: ' + E + '\n');
1002                         }
1003                 }
1004         }
1005 }
1006
1007 /******************************************************************************************************/
1008 /* This actually draws the change button and input widget for a given field */
1009 g.render_input = function(node,blob) {
1010         try {
1011                 // node = hbox ;    groupbox ->  hbox, hbox
1012
1013                 var groupbox = node.parentNode;
1014                 var caption = groupbox.firstChild;
1015                 var vbox = node.previousSibling;
1016                 var hbox = node;
1017                 var hbox2 = node.nextSibling;
1018
1019                 var input_cmd = blob.input;
1020                 var render_cmd = blob.render;
1021
1022                 var block = false; var first = true;
1023
1024                 function on_mouseover(ev) {
1025                         groupbox.setAttribute('style','background: white');
1026                 }
1027
1028                 function on_mouseout(ev) {
1029                         groupbox.setAttribute('style','');
1030                 }
1031
1032                 vbox.addEventListener('mouseover',on_mouseover,false);
1033                 vbox.addEventListener('mouseout',on_mouseout,false);
1034                 groupbox.addEventListener('mouseover',on_mouseover,false);
1035                 groupbox.addEventListener('mouseout',on_mouseout,false);
1036                 groupbox.firstChild.addEventListener('mouseover',on_mouseover,false);
1037                 groupbox.firstChild.addEventListener('mouseout',on_mouseout,false);
1038
1039                 function on_click(ev){
1040                         try {
1041                                 if (block) return; block = true;
1042
1043                                 function post_c(v) {
1044                                         try {
1045                                                 /* FIXME - kludgy */
1046                                                 var t = input_cmd.match('apply_stat_cat') ? 'stat_cat' : ( input_cmd.match('apply_owning_lib') ? 'owning_lib' : 'attribute' );
1047                                                 var f;
1048                                                 switch(t) {
1049                                                         case 'attribute' :
1050                                                                 f = input_cmd.match(/apply\("(.+?)",/)[1];
1051                                                         break;
1052                                                         case 'stat_cat' :
1053                                                                 f = input_cmd.match(/apply_stat_cat\((.+?),/)[1];
1054                                                         break;
1055                                                         case 'owning_lib' :
1056                                                                 f = null;
1057                                                         break;
1058                                                 }
1059                                                 g.changed[ hbox.id ] = { 'type' : t, 'field' : f, 'value' : v };
1060                                                 block = false;
1061                                                 setTimeout(
1062                                                         function() {
1063                                                                 g.summarize( g.copies );
1064                                                                 g.render();
1065                                                                 document.getElementById(caption.id).focus();
1066                                                         }, 0
1067                                                 );
1068                                         } catch(E) {
1069                                                 g.error.standard_unexpected_error_alert('post_c',E);
1070                                         }
1071                                 }
1072                                 var x; var c; eval( input_cmd );
1073                                 if (x) {
1074                                         util.widgets.remove_children(vbox);
1075                                         util.widgets.remove_children(hbox);
1076                                         util.widgets.remove_children(hbox2);
1077                                         hbox.appendChild(x);
1078                                         var apply = document.createElement('button');
1079                                         apply.setAttribute('label','Apply');
1080                                         apply.setAttribute('accesskey','A');
1081                                         hbox2.appendChild(apply);
1082                                         apply.addEventListener('command',function() { c(x.value); },false);
1083                                         var cancel = document.createElement('button');
1084                                         cancel.setAttribute('label','Cancel');
1085                                         cancel.addEventListener('command',function() { setTimeout( function() { g.summarize( g.copies ); g.render(); document.getElementById(caption.id).focus(); }, 0); }, false);
1086                                         hbox2.appendChild(cancel);
1087                                         setTimeout( function() { x.focus(); }, 0 );
1088                                 }
1089                         } catch(E) {
1090                                 g.error.standard_unexpected_error_alert('render_input',E);
1091                         }
1092                 }
1093                 vbox.addEventListener('click',on_click, false);
1094                 hbox.addEventListener('click',on_click, false);
1095                 caption.addEventListener('click',on_click, false);
1096                 caption.addEventListener('keypress',function(ev) {
1097                         if (ev.keyCode == 13 /* enter */ || ev.keyCode == 77 /* mac enter */) on_click();
1098                 }, false);
1099                 caption.setAttribute('style','-moz-user-focus: normal');
1100                 caption.setAttribute('onfocus','this.setAttribute("class","outline_me")');
1101                 caption.setAttribute('onblur','this.setAttribute("class","")');
1102
1103         } catch(E) {
1104                 g.error.sdump('D_ERROR',E + '\n');
1105         }
1106 }
1107
1108 /******************************************************************************************************/
1109 /* store the copies in the global xpcom stash */
1110
1111 g.stash_and_close = function() {
1112         try {
1113                 if (g.handle_update) {
1114                         try {
1115                                 var r = g.network.request(
1116                                         api.FM_ACP_FLESHED_BATCH_UPDATE.app,
1117                                         api.FM_ACP_FLESHED_BATCH_UPDATE.method,
1118                                         [ ses(), g.copies, true ]
1119                                 );
1120                                 if (typeof r.ilsevent != 'undefined') {
1121                                         g.error.standard_unexpected_error_alert('copy update',r);
1122                                 } else {
1123                                         alert('Items added/modified.');
1124                                 }
1125                                 /* FIXME -- revisit the return value here */
1126                         } catch(E) {
1127                                 alert('copy update error: ' + js2JSON(E));
1128                         }
1129                 }
1130                 //g.data.temp_copies = js2JSON( g.copies );
1131                 //g.data.stash('temp_copies');
1132                 xulG.copies = g.copies;
1133                 update_modal_xulG(xulG);
1134                 window.close();
1135         } catch(E) {
1136                 g.error.standard_unexpected_error_alert('stash and close',E);
1137         }
1138 }
1139
1140 /******************************************************************************************************/
1141 /* spawn copy notes interface */
1142
1143 g.copy_notes = function() {
1144         JSAN.use('util.window'); var win = new util.window();
1145         win.open(
1146                 urls.XUL_COPY_NOTES, 
1147                 //+ '?copy_id=' + window.escape(g.copies[0].id()),
1148                 'Copy Notes','chrome,resizable,modal',
1149                 { 'copy_id' : g.copies[0].id() }
1150         );
1151 }
1152