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