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