]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/util/list.js
more node preservation in list.js, and eliminate the Retrieving... label for rows...
[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 prev_treerow = treeitem.firstChild; /* FIXME: worry about hierarchal lists like copy_browser? */
588         var treerow = document.createElement('treerow');
589         while (prev_treerow.firstChild) {
590             treerow.appendChild( prev_treerow.removeChild( prev_treerow.firstChild ) );
591         }
592         treeitem.replaceChild( treerow, prev_treerow );
593         treerow.setAttribute('retrieve_id',params.retrieve_id);
594         if (params.row_properties) treerow.setAttribute('properties',params.row_properties);
595
596         s += ('tree = ' + this.node.nodeName + '\n');
597         s += ('treeitem = ' + treeitem.nodeName + '  treerow = ' + treerow.nodeName + '\n');
598
599         if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
600
601             s += 'found a retrieve_row function\n';
602
603             obj.put_retrieving_label(treerow);
604             treerow.addEventListener(
605                 'flesh',
606                 function() {
607
608                     if (treerow.getAttribute('retrieved') == 'true') return; /* already running */
609
610                     treerow.setAttribute('retrieved','true');
611
612                     //dump('fleshing = ' + params.retrieve_id + '\n');
613
614                     function inc_fleshed() {
615                         if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
616                         treerow.setAttribute('fleshed','true');
617                         obj.row_count.fleshed++;
618                         if (obj.row_count.fleshed >= obj.row_count.total) {
619                             setTimeout( function() { obj.exec_on_all_fleshed(); }, 0 );
620                         }
621                     }
622
623                     params.row_node = treeitem;
624                     params.on_retrieve = function(p) {
625                         try {
626                             p.row = params.row;
627                             obj._map_row_to_treecell(p,treerow);
628                             inc_fleshed();
629                             var idx = obj.node.contentView.getIndexOfItem( params.row_node );
630                             dump('idx = ' + idx + '\n');
631                             // if current row is selected, send another select event to re-sync data that the client code fetches on selects
632                             if ( obj.node.view.selection.isSelected( idx ) ) {
633                                 dump('dispatching select event for on_retrieve for idx = ' + idx + '\n');
634                                 util.widgets.dispatch('select',obj.node);
635                             }
636                         } catch(E) {
637                             // Let's not alert on this for now.  Getting contentView has no properties in record buckets under certain conditions
638                             dump('fixme2: ' + E + '\n');
639                         }
640                     }
641
642                     if (typeof params.retrieve_row == 'function') {
643
644                         params.retrieve_row( params );
645
646                     } else if (typeof obj.retrieve_row == 'function') {
647
648                             obj.retrieve_row( params );
649
650                     } else {
651                     
652                             inc_fleshed();
653                     }
654                 },
655                 false
656             );
657             if (typeof params.flesh_immediately != 'undefined') {
658                 if (params.flesh_immediately) {
659                     setTimeout(
660                         function() {
661                             util.widgets.dispatch('flesh',treerow);
662                         }, 0
663                     );
664                 }
665             }
666
667         } else {
668
669             s += 'did not find a retrieve_row function\n';
670
671             obj.put_retrieving_label(treerow);
672             treerow.addEventListener(
673                 'flesh',
674                 function() {
675                     //dump('fleshing anon\n');
676                     if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
677                     obj._map_row_to_treecell(params,treerow);
678                     treerow.setAttribute('retrieved','true');
679                     treerow.setAttribute('fleshed','true');
680                     obj.row_count.fleshed++;
681                     if (obj.row_count.fleshed >= obj.row_count.total) {
682                         setTimeout( function() { obj.exec_on_all_fleshed(); }, 0 );
683                     }
684                 },
685                 false
686             );
687             if (typeof params.flesh_immediately != 'undefined') {
688                 if (params.flesh_immediately) {
689                     setTimeout(
690                         function() {
691                             util.widgets.dispatch('flesh',treerow);
692                         }, 0
693                     );
694                 }
695             }
696
697         }
698
699             try {
700
701                 if (obj.trim_list && obj.row_count.total >= obj.trim_list) {
702                     // Remove oldest row
703                     //if (typeof params.to_bottom != 'undefined') 
704                     if (typeof params.to_top == 'undefined') {
705                         treechildren_node.removeChild( treechildren_node.firstChild );
706                     } else {
707                         treechildren_node.removeChild( treechildren_node.lastChild );
708                     }
709                 }
710             } catch(E) {
711             }
712
713         setTimeout( function() { obj.auto_retrieve(); }, 0 );
714
715         JSAN.use('util.widgets'); util.widgets.dispatch('select',obj.node);
716
717         this.error.sdump('D_LIST',s);
718
719         return params;
720     },
721
722     'put_retrieving_label' : function(treerow) {
723         var obj = this;
724         try {
725             /*
726             var cols_idx = 0;
727             dump('put_retrieving_label.  columns = ' + js2JSON(obj.columns) + '\n');
728             while( obj.columns[cols_idx] && obj.columns[cols_idx].hidden && obj.columns[cols_idx].hidden == 'true') {
729                 dump('\t' + cols_idx);
730                 var treecell = document.createElement('treecell');
731                 treerow.appendChild(treecell);
732                 cols_idx++;
733             }
734             */
735             var create_treecells = treerow.childNodes.length == 0;
736             for (var i = 0; i < obj.columns.length; i++) {
737                 var treecell = create_treecells ? document.createElement('treecell') : treerow.childNodes[i]; 
738                 if (treecell) {
739                     if (create_treecells) { 
740                         treecell.setAttribute('label',document.getElementById('offlineStrings').getString('list.row_retrieving'));
741                         treerow.appendChild(treecell); 
742                     }
743                 }
744             }
745             /*
746             dump('\t' + cols_idx + '\n');
747             */
748         } catch(E) {
749             alert('Error in list.js, put_retrieving_label(): ' + E);
750         }
751     },
752
753     'detect_visible' : function() {
754         var obj = this;
755         try {
756             //dump('detect_visible  obj.node = ' + obj.node + '\n');
757             /* FIXME - this is a hack.. if the implementation of tree changes, this could break */
758             try {
759                 /*var s = ''; var A = document.getAnonymousNodes(obj.node);
760                 for (var i in A) {
761                     var B = A[i];
762                     s += '\t' + (typeof B.nodeName != 'undefined' ? B.nodeName : B ) + '\n'; 
763                     if (typeof B.childNodes != 'undefined') for (var j = 0; j < B.childNodes.length; j++) {
764                         var C = B.childNodes[j];
765                         s += '\t\t' + C.nodeName + '\n';
766                     }
767                 }
768                 obj.error.sdump('D_XULRUNNER','document.getAnonymousNodes(' + obj.node.nodeName + ') = \n' + s + '\n');*/
769                 var scrollbar = document.getAnonymousNodes(obj.node)[2].firstChild;
770                 var curpos = scrollbar.getAttribute('curpos');
771                 var maxpos = scrollbar.getAttribute('maxpos');
772                 //alert('curpos = ' + curpos + ' maxpos = ' + maxpos + ' obj.curpos = ' + obj.curpos + ' obj.maxpos = ' + obj.maxpos + '\n');
773                 if ((curpos != obj.curpos) || (maxpos != obj.maxpos)) {
774                     if ( obj.auto_retrieve() > 0 ) {
775                         obj.curpos = curpos; obj.maxpos = maxpos;
776                     }
777                 }
778             } catch(E) {
779                 obj.error.sdump('D_XULRUNNER', 'List implementation changed? ' + E);
780             }
781         } catch(E) { obj.error.sdump('D_ERROR',E); }
782     },
783
784     'detect_visible_polling' : function() {
785         try {
786             //alert('detect_visible_polling');
787             var obj = this;
788             obj.detect_visible();
789             setTimeout(function() { try { obj.detect_visible_polling(); } catch(E) { alert(E); } },2000);
790         } catch(E) {
791             alert(E);
792         }
793     },
794
795
796     'auto_retrieve' : function(params) {
797         var obj = this;
798         switch (this.node.nodeName) {
799             case 'tree' : obj._auto_retrieve_tree(params); break;
800             default: throw('NYI: Need .auto_retrieve() for ' + obj.node.nodeName); break;
801         }
802     },
803
804     '_auto_retrieve_tree' : function (params) {
805         var obj = this;
806         if (!obj.auto_retrieve_in_progress) {
807             obj.auto_retrieve_in_progress = true;
808             setTimeout(
809                 function() {
810                     try {
811                             //alert('auto_retrieve\n');
812                             var count = 0;
813                             var startpos = obj.node.treeBoxObject.getFirstVisibleRow();
814                             var endpos = obj.node.treeBoxObject.getLastVisibleRow();
815                             if (startpos > endpos) endpos = obj.node.treeBoxObject.getPageLength();
816                             //dump('startpos = ' + startpos + ' endpos = ' + endpos + '\n');
817                             for (var i = startpos; i < endpos + 4; i++) {
818                                 try {
819                                     //dump('trying index ' + i + '\n');
820                                     var item = obj.node.contentView.getItemAtIndex(i).firstChild;
821                                     if (item && item.getAttribute('retrieved') != 'true' ) {
822                                         //dump('\tgot an unfleshed item = ' + item + ' = ' + item.nodeName + '\n');
823                                         util.widgets.dispatch('flesh',item); count++;
824                                     }
825                                 } catch(E) {
826                                     //dump(i + ' : ' + E + '\n');
827                                 }
828                             }
829                             obj.auto_retrieve_in_progress = false;
830                             return count;
831                     } catch(E) { alert(E); }
832                 }, 1
833             );
834         }
835     },
836
837     'exec_on_all_fleshed' : function() {
838         var obj = this;
839         try {
840             if (obj.on_all_fleshed) {
841                 if (typeof obj.on_all_fleshed == 'function') {
842                     dump('exec_on_all_fleshed == function\n');
843                     setTimeout( 
844                         function() { 
845                             try { obj.on_all_fleshed(); } catch(E) { obj.error.standard_unexpected_error_alert('_full_retrieve_tree callback',obj.on_all_fleshed); }
846                         }, 0 
847                     );
848                 } else if (typeof obj.on_all_fleshed.length != 'undefined') {
849                     dump('exec_on_all_fleshed == array\n');
850                     setTimeout(
851                         function() {
852                             try {
853                                 dump('exec_on_all_fleshed, processing on_all_fleshed array, length = ' + obj.on_all_fleshed.length + '\n');
854                                 var f = obj.on_all_fleshed.pop();
855                                 if (typeof f == 'function') { 
856                                     try { f(); } catch(E) { obj.error.standard_unexpected_error_alert('_full_retrieve_tree callback',f); } 
857                                 }
858                                 if (obj.on_all_fleshed.length > 0) arguments.callee(); 
859                             } catch(E) {
860                                 obj.error.standard_unexpected_error_alert('exec_on_all_fleshed callback error',E);
861                             }
862                         }, 0
863                     ); 
864                 } else {
865                     obj.error.standard_unexpected_error_alert('unexpected on_all_fleshed object: ', obj.on_all_fleshed);
866                 }
867             }
868         } catch(E) {
869             obj.error.standard_unexpected_error_alert('exec_on_all-fleshed error',E);
870         }
871     },
872
873     'full_retrieve' : function(params) {
874         var obj = this;
875         switch (this.node.nodeName) {
876             case 'tree' : obj._full_retrieve_tree(params); break;
877             default: throw('NYI: Need .full_retrieve() for ' + obj.node.nodeName); break;
878         }
879     },
880
881     '_full_retrieve_tree' : function(params) {
882         var obj = this;
883         try {
884             if (obj.row_count.fleshed >= obj.row_count.total) {
885                 dump('Full retrieve... tree seems to be in sync\n' + js2JSON(obj.row_count) + '\n');
886                 obj.exec_on_all_fleshed();
887             } else {
888                 dump('Full retrieve... syncing tree' + js2JSON(obj.row_count) + '\n');
889                 JSAN.use('util.widgets');
890                 var nodes = obj.treechildren.childNodes;
891                 for (var i = 0; i < nodes.length; i++) {
892                     util.widgets.dispatch('flesh',nodes[i].firstChild);
893                 }
894             }
895         } catch(E) {
896             obj.error.standard_unexpected_error_alert('_full_retrieve_tree',E);
897         }
898     },
899
900     '_append_to_listbox' : function (params) {
901
902         var obj = this;
903
904         if (typeof params.row == 'undefined') throw('util.list.append: Object must contain a row');
905
906         var s = ('util.list.append: params = ' + (params) + '\n');
907
908         var listitem = document.createElement('listitem');
909
910         s += ('listbox = ' + this.node + '  listitem = ' + listitem + '\n');
911
912         if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
913
914             setTimeout(
915                 function() {
916                     listitem.setAttribute('retrieve_id',params.retrieve_id);
917                     //FIXME//Make async and fire when row is visible in list
918                     var row;
919
920                     params.row_node = listitem;
921                     params.on_retrieve = function(row) {
922                         params.row = row;
923                         obj._map_row_to_listcell(params,listitem);
924                         obj.node.appendChild( listitem );
925                         util.widgets.dispatch('select',obj.node);
926                     }
927
928                     if (typeof params.retrieve_row == 'function') {
929
930                         row = params.retrieve_row( params );
931
932                     } else {
933
934                         if (typeof obj.retrieve_row == 'function') {
935
936                             row = obj.retrieve_row( params );
937
938                         }
939                     }
940                 }, 0
941             );
942         } else {
943             this._map_row_to_listcell(params,listitem);
944             this.node.appendChild( listitem );
945         }
946
947         this.error.sdump('D_LIST',s);
948         params.my_node = listitem;
949         return params;
950
951     },
952
953     '_map_row_to_treecell' : function(params,treerow) {
954         var obj = this;
955         var s = '';
956         //util.widgets.remove_children(treerow);
957         var create_treecells = treerow.childNodes.length == 0;
958
959         if (typeof params.map_row_to_column == 'function' || typeof this.map_row_to_column == 'function') {
960
961             for (var i = 0; i < this.columns.length; i++) {
962                 var treecell = create_treecells ? document.createElement('treecell') : treerow.childNodes[i];
963                 if ( this.columns[i].editable == false ) { treecell.setAttribute('editable','false'); }
964                 var label = '';
965                 if (params.skip_columns && (params.skip_columns.indexOf(i) != -1)) {
966                     treecell.setAttribute('label',label);
967                     treerow.appendChild( treecell );
968                     s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
969                     continue;
970                 }
971                 if (params.skip_all_columns_except && (params.skip_all_columns_except.indexOf(i) == -1)) {
972                     treecell.setAttribute('label',label);
973                     treerow.appendChild( treecell );
974                     s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
975                     continue;
976                 }
977     
978                 if (typeof params.map_row_to_column == 'function')  {
979     
980                     label = params.map_row_to_column(params.row,this.columns[i]);
981     
982                 } else if (typeof this.map_row_to_column == 'function') {
983     
984                     label = this.map_row_to_column(params.row,this.columns[i]);
985     
986                 }
987                 if (this.columns[i].type == 'checkbox') { treecell.setAttribute('value',label); } else { treecell.setAttribute('label',label ? label : ''); }
988                 if (create_treecells) { treerow.appendChild( treecell ); }
989                 s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
990             }
991         } else if (typeof params.map_row_to_columns == 'function' || typeof this.map_row_to_columns == 'function') {
992
993             var labels = [];
994
995             if (typeof params.map_row_to_columns == 'function') {
996
997                 labels = params.map_row_to_columns(params.row,this.columns);
998
999             } else if (typeof this.map_row_to_columns == 'function') {
1000
1001                 labels = this.map_row_to_columns(params.row,this.columns);
1002
1003             }
1004             for (var i = 0; i < labels.length; i++) {
1005                 var treecell = create_treecells ? document.createElement('treecell') : treerow.childNodes[i];
1006                 if ( this.columns[i].editable == false ) { treecell.setAttribute('editable','false'); }
1007                 if ( this.columns[i].type == 'checkbox') {
1008                     treecell.setAttribute('value', labels[i]);
1009                 } else {
1010                     treecell.setAttribute('label',typeof labels[i] == 'string' || typeof labels[i] == 'number' ? labels[i] : '');
1011                 }
1012                 if (create_treecells) { treerow.appendChild( treecell ); }
1013                 s += ('treecell = ' + treecell + ' with label = ' + labels[i] + '\n');
1014             }
1015
1016         } else {
1017
1018             throw('No row to column mapping function.');
1019         }
1020         this.error.sdump('D_LIST',s);
1021     },
1022
1023     '_map_row_to_listcell' : function(params,listitem) {
1024         var obj = this;
1025         var s = '';
1026         for (var i = 0; i < this.columns.length; i++) {
1027             var value = '';
1028             if (typeof params.map_row_to_column == 'function')  {
1029
1030                 value = params.map_row_to_column(params.row,this.columns[i]);
1031
1032             } else {
1033
1034                 if (typeof this.map_row_to_column == 'function') {
1035
1036                     value = this.map_row_to_column(params.row,this.columns[i]);
1037                 }
1038             }
1039             if (typeof value == 'string' || typeof value == 'number') {
1040                 var listcell = document.createElement('listcell');
1041                 listcell.setAttribute('label',value);
1042                 listitem.appendChild(listcell);
1043                 s += ('listcell = ' + listcell + ' with label = ' + value + '\n');
1044             } else {
1045                 listitem.appendChild(value);
1046                 s += ('listcell = ' + value + ' is really a ' + value.nodeName + '\n');
1047             }
1048         }
1049         this.error.sdump('D_LIST',s);
1050     },
1051
1052     'select_all' : function(params) {
1053         var obj = this;
1054         switch(this.node.nodeName) {
1055             case 'tree' : return this._select_all_from_tree(params); break;
1056             default: throw('NYI: Need ._select_all_from_() for ' + this.node.nodeName); break;
1057         }
1058     },
1059
1060     '_select_all_from_tree' : function(params) {
1061         var obj = this;
1062         this.node.view.selection.selectAll();
1063     },
1064
1065     'retrieve_selection' : function(params) {
1066         var obj = this;
1067         switch(this.node.nodeName) {
1068             case 'tree' : return this._retrieve_selection_from_tree(params); break;
1069             default: throw('NYI: Need ._retrieve_selection_from_() for ' + this.node.nodeName); break;
1070         }
1071     },
1072
1073     '_retrieve_selection_from_tree' : function(params) {
1074         var obj = this;
1075         var list = [];
1076         var start = new Object();
1077         var end = new Object();
1078         var numRanges = this.node.view.selection.getRangeCount();
1079         for (var t=0; t<numRanges; t++){
1080             this.node.view.selection.getRangeAt(t,start,end);
1081             for (var v=start.value; v<=end.value; v++){
1082                 var i = this.node.contentView.getItemAtIndex(v);
1083                 list.push( i );
1084             }
1085         }
1086         return list;
1087     },
1088
1089     'dump' : function(params) {
1090         var obj = this;
1091         switch(this.node.nodeName) {
1092             case 'tree' : return this._dump_tree(params); break;
1093             default: throw('NYI: Need .dump() for ' + this.node.nodeName); break;
1094         }
1095     },
1096
1097     '_dump_tree' : function(params) {
1098         var obj = this;
1099         var dump = [];
1100         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1101             var row = [];
1102             var treeitem = this.treechildren.childNodes[i];
1103             var treerow = treeitem.firstChild;
1104             for (var j = 0; j < treerow.childNodes.length; j++) {
1105                 row.push( treerow.childNodes[j].getAttribute('label') );
1106             }
1107             dump.push( row );
1108         }
1109         return dump;
1110     },
1111
1112     'dump_with_keys' : function(params) {
1113         var obj = this;
1114         switch(this.node.nodeName) {
1115             case 'tree' : return this._dump_tree_with_keys(params); break;
1116             default: throw('NYI: Need .dump_with_keys() for ' + this.node.nodeName); break;
1117         }
1118
1119     },
1120
1121     '_dump_tree_with_keys' : function(params) {
1122         var obj = this;
1123         var dump = [];
1124         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1125             var row = {};
1126             var treeitem = this.treechildren.childNodes[i];
1127             var treerow = treeitem.firstChild;
1128             for (var j = 0; j < treerow.childNodes.length; j++) {
1129                 row[ obj.columns[j].id ] = treerow.childNodes[j].getAttribute('label');
1130             }
1131             dump.push( row );
1132         }
1133         return dump;
1134     },
1135
1136     'dump_csv' : function(params) {
1137         var obj = this;
1138         switch(this.node.nodeName) {
1139             case 'tree' : return this._dump_tree_csv(params); break;
1140             default: throw('NYI: Need .dump_csv() for ' + this.node.nodeName); break;
1141         }
1142
1143     },
1144
1145     '_dump_tree_csv' : function(params) {
1146         var obj = this;
1147         var _dump = '';
1148         var ord_cols = [];
1149         for (var j = 0; j < obj.columns.length; j++) {
1150             if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1151                 /* skip */
1152             } else {
1153                 ord_cols.push( [ obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('ordinal'), j ] );
1154             }
1155         }
1156         ord_cols.sort( function(a,b) { 
1157             if ( Number( a[0] ) < Number( b[0] ) ) return -1; 
1158             if ( Number( a[0] ) > Number( b[0] ) ) return 1; 
1159             return 0;
1160         } );
1161         for (var j = 0; j < ord_cols.length; j++) {
1162             if (_dump) _dump += ',';
1163             _dump += '"' + obj.columns[ ord_cols[j][1] ].label.replace(/"/g, '""') + '"';
1164         }
1165         _dump += '\r\n';
1166         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1167             var row = '';
1168             var treeitem = this.treechildren.childNodes[i];
1169             var treerow = treeitem.firstChild;
1170             for (var j = 0; j < ord_cols.length; j++) {
1171                 if (row) row += ',';
1172                 row += '"' + treerow.childNodes[ ord_cols[j][1] ].getAttribute('label').replace(/"/g, '""') + '"';
1173             }
1174             _dump +=  row + '\r\n';
1175         }
1176         return _dump;
1177     },
1178
1179     'dump_extended_format' : function(params) {
1180         var obj = this;
1181         switch(this.node.nodeName) {
1182             case 'tree' : return this._dump_tree_extended_format(params); break;
1183             default: throw('NYI: Need .dump_extended_format() for ' + this.node.nodeName); break;
1184         }
1185
1186     },
1187
1188     '_dump_tree_extended_format' : function(params) {
1189         var obj = this;
1190         var _dump = '';
1191         var ord_cols = [];
1192         for (var j = 0; j < obj.columns.length; j++) {
1193             if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1194                 /* skip */
1195             } else {
1196                 ord_cols.push( [ obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('ordinal'), j ] );
1197             }
1198         }
1199         ord_cols.sort( function(a,b) { 
1200             if ( Number( a[0] ) < Number( b[0] ) ) return -1; 
1201             if ( Number( a[0] ) > Number( b[0] ) ) return 1; 
1202             return 0;
1203         } );
1204         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1205             var row = document.getElementById('offlineStrings').getString('list.dump_extended_format.record_separator') + '\r\n';
1206             var treeitem = this.treechildren.childNodes[i];
1207             var treerow = treeitem.firstChild;
1208             for (var j = 0; j < ord_cols.length; j++) {
1209                 row += obj.columns[ ord_cols[j][1] ].label + ': ' + treerow.childNodes[ ord_cols[j][1] ].getAttribute('label') + '\r\n';
1210             }
1211             _dump +=  row + '\r\n';
1212         }
1213         return _dump;
1214     },
1215
1216     'dump_csv_to_clipboard' : function(params) {
1217         var obj = this;
1218         if (typeof params == 'undefined') params = {};
1219         if (params.no_full_retrieve) {
1220             copy_to_clipboard( obj.dump_csv( params ) );
1221         } else {
1222             obj.wrap_in_full_retrieve( function() { copy_to_clipboard( obj.dump_csv( params ) ); } );
1223         }
1224     },
1225
1226     'dump_csv_to_printer' : function(params) {
1227         var obj = this;
1228         JSAN.use('util.print'); var print = new util.print();
1229         if (typeof params == 'undefined') params = {};
1230         if (params.no_full_retrieve) {
1231             print.simple( obj.dump_csv( params ), {'content_type':'text/plain'} );
1232         } else {
1233             obj.wrap_in_full_retrieve( 
1234                 function() { 
1235                     print.simple( obj.dump_csv( params ), {'content_type':'text/plain'} );
1236                 }
1237             );
1238         }
1239     },
1240
1241     'dump_extended_format_to_printer' : function(params) {
1242         var obj = this;
1243         JSAN.use('util.print'); var print = new util.print();
1244         if (typeof params == 'undefined') params = {};
1245         if (params.no_full_retrieve) {
1246             print.simple( obj.dump_extended_format( params ), {'content_type':'text/plain'} );
1247         } else {
1248             obj.wrap_in_full_retrieve( 
1249                 function() { 
1250                     print.simple( obj.dump_extended_format( params ), {'content_type':'text/plain'} );
1251                 }
1252             );
1253         }
1254     },
1255
1256     'dump_csv_to_file' : function(params) {
1257         var obj = this;
1258         JSAN.use('util.file'); var f = new util.file();
1259         if (typeof params == 'undefined') params = {};
1260         if (params.no_full_retrieve) {
1261             params.data = obj.dump_csv( params );
1262             params.not_json = true;
1263             if (!params.title) params.title = document.getElementById('offlineStrings').getString('list.save_csv_as');
1264             f.export_file( params );
1265         } else {
1266             obj.wrap_in_full_retrieve( 
1267                 function() { 
1268                     params.data = obj.dump_csv( params );
1269                     params.not_json = true;
1270                     if (!params.title) params.title = document.getElementById('offlineStrings').getString('list.save_csv_as');
1271                     f.export_file( params );
1272                 }
1273             );
1274         }
1275     },
1276
1277     'print' : function(params) {
1278         if (!params) params = {};
1279         switch(this.node.nodeName) {
1280             case 'tree' : return this._print_tree(params); break;
1281             default: throw('NYI: Need ._print() for ' + this.node.nodeName); break;
1282         }
1283     },
1284
1285     '_print_tree' : function(params) {
1286         var obj = this;
1287         try {
1288             JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
1289             if (!params.staff && data.list.au && data.list.au[0]) {
1290                 params.staff = data.list.au[0];
1291             }
1292             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() ]) {
1293                 params.lib = data.hash.aou[ data.list.au[0].ws_ou() ];
1294                 params.lib.children(null);
1295             }
1296             if (params.template && data.print_list_templates[ params.template ]) {
1297                 var template = data.print_list_templates[ params.template ];
1298                 for (var i in template) params[i] = template[i];
1299             }
1300             obj.wrap_in_full_retrieve(
1301                 function() {
1302                     try {
1303                         if (!params.list) params.list = obj.dump_with_keys();
1304                         JSAN.use('util.print'); var print = new util.print();
1305                         print.tree_list( params );
1306                         if (typeof params.callback == 'function') params.callback();
1307                     } catch(E) {
1308                         obj.error.standard_unexpected_error_alert('inner _print_tree',E);
1309                     }
1310                 }
1311             );
1312             
1313         } catch(E) {
1314             obj.error.standard_unexpected_error_alert('_print_tree',E);
1315         }
1316     },
1317
1318     'dump_selected_with_keys' : function(params) {
1319         var obj = this;
1320         switch(this.node.nodeName) {
1321             case 'tree' : return this._dump_tree_selection_with_keys(params); break;
1322             default: throw('NYI: Need .dump_selection_with_keys() for ' + this.node.nodeName); break;
1323         }
1324
1325     },
1326
1327     '_dump_tree_selection_with_keys' : function(params) {
1328         var obj = this;
1329         var dump = [];
1330         var list = obj._retrieve_selection_from_tree();
1331         for (var i = 0; i < list.length; i++) {
1332             var row = {};
1333             var treeitem = list[i];
1334             var treerow = treeitem.firstChild;
1335             for (var j = 0; j < treerow.childNodes.length; j++) {
1336                 var value = treerow.childNodes[j].getAttribute('label');
1337                 if (params.skip_hidden_columns) if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') continue;
1338                 var id = obj.columns[j].id; if (params.labels_instead_of_ids) id = obj.columns[j].label;
1339                 row[ id ] = value;
1340             }
1341             dump.push( row );
1342         }
1343         return dump;
1344     },
1345
1346     'clipboard' : function(params) {
1347         try {
1348             var obj = this;
1349             var dump = obj.dump_selected_with_keys({'skip_hidden_columns':true,'labels_instead_of_ids':true});
1350             JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
1351             data.list_clipboard = dump; data.stash('list_clipboard');
1352             JSAN.use('util.window'); var win = new util.window();
1353             win.open(urls.XUL_LIST_CLIPBOARD,'list_clipboard','chrome,resizable,modal');
1354             window.focus(); // sometimes the main window will lower after a clipboard action
1355         } catch(E) {
1356             this.error.standard_unexpected_error_alert('clipboard',E);
1357         }
1358     },
1359
1360     'dump_retrieve_ids' : function(params) {
1361         var obj = this;
1362         switch(this.node.nodeName) {
1363             case 'tree' : return this._dump_retrieve_ids_tree(params); break;
1364             default: throw('NYI: Need .dump_retrieve_ids() for ' + this.node.nodeName); break;
1365         }
1366     },
1367
1368     '_dump_retrieve_ids_tree' : function(params) {
1369         var obj = this;
1370         var dump = [];
1371         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1372             var treeitem = this.treechildren.childNodes[i];
1373             dump.push( treeitem.getAttribute('retrieve_id') );
1374         }
1375         return dump;
1376     },
1377
1378     'wrap_in_full_retrieve' : function(f) {
1379         var obj = this;
1380         if (typeof obj.on_all_fleshed == 'function') { // legacy
1381             obj.on_all_fleshed = [ obj.on_all_fleshed ];
1382         }
1383         if (! obj.on_all_fleshed) obj.on_all_fleshed = [];
1384         obj.on_all_fleshed.push(f);
1385         obj.full_retrieve();
1386     },
1387
1388     '_sort_tree' : function(col,sortDir) {
1389         var obj = this;
1390         try {
1391             if (obj.node.getAttribute('no_sort')) {
1392                 return;
1393             }
1394             var col_pos;
1395             for (var i = 0; i < obj.columns.length; i++) { 
1396                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1397             }
1398             obj.wrap_in_full_retrieve(
1399                 function() {
1400                     try {
1401                         JSAN.use('util.money');
1402                         var rows = [];
1403                         var treeitems = obj.treechildren.childNodes;
1404                         for (var i = 0; i < treeitems.length; i++) {
1405                             var treeitem = treeitems[i];
1406                             var treerow = treeitem.firstChild;
1407                             var treecell = treerow.childNodes[ col_pos ];
1408                             value = ( { 'value' : treecell ? treecell.getAttribute('label') : '', 'node' : treeitem } );
1409                             rows.push( value );
1410                         }
1411                         rows = rows.sort( function(a,b) { 
1412                             a = a.value; b = b.value; 
1413                             if (col.getAttribute('sort_type')) {
1414                                 switch(col.getAttribute('sort_type')) {
1415                                     case 'number' :
1416                                         a = Number(a); b = Number(b);
1417                                     break;
1418                                     case 'money' :
1419                                         a = util.money.dollars_float_to_cents_integer(a);
1420                                         b = util.money.dollars_float_to_cents_integer(b);
1421                                     break;
1422                                     case 'title' : /* special case for "a" and "the".  doesn't use marc 245 indicator */
1423                                         a = String( a ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1424                                         b = String( b ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1425                                     break;
1426                                     default:
1427                                         a = String( a ).toUpperCase();
1428                                         b = String( b ).toUpperCase();
1429                                     break;
1430                                 }
1431                             } else {
1432                                 if (typeof a == 'string' || typeof b == 'string') {
1433                                     a = String( a ).toUpperCase();
1434                                     b = String( b ).toUpperCase();
1435                                 }
1436                             }
1437                             if (a < b) return -1; 
1438                             if (a > b) return 1; 
1439                             return 0; 
1440                         } );
1441                         if (sortDir == 'asc') rows = rows.reverse();
1442                         while(obj.treechildren.lastChild) obj.treechildren.removeChild( obj.treechildren.lastChild );
1443                         for (var i = 0; i < rows.length; i++) {
1444                             obj.treechildren.appendChild( rows[i].node );
1445                         }
1446                         if (typeof obj.on_sort == 'function') obj.on_sort();
1447                     } catch(E) {
1448                         obj.error.standard_unexpected_error_alert('sorting',E); 
1449                     }
1450                 }
1451             );
1452         } catch(E) {
1453             obj.error.standard_unexpected_error_alert('pre sorting', E);
1454         }
1455     },
1456
1457     '_toggle_checkbox_column' : function(col,toggle) {
1458         var obj = this;
1459         try {
1460             if (obj.node.getAttribute('no_toggle')) {
1461                 return;
1462             }
1463             var col_pos;
1464             for (var i = 0; i < obj.columns.length; i++) { 
1465                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1466             }
1467             var treeitems = obj.treechildren.childNodes;
1468             for (var i = 0; i < treeitems.length; i++) {
1469                 var treeitem = treeitems[i];
1470                 var treerow = treeitem.firstChild;
1471                 var treecell = treerow.childNodes[ col_pos ];
1472                 treecell.setAttribute('value',(toggle == 'on'));
1473             }
1474             if (typeof obj.on_checkbox_toggle == 'function') obj.on_checkbox_toggle(toggle);
1475         } catch(E) {
1476             obj.error.standard_unexpected_error_alert('pre toggle', E);
1477         }
1478     },
1479
1480     'render_list_actions' : function(params) {
1481         var obj = this;
1482         switch(this.node.nodeName) {
1483             case 'tree' : return this._render_list_actions_for_tree(params); break;
1484             default: throw('NYI: Need ._render_list_actions() for ' + this.node.nodeName); break;
1485         }
1486     },
1487
1488     '_render_list_actions_for_tree' : function(params) {
1489         var obj = this;
1490         try {
1491             var btn = document.createElement('button');
1492             btn.setAttribute('id',obj.node.id + '_list_actions');
1493             btn.setAttribute('type','menu');
1494             btn.setAttribute('allowevents','true');
1495             //btn.setAttribute('oncommand','this.firstChild.showPopup();');
1496             btn.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.menu.label'));
1497             btn.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.menu.accesskey'));
1498             var mp = document.createElement('menupopup');
1499             btn.appendChild(mp);
1500             var mi = document.createElement('menuitem');
1501             mi.setAttribute('id',obj.node.id + '_clipfield');
1502             mi.setAttribute('disabled','true');
1503             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.field_to_clipboard.label'));
1504             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.field_to_clipboard.accesskey'));
1505             mp.appendChild(mi);
1506             mi = document.createElement('menuitem');
1507             mi.setAttribute('id',obj.node.id + '_csv_to_clipboard');
1508             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_clipboard.label'));
1509             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_clipboard.accesskey'));
1510             mp.appendChild(mi);
1511             mi = document.createElement('menuitem');
1512             mi.setAttribute('id',obj.node.id + '_csv_to_printer');
1513             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_printer.label'));
1514             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_printer.accesskey'));
1515             mp.appendChild(mi);
1516             mi = document.createElement('menuitem');
1517             mi.setAttribute('id',obj.node.id + '_extended_to_printer');
1518             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.extended_to_printer.label'));
1519             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.extended_to_printer.accesskey'));
1520             mp.appendChild(mi);
1521             mi = document.createElement('menuitem');
1522             mi.setAttribute('id',obj.node.id + '_csv_to_file');
1523             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_file.label'));
1524             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_file.accesskey'));
1525             mp.appendChild(mi);
1526             mi = document.createElement('menuitem');
1527             mi.setAttribute('id',obj.node.id + '_save_columns');
1528             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.save_column_configuration.label'));
1529             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.save_column_configuration.accesskey'));
1530             mp.appendChild(mi);
1531             return btn;
1532         } catch(E) {
1533             obj.error.standard_unexpected_error_alert('rendering list actions',E);
1534         }
1535     },
1536
1537     'set_list_actions' : function(params) {
1538         var obj = this;
1539         switch(this.node.nodeName) {
1540             case 'tree' : return this._set_list_actions_for_tree(params); break;
1541             default: throw('NYI: Need ._set_list_actions() for ' + this.node.nodeName); break;
1542         }
1543     },
1544
1545     '_set_list_actions_for_tree' : function(params) {
1546         // This should be called after the button element from render_list_actions has been appended to the DOM
1547         var obj = this;
1548         try {
1549             var x = document.getElementById(obj.node.id + '_clipfield');
1550             if (x) {
1551                 x.addEventListener(
1552                     'command',
1553                     function() {
1554                         obj.clipboard(params);
1555                         if (params && typeof params.on_complete == 'function') {
1556                             params.on_complete(params);
1557                         }
1558                     },
1559                     false
1560                 );
1561             }
1562             x = document.getElementById(obj.node.id + '_csv_to_clipboard');
1563             if (x) {
1564                 x.addEventListener(
1565                     'command',
1566                     function() {
1567                         obj.dump_csv_to_clipboard(params);
1568                         if (params && typeof params.on_complete == 'function') {
1569                             params.on_complete(params);
1570                         }
1571                     },
1572                     false
1573                 );
1574             }
1575             x = document.getElementById(obj.node.id + '_csv_to_printer');
1576             if (x) {
1577                 x.addEventListener(
1578                     'command',
1579                     function() {
1580                         obj.dump_csv_to_printer(params);
1581                         if (params && typeof params.on_complete == 'function') {
1582                             params.on_complete(params);
1583                         }
1584                     },
1585                     false
1586                 );
1587             }
1588             x = document.getElementById(obj.node.id + '_extended_to_printer');
1589             if (x) {
1590                 x.addEventListener(
1591                     'command',
1592                     function() {
1593                         obj.dump_extended_format_to_printer(params);
1594                         if (params && typeof params.on_complete == 'function') {
1595                             params.on_complete(params);
1596                         }
1597                     },
1598                     false
1599                 );
1600             }
1601
1602             x = document.getElementById(obj.node.id + '_csv_to_file');
1603             if (x) {
1604                 x.addEventListener(
1605                     'command',
1606                     function() {
1607                         obj.dump_csv_to_file(params);
1608                         if (params && typeof params.on_complete == 'function') {
1609                             params.on_complete(params);
1610                         }
1611                     },
1612                     false
1613                 );
1614             }
1615             x = document.getElementById(obj.node.id + '_save_columns');
1616             if (x) {
1617                 x.addEventListener(
1618                     'command',
1619                     function() {
1620                         obj.save_columns(params);
1621                         if (params && typeof params.on_complete == 'function') {
1622                             params.on_complete(params);
1623                         }
1624                     },
1625                     false
1626                 );
1627             }
1628
1629         } catch(E) {
1630             obj.error.standard_unexpected_error_alert('setting list actions',E);
1631         }
1632     },
1633
1634     // Takes fieldmapper class name and attempts to spit out column definitions suitable for .init
1635     'fm_columns' : function(hint,column_extras) {
1636         var obj = this;
1637         var columns = [];
1638         try {
1639             // requires the dojo library fieldmapper.autoIDL
1640             if (typeof fieldmapper == 'undefined') { throw 'fieldmapper undefined'; }
1641             if (typeof fieldmapper.IDL == 'undefined') { throw 'fieldmapper.IDL undefined'; }
1642             if (typeof fieldmapper.IDL.fmclasses == 'undefined') { throw 'fieldmapper.IDL.fmclasses undefined'; }
1643             if (typeof fieldmapper.IDL.fmclasses[hint] == 'undefined') { throw 'fieldmapper.IDL.fmclasses.' + hint + ' undefined'; }
1644             var my_class = fieldmapper.IDL.fmclasses[hint]; 
1645             JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
1646
1647             function col_def(my_field) {
1648                 var col_id = hint + '_' + my_field.name;
1649                 var def = {
1650                     'id' : col_id,
1651                     'label' : my_field.label || my_field.name,
1652                     'sort_type' : [ 'int', 'float', 'id', 'number' ].indexOf(my_field.datatype) > -1 ? 'number' : ( my_field.datatype == 'money' ? 'money' : 'default'),
1653                     'hidden' : my_field.virtual || my_field.datatype == 'link',
1654                     'flex' : 1
1655                 };                    
1656                 // my_field.datatype => bool float id int interval link money number org_unit text timestamp
1657                 if (my_field.datatype == 'link') {
1658                     def.render = function(my) { 
1659                         return typeof my[hint][my_field.name]() == 'object' ? my[hint][my_field.name]()[my_field.key]() : my[hint][my_field.name](); 
1660                     }
1661                 } else {
1662                     def.render = function(my) { return my[hint][my_field.name](); }
1663                 }
1664                 if (my_field.datatype == 'timestamp') {
1665                     dojo.require('dojo.date.locale');
1666                     dojo.require('dojo.date.stamp');
1667                     def.render = function(my) {
1668                         return dojo.date.locale.format( dojo.date.stamp.fromISOString(my[hint][my_field.name]()) );
1669                     }
1670                 }
1671                 if (my_field.datatype == 'org_unit') {
1672                     def.render = function(my) {
1673                         return typeof my[hint][my_field.name]() == 'object' ? my[hint][my_field.name]().shortname() : data.hash.aou[ my[hint][my_field.name]() ].shortname();
1674                     }
1675                 }
1676                 if (my_field.datatype == 'money') {
1677                     JSAN.use('util.money');
1678                     def.render = function(my) {
1679                         return util.money.sanitize( my[hint][my_field.name]() );
1680                     }
1681                 }
1682                 if (column_extras) {
1683                     if (column_extras['*']) {
1684                         for (var attr in column_extras['*']) {
1685                             def[attr] = column_extras['*'][attr];
1686                         }
1687                         if (column_extras['*']['expanded_label']) {
1688                             def.label = my_class.label + ': ' + def.label;
1689                         }
1690                     }
1691                     if (column_extras[col_id]) {
1692                         for (var attr in column_extras[col_id]) {
1693                             def[attr] = column_extras[col_id][attr];
1694                         }
1695                     }
1696                 }
1697                 return def;
1698             }
1699  
1700             for (var i = 0; i < my_class.fields.length; i++) {
1701                 var my_field = my_class.fields[i];
1702                 columns.push( col_def(my_field) );
1703             }
1704
1705         } catch(E) {
1706             obj.error.standard_unexpected_error_alert('fm_columns()',E);
1707         }
1708         return columns;
1709     },
1710     // Default for the map_row_to_columns function for .init
1711     'std_map_row_to_columns' : function(error_value) {
1712         return function(row,cols) {
1713             // row contains { 'my' : { 'acp' : {}, 'circ' : {}, 'mvr' : {} } }
1714             // cols contains all of the objects listed above in columns
1715
1716             var obj = {};
1717             JSAN.use('util.error'); obj.error = new util.error();
1718             JSAN.use('OpenILS.data'); obj.data = new OpenILS.data(); obj.data.init({'via':'stash'});
1719             JSAN.use('util.network'); obj.network = new util.network();
1720             JSAN.use('util.money');
1721
1722             var my = row.my;
1723             var values = [];
1724             var cmd = '';
1725             try {
1726                 for (var i = 0; i < cols.length; i++) {
1727                     switch (typeof cols[i].render) {
1728                         case 'function': try { values[i] = cols[i].render(my); } catch(E) { values[i] = error_value; obj.error.sdump('D_COLUMN_RENDER_ERROR',E); } break;
1729                         case 'string' : cmd += 'try { ' + cols[i].render + '; values['+i+'] = v; } catch(E) { values['+i+'] = error_value; }'; break;
1730                         default: cmd += 'values['+i+'] = "??? '+(typeof cols[i].render)+'"; ';
1731                     }
1732                 }
1733                 if (cmd) eval( cmd );
1734             } catch(E) {
1735                 obj.error.sdump('D_WARN','map_row_to_column: ' + E);
1736                 if (error_value) { value = error_value; } else { value = '   ' };
1737             }
1738             return values;
1739         }
1740     }
1741 }
1742 dump('exiting util.list.js\n');