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