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