]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/util/list.js
dc1570a841b9d008aecfb17b275396b73a65adce
[working/Evergreen.git] / Open-ILS / xul / staff_client / chrome / content / util / list.js
1 dump('entering util.list.js\n');
2
3 if (typeof main == 'undefined') main = {};
4 util.list = function (id) {
5
6     this.node = document.getElementById(id);
7
8     this.row_count = { 'total' : 0, 'fleshed' : 0 };
9
10     this.unique_row_counter = 0;
11
12     if (!this.node) throw('Could not find element ' + id);
13     switch(this.node.nodeName) {
14         case 'listbox' : 
15         case 'tree' : break;
16         case 'richlistbox' :
17             throw(this.node.nodeName + ' not yet supported'); break;
18         default: throw(this.node.nodeName + ' not supported'); break;
19     }
20
21     JSAN.use('util.error'); this.error = new util.error();
22
23     JSAN.use('OpenILS.data'); this.data = new OpenILS.data(); this.data.stash_retrieve();
24
25     return this;
26 };
27
28 util.list.prototype = {
29
30     'init' : function (params) {
31
32         var obj = this;
33
34         JSAN.use('util.widgets');
35
36         obj.printer_context = params.printer_context;
37
38         if (typeof params.map_row_to_column == 'function') obj.map_row_to_column = params.map_row_to_column;
39         if (typeof params.map_row_to_columns == 'function') {
40             obj.map_row_to_columns = params.map_row_to_columns;
41         } else {
42             obj.map_row_to_columns = obj.std_map_row_to_columns();
43         }
44         if (typeof params.retrieve_row == 'function') obj.retrieve_row = params.retrieve_row;
45
46         obj.prebuilt = false;
47         if (typeof params.prebuilt != 'undefined') obj.prebuilt = params.prebuilt;
48
49         if (typeof params.columns == 'undefined') throw('util.list.init: No columns');
50         obj.columns = [];
51         for (var i = 0; i < params.columns.length; i++) {
52             if (typeof params.columns[i] == 'object') {
53                 obj.columns.push( params.columns[i] );
54             } else {
55                 var cols = obj.fm_columns( params.columns[i] );
56                 for (var j = 0; j < cols.length; j++) {
57                     obj.columns.push( cols[j] );
58                 }
59             }
60         }
61
62         switch(obj.node.nodeName) {
63             case 'tree' : obj._init_tree(params); break;
64             case 'listbox' : obj._init_listbox(params); break;
65             default: throw('NYI: Need ._init() for ' + obj.node.nodeName); break;
66         }
67     },
68
69     '_init_tree' : function (params) {
70         var obj = this;
71         if (this.prebuilt) {
72         
73             this.treechildren = this.node.lastChild;    
74         
75         } else {
76             var treecols = document.createElement('treecols');
77             this.node.appendChild(treecols);
78             this.treecols = treecols;
79
80             var check_for_id_collisions = {};
81             for (var i = 0; i < this.columns.length; i++) {
82                 var treecol = document.createElement('treecol');
83                 for (var j in this.columns[i]) {
84                     var value = this.columns[i][j];
85                     if (j=='id') {
86                         if (typeof check_for_id_collisions[value] == 'undefined') {
87                             check_for_id_collisions[value] = true;
88                         } else {
89                             // Column id's are important for sorting and saving list configuration.  Collisions started happening because
90                             // we were using field names as id's, and then later combining column definitions for multiple objects that
91                             // shared field names.  The downside to this sort of automatic collision prevention is that these generated
92                             // id's can change as we add and remove columns, possibly breaking saved list configurations.
93                             dump('Column collision with id = ' + value + ', renaming to ');
94                             value = value + '_collision_' + i;
95                             dump(value + '\n');
96                         }
97                     }
98                     treecol.setAttribute(j,value);
99                 }
100                 treecols.appendChild(treecol);
101                 if (this.columns[i].type == 'checkbox') {
102                     treecol.addEventListener(
103                         'click',
104                         function(ev) {
105                             setTimeout(
106                                 function() {
107                                     var toggle = ev.target.getAttribute('toggleAll') || 'on';
108                                     if (toggle == 'off') toggle = 'on'; else toggle = 'off';
109                                     ev.target.setAttribute('toggleAll',toggle);
110                                     obj._toggle_checkbox_column(ev.target,toggle);
111                                 }, 0
112                             );
113                         },
114                         false
115                     );
116                 } else {
117                     treecol.addEventListener(
118                         'click', 
119                         function(ev) {
120                             function do_it() {
121                                 var sortDir = ev.target.getAttribute('sortDir') || 'desc';
122                                 if (sortDir == 'desc') sortDir = 'asc'; else sortDir = 'desc';
123                                 ev.target.setAttribute('sortDir',sortDir);
124                                 obj._sort_tree(ev.target,sortDir);
125                             }
126
127                             if (obj.row_count.total != obj.row_count.fleshed && (obj.row_count.total - obj.row_count.fleshed) > 50) {
128                                 var r = window.confirm(document.getElementById('offlineStrings').getFormattedString('list.row_fetch_warning',[obj.row_count.fleshed,obj.row_count.total]));
129
130                                 if (r) {
131                                     setTimeout( do_it, 0 );
132                                 }
133                             } else {
134                                     setTimeout( do_it, 0 );
135                             }
136                         },
137                         false
138                     );
139                 }
140                 var splitter = document.createElement('splitter');
141                 splitter.setAttribute('class','tree-splitter');
142                 treecols.appendChild(splitter);
143             }
144
145             var treechildren = document.createElement('treechildren');
146             this.node.appendChild(treechildren);
147             this.treechildren = treechildren;
148         }
149         if (typeof params.on_sort == 'function') {
150             this.on_sort = params.on_sort;
151         }
152         if (typeof params.on_checkbox_toggle == 'function') {
153             this.on_checkbox_toggle = params.on_checkbox_toggle;
154         }
155         this.node.addEventListener(
156             'select',
157             function(ev) {
158                 if (typeof params.on_select == 'function') {
159                     params.on_select(ev);
160                 }
161                 var x = document.getElementById(obj.node.id + '_clipfield');
162                 if (x) {
163                     var sel = obj.retrieve_selection();
164                     x.setAttribute('disabled', sel.length == 0);
165                 }
166             },
167             false
168         );
169         if (typeof params.on_click == 'function') {
170             this.node.addEventListener(
171                 'click',
172                 params.on_click,
173                 false
174             );
175         }
176         /*
177         this.node.addEventListener(
178             'mousemove',
179             function(ev) { obj.detect_visible(); },
180             false
181         );
182         */
183         this.node.addEventListener(
184             'keypress',
185             function(ev) { obj.auto_retrieve(); },
186             false
187         );
188         this.node.addEventListener(
189             'click',
190             function(ev) { obj.auto_retrieve(); },
191             false
192         );
193         window.addEventListener(
194             'resize',
195             function(ev) { obj.auto_retrieve(); },
196             false
197         );
198         /* FIXME -- find events on scrollbar to trigger this */
199         obj.detect_visible_polling();    
200         /*
201         var scrollbar = document.getAnonymousNodes( document.getAnonymousNodes(this.node)[1] )[1];
202         var slider = document.getAnonymousNodes( scrollbar )[2];
203         alert('scrollbar = ' + scrollbar.nodeName + ' grippy = ' + slider.nodeName);
204         scrollbar.addEventListener('click',function(){alert('sb click');},false);
205         scrollbar.addEventListener('command',function(){alert('sb command');},false);
206         scrollbar.addEventListener('scroll',function(){alert('sb scroll');},false);
207         slider.addEventListener('click',function(){alert('slider click');},false);
208         slider.addEventListener('command',function(){alert('slider command');},false);
209         slider.addEventListener('scroll',function(){alert('slider scroll');},false);
210         */
211         this.node.addEventListener('scroll',function(){ obj.auto_retrieve(); },false);
212
213         this.restores_columns(params);
214     },
215
216     '_init_listbox' : function (params) {
217         if (this.prebuilt) {
218         } else {
219             var listhead = document.createElement('listhead');
220             this.node.appendChild(listhead);
221
222             var listcols = document.createElement('listcols');
223             this.node.appendChild(listcols);
224
225             for (var i = 0; i < this.columns.length; i++) {
226                 var listheader = document.createElement('listheader');
227                 listhead.appendChild(listheader);
228                 var listcol = document.createElement('listcol');
229                 listcols.appendChild(listcol);
230                 for (var j in this.columns[i]) {
231                     listheader.setAttribute(j,this.columns[i][j]);
232                     listcol.setAttribute(j,this.columns[i][j]);
233                 };
234             }
235         }
236     },
237
238     'save_columns' : function (params) {
239         var obj = this;
240         if (obj.data.hash.aous['gui.disable_local_save_columns']) {
241             alert(document.getElementById('offlineStrings').getString('list.column_save_disabled'));
242         } else {
243             switch (this.node.nodeName) {
244                 case 'tree' : this._save_columns_tree(params); break;
245                 default: throw('NYI: Need .save_columns() for ' + this.node.nodeName); break;
246             }
247         }
248     },
249
250     '_save_columns_tree' : function (params) {
251         var obj = this;
252         try {
253             var id = obj.node.getAttribute('id'); if (!id) {
254                 alert("FIXME: The columns for this list cannot be saved because the list has no id.");
255                 return;
256             }
257             var my_cols = {};
258             var nl = obj.node.getElementsByTagName('treecol');
259             for (var i = 0; i < nl.length; i++) {
260                 var col = nl[i];
261                 var col_id = col.getAttribute('id');
262                 if (!col_id) {
263                     alert('FIXME: A column in this list does not have an id and cannot be saved');
264                     continue;
265                 }
266                 var col_hidden = col.getAttribute('hidden'); 
267                 var col_width = col.getAttribute('width'); 
268                 var col_ordinal = col.getAttribute('ordinal'); 
269                 my_cols[ col_id ] = { 'hidden' : col_hidden, 'width' : col_width, 'ordinal' : col_ordinal };
270             }
271             netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
272             JSAN.use('util.file'); var file = new util.file('tree_columns_for_'+window.escape(id));
273             file.set_object(my_cols);
274             file.close();
275             alert(document.getElementById('offlineStrings').getString('list.columns_saved'));
276         } catch(E) {
277             obj.error.standard_unexpected_error_alert('_save_columns_tree',E);
278         }
279     },
280
281     'restores_columns' : function (params) {
282         var obj = this;
283         switch (this.node.nodeName) {
284             case 'tree' : this._restores_columns_tree(params); break;
285             default: throw('NYI: Need .restores_columns() for ' + this.node.nodeName); break;
286         }
287     },
288
289     '_restores_columns_tree' : function (params) {
290         var obj = this;
291         try {
292             var id = obj.node.getAttribute('id'); if (!id) {
293                 alert("FIXME: The columns for this list cannot be restored because the list has no id.");
294                 return;
295             }
296
297             var my_cols;
298             if (! obj.data.hash.aous) { obj.data.hash.aous = {}; }
299             if (! obj.data.hash.aous['gui.disable_local_save_columns']) {
300                 netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
301                 JSAN.use('util.file'); var file = new util.file('tree_columns_for_'+window.escape(id));
302                 if (file._file.exists()) {
303                     my_cols = file.get_object(); file.close();
304                 }
305             }
306             /* local file will trump remote file if allowed, so save ourselves an http request if this is the case */
307             if (obj.data.hash.aous['url.remote_column_settings'] && ! my_cols ) {
308                 try {
309                     var x = new XMLHttpRequest();
310                     var url = obj.data.hash.aous['url.remote_column_settings'] + '/tree_columns_for_' + window.escape(id);
311                     x.open("GET", url, false);
312                     x.send(null);
313                     if (x.status == 200) {
314                         my_cols = JSON2js( x.responseText );
315                     }
316                 } catch(E) {
317                     // This can happen in the offline interface if you logged in previously and url.remote_column_settings is set.
318                     // 1) You may be really "offline" now
319                     // 2) the URL may just be a path component without a hostname (ie "/xul/column_settings/"), which won't work
320                     // when appended to chrome://open_ils_staff_client/
321                     dump('Error retrieving column settings from ' + url + ': ' + E + '\n');
322                 }
323             }
324
325             if (my_cols) {
326                 var nl = obj.node.getElementsByTagName('treecol');
327                 for (var i = 0; i < nl.length; i++) {
328                     var col = nl[i];
329                     var col_id = col.getAttribute('id');
330                     if (!col_id) {
331                         alert('FIXME: A column in this list does not have an id and cannot be saved');
332                         continue;
333                     }
334                     if (typeof my_cols[col_id] != 'undefined') {
335                         col.setAttribute('hidden',my_cols[col_id].hidden); 
336                         col.setAttribute('width',my_cols[col_id].width); 
337                         col.setAttribute('ordinal',my_cols[col_id].ordinal); 
338                     } else {
339                         obj.error.sdump('D_ERROR','WARNING: Column ' + col_id + ' did not have a saved state.');
340                     }
341                 }
342             }
343         } catch(E) {
344             obj.error.standard_unexpected_error_alert('_restore_columns_tree',E);
345         }
346     },
347
348     'clear' : function (params) {
349         var obj = this;
350         switch (this.node.nodeName) {
351             case 'tree' : this._clear_tree(params); break;
352             case 'listbox' : this._clear_listbox(params); break;
353             default: throw('NYI: Need .clear() for ' + this.node.nodeName); break;
354         }
355         this.error.sdump('D_LIST','Clearing list ' + this.node.getAttribute('id') + '\n');
356         this.row_count.total = 0;
357         this.row_count.fleshed = 0;
358         setTimeout( function() { obj.exec_on_all_fleshed(); }, 0 );
359     },
360
361     '_clear_tree' : function(params) {
362         var obj = this;
363         if (obj.error.sdump_levels.D_LIST_DUMP_ON_CLEAR) {
364             obj.error.sdump('D_LIST_DUMP_ON_CLEAR',obj.dump());
365         }
366         if (obj.error.sdump_levels.D_LIST_DUMP_WITH_KEYS_ON_CLEAR) {
367             obj.error.sdump('D_LIST_DUMP_WITH_KEYS_ON_CLEAR',obj.dump_with_keys());
368         }
369         while (obj.treechildren.lastChild) obj.treechildren.removeChild( obj.treechildren.lastChild );
370     },
371
372     '_clear_listbox' : function(params) {
373         var obj = this;
374         var items = [];
375         var nl = this.node.getElementsByTagName('listitem');
376         for (var i = 0; i < nl.length; i++) {
377             items.push( nl[i] );
378         }
379         for (var i = 0; i < items.length; i++) {
380             this.node.removeChild(items[i]);
381         }
382     },
383
384     'append' : function (params) {
385         var rnode;
386         var obj = this;
387         switch (this.node.nodeName) {
388             case 'tree' : rparams = this._append_to_tree(params); break;
389             case 'listbox' : rparams = this._append_to_listbox(params); break;
390             default: throw('NYI: Need .append() for ' + this.node.nodeName); break;
391         }
392         if (rparams && params.attributes) {
393             for (var i in params.attributes) {
394                 rparams.my_node.setAttribute(i,params.attributes[i]);
395             }
396         }
397         this.row_count.total++;
398         if (this.row_count.fleshed == this.row_count.total) {
399             setTimeout( function() { obj.exec_on_all_fleshed(); }, 0 );
400         }
401         rparams.my_node.setAttribute('unique_row_counter',obj.unique_row_counter);
402         rparams.unique_row_counter = obj.unique_row_counter++;
403         if (typeof params.on_append == 'function') {
404             params.on_append(rparams);
405         }
406         return rparams;
407     },
408     
409     'refresh_row' : function (params) {
410         var rnode;
411         var obj = this;
412         switch (this.node.nodeName) {
413             case 'tree' : rparams = this._refresh_row_in_tree(params); break;
414             default: throw('NYI: Need .refresh_row() for ' + this.node.nodeName); break;
415         }
416         if (rparams && params.attributes) {
417             for (var i in params.attributes) {
418                 rparams.my_node.setAttribute(i,params.attributes[i]);
419             }
420         }
421         this.row_count.fleshed--;
422         return rparams;
423     },
424
425
426     '_append_to_tree' : function (params) {
427
428         var obj = this;
429
430         if (typeof params.row == 'undefined') throw('util.list.append: Object must contain a row');
431
432         var s = ('util.list.append: params = ' + (params) + '\n');
433
434         var treechildren_node = this.treechildren;
435
436         if (params.node && params.node.nodeName == 'treeitem') {
437             params.node.setAttribute('container','true'); /* params.node.setAttribute('open','true'); */
438             if (params.node.lastChild.nodeName == 'treechildren') {
439                 treechildren_node = params.node.lastChild;
440             } else {
441                 treechildren_node = document.createElement('treechildren');
442                 params.node.appendChild(treechildren_node);
443             }
444         }
445
446         var treeitem = document.createElement('treeitem');
447         treeitem.setAttribute('retrieve_id',params.retrieve_id);
448         if (typeof params.to_bottom != 'undefined') {
449             treechildren_node.appendChild( treeitem );
450             if (typeof params.no_auto_select == 'undefined') {
451                 if (!obj.auto_select_pending) {
452                     obj.auto_select_pending = true;
453                     setTimeout(function() {
454                         dump('auto-selecting\n');
455                         var idx = Number(obj.node.view.rowCount)-1;
456                         try { obj.node.view.selection.select(idx); } catch(E) { obj.error.sdump('D_WARN','tree auto select: ' + E + '\n'); }
457                         try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_WARN','tree auto select, on_select: ' + E + '\n'); }
458                         obj.auto_select_pending = false;
459                         try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(idx).firstChild); } catch(E) { obj.error.sdump('D_WARN','tree auto select, flesh: ' + E + '\n'); }
460                     }, 1000);
461                 }
462             }
463         } else {
464             if (treechildren_node.firstChild) {
465                 treechildren_node.insertBefore( treeitem, treechildren_node.firstChild );
466             } else {
467                 treechildren_node.appendChild( treeitem );
468             }
469             if (typeof params.no_auto_select == 'undefined') {
470                 if (!obj.auto_select_pending) {
471                     obj.auto_select_pending = true;
472                     setTimeout(function() {
473                         try { obj.node.view.selection.select(0); } catch(E) { obj.error.sdump('D_WARN','tree auto select: ' + E + '\n'); }
474                         try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_WARN','tree auto select, on_select: ' + E + '\n'); }
475                         obj.auto_select_pending = false;
476                         try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(0).firstChild); } catch(E) { obj.error.sdump('D_WARN','tree auto select, flesh: ' + E + '\n'); }
477                     }, 1000);
478                 }
479             }
480         }
481         var treerow = document.createElement('treerow');
482         treeitem.appendChild( treerow );
483         treerow.setAttribute('retrieve_id',params.retrieve_id);
484         if (params.row_properties) treerow.setAttribute('properties',params.row_properties);
485
486         s += ('tree = ' + this.node + '  treechildren = ' + treechildren_node + '\n');
487         s += ('treeitem = ' + treeitem + '  treerow = ' + treerow + '\n');
488
489         obj.put_retrieving_label(treerow);
490
491         if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
492             treerow.addEventListener(
493                 'flesh',
494                 function() {
495
496                     if (treerow.getAttribute('retrieved') == 'true') return; /* already running */
497
498                     treerow.setAttribute('retrieved','true');
499
500                     //dump('fleshing = ' + params.retrieve_id + '\n');
501
502                     function inc_fleshed() {
503                         if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
504                         treerow.setAttribute('fleshed','true');
505                         obj.row_count.fleshed++;
506                         if (obj.row_count.fleshed >= obj.row_count.total) {
507                             setTimeout( function() { obj.exec_on_all_fleshed(); }, 0 );
508                         }
509                     }
510
511                     params.row_node = treeitem;
512                     params.on_retrieve = function(p) {
513                         try {
514                             p.row = params.row;
515                             obj._map_row_to_treecell(p,treerow);
516                             inc_fleshed();
517                             var idx = obj.node.contentView.getIndexOfItem( params.row_node );
518                             dump('idx = ' + idx + '\n');
519                             // if current row is selected, send another select event to re-sync data that the client code fetches on selects
520                             if ( obj.node.view.selection.isSelected( idx ) ) {
521                                 dump('dispatching select event for on_retrieve for idx = ' + idx + '\n');
522                                 util.widgets.dispatch('select',obj.node);
523                             }
524                         } catch(E) {
525                             // Let's not alert on this for now.  Getting contentView has no properties in record buckets under certain conditions
526                             dump('fixme2: ' + E + '\n');
527                         }
528                     }
529
530                     if (typeof params.retrieve_row == 'function') {
531
532                         params.retrieve_row( params );
533
534                     } else if (typeof obj.retrieve_row == 'function') {
535
536                             obj.retrieve_row( params );
537
538                     } else {
539                     
540                             inc_fleshed();
541                     }
542                 },
543                 false
544             );
545             if (typeof params.flesh_immediately != 'undefined') {
546                 if (params.flesh_immediately) {
547                     setTimeout(
548                         function() {
549                             util.widgets.dispatch('flesh',treerow);
550                         }, 0
551                     );
552                 }
553             }
554         } else {
555             treerow.addEventListener(
556                 'flesh',
557                 function() {
558                     //dump('fleshing anon\n');
559                     if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
560                     obj._map_row_to_treecell(params,treerow);
561                     treerow.setAttribute('retrieved','true');
562                     treerow.setAttribute('fleshed','true');
563                     obj.row_count.fleshed++;
564                     if (obj.row_count.fleshed >= obj.row_count.total) {
565                         setTimeout( function() { obj.exec_on_all_fleshed(); }, 0 );
566                     }
567                 },
568                 false
569             );
570             if (typeof params.flesh_immediately != 'undefined') {
571                 if (params.flesh_immediately) {
572                     setTimeout(
573                         function() {
574                             util.widgets.dispatch('flesh',treerow);
575                         }, 0
576                     );
577                 }
578             }
579         }
580         this.error.sdump('D_LIST',s);
581
582             try {
583
584                 if (obj.trim_list && obj.row_count.total >= obj.trim_list) {
585                     // Remove oldest row
586                     //if (typeof params.to_bottom != 'undefined') 
587                     if (typeof params.to_top == 'undefined') {
588                         if (typeof params.on_delete == 'function') { params.on_delete( treechildren_node.firstChild.getAttribute('unique_row_counter') ); }
589                         treechildren_node.removeChild( treechildren_node.firstChild );
590                     } else {
591                         if (typeof params.on_delete == 'function') { params.on_delete( treechildren_node.lastChild.getAttribute('unique_row_counter') ); }
592                         treechildren_node.removeChild( treechildren_node.lastChild );
593                     }
594                 }
595             } catch(E) {
596             }
597
598         setTimeout( function() { obj.auto_retrieve(); }, 0 );
599
600         params.my_node = treeitem;
601         return params;
602     },
603
604     '_refresh_row_in_tree' : function (params) {
605
606         var obj = this;
607
608         if (typeof params.row == 'undefined') throw('util.list.refresh_row: Object must contain a row');
609         if (typeof params.my_node == 'undefined') throw('util.list.refresh_row: Object must contain a my_node');
610         if (params.my_node.nodeName != 'treeitem') throw('util.list.refresh_rwo: my_node must be a treeitem');
611
612         var s = ('util.list.refresh_row: params = ' + (params) + '\n');
613
614         var treeitem = params.my_node;
615         treeitem.setAttribute('retrieve_id',params.retrieve_id);
616         if (typeof params.to_bottom != 'undefined') {
617             if (typeof params.no_auto_select == 'undefined') {
618                 if (!obj.auto_select_pending) {
619                     obj.auto_select_pending = true;
620                     setTimeout(function() {
621                         dump('auto-selecting\n');
622                         var idx = Number(obj.node.view.rowCount)-1;
623                         try { obj.node.view.selection.select(idx); } catch(E) { obj.error.sdump('D_WARN','tree auto select: ' + E + '\n'); }
624                         try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_WARN','tree auto select, on_select: ' + E + '\n'); }
625                         obj.auto_select_pending = false;
626                         try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(idx).firstChild); } catch(E) { obj.error.sdump('D_WARN','tree auto select, flesh: ' + E + '\n'); }
627                     }, 1000);
628                 }
629             }
630         }
631         //var delete_me = [];
632         //for (var i in treeitem.childNodes) if (treeitem.childNodes[i].nodeName == 'treerow') delete_me.push(treeitem.childNodes[i]);
633         //for (var i = 0; i < delete_me.length; i++) treeitem.removeChild(delete_me[i]);
634         var prev_treerow = treeitem.firstChild; /* FIXME: worry about hierarchal lists like copy_browser? */
635         var treerow = document.createElement('treerow');
636         while (prev_treerow.firstChild) {
637             treerow.appendChild( prev_treerow.removeChild( prev_treerow.firstChild ) );
638         }
639         treeitem.replaceChild( treerow, prev_treerow );
640         treerow.setAttribute('retrieve_id',params.retrieve_id);
641         if (params.row_properties) treerow.setAttribute('properties',params.row_properties);
642
643         s += ('tree = ' + this.node.nodeName + '\n');
644         s += ('treeitem = ' + treeitem.nodeName + '  treerow = ' + treerow.nodeName + '\n');
645
646         obj.put_retrieving_label(treerow);
647
648         if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
649
650             s += 'found a retrieve_row function\n';
651
652             treerow.addEventListener(
653                 'flesh',
654                 function() {
655
656                     if (treerow.getAttribute('retrieved') == 'true') return; /* already running */
657
658                     treerow.setAttribute('retrieved','true');
659
660                     //dump('fleshing = ' + params.retrieve_id + '\n');
661
662                     function inc_fleshed() {
663                         if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
664                         treerow.setAttribute('fleshed','true');
665                         obj.row_count.fleshed++;
666                         if (obj.row_count.fleshed >= obj.row_count.total) {
667                             setTimeout( function() { obj.exec_on_all_fleshed(); }, 0 );
668                         }
669                     }
670
671                     params.row_node = treeitem;
672                     params.on_retrieve = function(p) {
673                         try {
674                             p.row = params.row;
675                             obj._map_row_to_treecell(p,treerow);
676                             inc_fleshed();
677                             var idx = obj.node.contentView.getIndexOfItem( params.row_node );
678                             dump('idx = ' + idx + '\n');
679                             // if current row is selected, send another select event to re-sync data that the client code fetches on selects
680                             if ( obj.node.view.selection.isSelected( idx ) ) {
681                                 dump('dispatching select event for on_retrieve for idx = ' + idx + '\n');
682                                 util.widgets.dispatch('select',obj.node);
683                             }
684                         } catch(E) {
685                             // Let's not alert on this for now.  Getting contentView has no properties in record buckets under certain conditions
686                             dump('fixme2: ' + E + '\n');
687                         }
688                     }
689
690                     if (typeof params.retrieve_row == 'function') {
691
692                         params.retrieve_row( params );
693
694                     } else if (typeof obj.retrieve_row == 'function') {
695
696                             obj.retrieve_row( params );
697
698                     } else {
699                     
700                             inc_fleshed();
701                     }
702                 },
703                 false
704             );
705             if (typeof params.flesh_immediately != 'undefined') {
706                 if (params.flesh_immediately) {
707                     setTimeout(
708                         function() {
709                             util.widgets.dispatch('flesh',treerow);
710                         }, 0
711                     );
712                 }
713             }
714
715         } else {
716
717             s += 'did not find a retrieve_row function\n';
718
719             treerow.addEventListener(
720                 'flesh',
721                 function() {
722                     //dump('fleshing anon\n');
723                     if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
724                     obj._map_row_to_treecell(params,treerow);
725                     treerow.setAttribute('retrieved','true');
726                     treerow.setAttribute('fleshed','true');
727                     obj.row_count.fleshed++;
728                     if (obj.row_count.fleshed >= obj.row_count.total) {
729                         setTimeout( function() { obj.exec_on_all_fleshed(); }, 0 );
730                     }
731                 },
732                 false
733             );
734             if (typeof params.flesh_immediately != 'undefined') {
735                 if (params.flesh_immediately) {
736                     setTimeout(
737                         function() {
738                             util.widgets.dispatch('flesh',treerow);
739                         }, 0
740                     );
741                 }
742             }
743
744         }
745
746             try {
747
748                 if (obj.trim_list && obj.row_count.total >= obj.trim_list) {
749                     // Remove oldest row
750                     //if (typeof params.to_bottom != 'undefined') 
751                     if (typeof params.to_top == 'undefined') {
752                         treechildren_node.removeChild( treechildren_node.firstChild );
753                     } else {
754                         treechildren_node.removeChild( treechildren_node.lastChild );
755                     }
756                 }
757             } catch(E) {
758             }
759
760         setTimeout( function() { obj.auto_retrieve(); }, 0 );
761
762         JSAN.use('util.widgets'); util.widgets.dispatch('select',obj.node);
763
764         this.error.sdump('D_LIST',s);
765
766         return params;
767     },
768
769     'put_retrieving_label' : function(treerow) {
770         var obj = this;
771         try {
772             for (var i = 0; i < obj.columns.length; i++) {
773                 var treecell;
774                 if (typeof treerow.childNodes[i] == 'undefined') {
775                     treecell = document.createElement('treecell');
776                     treerow.appendChild(treecell);
777                 } else {
778                     treecell = treerow.childNodes[i];
779                 }
780                 treecell.setAttribute('label',document.getElementById('offlineStrings').getString('list.row_retrieving'));
781             }
782         } catch(E) {
783             alert('Error in list.js, put_retrieving_label(): ' + E);
784         }
785     },
786
787     'detect_visible' : function() {
788         var obj = this;
789         try {
790             //dump('detect_visible  obj.node = ' + obj.node + '\n');
791             /* FIXME - this is a hack.. if the implementation of tree changes, this could break */
792             try {
793                 /*var s = ''; var A = document.getAnonymousNodes(obj.node);
794                 for (var i in A) {
795                     var B = A[i];
796                     s += '\t' + (typeof B.nodeName != 'undefined' ? B.nodeName : B ) + '\n'; 
797                     if (typeof B.childNodes != 'undefined') for (var j = 0; j < B.childNodes.length; j++) {
798                         var C = B.childNodes[j];
799                         s += '\t\t' + C.nodeName + '\n';
800                     }
801                 }
802                 obj.error.sdump('D_XULRUNNER','document.getAnonymousNodes(' + obj.node.nodeName + ') = \n' + s + '\n');*/
803                 var scrollbar = document.getAnonymousNodes(obj.node)[2].firstChild;
804                 var curpos = scrollbar.getAttribute('curpos');
805                 var maxpos = scrollbar.getAttribute('maxpos');
806                 //alert('curpos = ' + curpos + ' maxpos = ' + maxpos + ' obj.curpos = ' + obj.curpos + ' obj.maxpos = ' + obj.maxpos + '\n');
807                 if ((curpos != obj.curpos) || (maxpos != obj.maxpos)) {
808                     if ( obj.auto_retrieve() > 0 ) {
809                         obj.curpos = curpos; obj.maxpos = maxpos;
810                     }
811                 }
812             } catch(E) {
813                 obj.error.sdump('D_XULRUNNER', 'List implementation changed? ' + E);
814             }
815         } catch(E) { obj.error.sdump('D_ERROR',E); }
816     },
817
818     'detect_visible_polling' : function() {
819         try {
820             //alert('detect_visible_polling');
821             var obj = this;
822             obj.detect_visible();
823             setTimeout(function() { try { obj.detect_visible_polling(); } catch(E) { alert(E); } },2000);
824         } catch(E) {
825             alert(E);
826         }
827     },
828
829
830     'auto_retrieve' : function(params) {
831         var obj = this;
832         switch (this.node.nodeName) {
833             case 'tree' : obj._auto_retrieve_tree(params); break;
834             default: throw('NYI: Need .auto_retrieve() for ' + obj.node.nodeName); break;
835         }
836     },
837
838     '_auto_retrieve_tree' : function (params) {
839         var obj = this;
840         if (!obj.auto_retrieve_in_progress) {
841             obj.auto_retrieve_in_progress = true;
842             setTimeout(
843                 function() {
844                     try {
845                             //alert('auto_retrieve\n');
846                             var count = 0;
847                             var startpos = obj.node.treeBoxObject.getFirstVisibleRow();
848                             var endpos = obj.node.treeBoxObject.getLastVisibleRow();
849                             if (startpos > endpos) endpos = obj.node.treeBoxObject.getPageLength();
850                             //dump('startpos = ' + startpos + ' endpos = ' + endpos + '\n');
851                             for (var i = startpos; i < endpos + 4; i++) {
852                                 try {
853                                     //dump('trying index ' + i + '\n');
854                                     var item = obj.node.contentView.getItemAtIndex(i).firstChild;
855                                     if (item && item.getAttribute('retrieved') != 'true' ) {
856                                         //dump('\tgot an unfleshed item = ' + item + ' = ' + item.nodeName + '\n');
857                                         util.widgets.dispatch('flesh',item); count++;
858                                     }
859                                 } catch(E) {
860                                     //dump(i + ' : ' + E + '\n');
861                                 }
862                             }
863                             obj.auto_retrieve_in_progress = false;
864                             return count;
865                     } catch(E) { alert(E); }
866                 }, 1
867             );
868         }
869     },
870
871     'exec_on_all_fleshed' : function() {
872         var obj = this;
873         try {
874             if (obj.on_all_fleshed) {
875                 if (typeof obj.on_all_fleshed == 'function') {
876                     dump('exec_on_all_fleshed == function\n');
877                     setTimeout( 
878                         function() { 
879                             try { obj.on_all_fleshed(); } catch(E) { obj.error.standard_unexpected_error_alert('_full_retrieve_tree callback',obj.on_all_fleshed); }
880                         }, 0 
881                     );
882                 } else if (typeof obj.on_all_fleshed.length != 'undefined') {
883                     dump('exec_on_all_fleshed == array\n');
884                     setTimeout(
885                         function() {
886                             try {
887                                 dump('exec_on_all_fleshed, processing on_all_fleshed array, length = ' + obj.on_all_fleshed.length + '\n');
888                                 var f = obj.on_all_fleshed.pop();
889                                 if (typeof f == 'function') { 
890                                     try { f(); } catch(E) { obj.error.standard_unexpected_error_alert('_full_retrieve_tree callback',f); } 
891                                 }
892                                 if (obj.on_all_fleshed.length > 0) arguments.callee(); 
893                             } catch(E) {
894                                 obj.error.standard_unexpected_error_alert('exec_on_all_fleshed callback error',E);
895                             }
896                         }, 0
897                     ); 
898                 } else {
899                     obj.error.standard_unexpected_error_alert('unexpected on_all_fleshed object: ', obj.on_all_fleshed);
900                 }
901             }
902         } catch(E) {
903             obj.error.standard_unexpected_error_alert('exec_on_all-fleshed error',E);
904         }
905     },
906
907     'full_retrieve' : function(params) {
908         var obj = this;
909         switch (this.node.nodeName) {
910             case 'tree' : obj._full_retrieve_tree(params); break;
911             default: throw('NYI: Need .full_retrieve() for ' + obj.node.nodeName); break;
912         }
913     },
914
915     '_full_retrieve_tree' : function(params) {
916         var obj = this;
917         try {
918             if (obj.row_count.fleshed >= obj.row_count.total) {
919                 dump('Full retrieve... tree seems to be in sync\n' + js2JSON(obj.row_count) + '\n');
920                 obj.exec_on_all_fleshed();
921             } else {
922                 dump('Full retrieve... syncing tree' + js2JSON(obj.row_count) + '\n');
923                 JSAN.use('util.widgets');
924                 var nodes = obj.treechildren.childNodes;
925                 for (var i = 0; i < nodes.length; i++) {
926                     util.widgets.dispatch('flesh',nodes[i].firstChild);
927                 }
928             }
929         } catch(E) {
930             obj.error.standard_unexpected_error_alert('_full_retrieve_tree',E);
931         }
932     },
933
934     '_append_to_listbox' : function (params) {
935
936         var obj = this;
937
938         if (typeof params.row == 'undefined') throw('util.list.append: Object must contain a row');
939
940         var s = ('util.list.append: params = ' + (params) + '\n');
941
942         var listitem = document.createElement('listitem');
943
944         s += ('listbox = ' + this.node + '  listitem = ' + listitem + '\n');
945
946         if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
947
948             setTimeout(
949                 function() {
950                     listitem.setAttribute('retrieve_id',params.retrieve_id);
951                     //FIXME//Make async and fire when row is visible in list
952                     var row;
953
954                     params.row_node = listitem;
955                     params.on_retrieve = function(row) {
956                         params.row = row;
957                         obj._map_row_to_listcell(params,listitem);
958                         obj.node.appendChild( listitem );
959                         util.widgets.dispatch('select',obj.node);
960                     }
961
962                     if (typeof params.retrieve_row == 'function') {
963
964                         row = params.retrieve_row( params );
965
966                     } else {
967
968                         if (typeof obj.retrieve_row == 'function') {
969
970                             row = obj.retrieve_row( params );
971
972                         }
973                     }
974                 }, 0
975             );
976         } else {
977             this._map_row_to_listcell(params,listitem);
978             this.node.appendChild( listitem );
979         }
980
981         this.error.sdump('D_LIST',s);
982         params.my_node = listitem;
983         return params;
984
985     },
986
987     '_map_row_to_treecell' : function(params,treerow) {
988         var obj = this;
989         var s = '';
990
991         if (typeof params.map_row_to_column == 'function' || typeof this.map_row_to_column == 'function') {
992
993             for (var i = 0; i < this.columns.length; i++) {
994                 var treecell;
995                 if (typeof treerow.childNodes[i] == 'undefined') {
996                     treecell = document.createElement('treecell');
997                     treerow.appendChild( treecell );
998                 } else {
999                     treecell = treerow.childNodes[i];
1000                 }
1001                 
1002                 if ( this.columns[i].editable == false ) { treecell.setAttribute('editable','false'); }
1003                 var label = '';
1004
1005                 // What skip columns is doing is rendering the treecells as blank/empty
1006                 if (params.skip_columns && (params.skip_columns.indexOf(i) != -1)) {
1007                     treecell.setAttribute('label',label);
1008                     s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
1009                     continue;
1010                 }
1011                 if (params.skip_all_columns_except && (params.skip_all_columns_except.indexOf(i) == -1)) {
1012                     treecell.setAttribute('label',label);
1013                     s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
1014                     continue;
1015                 }
1016     
1017                 if (typeof params.map_row_to_column == 'function')  {
1018     
1019                     label = params.map_row_to_column(params.row,this.columns[i]);
1020     
1021                 } else if (typeof this.map_row_to_column == 'function') {
1022     
1023                     label = this.map_row_to_column(params.row,this.columns[i]);
1024     
1025                 }
1026                 if (this.columns[i].type == 'checkbox') { treecell.setAttribute('value',label); } else { treecell.setAttribute('label',label ? label : ''); }
1027                 s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
1028             }
1029         } else if (typeof params.map_row_to_columns == 'function' || typeof this.map_row_to_columns == 'function') {
1030
1031             var labels = [];
1032
1033             if (typeof params.map_row_to_columns == 'function') {
1034
1035                 labels = params.map_row_to_columns(params.row,this.columns);
1036
1037             } else if (typeof this.map_row_to_columns == 'function') {
1038
1039                 labels = this.map_row_to_columns(params.row,this.columns);
1040
1041             }
1042             for (var i = 0; i < labels.length; i++) {
1043                 var treecell;
1044                 if (typeof treerow.childNodes[i] == 'undefined') {
1045                     treecell = document.createElement('treecell');
1046                     treerow.appendChild(treecell);
1047                 } else {
1048                     treecell = treerow.childNodes[i];
1049                 }
1050                 if ( this.columns[i].editable == false ) { treecell.setAttribute('editable','false'); }
1051                 if ( this.columns[i].type == 'checkbox') {
1052                     treecell.setAttribute('value', labels[i]);
1053                 } else {
1054                     treecell.setAttribute('label',typeof labels[i] == 'string' || typeof labels[i] == 'number' ? labels[i] : '');
1055                 }
1056                 s += ('treecell = ' + treecell + ' with label = ' + labels[i] + '\n');
1057             }
1058
1059         } else {
1060
1061             throw('No row to column mapping function.');
1062         }
1063         this.error.sdump('D_LIST',s);
1064     },
1065
1066     '_map_row_to_listcell' : function(params,listitem) {
1067         var obj = this;
1068         var s = '';
1069         for (var i = 0; i < this.columns.length; i++) {
1070             var value = '';
1071             if (typeof params.map_row_to_column == 'function')  {
1072
1073                 value = params.map_row_to_column(params.row,this.columns[i]);
1074
1075             } else {
1076
1077                 if (typeof this.map_row_to_column == 'function') {
1078
1079                     value = this.map_row_to_column(params.row,this.columns[i]);
1080                 }
1081             }
1082             if (typeof value == 'string' || typeof value == 'number') {
1083                 var listcell = document.createElement('listcell');
1084                 listcell.setAttribute('label',value);
1085                 listitem.appendChild(listcell);
1086                 s += ('listcell = ' + listcell + ' with label = ' + value + '\n');
1087             } else {
1088                 listitem.appendChild(value);
1089                 s += ('listcell = ' + value + ' is really a ' + value.nodeName + '\n');
1090             }
1091         }
1092         this.error.sdump('D_LIST',s);
1093     },
1094
1095     'select_all' : function(params) {
1096         var obj = this;
1097         switch(this.node.nodeName) {
1098             case 'tree' : return this._select_all_from_tree(params); break;
1099             default: throw('NYI: Need ._select_all_from_() for ' + this.node.nodeName); break;
1100         }
1101     },
1102
1103     '_select_all_from_tree' : function(params) {
1104         var obj = this;
1105         this.node.view.selection.selectAll();
1106     },
1107
1108     'retrieve_selection' : function(params) {
1109         var obj = this;
1110         switch(this.node.nodeName) {
1111             case 'tree' : return this._retrieve_selection_from_tree(params); break;
1112             default: throw('NYI: Need ._retrieve_selection_from_() for ' + this.node.nodeName); break;
1113         }
1114     },
1115
1116     '_retrieve_selection_from_tree' : function(params) {
1117         var obj = this;
1118         var list = [];
1119         var start = new Object();
1120         var end = new Object();
1121         var numRanges = this.node.view.selection.getRangeCount();
1122         for (var t=0; t<numRanges; t++){
1123             this.node.view.selection.getRangeAt(t,start,end);
1124             for (var v=start.value; v<=end.value; v++){
1125                 var i = this.node.contentView.getItemAtIndex(v);
1126                 list.push( i );
1127             }
1128         }
1129         return list;
1130     },
1131
1132     'dump' : function(params) {
1133         var obj = this;
1134         switch(this.node.nodeName) {
1135             case 'tree' : return this._dump_tree(params); break;
1136             default: throw('NYI: Need .dump() for ' + this.node.nodeName); break;
1137         }
1138     },
1139
1140     '_dump_tree' : function(params) {
1141         var obj = this;
1142         var dump = [];
1143         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1144             var row = [];
1145             var treeitem = this.treechildren.childNodes[i];
1146             var treerow = treeitem.firstChild;
1147             for (var j = 0; j < treerow.childNodes.length; j++) {
1148                 row.push( treerow.childNodes[j].getAttribute('label') );
1149             }
1150             dump.push( row );
1151         }
1152         return dump;
1153     },
1154
1155     'dump_with_keys' : function(params) {
1156         var obj = this;
1157         switch(this.node.nodeName) {
1158             case 'tree' : return this._dump_tree_with_keys(params); break;
1159             default: throw('NYI: Need .dump_with_keys() for ' + this.node.nodeName); break;
1160         }
1161
1162     },
1163
1164     '_dump_tree_with_keys' : function(params) {
1165         var obj = this;
1166         var dump = [];
1167         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1168             var row = {};
1169             var treeitem = this.treechildren.childNodes[i];
1170             var treerow = treeitem.firstChild;
1171             for (var j = 0; j < treerow.childNodes.length; j++) {
1172                 if (typeof obj.columns[j] == 'undefined') {
1173                     dump('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n');
1174                     dump('_dump_tree_with_keys @ ' + location.href + '\n');
1175                     dump('\ttreerow.childNodes.length='+treerow.childNodes.length+' j='+j+' obj.columns.length='+obj.columns.length+'\n');
1176                     debugger;
1177                 } else {
1178                     row[ obj.columns[j].id ] = treerow.childNodes[j].getAttribute('label');
1179                 }
1180             }
1181             dump.push( row );
1182         }
1183         return dump;
1184     },
1185
1186     'dump_csv' : function(params) {
1187         var obj = this;
1188         switch(this.node.nodeName) {
1189             case 'tree' : return this._dump_tree_csv(params); break;
1190             default: throw('NYI: Need .dump_csv() for ' + this.node.nodeName); break;
1191         }
1192
1193     },
1194
1195     '_dump_tree_csv' : function(params) {
1196         var obj = this;
1197         var _dump = '';
1198         var ord_cols = [];
1199         for (var j = 0; j < obj.columns.length; j++) {
1200             if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1201                 /* skip */
1202             } else {
1203                 ord_cols.push( [ obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('ordinal'), j ] );
1204             }
1205         }
1206         ord_cols.sort( function(a,b) { 
1207             if ( Number( a[0] ) < Number( b[0] ) ) return -1; 
1208             if ( Number( a[0] ) > Number( b[0] ) ) return 1; 
1209             return 0;
1210         } );
1211         for (var j = 0; j < ord_cols.length; j++) {
1212             if (_dump) _dump += ',';
1213             _dump += '"' + obj.columns[ ord_cols[j][1] ].label.replace(/"/g, '""') + '"';
1214         }
1215         _dump += '\r\n';
1216         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1217             var row = '';
1218             var treeitem = this.treechildren.childNodes[i];
1219             var treerow = treeitem.firstChild;
1220             for (var j = 0; j < ord_cols.length; j++) {
1221                 if (row) row += ',';
1222                 row += '"' + treerow.childNodes[ ord_cols[j][1] ].getAttribute('label').replace(/"/g, '""') + '"';
1223             }
1224             _dump +=  row + '\r\n';
1225         }
1226         return _dump;
1227     },
1228
1229     'dump_extended_format' : function(params) {
1230         var obj = this;
1231         switch(this.node.nodeName) {
1232             case 'tree' : return this._dump_tree_extended_format(params); break;
1233             default: throw('NYI: Need .dump_extended_format() for ' + this.node.nodeName); break;
1234         }
1235
1236     },
1237
1238     '_dump_tree_extended_format' : function(params) {
1239         var obj = this;
1240         var _dump = '';
1241         var ord_cols = [];
1242         for (var j = 0; j < obj.columns.length; j++) {
1243             if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1244                 /* skip */
1245             } else {
1246                 ord_cols.push( [ obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('ordinal'), j ] );
1247             }
1248         }
1249         ord_cols.sort( function(a,b) { 
1250             if ( Number( a[0] ) < Number( b[0] ) ) return -1; 
1251             if ( Number( a[0] ) > Number( b[0] ) ) return 1; 
1252             return 0;
1253         } );
1254         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1255             var row = document.getElementById('offlineStrings').getString('list.dump_extended_format.record_separator') + '\r\n';
1256             var treeitem = this.treechildren.childNodes[i];
1257             var treerow = treeitem.firstChild;
1258             for (var j = 0; j < ord_cols.length; j++) {
1259                 row += obj.columns[ ord_cols[j][1] ].label + ': ' + treerow.childNodes[ ord_cols[j][1] ].getAttribute('label') + '\r\n';
1260             }
1261             _dump +=  row + '\r\n';
1262         }
1263         return _dump;
1264     },
1265
1266     'dump_csv_to_clipboard' : function(params) {
1267         var obj = this;
1268         if (typeof params == 'undefined') params = {};
1269         if (params.no_full_retrieve) {
1270             copy_to_clipboard( obj.dump_csv( params ) );
1271         } else {
1272             obj.wrap_in_full_retrieve( function() { copy_to_clipboard( obj.dump_csv( params ) ); } );
1273         }
1274     },
1275
1276     'dump_csv_to_printer' : function(params) {
1277         var obj = this;
1278         JSAN.use('util.print'); var print = new util.print(params.printer_context || obj.printer_context);
1279         if (typeof params == 'undefined') params = {};
1280         if (params.no_full_retrieve) {
1281             print.simple( obj.dump_csv( params ), {'content_type':'text/plain'} );
1282         } else {
1283             obj.wrap_in_full_retrieve( 
1284                 function() { 
1285                     print.simple( obj.dump_csv( params ), {'content_type':'text/plain'} );
1286                 }
1287             );
1288         }
1289     },
1290
1291     'dump_extended_format_to_printer' : function(params) {
1292         var obj = this;
1293         JSAN.use('util.print'); var print = new util.print(params.printer_context || obj.printer_context);
1294         if (typeof params == 'undefined') params = {};
1295         if (params.no_full_retrieve) {
1296             print.simple( obj.dump_extended_format( params ), {'content_type':'text/plain'} );
1297         } else {
1298             obj.wrap_in_full_retrieve( 
1299                 function() { 
1300                     print.simple( obj.dump_extended_format( params ), {'content_type':'text/plain'} );
1301                 }
1302             );
1303         }
1304     },
1305
1306     'dump_csv_to_file' : function(params) {
1307         var obj = this;
1308         JSAN.use('util.file'); var f = new util.file();
1309         if (typeof params == 'undefined') params = {};
1310         if (params.no_full_retrieve) {
1311             params.data = obj.dump_csv( params );
1312             params.not_json = true;
1313             if (!params.title) params.title = document.getElementById('offlineStrings').getString('list.save_csv_as');
1314             f.export_file( params );
1315         } else {
1316             obj.wrap_in_full_retrieve( 
1317                 function() { 
1318                     params.data = obj.dump_csv( params );
1319                     params.not_json = true;
1320                     if (!params.title) params.title = document.getElementById('offlineStrings').getString('list.save_csv_as');
1321                     f.export_file( params );
1322                 }
1323             );
1324         }
1325     },
1326
1327     'print' : function(params) {
1328         if (!params) params = {};
1329         switch(this.node.nodeName) {
1330             case 'tree' : return this._print_tree(params); break;
1331             default: throw('NYI: Need ._print() for ' + this.node.nodeName); break;
1332         }
1333     },
1334
1335     '_print_tree' : function(params) {
1336         var obj = this;
1337         try {
1338             var data = obj.data; data.stash_retrieve();
1339             if (!params.staff && data.list.au && data.list.au[0]) {
1340                 params.staff = data.list.au[0];
1341             }
1342             if (!params.lib && data.list.au && data.list.au[0] && data.list.au[0].ws_ou() && data.hash.aou && data.hash.aou[ data.list.au[0].ws_ou() ]) {
1343                 params.lib = data.hash.aou[ data.list.au[0].ws_ou() ];
1344                 params.lib.children(null);
1345             }
1346             if (params.template && data.print_list_templates[ params.template ]) {
1347                 var template = data.print_list_templates[ params.template ];
1348                 for (var i in template) params[i] = template[i];
1349             }
1350             obj.wrap_in_full_retrieve(
1351                 function() {
1352                     try {
1353                         if (!params.list) params.list = obj.dump_with_keys();
1354                         JSAN.use('util.print'); var print = new util.print(params.printer_context || obj.printer_context);
1355                         print.tree_list( params );
1356                         if (typeof params.callback == 'function') params.callback();
1357                     } catch(E) {
1358                         obj.error.standard_unexpected_error_alert('inner _print_tree',E);
1359                     }
1360                 }
1361             );
1362             
1363         } catch(E) {
1364             obj.error.standard_unexpected_error_alert('_print_tree',E);
1365         }
1366     },
1367
1368     'dump_selected_with_keys' : function(params) {
1369         var obj = this;
1370         switch(this.node.nodeName) {
1371             case 'tree' : return this._dump_tree_selection_with_keys(params); break;
1372             default: throw('NYI: Need .dump_selection_with_keys() for ' + this.node.nodeName); break;
1373         }
1374
1375     },
1376
1377     '_dump_tree_selection_with_keys' : function(params) {
1378         var obj = this;
1379         var dump = [];
1380         var list = obj._retrieve_selection_from_tree();
1381         for (var i = 0; i < list.length; i++) {
1382             var row = {};
1383             var treeitem = list[i];
1384             var treerow = treeitem.firstChild;
1385             for (var j = 0; j < treerow.childNodes.length; j++) {
1386                 var value = treerow.childNodes[j].getAttribute('label');
1387                 if (params.skip_hidden_columns) if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') continue;
1388                 if (typeof obj.columns[j] == 'undefined') {
1389                     dump('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n');
1390                     dump('_dump_tree_selection_with_keys @ ' + location.href + '\n');
1391                     dump('\ttreerow.childNodes.length='+treerow.childNodes.length+' j='+j+' obj.columns.length='+obj.columns.length+'\n');
1392                     debugger;
1393                 } else {
1394                     var id = obj.columns[j].id; if (params.labels_instead_of_ids) id = obj.columns[j].label;
1395                     row[ id ] = value;
1396                 }
1397             }
1398             dump.push( row );
1399         }
1400         return dump;
1401     },
1402
1403     'clipboard' : function(params) {
1404         try {
1405             var obj = this;
1406             var dump = obj.dump_selected_with_keys({'skip_hidden_columns':true,'labels_instead_of_ids':true});
1407             obj.data.stash_retrieve();
1408             obj.data.list_clipboard = dump; obj.data.stash('list_clipboard');
1409             JSAN.use('util.window'); var win = new util.window();
1410             win.open(urls.XUL_LIST_CLIPBOARD,'list_clipboard','chrome,resizable,modal');
1411             window.focus(); // sometimes the main window will lower after a clipboard action
1412         } catch(E) {
1413             this.error.standard_unexpected_error_alert('clipboard',E);
1414         }
1415     },
1416
1417     'dump_retrieve_ids' : function(params) {
1418         var obj = this;
1419         switch(this.node.nodeName) {
1420             case 'tree' : return this._dump_retrieve_ids_tree(params); break;
1421             default: throw('NYI: Need .dump_retrieve_ids() for ' + this.node.nodeName); break;
1422         }
1423     },
1424
1425     '_dump_retrieve_ids_tree' : function(params) {
1426         var obj = this;
1427         var dump = [];
1428         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1429             var treeitem = this.treechildren.childNodes[i];
1430             dump.push( treeitem.getAttribute('retrieve_id') );
1431         }
1432         return dump;
1433     },
1434
1435     'wrap_in_full_retrieve' : function(f) {
1436         var obj = this;
1437         if (typeof obj.on_all_fleshed == 'function') { // legacy
1438             obj.on_all_fleshed = [ obj.on_all_fleshed ];
1439         }
1440         if (! obj.on_all_fleshed) obj.on_all_fleshed = [];
1441         obj.on_all_fleshed.push(f);
1442         obj.full_retrieve();
1443     },
1444
1445     '_sort_tree' : function(col,sortDir) {
1446         var obj = this;
1447         try {
1448             if (obj.node.getAttribute('no_sort')) {
1449                 return;
1450             }
1451             var col_pos;
1452             for (var i = 0; i < obj.columns.length; i++) { 
1453                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1454             }
1455             obj.wrap_in_full_retrieve(
1456                 function() {
1457                     try {
1458                         JSAN.use('util.money');
1459                         var rows = [];
1460                         var treeitems = obj.treechildren.childNodes;
1461                         for (var i = 0; i < treeitems.length; i++) {
1462                             var treeitem = treeitems[i];
1463                             var treerow = treeitem.firstChild;
1464                             var treecell = treerow.childNodes[ col_pos ];
1465                             value = ( { 'value' : treecell ? treecell.getAttribute('label') : '', 'node' : treeitem } );
1466                             rows.push( value );
1467                         }
1468                         rows = rows.sort( function(a,b) { 
1469                             a = a.value; b = b.value; 
1470                             if (col.getAttribute('sort_type')) {
1471                                 switch(col.getAttribute('sort_type')) {
1472                                     case 'date' :
1473                                         JSAN.use('util.date'); // to pull in dojo.date.locale
1474                                         a = dojo.date.locale.parse(a,{});
1475                                         b = dojo.date.locale.parse(b,{});
1476                                     break;
1477                                     case 'number' :
1478                                         a = Number(a); b = Number(b);
1479                                     break;
1480                                     case 'money' :
1481                                         a = util.money.dollars_float_to_cents_integer(a);
1482                                         b = util.money.dollars_float_to_cents_integer(b);
1483                                     break;
1484                                     case 'title' : /* special case for "a" and "the".  doesn't use marc 245 indicator */
1485                                         a = String( a ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1486                                         b = String( b ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1487                                     break;
1488                                     default:
1489                                         a = String( a ).toUpperCase();
1490                                         b = String( b ).toUpperCase();
1491                                     break;
1492                                 }
1493                             } else {
1494                                 if (typeof a == 'string' || typeof b == 'string') {
1495                                     a = String( a ).toUpperCase();
1496                                     b = String( b ).toUpperCase();
1497                                 }
1498                             }
1499                             //dump('sorting: type = ' + col.getAttribute('sort_type') + ' a = ' + a + ' b = ' + b + ' a<b= ' + (a<b) + ' a>b= ' + (a>b) + '\n');
1500                             if (a < b) return -1; 
1501                             if (a > b) return 1; 
1502                             return 0; 
1503                         } );
1504                         if (sortDir == 'asc') rows = rows.reverse();
1505                         while(obj.treechildren.lastChild) obj.treechildren.removeChild( obj.treechildren.lastChild );
1506                         for (var i = 0; i < rows.length; i++) {
1507                             obj.treechildren.appendChild( rows[i].node );
1508                         }
1509                         if (typeof obj.on_sort == 'function') obj.on_sort();
1510                     } catch(E) {
1511                         obj.error.standard_unexpected_error_alert('sorting',E); 
1512                     }
1513                 }
1514             );
1515         } catch(E) {
1516             obj.error.standard_unexpected_error_alert('pre sorting', E);
1517         }
1518     },
1519
1520     '_toggle_checkbox_column' : function(col,toggle) {
1521         var obj = this;
1522         try {
1523             if (obj.node.getAttribute('no_toggle')) {
1524                 return;
1525             }
1526             var col_pos;
1527             for (var i = 0; i < obj.columns.length; i++) { 
1528                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1529             }
1530             var treeitems = obj.treechildren.childNodes;
1531             for (var i = 0; i < treeitems.length; i++) {
1532                 var treeitem = treeitems[i];
1533                 var treerow = treeitem.firstChild;
1534                 var treecell = treerow.childNodes[ col_pos ];
1535                 treecell.setAttribute('value',(toggle == 'on'));
1536             }
1537             if (typeof obj.on_checkbox_toggle == 'function') obj.on_checkbox_toggle(toggle);
1538         } catch(E) {
1539             obj.error.standard_unexpected_error_alert('pre toggle', E);
1540         }
1541     },
1542
1543     'render_list_actions' : function(params) {
1544         var obj = this;
1545         switch(this.node.nodeName) {
1546             case 'tree' : return this._render_list_actions_for_tree(params); break;
1547             default: throw('NYI: Need ._render_list_actions() for ' + this.node.nodeName); break;
1548         }
1549     },
1550
1551     '_render_list_actions_for_tree' : function(params) {
1552         var obj = this;
1553         try {
1554             var btn = document.createElement('button');
1555             btn.setAttribute('id',obj.node.id + '_list_actions');
1556             btn.setAttribute('type','menu');
1557             btn.setAttribute('allowevents','true');
1558             //btn.setAttribute('oncommand','this.firstChild.showPopup();');
1559             btn.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.menu.label'));
1560             btn.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.menu.accesskey'));
1561             var mp = document.createElement('menupopup');
1562             btn.appendChild(mp);
1563             var mi = document.createElement('menuitem');
1564             mi.setAttribute('id',obj.node.id + '_clipfield');
1565             mi.setAttribute('disabled','true');
1566             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.field_to_clipboard.label'));
1567             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.field_to_clipboard.accesskey'));
1568             mp.appendChild(mi);
1569             mi = document.createElement('menuitem');
1570             mi.setAttribute('id',obj.node.id + '_csv_to_clipboard');
1571             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_clipboard.label'));
1572             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_clipboard.accesskey'));
1573             mp.appendChild(mi);
1574             mi = document.createElement('menuitem');
1575             mi.setAttribute('id',obj.node.id + '_csv_to_printer');
1576             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_printer.label'));
1577             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_printer.accesskey'));
1578             mp.appendChild(mi);
1579             mi = document.createElement('menuitem');
1580             mi.setAttribute('id',obj.node.id + '_extended_to_printer');
1581             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.extended_to_printer.label'));
1582             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.extended_to_printer.accesskey'));
1583             mp.appendChild(mi);
1584             mi = document.createElement('menuitem');
1585             mi.setAttribute('id',obj.node.id + '_csv_to_file');
1586             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_file.label'));
1587             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_file.accesskey'));
1588             mp.appendChild(mi);
1589             mi = document.createElement('menuitem');
1590             mi.setAttribute('id',obj.node.id + '_save_columns');
1591             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.save_column_configuration.label'));
1592             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.save_column_configuration.accesskey'));
1593             if (obj.data.hash.aous['gui.disable_local_save_columns']) {
1594                 mi.setAttribute('disabled','true');
1595             }
1596             mp.appendChild(mi);
1597             return btn;
1598         } catch(E) {
1599             obj.error.standard_unexpected_error_alert('rendering list actions',E);
1600         }
1601     },
1602
1603     'set_list_actions' : function(params) {
1604         var obj = this;
1605         switch(this.node.nodeName) {
1606             case 'tree' : return this._set_list_actions_for_tree(params); break;
1607             default: throw('NYI: Need ._set_list_actions() for ' + this.node.nodeName); break;
1608         }
1609     },
1610
1611     '_set_list_actions_for_tree' : function(params) {
1612         // This should be called after the button element from render_list_actions has been appended to the DOM
1613         var obj = this;
1614         try {
1615             var x = document.getElementById(obj.node.id + '_clipfield');
1616             if (x) {
1617                 x.addEventListener(
1618                     'command',
1619                     function() {
1620                         obj.clipboard(params);
1621                         if (params && typeof params.on_complete == 'function') {
1622                             params.on_complete(params);
1623                         }
1624                     },
1625                     false
1626                 );
1627             }
1628             x = document.getElementById(obj.node.id + '_csv_to_clipboard');
1629             if (x) {
1630                 x.addEventListener(
1631                     'command',
1632                     function() {
1633                         obj.dump_csv_to_clipboard(params);
1634                         if (params && typeof params.on_complete == 'function') {
1635                             params.on_complete(params);
1636                         }
1637                     },
1638                     false
1639                 );
1640             }
1641             x = document.getElementById(obj.node.id + '_csv_to_printer');
1642             if (x) {
1643                 x.addEventListener(
1644                     'command',
1645                     function() {
1646                         obj.dump_csv_to_printer(params);
1647                         if (params && typeof params.on_complete == 'function') {
1648                             params.on_complete(params);
1649                         }
1650                     },
1651                     false
1652                 );
1653             }
1654             x = document.getElementById(obj.node.id + '_extended_to_printer');
1655             if (x) {
1656                 x.addEventListener(
1657                     'command',
1658                     function() {
1659                         obj.dump_extended_format_to_printer(params);
1660                         if (params && typeof params.on_complete == 'function') {
1661                             params.on_complete(params);
1662                         }
1663                     },
1664                     false
1665                 );
1666             }
1667
1668             x = document.getElementById(obj.node.id + '_csv_to_file');
1669             if (x) {
1670                 x.addEventListener(
1671                     'command',
1672                     function() {
1673                         obj.dump_csv_to_file(params);
1674                         if (params && typeof params.on_complete == 'function') {
1675                             params.on_complete(params);
1676                         }
1677                     },
1678                     false
1679                 );
1680             }
1681             x = document.getElementById(obj.node.id + '_save_columns');
1682             if (x) {
1683                 x.addEventListener(
1684                     'command',
1685                     function() {
1686                         obj.save_columns(params);
1687                         if (params && typeof params.on_complete == 'function') {
1688                             params.on_complete(params);
1689                         }
1690                     },
1691                     false
1692                 );
1693             }
1694
1695         } catch(E) {
1696             obj.error.standard_unexpected_error_alert('setting list actions',E);
1697         }
1698     },
1699
1700     // Takes fieldmapper class name and attempts to spit out column definitions suitable for .init
1701     'fm_columns' : function(hint,column_extras,prefix) {
1702         var obj = this;
1703         var columns = [];
1704         if (!prefix) { prefix = ''; }
1705         try {
1706             // requires the dojo library fieldmapper.autoIDL
1707             if (typeof fieldmapper == 'undefined') { throw 'fieldmapper undefined'; }
1708             if (typeof fieldmapper.IDL == 'undefined') { throw 'fieldmapper.IDL undefined'; }
1709             if (typeof fieldmapper.IDL.fmclasses == 'undefined') { throw 'fieldmapper.IDL.fmclasses undefined'; }
1710             if (typeof fieldmapper.IDL.fmclasses[hint] == 'undefined') { throw 'fieldmapper.IDL.fmclasses.' + hint + ' undefined'; }
1711             var my_class = fieldmapper.IDL.fmclasses[hint]; 
1712             var data = obj.data; data.stash_retrieve();
1713
1714             function col_def(my_field) {
1715                 var col_id = prefix + hint + '_' + my_field.name;
1716                 var dataobj = hint;
1717                 var datafield = my_field.name;
1718                 if (column_extras) {
1719                     if (column_extras['*']) {
1720                         if (column_extras['*']['dataobj']) {
1721                             dataobj = column_extras['*']['dataobj'];
1722                         }
1723                     }
1724                     if (column_extras[col_id]) {
1725                         if (column_extras[col_id]['dataobj']) {
1726                             dataobj = column_extras[col_id]['dataobj'];
1727                         }
1728                         if (column_extras[col_id]['datafield']) {
1729                             datafield = column_extras[col_id]['datafield'];
1730                         }
1731                     }
1732                 }
1733                 var def = {
1734                     'id' : col_id,
1735                     'label' : my_field.label || my_field.name,
1736                     'sort_type' : [ 'int', 'float', 'id', 'number' ].indexOf(my_field.datatype) > -1 ? 'number' : 
1737                         ( my_field.datatype == 'money' ? 'money' : 
1738                         ( my_field.datatype == 'timestamp' ? 'date' : 'default')),
1739                     'hidden' : my_field.virtual || my_field.datatype == 'link',
1740                     'flex' : 1
1741                 };                    
1742                 // my_field.datatype => bool float id int interval link money number org_unit text timestamp
1743                 if (my_field.datatype == 'link') {
1744                     def.render = function(my) { 
1745                         return typeof my[dataobj][datafield]() == 'object' ? my[dataobj][datafield]()[my_field.key]() : my[dataobj][datafield](); 
1746                     }
1747                 } else {
1748                     def.render = function(my) { return my[dataobj][datafield](); }
1749                 }
1750                 if (my_field.datatype == 'timestamp') {
1751                     JSAN.use('util.date');
1752                     def.render = function(my) {
1753                         return util.date.formatted_date( my[dataobj][datafield](), '%{localized}' );
1754                     }
1755                 }
1756                 if (my_field.datatype == 'org_unit') {
1757                     def.render = function(my) {
1758                         return typeof my[dataobj][datafield]() == 'object' ? my[dataobj][datafield]().shortname() : data.hash.aou[ my[dataobj][datafield]() ].shortname();
1759                     }
1760                 }
1761                 if (my_field.datatype == 'money') {
1762                     JSAN.use('util.money');
1763                     def.render = function(my) {
1764                         return util.money.sanitize( my[dataobj][datafield]() );
1765                     }
1766                 }
1767                 if (column_extras) {
1768                     if (column_extras['*']) {
1769                         for (var attr in column_extras['*']) {
1770                             def[attr] = column_extras['*'][attr];
1771                         }
1772                         if (column_extras['*']['expanded_label']) {
1773                             def.label = my_class.label + ': ' + def.label;
1774                         }
1775                         if (column_extras['*']['label_prefix']) {
1776                             def.label = column_extras['*']['label_prefix'] + def.label;
1777                         }
1778                         if (column_extras['*']['remove_virtual']) {
1779                             if (my_field.virtual) {
1780                                 def.remove_me = true;
1781                             }
1782                         }
1783                     }
1784                     if (column_extras[col_id]) {
1785                         for (var attr in column_extras[col_id]) {
1786                             def[attr] = column_extras[col_id][attr];
1787                         }
1788                         if (column_extras[col_id]['keep_me']) {
1789                             def.remove_me = false;
1790                         }
1791                         if (column_extras[col_id]['label_prefix']) {
1792                             def.label = column_extras[col_id]['label_prefix'] + def.label;
1793                         }
1794                     }
1795                 }
1796                 if (def.remove_me) {
1797                     dump('Skipping ' + def.label + '\n');
1798                     return null;
1799                 } else {
1800                     dump('Defining ' + def.label + '\n');
1801                     return def;
1802                 }
1803             }
1804  
1805             for (var i = 0; i < my_class.fields.length; i++) {
1806                 var my_field = my_class.fields[i];
1807                 var def = col_def(my_field);
1808                 if (def) {
1809                     columns.push( def );
1810                 }
1811             }
1812
1813         } catch(E) {
1814             obj.error.standard_unexpected_error_alert('fm_columns()',E);
1815         }
1816         return columns;
1817     },
1818     // Default for the map_row_to_columns function for .init
1819     'std_map_row_to_columns' : function(error_value) {
1820         return function(row,cols) {
1821             // row contains { 'my' : { 'acp' : {}, 'circ' : {}, 'mvr' : {} } }
1822             // cols contains all of the objects listed above in columns
1823
1824             var obj = {};
1825             JSAN.use('util.error'); obj.error = new util.error();
1826             JSAN.use('OpenILS.data'); obj.data = new OpenILS.data(); obj.data.init({'via':'stash'});
1827             JSAN.use('util.network'); obj.network = new util.network();
1828             JSAN.use('util.money');
1829
1830             var my = row.my;
1831             var values = [];
1832             var cmd = '';
1833             try {
1834                 for (var i = 0; i < cols.length; i++) {
1835                     switch (typeof cols[i].render) {
1836                         case 'function': try { values[i] = cols[i].render(my); } catch(E) { values[i] = error_value; obj.error.sdump('D_COLUMN_RENDER_ERROR',E); } break;
1837                         case 'string' : cmd += 'try { ' + cols[i].render + '; values['+i+'] = v; } catch(E) { values['+i+'] = error_value; }'; break;
1838                         default: cmd += 'values['+i+'] = "??? '+(typeof cols[i].render)+'"; ';
1839                     }
1840                 }
1841                 if (cmd) eval( cmd );
1842             } catch(E) {
1843                 obj.error.sdump('D_WARN','map_row_to_column: ' + E);
1844                 if (error_value) { value = error_value; } else { value = '   ' };
1845             }
1846             return values;
1847         }
1848     }
1849 }
1850 dump('exiting util.list.js\n');