Replace a few hardcoded strings in the serials JavaScript
[working/Evergreen.git] / Open-ILS / xul / staff_client / server / serial / editor_base.js
1 dump('entering serial/editor_base.js\n');
2 // vim:et:sw=4:ts=4:
3
4 if (typeof serial == 'undefined') serial = {};
5
6 serial.editor_base = {
7
8     'editor_base_init' : function (params) {
9         var obj = this;
10         try {
11             /******************************************************************************************************/
12             /* setup JSAN and some initial libraries */
13
14             netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
15             if (typeof JSAN == 'undefined') {
16                 throw( $('commonStrings').getString('common.jsan.missing') );
17             }
18             JSAN.errorLevel = "die"; // none, warn, or die
19             JSAN.addRepository('/xul/server/');
20             JSAN.use('util.error'); obj.error = new util.error();
21             obj.error.sdump('D_TRACE','my_init() for serial/editor_base.js');
22
23             JSAN.use('util.functional');
24             JSAN.use('OpenILS.data'); obj.data = new OpenILS.data(); obj.data.init({'via':'stash'});
25             JSAN.use('util.network'); obj.network = new util.network();
26
27
28             /******************************************************************************************************/
29             /* base vars */
30
31             obj.docid = xul_param('docid',{'modal_xulG':true});
32             
33             if (typeof params.handle_update == 'undefined') {
34                 obj.handle_update = xul_param('handle_update',{'modal_xulG':true});
35             } else {
36                 obj.handle_update = params.handle_update;
37             }
38
39             obj.trigger_refresh = params.trigger_refresh;
40             obj.refresh_command = params.refresh_command;
41             var fm_type = obj.fm_type;
42             var fm_type_plural = obj.fm_type_plural;
43             var retrieve_function = params.retrieve_function;
44             var retrieve_params = params.retrieve_params;
45             if (!retrieve_params) {
46                 retrieve_params = [];
47             }
48             if (params.xul_id_prefix) {
49                 obj.xul_id_prefix = params.xul_id_prefix;
50             } else {
51                 obj.xul_id_prefix = fm_type;
52             }
53
54             /******************************************************************************************************/
55             /* Get the fm_type ids from various sources and flesh them */
56
57             var fm_type_ids = params[fm_type + '_ids'];
58             if (!fm_type_ids) fm_type_ids = xul_param(fm_type + '_ids',{'concat':true,'JSON2js_if_cgi':true,'JSON2js_if_xulG':true,'JSON2js_if_xpcom':true,'stash_name':'temp_' + fm_type + '_ids','clear_xpcom':true,'modal_xulG':true});
59             if (!fm_type_ids) fm_type_ids = [];
60
61             obj[fm_type_plural] = [];
62             retrieve_params.push(fm_type_ids);
63             if (fm_type_ids.length > 0) obj[fm_type_plural] = obj.network.simple_request(
64                 retrieve_function,
65                 retrieve_params
66             );
67
68
69             /******************************************************************************************************/
70             /* And other fleshed copies if any */
71
72             if (!obj[fm_type_plural]) obj[fm_type_plural] = [];
73             var fms = params[fm_type_plural];
74             if (!fms) fms = xul_param(fm_type_plural,{'concat':true,'JSON2js_if_cgi':true,'JSON2js_if_xpcom':true,'stash_name':'temp_' + fm_type_plural,'clear_xpcom':true,'modal_xulG':true})
75             if (fms) obj[fm_type_plural] = obj[fm_type_plural].concat(fms);
76
77
78             // If we have just one, wrap in array
79             if (!obj[fm_type_plural].length) {
80                 obj[fm_type_plural] = [obj[fm_type_plural]];
81             }
82
83
84             /******************************************************************************************************/
85
86             //obj.init_panes0();
87             obj.init_panes();
88
89             /******************************************************************************************************/
90             /* Is the interface an editor or a viewer, single or multi copy, existing copies or new copies? */
91
92             var do_edit;
93             if (typeof params.do_edit == 'undefined') {
94                 do_edit = xul_param('do_edit',{'modal_xulG':true});
95             } else {
96                 do_edit = params.do_edit;
97             }
98
99             if (do_edit) { 
100
101                 // Editor desired, but let's check permissions
102                 obj.do_edit = false;
103
104                 try {
105                     /* FIXME: add permission check
106                     var check = obj.network.simple_request(
107                         'PERM_MULTI_ORG_CHECK',
108                         [ 
109                             ses(), 
110                             obj.data.list.au[0].id(), 
111                             util.functional.map_list(
112                                 obj[fm_type_plural],
113                                 function (o) {
114                                     var lib;
115                                     var cn_id = o.call_number();
116                                     if (cn_id == -1) {
117                                         lib = o.circ_lib(); // base perms on circ_lib instead of owning_lib if pre-cat
118                                     } else {
119                                         if (! obj.map_acn[ cn_id ]) {
120                                             var req = obj.network.simple_request('FM_ACN_RETRIEVE.authoritative',[ cn_id ]);
121                                             if (typeof req.ilsevent == 'undefined') {
122                                                 obj.map_acn[ cn_id ] = req;
123                                                 lib = obj.map_acn[ cn_id ].owning_lib();
124                                             } else {
125                                                 lib = o.circ_lib();
126                                             }
127                                         } else {
128                                             lib = obj.map_acn[ cn_id ].owning_lib();
129                                         }
130                                     }
131                                     return typeof lib == 'object' ? lib.id() : lib;
132                                 }
133                             ),
134                             obj[fm_type_plural].length == 1 ? [ 'UPDATE_COPY' ] : [ 'UPDATE_COPY', 'UPDATE_BATCH_COPY' ]
135                         ]
136                     ); */
137                     var check = [];
138                     obj.do_edit = check.length == 0;
139                 } catch(E) {
140                     obj.error.standard_unexpected_error_alert('batch permission check',E);
141                 }
142
143                 if (obj.do_edit) {
144                     $(obj.xul_id_prefix + '_save').setAttribute('hidden','false'); 
145                 } else {
146                     $('top_nav').setAttribute('hidden','true');
147                 }
148             } else {
149                 $('top_nav').setAttribute('hidden','true');
150             }
151
152
153             if (obj[fm_type_plural].length > 0 && obj[fm_type_plural][0].isnew()) {
154                 obj.mode = 'create';
155                 if (obj.can_have_notes) $(obj.xul_id_prefix + '_notes').setAttribute('hidden','true');
156                 $(obj.xul_id_prefix + '_save').setAttribute('label', $('serialStrings').getString('staff.serial.' + fm_type + '_editor.create'));
157                 $(obj.xul_id_prefix + '_save').setAttribute('accesskey', $('serialStrings').getString('staff.serial.' + fm_type + '_editor.create.accesskey'));
158             } else if (obj.mode == 'create') { // switching from create to modify
159                 obj.mode = 'modify';
160                 if (obj.can_have_notes) $(obj.xul_id_prefix + '_notes').setAttribute('hidden','false');
161                 $(obj.xul_id_prefix + '_save').setAttribute('label', $('serialStrings').getString('staff.serial.' + fm_type + '_editor.modify'));
162                 $(obj.xul_id_prefix + '_save').setAttribute('accesskey', $('serialStrings').getString('staff.serial.' + fm_type + '_editor.modify.accesskey'));
163             }
164 /*else {
165                 obj.panes_and_field_names.left_pane = 
166                     [
167                         [
168                             $('catStrings').getString('staff.cat.copy_editor.status'),
169                             { 
170                                 render: 'typeof fm.status() == "object" ? fm.status().name() : obj.data.hash.ccs[ fm.status() ].name()', 
171                                 input: obj.safe_to_edit_copy_status() ? 'c = function(v){ obj.apply("status",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( obj.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,
172                                 //input: 'c = function(v){ obj.apply("status",v); if (typeof post_c == "function") post_c(v); }; x = util.widgets.make_menulist( util.functional.map_list( util.functional.filter_list( obj.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);',
173                             }
174                         ]
175                     ].concat(obj.panes_and_field_names.left_pane);
176             }*/
177
178             if (obj[fm_type_plural].length != 1) {
179                 if (obj.can_have_notes) $(obj.xul_id_prefix + '_notes').setAttribute('hidden','true');
180             }
181
182             // clear change markers
183             obj.changed = {};
184
185             /******************************************************************************************************/
186             /* Show the Record Details? (only for 'in_modal' mode)*/
187
188             var bdb;
189             if (xul_param('in_modal',{'modal_xulG':true}) && obj.docid) {
190                 bdb = document.getElementById('brief_display_box'); while(bdb.firstChild) bdb.removeChild(bdb.lastChild);
191                 var brief_display = document.createElement('iframe'); bdb.appendChild(brief_display); 
192                 brief_display.setAttribute( 'src', urls.XUL_BIB_BRIEF + '?docid=' + obj.docid); // this is a modal window, so can't push in xulG
193                 brief_display.setAttribute( 'flex','1' );
194             }
195
196             /******************************************************************************************************/
197             /* Backup copies :) */
198
199             obj['original_' + fm_type_plural] = js2JSON( obj[fm_type_plural] );
200
201         } catch(E) {
202             var err_msg = $("commonStrings").getFormattedString('common.exception', ['serial/' + fm_type +'_editor.js - init', E]);
203             try { obj.error.sdump('D_ERROR',err_msg); } catch(E) { dump(err_msg); dump(js2JSON(E)); }
204             alert(err_msg);
205         }
206     },
207
208     /******************************************************************************************************/
209     /* Restore backup copies */
210
211     'editor_base_reset' : function() {
212         var obj = this;
213         var fm_type_plural = obj.fm_type_plural;
214
215         obj.changed = {};
216         obj[fm_type_plural] = JSON2js( obj['original_' + fm_type_plural] );
217         obj.summarize( obj[fm_type_plural] );
218         obj.render();
219     },
220
221     /******************************************************************************************************/
222     /* Apply a value to a specific field on all the copies being edited */
223     /* Don't forget to use util.money.sanitize if dealing with money values */
224
225     'editor_base_apply' : function(field, value, loop_func) {
226         var obj = this;
227         var fm_type_plural = obj.fm_type_plural;
228
229         var do_loop_func = (typeof loop_func == 'function');
230
231         obj.error.sdump('D_TRACE','applying field = <' + field + '>  value = <' + value + '>\n');
232         if (value == '<HACK:KLUDGE:NULL>') value = null;
233         for (var i = 0; i < obj[fm_type_plural].length; i++) {
234             var fm = obj[fm_type_plural][i];
235             try {
236                 fm[field]( value ); fm.ischanged('1');
237                 if (do_loop_func) {
238                     loop_func(fm);
239                 }
240             } catch(E) {
241                 alert(E);
242             }
243         }
244     },
245
246
247     /******************************************************************************************************/
248     /* This loops through all our fieldnames and all the copies, tallying up counts for the different values */
249
250     'editor_base_summarize' : function(my_fms) {
251         var obj = this;
252         /******************************************************************************************************/
253         /* Setup */
254
255         JSAN.use('util.date'); JSAN.use('util.money');
256         obj.summary = {};
257         obj.field_names = [];
258         for (var i in obj.panes_and_field_names) {
259             obj.field_names = obj.field_names.concat( obj.panes_and_field_names[i] );
260         }
261
262         /******************************************************************************************************/
263         /* Loop through the field names */
264
265         for (var i = 0; i < obj.field_names.length; i++) {
266
267             var field_name = obj.field_names[i][0];
268             var render = obj.field_names[i][1].render;
269             var attr = obj.field_names[i][1].attr;
270             var value_key = obj.field_names[i][1].value_key;
271             var dropdown_key = obj.field_names[i][1].dropdown_key;
272             obj.summary[ field_name ] = {};
273
274             /******************************************************************************************************/
275             /* Loop through the copies */
276
277             for (var j = 0; j < my_fms.length; j++) {
278
279                 var fm = my_fms[j];
280                 var cmd = render || ('fm.' + field_name + '();');
281                 var value = '???';
282
283                 /**********************************************************************************************/
284                 /* Try to retrieve the value for this field for this copy */
285
286                 try { 
287                     value = eval( cmd );
288                     if (typeof(value) == 'undefined') {
289                         value = "";
290                     }
291                     if (dropdown_key) {
292                         obj.editor_values[value_key] = eval(dropdown_key);
293                     } else if (value_key) {
294                         obj.editor_values[value_key] = value;
295                     }
296                     if (value == "") {
297                         value = $('serialStrings').getString('serial.editor_base.unset');
298                     }
299                 } catch(E) { 
300                     obj.error.sdump('D_ERROR','Attempted ' + cmd + '\n' +  E + '\n'); 
301                 }
302                 if (typeof value == 'object' && value != null) {
303                     alert('FIXME: field_name = <' + field_name + '>  value = <' + js2JSON(value) + '>\n');
304                 }
305
306                 /**********************************************************************************************/
307                 /* Tally the count */
308
309                 if (obj.summary[ field_name ][ value ]) {
310                     obj.summary[ field_name ][ value ]++;
311                 } else {
312                     obj.summary[ field_name ][ value ] = 1;
313                 }
314             }
315         }
316         obj.error.sdump('D_TRACE','summary = ' + js2JSON(obj.summary) + '\n');
317     },
318
319     /******************************************************************************************************/
320     /* Display the summarized data and inputs for editing */
321
322     'editor_base_render' : function() {
323         var obj = this;
324         var fm_type = obj.fm_type;
325
326         /******************************************************************************************************/
327         /* Library setup and clear any existing interface */
328
329         JSAN.use('util.widgets'); JSAN.use('util.date'); JSAN.use('util.money'); JSAN.use('util.functional');
330
331         for (var i in obj.panes_and_field_names) {
332             var p = document.getElementById(i);
333             if (p) util.widgets.remove_children(p);
334         }
335
336         /******************************************************************************************************/
337         /* Prepare the panes */
338
339         var groupbox; var caption; var vbox; var grid; var rows;
340         
341         /******************************************************************************************************/
342         /* Loop through the field names */
343
344         for (h in obj.panes_and_field_names) {
345             if (!document.getElementById(h)) continue;
346             for (var i = 0; i < obj.panes_and_field_names[h].length; i++) {
347                 try {
348                     var f = obj.panes_and_field_names[h][i]; var fn = f[0]; var attr = f[1].attr;
349                     groupbox = document.createElement('groupbox'); document.getElementById(h).appendChild(groupbox);
350                     if (attr) {
351                         for (var a in attr) {
352                             groupbox.setAttribute(a,attr[a]);
353                         }
354                     }
355                     if (typeof obj.changed[fn] != 'undefined') {
356                         groupbox.setAttribute('class','copy_editor_field_changed');
357                     }
358                     caption = document.createElement('caption'); groupbox.appendChild(caption);
359                     caption.setAttribute('label',fn); caption.setAttribute('id','caption_'+fn);
360                     vbox = document.createElement('vbox'); groupbox.appendChild(vbox);
361                     grid = util.widgets.make_grid( [ { 'flex' : 1 }, {}, {} ] ); vbox.appendChild(grid);
362                     grid.setAttribute('flex','1');
363                     rows = grid.lastChild;
364                     var row;
365                     
366                     /**************************************************************************************/
367                     /* Loop through each value for the field */
368
369                     for (var j in obj.summary[fn]) {
370                         var value = j; var count = obj.summary[fn][j];
371                         row = document.createElement('row'); rows.appendChild(row);
372                         var label1 = document.createElement('description'); row.appendChild(label1);
373                         label1.setAttribute('id',fn + '_label');
374                         //if (obj.special_exception[ fn ]) {
375                         //      obj.special_exception[ fn ]( label1, value );
376                         //} else {
377                             label1.appendChild( document.createTextNode(value) );
378                         //}
379                         var label2 = document.createElement('description'); row.appendChild(label2);
380                         var fm_count;
381                         if (count == 1) {
382                             fm_count = $('serialStrings').getString('staff.serial.' + fm_type +'_editor.count');
383                         } else {
384                             fm_count = $('serialStrings').getFormattedString('staff.serial.' + fm_type +'_editor.count.plural', [count]);
385                         }
386                         label2.appendChild( document.createTextNode(fm_count) );
387                     }
388                     var hbox = document.createElement('hbox'); 
389                     hbox.setAttribute('id',fn);
390                     groupbox.appendChild(hbox);
391                     var hbox2 = document.createElement('hbox');
392                     groupbox.appendChild(hbox2);
393
394                     /**************************************************************************************/
395                     /* Render the input widget */
396
397                     if (f[1].input && obj.do_edit) {
398                         obj.render_input(hbox,f[1]);
399                     }
400
401                 } catch(E) {
402                     obj.error.sdump('D_ERROR','copy editor: ' + E + '\n');
403                 }
404             }
405         }
406         
407         
408         /******************************************************************************************************/
409         /* Synchronize stat cat visibility with library filter menu, and default template selection */
410         JSAN.use('util.file'); 
411         var file = new util.file(fm_type + '_editor_prefs.'+obj.data.server_unadorned);
412         obj[fm_type + '_editor_prefs'] = util.widgets.load_attributes(file);
413         for (var i in obj[fm_type + '_editor_prefs']) {
414             if (i.match(/filter_/) && obj[fm_type + '_editor_prefs'][i].checked == '') {
415                 try { 
416                     obj.toggle_stat_cat_display( document.getElementById(i) ); 
417                 } catch(E) { alert(E); }
418             }
419         }
420         if (obj.template_menu) obj.template_menu.value = obj.template_menu.getAttribute('value');
421
422     },
423
424     /******************************************************************************************************/
425     /* This actually draws the change button and input widget for a given field */
426     'editor_base_render_input' : function(node, blob) {
427         var obj = this;
428         var fm_type_plural = obj.fm_type_plural;
429
430         try {
431             // node = hbox ;    groupbox ->  hbox, hbox
432
433             var groupbox = node.parentNode;
434             var caption = groupbox.firstChild;
435             var vbox = node.previousSibling;
436             var hbox = node;
437             var hbox2 = node.nextSibling;
438
439             var input_cmd = blob.input;
440             var render_cmd = blob.render;
441             var attr = blob.attr;
442
443             var block = false; var first = true;
444
445             function on_mouseover(ev) {
446                 groupbox.setAttribute('style','background: white');
447             }
448
449             function on_mouseout(ev) {
450                 groupbox.setAttribute('style','');
451             }
452
453             vbox.addEventListener('mouseover',on_mouseover,false);
454             vbox.addEventListener('mouseout',on_mouseout,false);
455             groupbox.addEventListener('mouseover',on_mouseover,false);
456             groupbox.addEventListener('mouseout',on_mouseout,false);
457             groupbox.firstChild.addEventListener('mouseover',on_mouseover,false);
458             groupbox.firstChild.addEventListener('mouseout',on_mouseout,false);
459
460             function on_click(ev){
461                 try {
462                     if (block) return; block = true;
463
464                     function post_c(v, unchanged) {
465                         try {
466                             /* dbw2 not needed?
467                             var t = input_cmd.match('apply_stat_cat') ? 'stat_cat' : ( input_cmd.match('apply_owning_lib') ? 'owning_lib' : 'attribute' );
468                             var f;
469                             switch(t) {
470                                 case 'attribute' :
471                                     f = input_cmd.match(/apply.?\("(.+?)",/)[1];
472                                 break;
473                                 case 'stat_cat' :
474                                     f = input_cmd.match(/apply_stat_cat\((.+?),/)[1];
475                                 break;
476                                 case 'owning_lib' :
477                                     f = null;
478                                 break;
479                             }
480                             obj.changed[ hbox.id ] = { 'type' : t, 'field' : f, 'value' : v }; */
481                             if (!unchanged) {
482                                 obj.changed[ hbox.id ] = true;
483                             }
484                             block = false;
485                             setTimeout(
486                                 function() {
487                                     obj.summarize( obj[fm_type_plural] );
488                                     obj.render();
489                                     document.getElementById(caption.id).focus();
490                                 }, 0
491                             );
492                         } catch(E) {
493                             obj.error.standard_unexpected_error_alert('post_c',E);
494                         }
495                     }
496                     var x; var c; eval( input_cmd );
497                     if (x) {
498                         util.widgets.remove_children(vbox);
499                         util.widgets.remove_children(hbox);
500                         util.widgets.remove_children(hbox2);
501                         hbox.appendChild(x);
502                         var apply = document.createElement('button');
503                         apply.setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.apply.label'));
504                         apply.setAttribute('accesskey', $('catStrings').getString('staff.cat.copy_editor.apply.accesskey'));
505                         hbox2.appendChild(apply);
506                         apply.addEventListener('command',function() { c(x.value); },false);
507                         var cancel = document.createElement('button');
508                         cancel.setAttribute('label', $('catStrings').getString('staff.cat.copy_editor.cancel.label'));
509                         cancel.addEventListener('command',function() { setTimeout( function() { obj.summarize( obj[fm_type_plural] ); obj.render(); document.getElementById(caption.id).focus(); }, 0); }, false);
510                         hbox2.appendChild(cancel);
511                         setTimeout( function() { x.focus(); }, 0 );
512                     }
513                 } catch(E) {
514                     obj.error.standard_unexpected_error_alert('render_input',E);
515                 }
516             }
517             vbox.addEventListener('click',on_click, false);
518             hbox.addEventListener('click',on_click, false);
519             caption.addEventListener('click',on_click, false);
520             caption.addEventListener('keypress',function(ev) {
521                 if (ev.keyCode == 13 /* enter */ || ev.keyCode == 77 /* mac enter */) on_click();
522             }, false);
523             caption.setAttribute('style','-moz-user-focus: normal');
524             caption.setAttribute('onfocus','this.setAttribute("class","outline_me")');
525             caption.setAttribute('onblur','this.setAttribute("class","")');
526
527         } catch(E) {
528             obj.error.sdump('D_ERROR',E + '\n');
529         }
530     },
531
532     /******************************************************************************************************/
533     /* save or store the updated fms as appropriate */
534
535     'editor_base_save' : function(update_method) {
536         var obj = this;
537         var fm_type_plural = obj.fm_type_plural;
538         var fm_type= obj.fm_type;
539
540         try {
541             if (obj.handle_update) {
542                 try {
543                     //send fms to the update function
544                     var r = obj.network.request(
545                         'open-ils.serial',
546                         update_method,
547                         [ ses(), obj[fm_type_plural] ]
548                     );
549                     if (typeof r.ilsevent != 'undefined') {
550                         obj.error.standard_unexpected_error_alert('serial ' + fm_type + ' update',r);
551                     } else {
552                         alert($('serialStrings').getString('staff.serial.editor_base.handle_update.success'));
553                         obj.changed = {};
554                         if (obj.trigger_refresh) {
555                             obj.refresh_command();
556                         } else {
557                             obj.render();
558                         }
559                     }
560                     /* FIXME -- revisit the return value here */
561                 } catch(E) {
562                     alert($('serialStrings').getString('staff.serial.editor_base.handle_update.error') + ' ' + js2JSON(E));
563                 }
564             } else if (xul_param('in_modal',{'modal_xulG':true})) {
565                 // TODO: this is to perhaps allow this editor to be called
566                 // in a modal window, but is unfinished functionality
567                 var xulG = {};
568                 xulG[fm_type_plural] = obj[fm_type_plural];
569                 update_modal_xulG(xulG);
570             } else {
571                 obj.data['temp_' + fm_type_plural] = js2JSON( obj[fm_type_plural] );
572                 obj.data.stash('temp_' + fm_type_plural);
573             }
574
575             if (xul_param('in_modal',{'modal_xulG':true})) {
576                 window.close();
577             }
578         } catch(E) {
579             obj.error.standard_unexpected_error_alert(fm_type + '_editor save',E);
580         }
581     },
582
583     /******************************************************************************************************/
584     'editor_base_save_attributes' : function() {
585         var obj = this;
586         var fm_type = obj.fm_type;
587
588         JSAN.use('util.widgets'); JSAN.use('util.file'); var file = new util.file(fm_type + '_editor_prefs.'+obj.data.server_unadorned);
589         var what_to_save = {};
590         for (var i in obj[fm_type + '_editor_prefs']) {
591             what_to_save[i] = [];
592             for (var j in obj[fm_type + '_editor_prefs'][i]) what_to_save[i].push(j);
593         }
594         util.widgets.save_attributes(file, what_to_save );
595     }
596 };
597
598 dump('exiting serial/editor_base.js\n');