]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/util/list.js
e18e69340a00db9ab12ee08dab67c80c11c92c88
[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         for (var j = 0; j < obj.columns.length; j++) {
1128             if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1129                 /* skip */
1130             } else {
1131                 if (dump) dump += ',';
1132                 dump += '"' + obj.columns[j].label.replace(/"/g, '""') + '"';
1133             }
1134         }
1135         dump += '\r\n';
1136         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1137             var row = '';
1138             var treeitem = this.treechildren.childNodes[i];
1139             var treerow = treeitem.firstChild;
1140             for (var j = 0; j < treerow.childNodes.length; j++) {
1141                 if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1142                     /* skip */
1143                 } else {
1144                     if (row) row += ',';
1145                     row += '"' + treerow.childNodes[j].getAttribute('label').replace(/"/g, '""') + '"';
1146                 }
1147             }
1148             dump +=  row + '\r\n';
1149         }
1150         return dump;
1151     },
1152
1153     'dump_csv_to_clipboard' : function(params) {
1154         var obj = this;
1155         if (typeof params == 'undefined') params = {};
1156         if (params.no_full_retrieve) {
1157             copy_to_clipboard( obj.dump_csv( params ) );
1158         } else {
1159             obj.wrap_in_full_retrieve( function() { copy_to_clipboard( obj.dump_csv( params ) ); } );
1160         }
1161     },
1162
1163     'dump_csv_to_printer' : function(params) {
1164         var obj = this;
1165         JSAN.use('util.print'); var print = new util.print();
1166         if (typeof params == 'undefined') params = {};
1167         if (params.no_full_retrieve) {
1168             print.simple( obj.dump_csv( params ), {'content_type':'text/plain'} );
1169         } else {
1170             obj.wrap_in_full_retrieve( 
1171                 function() { 
1172                     print.simple( obj.dump_csv( params ), {'content_type':'text/plain'} );
1173                 }
1174             );
1175         }
1176     },
1177
1178     'dump_csv_to_file' : function(params) {
1179         var obj = this;
1180         JSAN.use('util.file'); var f = new util.file();
1181         if (typeof params == 'undefined') params = {};
1182         if (params.no_full_retrieve) {
1183             params.data = obj.dump_csv( params );
1184             params.not_json = true;
1185             if (!params.title) params.title = document.getElementById('offlineStrings').getString('list.save_csv_as');
1186             f.export_file( params );
1187         } else {
1188             obj.wrap_in_full_retrieve( 
1189                 function() { 
1190                     params.data = obj.dump_csv( params );
1191                     params.not_json = true;
1192                     if (!params.title) params.title = document.getElementById('offlineStrings').getString('list.save_csv_as');
1193                     f.export_file( params );
1194                 }
1195             );
1196         }
1197     },
1198
1199     'print' : function(params) {
1200         if (!params) params = {};
1201         switch(this.node.nodeName) {
1202             case 'tree' : return this._print_tree(params); break;
1203             default: throw('NYI: Need ._print() for ' + this.node.nodeName); break;
1204         }
1205     },
1206
1207     '_print_tree' : function(params) {
1208         var obj = this;
1209         try {
1210             JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
1211             if (!params.staff && data.list.au && data.list.au[0]) {
1212                 params.staff = data.list.au[0];
1213             }
1214             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() ]) {
1215                 params.lib = data.hash.aou[ data.list.au[0].ws_ou() ];
1216                 params.lib.children(null);
1217             }
1218             if (params.template && data.print_list_templates[ params.template ]) {
1219                 var template = data.print_list_templates[ params.template ];
1220                 for (var i in template) params[i] = template[i];
1221             }
1222             obj.wrap_in_full_retrieve(
1223                 function() {
1224                     try {
1225                         if (!params.list) params.list = obj.dump_with_keys();
1226                         JSAN.use('util.print'); var print = new util.print();
1227                         print.tree_list( params );
1228                         if (typeof params.callback == 'function') params.callback();
1229                     } catch(E) {
1230                         obj.error.standard_unexpected_error_alert('inner _print_tree',E);
1231                     }
1232                 }
1233             );
1234             
1235         } catch(E) {
1236             obj.error.standard_unexpected_error_alert('_print_tree',E);
1237         }
1238     },
1239
1240     'dump_selected_with_keys' : function(params) {
1241         var obj = this;
1242         switch(this.node.nodeName) {
1243             case 'tree' : return this._dump_tree_selection_with_keys(params); break;
1244             default: throw('NYI: Need .dump_selection_with_keys() for ' + this.node.nodeName); break;
1245         }
1246
1247     },
1248
1249     '_dump_tree_selection_with_keys' : function(params) {
1250         var obj = this;
1251         var dump = [];
1252         var list = obj._retrieve_selection_from_tree();
1253         for (var i = 0; i < list.length; i++) {
1254             var row = {};
1255             var treeitem = list[i];
1256             var treerow = treeitem.firstChild;
1257             for (var j = 0; j < treerow.childNodes.length; j++) {
1258                 var value = treerow.childNodes[j].getAttribute('label');
1259                 if (params.skip_hidden_columns) if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') continue;
1260                 var id = obj.columns[j].id; if (params.labels_instead_of_ids) id = obj.columns[j].label;
1261                 row[ id ] = value;
1262             }
1263             dump.push( row );
1264         }
1265         return dump;
1266     },
1267
1268     'clipboard' : function(params) {
1269         try {
1270             var obj = this;
1271             var dump = obj.dump_selected_with_keys({'skip_hidden_columns':true,'labels_instead_of_ids':true});
1272             JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
1273             data.list_clipboard = dump; data.stash('list_clipboard');
1274             JSAN.use('util.window'); var win = new util.window();
1275             win.open(urls.XUL_LIST_CLIPBOARD,'list_clipboard','chrome,resizable,modal');
1276             window.focus(); // sometimes the main window will lower after a clipboard action
1277         } catch(E) {
1278             this.error.standard_unexpected_error_alert('clipboard',E);
1279         }
1280     },
1281
1282     'dump_retrieve_ids' : function(params) {
1283         var obj = this;
1284         switch(this.node.nodeName) {
1285             case 'tree' : return this._dump_retrieve_ids_tree(params); break;
1286             default: throw('NYI: Need .dump_retrieve_ids() for ' + this.node.nodeName); break;
1287         }
1288     },
1289
1290     '_dump_retrieve_ids_tree' : function(params) {
1291         var obj = this;
1292         var dump = [];
1293         for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1294             var treeitem = this.treechildren.childNodes[i];
1295             dump.push( treeitem.getAttribute('retrieve_id') );
1296         }
1297         return dump;
1298     },
1299
1300     'wrap_in_full_retrieve' : function(f) {
1301         var obj = this;
1302         if (typeof obj.on_all_fleshed == 'function') { // legacy
1303             obj.on_all_fleshed = [ obj.on_all_fleshed ];
1304         }
1305         if (! obj.on_all_fleshed) obj.on_all_fleshed = [];
1306         obj.on_all_fleshed.push(f);
1307         obj.full_retrieve();
1308     },
1309
1310     '_sort_tree' : function(col,sortDir) {
1311         var obj = this;
1312         try {
1313             if (obj.node.getAttribute('no_sort')) {
1314                 return;
1315             }
1316             var col_pos;
1317             for (var i = 0; i < obj.columns.length; i++) { 
1318                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1319             }
1320             obj.wrap_in_full_retrieve(
1321                 function() {
1322                     try {
1323                         JSAN.use('util.money');
1324                         var rows = [];
1325                         var treeitems = obj.treechildren.childNodes;
1326                         for (var i = 0; i < treeitems.length; i++) {
1327                             var treeitem = treeitems[i];
1328                             var treerow = treeitem.firstChild;
1329                             var treecell = treerow.childNodes[ col_pos ];
1330                             value = ( { 'value' : treecell ? treecell.getAttribute('label') : '', 'node' : treeitem } );
1331                             rows.push( value );
1332                         }
1333                         rows = rows.sort( function(a,b) { 
1334                             a = a.value; b = b.value; 
1335                             if (col.getAttribute('sort_type')) {
1336                                 switch(col.getAttribute('sort_type')) {
1337                                     case 'number' :
1338                                         a = Number(a); b = Number(b);
1339                                     break;
1340                                     case 'money' :
1341                                         a = util.money.dollars_float_to_cents_integer(a);
1342                                         b = util.money.dollars_float_to_cents_integer(b);
1343                                     break;
1344                                     case 'title' : /* special case for "a" and "the".  doesn't use marc 245 indicator */
1345                                         a = String( a ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1346                                         b = String( b ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1347                                     break;
1348                                     default:
1349                                         a = String( a ).toUpperCase();
1350                                         b = String( b ).toUpperCase();
1351                                     break;
1352                                 }
1353                             } else {
1354                                 if (typeof a == 'string' || typeof b == 'string') {
1355                                     a = String( a ).toUpperCase();
1356                                     b = String( b ).toUpperCase();
1357                                 }
1358                             }
1359                             if (a < b) return -1; 
1360                             if (a > b) return 1; 
1361                             return 0; 
1362                         } );
1363                         if (sortDir == 'asc') rows = rows.reverse();
1364                         while(obj.treechildren.lastChild) obj.treechildren.removeChild( obj.treechildren.lastChild );
1365                         for (var i = 0; i < rows.length; i++) {
1366                             obj.treechildren.appendChild( rows[i].node );
1367                         }
1368                         if (typeof obj.on_sort == 'function') obj.on_sort();
1369                     } catch(E) {
1370                         obj.error.standard_unexpected_error_alert('sorting',E); 
1371                     }
1372                 }
1373             );
1374         } catch(E) {
1375             obj.error.standard_unexpected_error_alert('pre sorting', E);
1376         }
1377     },
1378
1379     '_toggle_checkbox_column' : function(col,toggle) {
1380         var obj = this;
1381         try {
1382             if (obj.node.getAttribute('no_toggle')) {
1383                 return;
1384             }
1385             var col_pos;
1386             for (var i = 0; i < obj.columns.length; i++) { 
1387                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1388             }
1389             var treeitems = obj.treechildren.childNodes;
1390             for (var i = 0; i < treeitems.length; i++) {
1391                 var treeitem = treeitems[i];
1392                 var treerow = treeitem.firstChild;
1393                 var treecell = treerow.childNodes[ col_pos ];
1394                 treecell.setAttribute('value',(toggle == 'on'));
1395             }
1396             if (typeof obj.on_checkbox_toggle == 'function') obj.on_checkbox_toggle(toggle);
1397         } catch(E) {
1398             obj.error.standard_unexpected_error_alert('pre toggle', E);
1399         }
1400     },
1401
1402     'render_list_actions' : function(params) {
1403         var obj = this;
1404         switch(this.node.nodeName) {
1405             case 'tree' : return this._render_list_actions_for_tree(params); break;
1406             default: throw('NYI: Need ._render_list_actions() for ' + this.node.nodeName); break;
1407         }
1408     },
1409
1410     '_render_list_actions_for_tree' : function(params) {
1411         var obj = this;
1412         try {
1413             var btn = document.createElement('button');
1414             btn.setAttribute('id',obj.node.id + '_list_actions');
1415             btn.setAttribute('type','menu');
1416             btn.setAttribute('allowevents','true');
1417             //btn.setAttribute('oncommand','this.firstChild.showPopup();');
1418             btn.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.menu.label'));
1419             btn.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.menu.accesskey'));
1420             var mp = document.createElement('menupopup');
1421             btn.appendChild(mp);
1422             var mi = document.createElement('menuitem');
1423             mi.setAttribute('id',obj.node.id + '_clipfield');
1424             mi.setAttribute('disabled','true');
1425             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.field_to_clipboard.label'));
1426             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.field_to_clipboard.accesskey'));
1427             mp.appendChild(mi);
1428             mi = document.createElement('menuitem');
1429             mi.setAttribute('id',obj.node.id + '_csv_to_clipboard');
1430             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_clipboard.label'));
1431             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_clipboard.accesskey'));
1432             mp.appendChild(mi);
1433             mi = document.createElement('menuitem');
1434             mi.setAttribute('id',obj.node.id + '_csv_to_printer');
1435             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_printer.label'));
1436             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_printer.accesskey'));
1437             mp.appendChild(mi);
1438             mi = document.createElement('menuitem');
1439             mi.setAttribute('id',obj.node.id + '_csv_to_file');
1440             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.csv_to_file.label'));
1441             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.csv_to_file.accesskey'));
1442             mp.appendChild(mi);
1443             mi = document.createElement('menuitem');
1444             mi.setAttribute('id',obj.node.id + '_save_columns');
1445             mi.setAttribute('label',document.getElementById('offlineStrings').getString('list.actions.save_column_configuration.label'));
1446             mi.setAttribute('accesskey',document.getElementById('offlineStrings').getString('list.actions.save_column_configuration.accesskey'));
1447             mp.appendChild(mi);
1448             return btn;
1449         } catch(E) {
1450             obj.error.standard_unexpected_error_alert('rendering list actions',E);
1451         }
1452     },
1453
1454     'set_list_actions' : function(params) {
1455         var obj = this;
1456         switch(this.node.nodeName) {
1457             case 'tree' : return this._set_list_actions_for_tree(params); break;
1458             default: throw('NYI: Need ._set_list_actions() for ' + this.node.nodeName); break;
1459         }
1460     },
1461
1462     '_set_list_actions_for_tree' : function(params) {
1463         // This should be called after the button element from render_list_actions has been appended to the DOM
1464         var obj = this;
1465         try {
1466             var x = document.getElementById(obj.node.id + '_clipfield');
1467             if (x) {
1468                 x.addEventListener(
1469                     'command',
1470                     function() {
1471                         obj.clipboard(params);
1472                         if (params && typeof params.on_complete == 'function') {
1473                             params.on_complete(params);
1474                         }
1475                     },
1476                     false
1477                 );
1478             }
1479             x = document.getElementById(obj.node.id + '_csv_to_clipboard');
1480             if (x) {
1481                 x.addEventListener(
1482                     'command',
1483                     function() {
1484                         obj.dump_csv_to_clipboard(params);
1485                         if (params && typeof params.on_complete == 'function') {
1486                             params.on_complete(params);
1487                         }
1488                     },
1489                     false
1490                 );
1491             }
1492             x = document.getElementById(obj.node.id + '_csv_to_printer');
1493             if (x) {
1494                 x.addEventListener(
1495                     'command',
1496                     function() {
1497                         obj.dump_csv_to_printer(params);
1498                         if (params && typeof params.on_complete == 'function') {
1499                             params.on_complete(params);
1500                         }
1501                     },
1502                     false
1503                 );
1504             }
1505             x = document.getElementById(obj.node.id + '_csv_to_file');
1506             if (x) {
1507                 x.addEventListener(
1508                     'command',
1509                     function() {
1510                         obj.dump_csv_to_file(params);
1511                         if (params && typeof params.on_complete == 'function') {
1512                             params.on_complete(params);
1513                         }
1514                     },
1515                     false
1516                 );
1517             }
1518             x = document.getElementById(obj.node.id + '_save_columns');
1519             if (x) {
1520                 x.addEventListener(
1521                     'command',
1522                     function() {
1523                         obj.save_columns(params);
1524                         if (params && typeof params.on_complete == 'function') {
1525                             params.on_complete(params);
1526                         }
1527                     },
1528                     false
1529                 );
1530             }
1531
1532         } catch(E) {
1533             obj.error.standard_unexpected_error_alert('setting list actions',E);
1534         }
1535     },
1536
1537     // Takes fieldmapper class name and attempts to spit out column definitions suitable for .init
1538     'fm_columns' : function(hint,column_extras) {
1539         var obj = this;
1540         var columns = [];
1541         try {
1542             // requires the dojo library fieldmapper.autoIDL
1543             if (typeof fieldmapper == 'undefined') { throw 'fieldmapper undefined'; }
1544             if (typeof fieldmapper.IDL == 'undefined') { throw 'fieldmapper.IDL undefined'; }
1545             if (typeof fieldmapper.IDL.fmclasses == 'undefined') { throw 'fieldmapper.IDL.fmclasses undefined'; }
1546             if (typeof fieldmapper.IDL.fmclasses[hint] == 'undefined') { throw 'fieldmapper.IDL.fmclasses.' + hint + ' undefined'; }
1547             var my_class = fieldmapper.IDL.fmclasses[hint]; 
1548
1549             function col_def(my_field) {
1550                 var col_id = hint + '_' + my_field.name;
1551                 var def = {
1552                     'id' : col_id,
1553                     'label' : my_field.label || my_field.name,
1554                     'sort_type' : [ 'int', 'float', 'id', 'number' ].indexOf(my_field.datatype) > -1 ? 'number' : ( my_field.datatype == 'money' ? 'money' : 'default'),
1555                     'hidden' : [ 'isnew', 'ischanged', 'isdeleted' ].indexOf(my_field.name) > -1,
1556                     'flex' : 1
1557                 };                    
1558                 // my_field.datatype => bool float id int interval link money number org_unit text timestamp
1559                 def.render = function(my) { return my[hint][my_field.name](); }
1560                 if (my_field.datatype == 'timestamp') {
1561                     dojo.require('dojo.date.locale');
1562                     dojo.require('dojo.date.stamp');
1563                     def.render = function(my) {
1564                         return dojo.date.locale.format( dojo.date.stamp.fromISOString(my[hint][my_field.name]()) );
1565                     }
1566                 }
1567                 if (my_field.datatype == 'org_unit') {
1568                     def.render = function(my) {
1569                         return typeof my[hint][my_field.name]() == 'object' ? my[hint][my_field.name]().shortname() : data.hash.aou[ my[hint][my_field.name]() ].shortname();
1570                     }
1571                 }
1572                 if (my_field.datatype == 'money') {
1573                     JSAN.use('util.money');
1574                     def.render = function(my) {
1575                         return util.money.sanitize( my[hint][my_field.name]() );
1576                     }
1577                 }
1578                 if (column_extras) {
1579                     if (column_extras['*']) {
1580                         for (var attr in column_extras['*']) {
1581                             def[attr] = column_extras['*'][attr];
1582                         }
1583                         if (column_extras['*']['expanded_label']) {
1584                             def.label = my_class.label + ': ' + def.label;
1585                         }
1586                     }
1587                     if (column_extras[col_id]) {
1588                         for (var attr in column_extras[col_id]) {
1589                             def[attr] = column_extras[col_id][attr];
1590                         }
1591                     }
1592                 }
1593                 return def;
1594             }
1595  
1596             for (var i = 0; i < my_class.fields.length; i++) {
1597                 var my_field = my_class.fields[i];
1598                 columns.push( col_def(my_field) );
1599             }
1600
1601         } catch(E) {
1602             obj.error.standard_unexpected_error_alert('fm_columns()',E);
1603         }
1604         return columns;
1605     },
1606     // Default for the map_row_to_columns function for .init
1607     'std_map_row_to_columns' : function(error_value) {
1608         return function(row,cols) {
1609             // row contains { 'my' : { 'acp' : {}, 'circ' : {}, 'mvr' : {} } }
1610             // cols contains all of the objects listed above in columns
1611
1612             var obj = {};
1613             JSAN.use('util.error'); obj.error = new util.error();
1614             JSAN.use('OpenILS.data'); obj.data = new OpenILS.data(); obj.data.init({'via':'stash'});
1615             JSAN.use('util.network'); obj.network = new util.network();
1616             JSAN.use('util.money');
1617
1618             var my = row.my;
1619             var values = [];
1620             var cmd = '';
1621             try {
1622                 for (var i = 0; i < cols.length; i++) {
1623                     switch (typeof cols[i].render) {
1624                         case 'function': try { values[i] = cols[i].render(my); } catch(E) { values[i] = error_value; obj.error.sdump('D_COLUMN_RENDER_ERROR',E); } break;
1625                         case 'string' : cmd += 'try { ' + cols[i].render + '; values['+i+'] = v; } catch(E) { values['+i+'] = error_value; }'; break;
1626                         default: cmd += 'values['+i+'] = "??? '+(typeof cols[i].render)+'"; ';
1627                     }
1628                 }
1629                 if (cmd) eval( cmd );
1630             } catch(E) {
1631                 obj.error.sdump('D_WARN','map_row_to_column: ' + E);
1632                 if (error_value) { value = error_value; } else { value = '   ' };
1633             }
1634             return values;
1635         }
1636     }
1637 }
1638 dump('exiting util.list.js\n');