]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/util/list.js
Bug fix for list class, where refresh-row functionality broke the on_all_fleshed...
[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         if (!this.node) throw('Could not find element ' + id);
11         switch(this.node.nodeName) {
12                 case 'listbox' : 
13                 case 'tree' : break;
14                 case 'richlistbox' :
15                         throw(this.node.nodeName + ' not yet supported'); break;
16                 default: throw(this.node.nodeName + ' not supported'); break;
17         }
18
19         JSAN.use('util.error'); this.error = new util.error();
20
21         return this;
22 };
23
24 util.list.prototype = {
25
26         'init' : function (params) {
27
28                 var obj = this;
29
30                 JSAN.use('util.widgets');
31
32                 if (typeof params.map_row_to_column == 'function') obj.map_row_to_column = params.map_row_to_column;
33                 if (typeof params.map_row_to_columns == 'function') obj.map_row_to_columns = params.map_row_to_columns;
34                 if (typeof params.retrieve_row == 'function') obj.retrieve_row = params.retrieve_row;
35
36                 obj.prebuilt = false;
37                 if (typeof params.prebuilt != 'undefined') obj.prebuilt = params.prebuilt;
38
39                 if (typeof params.columns == 'undefined') throw('util.list.init: No columns');
40                 obj.columns = params.columns;
41
42                 switch(obj.node.nodeName) {
43                         case 'tree' : obj._init_tree(params); break;
44                         case 'listbox' : obj._init_listbox(params); break;
45                         default: throw('NYI: Need ._init() for ' + obj.node.nodeName); break;
46                 }
47         },
48
49         'register_all_fleshed_callback' : function(f) {
50                 this.on_all_fleshed = f;
51         },
52
53         '_init_tree' : function (params) {
54                 var obj = this;
55                 if (this.prebuilt) {
56                 
57                         this.treechildren = this.node.lastChild;        
58                 
59                 } else {
60                         var treecols = document.createElement('treecols');
61                         this.node.appendChild(treecols);
62                         this.treecols = treecols;
63
64                         for (var i = 0; i < this.columns.length; i++) {
65                                 var treecol = document.createElement('treecol');
66                                 for (var j in this.columns[i]) {
67                                         treecol.setAttribute(j,this.columns[i][j]);
68                                 }
69                                 treecols.appendChild(treecol);
70                                 treecol.addEventListener(
71                                         'click', 
72                                         function(ev) {
73                                                 function do_it() {
74                                                         var sortDir = ev.target.getAttribute('sortDir') || 'desc';
75                                                         if (sortDir == 'desc') sortDir = 'asc'; else sortDir = 'desc';
76                                                         ev.target.setAttribute('sortDir',sortDir);
77                                                         obj._sort_tree(ev.target,sortDir);
78                                                 }
79
80                                                 if (obj.row_count.total != obj.row_count.fleshed && (obj.row_count.total - obj.row_count.fleshed) > 50) {
81                                                         var r = window.confirm('WARNING: Only ' + obj.row_count.fleshed + ' out of ' + obj.row_count.total + ' rows in this list have been retrieved for immediate viewing.  Sorting this list requires that all these rows be retrieved, and this may take some time and lag the staff client.  Would you like to proceed?');
82
83                                                         if (r) {
84                                                                 setTimeout( do_it, 0 );
85                                                         }
86                                                 } else {
87                                                                 setTimeout( do_it, 0 );
88                                                 }
89                                         },
90                                         false
91                                 );
92                                 var splitter = document.createElement('splitter');
93                                 splitter.setAttribute('class','tree-splitter');
94                                 treecols.appendChild(splitter);
95                         }
96
97                         var treechildren = document.createElement('treechildren');
98                         this.node.appendChild(treechildren);
99                         this.treechildren = treechildren;
100                 }
101                 if (typeof params.on_select == 'function') {
102                         this.node.addEventListener(
103                                 'select',
104                                 params.on_select,
105                                 false
106                         );
107                 }
108                 if (typeof params.on_click == 'function') {
109                         this.node.addEventListener(
110                                 'click',
111                                 params.on_click,
112                                 false
113                         );
114                 }
115                 /*
116                 this.node.addEventListener(
117                         'mousemove',
118                         function(ev) { obj.detect_visible(); },
119                         false
120                 );
121                 */
122                 this.node.addEventListener(
123                         'keypress',
124                         function(ev) { obj.auto_retrieve(); },
125                         false
126                 );
127                 this.node.addEventListener(
128                         'click',
129                         function(ev) { obj.auto_retrieve(); },
130                         false
131                 );
132                 window.addEventListener(
133                         'resize',
134                         function(ev) { obj.auto_retrieve(); },
135                         false
136                 );
137                 /* FIXME -- find events on scrollbar to trigger this */
138                 obj.detect_visible_polling();   
139                 /*
140                 var scrollbar = document.getAnonymousNodes( document.getAnonymousNodes(this.node)[1] )[1];
141                 var slider = document.getAnonymousNodes( scrollbar )[2];
142                 alert('scrollbar = ' + scrollbar.nodeName + ' grippy = ' + slider.nodeName);
143                 scrollbar.addEventListener('click',function(){alert('sb click');},false);
144                 scrollbar.addEventListener('command',function(){alert('sb command');},false);
145                 scrollbar.addEventListener('scroll',function(){alert('sb scroll');},false);
146                 slider.addEventListener('click',function(){alert('slider click');},false);
147                 slider.addEventListener('command',function(){alert('slider command');},false);
148                 slider.addEventListener('scroll',function(){alert('slider scroll');},false);
149                 */
150                 this.node.addEventListener('scroll',function(){ obj.auto_retrieve(); },false);
151
152                 this.restores_columns(params);
153         },
154
155         '_init_listbox' : function (params) {
156                 if (this.prebuilt) {
157                 } else {
158                         var listhead = document.createElement('listhead');
159                         this.node.appendChild(listhead);
160
161                         var listcols = document.createElement('listcols');
162                         this.node.appendChild(listcols);
163
164                         for (var i = 0; i < this.columns.length; i++) {
165                                 var listheader = document.createElement('listheader');
166                                 listhead.appendChild(listheader);
167                                 var listcol = document.createElement('listcol');
168                                 listcols.appendChild(listcol);
169                                 for (var j in this.columns[i]) {
170                                         listheader.setAttribute(j,this.columns[i][j]);
171                                         listcol.setAttribute(j,this.columns[i][j]);
172                                 };
173                         }
174                 }
175         },
176
177         'save_columns' : function (params) {
178                 var obj = this;
179                 switch (this.node.nodeName) {
180                         case 'tree' : this._save_columns_tree(params); break;
181                         default: throw('NYI: Need .save_columns() for ' + this.node.nodeName); break;
182                 }
183         },
184
185         '_save_columns_tree' : function (params) {
186                 var obj = this;
187                 try {
188                         var id = obj.node.getAttribute('id'); if (!id) {
189                                 alert("FIXME: The columns for this list cannot be saved because the list has no id.");
190                                 return;
191                         }
192                         var my_cols = {};
193                         var nl = obj.node.getElementsByTagName('treecol');
194                         for (var i = 0; i < nl.length; i++) {
195                                 var col = nl[i];
196                                 var col_id = col.getAttribute('id');
197                                 if (!col_id) {
198                                         alert('FIXME: A column in this list does not have an id and cannot be saved');
199                                         continue;
200                                 }
201                                 var col_hidden = col.getAttribute('hidden'); 
202                                 var col_width = col.getAttribute('width'); 
203                                 var col_ordinal = col.getAttribute('ordinal'); 
204                                 my_cols[ col_id ] = { 'hidden' : col_hidden, 'width' : col_width, 'ordinal' : col_ordinal };
205                         }
206                         netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
207                         JSAN.use('util.file'); var file = new util.file('tree_columns_for_'+window.escape(id));
208                         file.set_object(my_cols);
209                         file.close();
210                         alert('Columns saved.');
211                 } catch(E) {
212                         obj.error.standard_unexpected_error_alert('_save_columns_tree',E);
213                 }
214         },
215
216         'restores_columns' : function (params) {
217                 var obj = this;
218                 switch (this.node.nodeName) {
219                         case 'tree' : this._restores_columns_tree(params); break;
220                         default: throw('NYI: Need .restores_columns() for ' + this.node.nodeName); break;
221                 }
222         },
223
224         '_restores_columns_tree' : function (params) {
225                 var obj = this;
226                 try {
227                         var id = obj.node.getAttribute('id'); if (!id) {
228                                 alert("FIXME: The columns for this list cannot be restored because the list has no id.");
229                                 return;
230                         }
231
232                         netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
233                         JSAN.use('util.file'); var file = new util.file('tree_columns_for_'+window.escape(id));
234                         if (file._file.exists()) {
235                                 var my_cols = file.get_object(); file.close();
236                                 var nl = obj.node.getElementsByTagName('treecol');
237                                 for (var i = 0; i < nl.length; i++) {
238                                         var col = nl[i];
239                                         var col_id = col.getAttribute('id');
240                                         if (!col_id) {
241                                                 alert('FIXME: A column in this list does not have an id and cannot be saved');
242                                                 continue;
243                                         }
244                                         if (typeof my_cols[col_id] != 'undefined') {
245                                                 col.setAttribute('hidden',my_cols[col_id].hidden); 
246                                                 col.setAttribute('width',my_cols[col_id].width); 
247                                                 col.setAttribute('ordinal',my_cols[col_id].ordinal); 
248                                         } else {
249                                                 obj.error.sdump('D_ERROR','WARNING: Column ' + col_id + ' did not have a saved state.');
250                                         }
251                                 }
252                         }
253                 } catch(E) {
254                         obj.error.standard_unexpected_error_alert('_restore_columns_tree',E);
255                 }
256         },
257
258         'clear' : function (params) {
259                 var obj = this;
260                 switch (this.node.nodeName) {
261                         case 'tree' : this._clear_tree(params); break;
262                         case 'listbox' : this._clear_listbox(params); break;
263                         default: throw('NYI: Need .clear() for ' + this.node.nodeName); break;
264                 }
265                 this.error.sdump('D_LIST','Clearing list ' + this.node.getAttribute('id') + '\n');
266                 this.row_count.total = 0;
267                 this.row_count.fleshed = 0;
268                 if (typeof obj.on_all_fleshed == 'function') {
269                         setTimeout( function() { obj.on_all_fleshed(); }, 0 );
270                 }
271         },
272
273         '_clear_tree' : function(params) {
274                 var obj = this;
275                 if (obj.error.sdump_levels.D_LIST_DUMP_ON_CLEAR) {
276                         obj.error.sdump('D_LIST_DUMP_ON_CLEAR',obj.dump());
277                 }
278                 if (obj.error.sdump_levels.D_LIST_DUMP_WITH_KEYS_ON_CLEAR) {
279                         obj.error.sdump('D_LIST_DUMP_WITH_KEYS_ON_CLEAR',obj.dump_with_keys());
280                 }
281                 while (obj.treechildren.lastChild) obj.treechildren.removeChild( obj.treechildren.lastChild );
282         },
283
284         '_clear_listbox' : function(params) {
285                 var obj = this;
286                 var items = [];
287                 var nl = this.node.getElementsByTagName('listitem');
288                 for (var i = 0; i < nl.length; i++) {
289                         items.push( nl[i] );
290                 }
291                 for (var i = 0; i < items.length; i++) {
292                         this.node.removeChild(items[i]);
293                 }
294         },
295
296         'append' : function (params) {
297                 var rnode;
298                 var obj = this;
299                 switch (this.node.nodeName) {
300                         case 'tree' : rparams = this._append_to_tree(params); break;
301                         case 'listbox' : rparams = this._append_to_listbox(params); break;
302                         default: throw('NYI: Need .append() for ' + this.node.nodeName); break;
303                 }
304                 if (rparams && params.attributes) {
305                         for (var i in params.attributes) {
306                                 rparams.my_node.setAttribute(i,params.attributes[i]);
307                         }
308                 }
309                 this.row_count.total++;
310                 if (this.row_count.fleshed == this.row_count.total) {
311                         if (typeof this.on_all_fleshed == 'function') {
312                                 setTimeout( function() { obj.on_all_fleshed(); }, 0 );
313                         }
314                 }
315                 return rparams;
316         },
317         
318         'refresh_row' : function (params) {
319                 var rnode;
320                 var obj = this;
321                 switch (this.node.nodeName) {
322                         case 'tree' : rparams = this._refresh_row_in_tree(params); break;
323                         default: throw('NYI: Need .refresh_row() for ' + this.node.nodeName); break;
324                 }
325                 if (rparams && params.attributes) {
326                         for (var i in params.attributes) {
327                                 rparams.my_node.setAttribute(i,params.attributes[i]);
328                         }
329                 }
330         this.row_count.fleshed--;
331                 return rparams;
332         },
333
334
335         '_append_to_tree' : function (params) {
336
337                 var obj = this;
338
339                 if (typeof params.row == 'undefined') throw('util.list.append: Object must contain a row');
340
341                 var s = ('util.list.append: params = ' + (params) + '\n');
342
343                 var treechildren_node = this.treechildren;
344
345                 if (params.node && params.node.nodeName == 'treeitem') {
346                         params.node.setAttribute('container','true'); /* params.node.setAttribute('open','true'); */
347                         if (params.node.lastChild.nodeName == 'treechildren') {
348                                 treechildren_node = params.node.lastChild;
349                         } else {
350                                 treechildren_node = document.createElement('treechildren');
351                                 params.node.appendChild(treechildren_node);
352                         }
353                 }
354
355                 var treeitem = document.createElement('treeitem');
356                 treeitem.setAttribute('retrieve_id',params.retrieve_id);
357                 if (typeof params.to_bottom != 'undefined') {
358                         treechildren_node.appendChild( treeitem );
359                         if (typeof params.no_auto_select == 'undefined') {
360                                 if (!obj.auto_select_pending) {
361                                         obj.auto_select_pending = true;
362                                         setTimeout(function() {
363                                                 dump('auto-selecting\n');
364                                                 var idx = Number(obj.node.view.rowCount)-1;
365                                                 try { obj.node.view.selection.select(idx); } catch(E) { obj.error.sdump('D_ALERT','tree auto select: ' + E + '\n'); }
366                                                 try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, on_select: ' + E + '\n'); }
367                                                 obj.auto_select_pending = false;
368                                                 try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(idx).firstChild); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, flesh: ' + E + '\n'); }
369                                         }, 1000);
370                                 }
371                         }
372                 } else {
373                         if (treechildren_node.firstChild) {
374                                 treechildren_node.insertBefore( treeitem, treechildren_node.firstChild );
375                         } else {
376                                 treechildren_node.appendChild( treeitem );
377                         }
378                         if (typeof params.no_auto_select == 'undefined') {
379                                 if (!obj.auto_select_pending) {
380                                         obj.auto_select_pending = true;
381                                         setTimeout(function() {
382                                                 try { obj.node.view.selection.select(0); } catch(E) { obj.error.sdump('D_ALERT','tree auto select: ' + E + '\n'); }
383                                                 try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, on_select: ' + E + '\n'); }
384                                                 obj.auto_select_pending = false;
385                                                 try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(0).firstChild); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, flesh: ' + E + '\n'); }
386                                         }, 1000);
387                                 }
388                         }
389                 }
390                 var treerow = document.createElement('treerow');
391                 treeitem.appendChild( treerow );
392                 treerow.setAttribute('retrieve_id',params.retrieve_id);
393
394                 s += ('tree = ' + this.node + '  treechildren = ' + treechildren_node + '\n');
395                 s += ('treeitem = ' + treeitem + '  treerow = ' + treerow + '\n');
396
397                 if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
398
399                         obj.put_retrieving_label(treerow);
400                         treerow.addEventListener(
401                                 'flesh',
402                                 function() {
403
404                                         if (treerow.getAttribute('retrieved') == 'true') return; /* already running */
405
406                                         treerow.setAttribute('retrieved','true');
407
408                                         //dump('fleshing = ' + params.retrieve_id + '\n');
409
410                                         function inc_fleshed() {
411                                                 if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
412                                                 treerow.setAttribute('fleshed','true');
413                                                 obj.row_count.fleshed++;
414                                                 if (obj.row_count.fleshed >= obj.row_count.total) {
415                                                         if (typeof obj.on_all_fleshed == 'function') {
416                                                                 setTimeout( function() { obj.on_all_fleshed(); }, 0 );
417                                                         }
418                                                 }
419                                         }
420
421                                         params.row_node = treeitem;
422                                         params.on_retrieve = function(p) {
423                                                 try {
424                                                         p.row = params.row;
425                                                         obj._map_row_to_treecell(p,treerow);
426                                                         inc_fleshed();
427                                                         var idx = obj.node.contentView.getIndexOfItem( params.row_node );
428                                                         dump('idx = ' + idx + '\n');
429                                                         // if current row is selected, send another select event to re-sync data that the client code fetches on selects
430                                                         if ( obj.node.view.selection.isSelected( idx ) ) {
431                                                                 dump('dispatching select event for on_retrieve for idx = ' + idx + '\n');
432                                                                 util.widgets.dispatch('select',params.row_node);
433                                                         }
434                                                 } catch(E) {
435                                                         alert('fixme2: ' + E);
436                                                 }
437                                         }
438
439                                         if (typeof params.retrieve_row == 'function') {
440
441                                                 params.retrieve_row( params );
442
443                                         } else if (typeof obj.retrieve_row == 'function') {
444
445                                                         obj.retrieve_row( params );
446
447                                         } else {
448                                         
449                                                         inc_fleshed();
450                                         }
451                                 },
452                                 false
453                         );
454                         /*
455                         setTimeout(
456                                 function() {
457                                         util.widgets.dispatch('flesh',treerow);
458                                 }, 0
459                         );
460                         */
461                 } else {
462                         obj.put_retrieving_label(treerow);
463                         treerow.addEventListener(
464                                 'flesh',
465                                 function() {
466                                         //dump('fleshing anon\n');
467                                         if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
468                                         obj._map_row_to_treecell(params,treerow);
469                                         treerow.setAttribute('retrieved','true');
470                                         treerow.setAttribute('fleshed','true');
471                                         obj.row_count.fleshed++;
472                                         if (obj.row_count.fleshed >= obj.row_count.total) {
473                                                 if (typeof obj.on_all_fleshed == 'function') {
474                                                         setTimeout( function() { obj.on_all_fleshed(); }, 0 );
475                                                 }
476                                         }
477                                 },
478                                 false
479                         );
480                         /*
481                         setTimeout(
482                                 function() {
483                                         util.widgets.dispatch('flesh',treerow);
484                                 }, 0
485                         );
486                         */
487                 }
488                 this.error.sdump('D_LIST',s);
489
490                         try {
491
492                                 if (obj.trim_list && obj.row_count.total >= obj.trim_list) {
493                                         // Remove oldest row
494                                         //if (typeof params.to_bottom != 'undefined') 
495                                         if (typeof params.to_top == 'undefined') {
496                                                 treechildren_node.removeChild( treechildren_node.firstChild );
497                                         } else {
498                                                 treechildren_node.removeChild( treechildren_node.lastChild );
499                                         }
500                                 }
501                         } catch(E) {
502                         }
503
504                 setTimeout( function() { obj.auto_retrieve(); }, 0 );
505
506                 params.my_node = treeitem;
507                 return params;
508         },
509
510         '_refresh_row_in_tree' : function (params) {
511
512                 var obj = this;
513
514                 if (typeof params.row == 'undefined') throw('util.list.refresh_row: Object must contain a row');
515                 if (typeof params.my_node == 'undefined') throw('util.list.refresh_row: Object must contain a my_node');
516                 if (params.my_node.nodeName != 'treeitem') throw('util.list.refresh_rwo: my_node must be a treeitem');
517
518                 var s = ('util.list.refresh_row: params = ' + (params) + '\n');
519
520                 var treeitem = params.my_node;
521                 treeitem.setAttribute('retrieve_id',params.retrieve_id);
522                 if (typeof params.to_bottom != 'undefined') {
523                         if (typeof params.no_auto_select == 'undefined') {
524                                 if (!obj.auto_select_pending) {
525                                         obj.auto_select_pending = true;
526                                         setTimeout(function() {
527                                                 dump('auto-selecting\n');
528                                                 var idx = Number(obj.node.view.rowCount)-1;
529                                                 try { obj.node.view.selection.select(idx); } catch(E) { obj.error.sdump('D_ALERT','tree auto select: ' + E + '\n'); }
530                                                 try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, on_select: ' + E + '\n'); }
531                                                 obj.auto_select_pending = false;
532                                                 try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(idx).firstChild); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, flesh: ' + E + '\n'); }
533                                         }, 1000);
534                                 }
535                         }
536                 }
537                 var delete_me = [];
538                 for (var i in treeitem.childNodes) if (treeitem.childNodes[i].nodeName == 'treerow') delete_me.push(treeitem.childNodes[i]);
539                 for (var i = 0; i < delete_me.length; i++) treeitem.removeChild(delete_me[i]);
540                 var treerow = document.createElement('treerow');
541                 treeitem.appendChild( treerow );
542                 treerow.setAttribute('retrieve_id',params.retrieve_id);
543
544                 s += ('tree = ' + this.node + '\n');
545                 s += ('treeitem = ' + treeitem + '  treerow = ' + treerow + '\n');
546
547                 if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
548
549                         obj.put_retrieving_label(treerow);
550                         treerow.addEventListener(
551                                 'flesh',
552                                 function() {
553
554                                         if (treerow.getAttribute('retrieved') == 'true') return; /* already running */
555
556                                         treerow.setAttribute('retrieved','true');
557
558                                         //dump('fleshing = ' + params.retrieve_id + '\n');
559
560                                         function inc_fleshed() {
561                                                 if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
562                                                 treerow.setAttribute('fleshed','true');
563                                                 obj.row_count.fleshed++;
564                                                 if (obj.row_count.fleshed >= obj.row_count.total) {
565                                                         if (typeof obj.on_all_fleshed == 'function') {
566                                                                 setTimeout( function() { obj.on_all_fleshed(); }, 0 );
567                                                         }
568                                                 }
569                                         }
570
571                                         params.row_node = treeitem;
572                                         params.on_retrieve = function(p) {
573                                                 try {
574                                                         p.row = params.row;
575                                                         obj._map_row_to_treecell(p,treerow);
576                                                         inc_fleshed();
577                                                         var idx = obj.node.contentView.getIndexOfItem( params.row_node );
578                                                         dump('idx = ' + idx + '\n');
579                                                         // if current row is selected, send another select event to re-sync data that the client code fetches on selects
580                                                         if ( obj.node.view.selection.isSelected( idx ) ) {
581                                                                 dump('dispatching select event for on_retrieve for idx = ' + idx + '\n');
582                                                                 util.widgets.dispatch('select',params.row_node);
583                                                         }
584                                                 } catch(E) {
585                                                         alert('fixme2: ' + E);
586                                                 }
587                                         }
588
589                                         if (typeof params.retrieve_row == 'function') {
590
591                                                 params.retrieve_row( params );
592
593                                         } else if (typeof obj.retrieve_row == 'function') {
594
595                                                         obj.retrieve_row( params );
596
597                                         } else {
598                                         
599                                                         inc_fleshed();
600                                         }
601                                 },
602                                 false
603                         );
604                         /*
605                         setTimeout(
606                                 function() {
607                                         util.widgets.dispatch('flesh',treerow);
608                                 }, 0
609                         );
610                         */
611                 } else {
612                         obj.put_retrieving_label(treerow);
613                         treerow.addEventListener(
614                                 'flesh',
615                                 function() {
616                                         //dump('fleshing anon\n');
617                                         if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
618                                         obj._map_row_to_treecell(params,treerow);
619                                         treerow.setAttribute('retrieved','true');
620                                         treerow.setAttribute('fleshed','true');
621                                         obj.row_count.fleshed++;
622                                         if (obj.row_count.fleshed >= obj.row_count.total) {
623                                                 if (typeof obj.on_all_fleshed == 'function') {
624                                                         setTimeout( function() { obj.on_all_fleshed(); }, 0 );
625                                                 }
626                                         }
627                                 },
628                                 false
629                         );
630                         /*
631                         setTimeout(
632                                 function() {
633                                         util.widgets.dispatch('flesh',treerow);
634                                 }, 0
635                         );
636                         */
637                 }
638                 this.error.sdump('D_LIST',s);
639
640                         try {
641
642                                 if (obj.trim_list && obj.row_count.total >= obj.trim_list) {
643                                         // Remove oldest row
644                                         //if (typeof params.to_bottom != 'undefined') 
645                                         if (typeof params.to_top == 'undefined') {
646                                                 treechildren_node.removeChild( treechildren_node.firstChild );
647                                         } else {
648                                                 treechildren_node.removeChild( treechildren_node.lastChild );
649                                         }
650                                 }
651                         } catch(E) {
652                         }
653
654                 setTimeout( function() { obj.auto_retrieve(); }, 0 );
655
656                 return params;
657         },
658
659         'put_retrieving_label' : function(treerow) {
660                 var obj = this;
661                 try {
662                         /*
663                         var cols_idx = 0;
664                         dump('put_retrieving_label.  columns = ' + js2JSON(obj.columns) + '\n');
665                         while( obj.columns[cols_idx] && obj.columns[cols_idx].hidden && obj.columns[cols_idx].hidden == 'true') {
666                                 dump('\t' + cols_idx);
667                                 var treecell = document.createElement('treecell');
668                                 treerow.appendChild(treecell);
669                                 cols_idx++;
670                         }
671                         */
672                         for (var i = 0; i < obj.columns.length; i++) {
673                         var treecell = document.createElement('treecell'); treecell.setAttribute('label','Retrieving...');
674                         treerow.appendChild(treecell);
675                         }
676                         /*
677                         dump('\t' + cols_idx + '\n');
678                         */
679                 } catch(E) {
680                         alert(E);
681                 }
682         },
683
684         'detect_visible' : function() {
685                 var obj = this;
686                 try {
687                         //dump('detect_visible  obj.node = ' + obj.node + '\n');
688                         /* FIXME - this is a hack.. if the implementation of tree changes, this could break */
689                         try {
690                                 var scrollbar = document.getAnonymousNodes( document.getAnonymousNodes(obj.node)[1] )[1];
691                                 var curpos = scrollbar.getAttribute('curpos');
692                                 var maxpos = scrollbar.getAttribute('maxpos');
693                                 //alert('curpos = ' + curpos + ' maxpos = ' + maxpos + ' obj.curpos = ' + obj.curpos + ' obj.maxpos = ' + obj.maxpos + '\n');
694                                 if ((curpos != obj.curpos) || (maxpos != obj.maxpos)) {
695                                         if ( obj.auto_retrieve() > 0 ) {
696                                                 obj.curpos = curpos; obj.maxpos = maxpos;
697                                         }
698                                 }
699                         } catch(E) {
700                                 obj.error.sdump('D_XULRUNNER', 'List implementation changed? ' + E);
701                         }
702                 } catch(E) { obj.error.sdump('D_ERROR',E); }
703         },
704
705         'detect_visible_polling' : function() {
706                 try {
707                         //alert('detect_visible_polling');
708                         var obj = this;
709                         obj.detect_visible();
710                         setTimeout(function() { try { obj.detect_visible_polling(); } catch(E) { alert(E); } },2000);
711                 } catch(E) {
712                         alert(E);
713                 }
714         },
715
716
717         'auto_retrieve' : function(params) {
718                 var obj = this;
719                 switch (this.node.nodeName) {
720                         case 'tree' : obj._auto_retrieve_tree(params); break;
721                         default: throw('NYI: Need .auto_retrieve() for ' + obj.node.nodeName); break;
722                 }
723         },
724
725         '_auto_retrieve_tree' : function (params) {
726                 var obj = this;
727                 if (!obj.auto_retrieve_in_progress) {
728                         obj.auto_retrieve_in_progress = true;
729                         setTimeout(
730                                 function() {
731                                         try {
732                                                         //alert('auto_retrieve\n');
733                                                         var count = 0;
734                                                         var startpos = obj.node.treeBoxObject.getFirstVisibleRow();
735                                                         var endpos = obj.node.treeBoxObject.getLastVisibleRow();
736                                                         if (startpos > endpos) endpos = obj.node.treeBoxObject.getPageLength();
737                                                         //dump('startpos = ' + startpos + ' endpos = ' + endpos + '\n');
738                                                         for (var i = startpos; i < endpos + 4; i++) {
739                                                                 try {
740                                                                         //dump('trying index ' + i + '\n');
741                                                                         var item = obj.node.contentView.getItemAtIndex(i).firstChild;
742                                                                         if (item && item.getAttribute('retrieved') != 'true' ) {
743                                                                                 //dump('\tgot an unfleshed item = ' + item + ' = ' + item.nodeName + '\n');
744                                                                                 util.widgets.dispatch('flesh',item); count++;
745                                                                         }
746                                                                 } catch(E) {
747                                                                         //dump(i + ' : ' + E + '\n');
748                                                                 }
749                                                         }
750                                                         obj.auto_retrieve_in_progress = false;
751                                                         return count;
752                                         } catch(E) { alert(E); }
753                                 }, 1
754                         );
755                 }
756         },
757
758         'full_retrieve' : function(params) {
759                 var obj = this;
760                 switch (this.node.nodeName) {
761                         case 'tree' : obj._full_retrieve_tree(params); break;
762                         default: throw('NYI: Need .full_retrieve() for ' + obj.node.nodeName); break;
763                 }
764         },
765
766         '_full_retrieve_tree' : function(params) {
767                 var obj = this;
768                 try {
769                         if (obj.row_count.fleshed >= obj.row_count.total) {
770                                 dump('Full retrieve... tree seems to be in sync\n' + js2JSON(obj.row_count) + '\n');
771                                 if (typeof obj.on_all_fleshed == 'function') {
772                                         setTimeout( function() { obj.on_all_fleshed(); }, 0 );
773                                 } else {
774                                         dump('.full_retrieve called with no callback?' + '\n');
775                                 }
776                         } else {
777                                 dump('Full retrieve... syncing tree' + js2JSON(obj.row_count) + '\n');
778                                 JSAN.use('util.widgets');
779                                 var nodes = obj.treechildren.childNodes;
780                                 for (var i = 0; i < nodes.length; i++) {
781                                         util.widgets.dispatch('flesh',nodes[i].firstChild);
782                                 }
783                         }
784                 } catch(E) {
785                         obj.error.standard_unexpected_error_alert('_full_retrieve_tree',E);
786                 }
787         },
788
789         '_append_to_listbox' : function (params) {
790
791                 var obj = this;
792
793                 if (typeof params.row == 'undefined') throw('util.list.append: Object must contain a row');
794
795                 var s = ('util.list.append: params = ' + (params) + '\n');
796
797                 var listitem = document.createElement('listitem');
798
799                 s += ('listbox = ' + this.node + '  listitem = ' + listitem + '\n');
800
801                 if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
802
803                         setTimeout(
804                                 function() {
805                                         listitem.setAttribute('retrieve_id',params.retrieve_id);
806                                         //FIXME//Make async and fire when row is visible in list
807                                         var row;
808
809                                         params.row_node = listitem;
810                                         params.on_retrieve = function(row) {
811                                                 params.row = row;
812                                                 obj._map_row_to_listcell(params,listitem);
813                                                 obj.node.appendChild( listitem );
814                                                 util.widgets.dispatch('select',params.row_node);
815                                         }
816
817                                         if (typeof params.retrieve_row == 'function') {
818
819                                                 row = params.retrieve_row( params );
820
821                                         } else {
822
823                                                 if (typeof obj.retrieve_row == 'function') {
824
825                                                         row = obj.retrieve_row( params );
826
827                                                 }
828                                         }
829                                 }, 0
830                         );
831                 } else {
832                         this._map_row_to_listcell(params,listitem);
833                         this.node.appendChild( listitem );
834                 }
835
836                 this.error.sdump('D_LIST',s);
837                 params.my_node = listitem;
838                 return params;
839
840         },
841
842         '_map_row_to_treecell' : function(params,treerow) {
843                 var obj = this;
844                 var s = '';
845                 util.widgets.remove_children(treerow);
846
847                 if (typeof params.map_row_to_column == 'function' || typeof this.map_row_to_column == 'function') {
848
849                         for (var i = 0; i < this.columns.length; i++) {
850                                 var treecell = document.createElement('treecell');
851                                 var label = '';
852                                 if (params.skip_columns && (params.skip_columns.indexOf(i) != -1)) {
853                                         treecell.setAttribute('label',label);
854                                         treerow.appendChild( treecell );
855                                         s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
856                                         continue;
857                                 }
858                                 if (params.skip_all_columns_except && (params.skip_all_columns_except.indexOf(i) == -1)) {
859                                         treecell.setAttribute('label',label);
860                                         treerow.appendChild( treecell );
861                                         s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
862                                         continue;
863                                 }
864         
865                                 if (typeof params.map_row_to_column == 'function')  {
866         
867                                         label = params.map_row_to_column(params.row,this.columns[i]);
868         
869                                 } else if (typeof this.map_row_to_column == 'function') {
870         
871                                         label = this.map_row_to_column(params.row,this.columns[i]);
872         
873                                 }
874                                 treecell.setAttribute('label',label ? label : '');
875                                 treerow.appendChild( treecell );
876                                 s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
877                         }
878                 } else if (typeof params.map_row_to_columns == 'function' || typeof this.map_row_to_columns == 'function') {
879
880                         var labels = [];
881
882                         if (typeof params.map_row_to_columns == 'function') {
883
884                                 labels = params.map_row_to_columns(params.row,this.columns);
885
886                         } else if (typeof this.map_row_to_columns == 'function') {
887
888                                 labels = this.map_row_to_columns(params.row,this.columns);
889
890                         }
891                         for (var i = 0; i < labels.length; i++) {
892                                 var treecell = document.createElement('treecell');
893                                 treecell.setAttribute('label',typeof labels[i] == 'string' || typeof labels[i] == 'number' ? labels[i] : '');
894                                 treerow.appendChild( treecell );
895                                 s += ('treecell = ' + treecell + ' with label = ' + labels[i] + '\n');
896                         }
897
898                 } else {
899
900                         throw('No row to column mapping function.');
901                 }
902                 this.error.sdump('D_LIST',s);
903         },
904
905         '_map_row_to_listcell' : function(params,listitem) {
906                 var obj = this;
907                 var s = '';
908                 for (var i = 0; i < this.columns.length; i++) {
909                         var value = '';
910                         if (typeof params.map_row_to_column == 'function')  {
911
912                                 value = params.map_row_to_column(params.row,this.columns[i]);
913
914                         } else {
915
916                                 if (typeof this.map_row_to_column == 'function') {
917
918                                         value = this.map_row_to_column(params.row,this.columns[i]);
919                                 }
920                         }
921                         if (typeof value == 'string' || typeof value == 'number') {
922                                 var listcell = document.createElement('listcell');
923                                 listcell.setAttribute('label',value);
924                                 listitem.appendChild(listcell);
925                                 s += ('listcell = ' + listcell + ' with label = ' + value + '\n');
926                         } else {
927                                 listitem.appendChild(value);
928                                 s += ('listcell = ' + value + ' is really a ' + value.nodeName + '\n');
929                         }
930                 }
931                 this.error.sdump('D_LIST',s);
932         },
933
934         'select_all' : function(params) {
935                 var obj = this;
936                 switch(this.node.nodeName) {
937                         case 'tree' : return this._select_all_from_tree(params); break;
938                         default: throw('NYI: Need ._select_all_from_() for ' + this.node.nodeName); break;
939                 }
940         },
941
942         '_select_all_from_tree' : function(params) {
943                 var obj = this;
944                 this.node.view.selection.selectAll();
945         },
946
947         'retrieve_selection' : function(params) {
948                 var obj = this;
949                 switch(this.node.nodeName) {
950                         case 'tree' : return this._retrieve_selection_from_tree(params); break;
951                         default: throw('NYI: Need ._retrieve_selection_from_() for ' + this.node.nodeName); break;
952                 }
953         },
954
955         '_retrieve_selection_from_tree' : function(params) {
956                 var obj = this;
957                 var list = [];
958                 var start = new Object();
959                 var end = new Object();
960                 var numRanges = this.node.view.selection.getRangeCount();
961                 for (var t=0; t<numRanges; t++){
962                         this.node.view.selection.getRangeAt(t,start,end);
963                         for (var v=start.value; v<=end.value; v++){
964                                 var i = this.node.contentView.getItemAtIndex(v);
965                                 list.push( i );
966                         }
967                 }
968                 return list;
969         },
970
971         'dump' : function(params) {
972                 var obj = this;
973                 switch(this.node.nodeName) {
974                         case 'tree' : return this._dump_tree(params); break;
975                         default: throw('NYI: Need .dump() for ' + this.node.nodeName); break;
976                 }
977         },
978
979         '_dump_tree' : function(params) {
980                 var obj = this;
981                 var dump = [];
982                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
983                         var row = [];
984                         var treeitem = this.treechildren.childNodes[i];
985                         var treerow = treeitem.firstChild;
986                         for (var j = 0; j < treerow.childNodes.length; j++) {
987                                 row.push( treerow.childNodes[j].getAttribute('label') );
988                         }
989                         dump.push( row );
990                 }
991                 return dump;
992         },
993
994         'dump_with_keys' : function(params) {
995                 var obj = this;
996                 switch(this.node.nodeName) {
997                         case 'tree' : return this._dump_tree_with_keys(params); break;
998                         default: throw('NYI: Need .dump_with_keys() for ' + this.node.nodeName); break;
999                 }
1000
1001         },
1002
1003         '_dump_tree_with_keys' : function(params) {
1004                 var obj = this;
1005                 var dump = [];
1006                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1007                         var row = {};
1008                         var treeitem = this.treechildren.childNodes[i];
1009                         var treerow = treeitem.firstChild;
1010                         for (var j = 0; j < treerow.childNodes.length; j++) {
1011                                 row[ obj.columns[j].id ] = treerow.childNodes[j].getAttribute('label');
1012                         }
1013                         dump.push( row );
1014                 }
1015                 return dump;
1016         },
1017
1018         'dump_csv' : function(params) {
1019                 var obj = this;
1020                 switch(this.node.nodeName) {
1021                         case 'tree' : return this._dump_tree_csv(params); break;
1022                         default: throw('NYI: Need .dump_csv() for ' + this.node.nodeName); break;
1023                 }
1024
1025         },
1026
1027         '_dump_tree_csv' : function(params) {
1028                 var obj = this;
1029                 var dump = '';
1030                 for (var j = 0; j < obj.columns.length; j++) {
1031                         if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1032                                 /* skip */
1033                         } else {
1034                                 if (dump) dump += ',';
1035                                 dump += '"' + obj.columns[j].label.replace(/"/g, '""') + '"';
1036                         }
1037                 }
1038                 dump += '\r\n';
1039                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1040                         var row = '';
1041                         var treeitem = this.treechildren.childNodes[i];
1042                         var treerow = treeitem.firstChild;
1043                         for (var j = 0; j < treerow.childNodes.length; j++) {
1044                                 if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1045                                         /* skip */
1046                                 } else {
1047                                         if (row) row += ',';
1048                                         row += '"' + treerow.childNodes[j].getAttribute('label').replace(/"/g, '""') + '"';
1049                                 }
1050                         }
1051                         dump +=  row + '\r\n';
1052                 }
1053                 return dump;
1054         },
1055
1056         'dump_selected_with_keys' : function(params) {
1057                 var obj = this;
1058                 switch(this.node.nodeName) {
1059                         case 'tree' : return this._dump_tree_selection_with_keys(params); break;
1060                         default: throw('NYI: Need .dump_selection_with_keys() for ' + this.node.nodeName); break;
1061                 }
1062
1063         },
1064
1065         '_dump_tree_selection_with_keys' : function(params) {
1066                 var obj = this;
1067                 var dump = [];
1068                 var list = obj._retrieve_selection_from_tree();
1069                 for (var i = 0; i < list.length; i++) {
1070                         var row = {};
1071                         var treeitem = list[i];
1072                         var treerow = treeitem.firstChild;
1073                         for (var j = 0; j < treerow.childNodes.length; j++) {
1074                                 var value = treerow.childNodes[j].getAttribute('label');
1075                                 if (params.skip_hidden_columns) if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') continue;
1076                                 var id = obj.columns[j].id; if (params.labels_instead_of_ids) id = obj.columns[j].label;
1077                                 row[ id ] = value;
1078                         }
1079                         dump.push( row );
1080                 }
1081                 return dump;
1082         },
1083
1084         'clipboard' : function() {
1085                 try {
1086                         var obj = this;
1087                         var dump = obj.dump_selected_with_keys({'skip_hidden_columns':true,'labels_instead_of_ids':true});
1088                         JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
1089                         data.list_clipboard = dump; data.stash('list_clipboard');
1090                         JSAN.use('util.window'); var win = new util.window();
1091                         win.open(urls.XUL_LIST_CLIPBOARD,'list_clipboard','chrome,resizable,modal');
1092                 } catch(E) {
1093                         this.error.standard_unexpected_error_alert('clipboard',E);
1094                 }
1095         },
1096
1097         'dump_retrieve_ids' : function(params) {
1098                 var obj = this;
1099                 switch(this.node.nodeName) {
1100                         case 'tree' : return this._dump_retrieve_ids_tree(params); break;
1101                         default: throw('NYI: Need .dump_retrieve_ids() for ' + this.node.nodeName); break;
1102                 }
1103         },
1104
1105         '_dump_retrieve_ids_tree' : function(params) {
1106                 var obj = this;
1107                 var dump = [];
1108                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1109                         var treeitem = this.treechildren.childNodes[i];
1110                         dump.push( treeitem.getAttribute('retrieve_id') );
1111                 }
1112                 return dump;
1113         },
1114
1115         '_sort_tree' : function(col,sortDir) {
1116                 var obj = this;
1117                 try {
1118                         if (obj.node.getAttribute('no_sort')) {
1119                                 return;
1120                         }
1121                         if (obj.on_all_fleshed) {
1122                                 var r = window.confirm('This list is busy rendering/retrieving data.  Abort current action and proceed?');
1123                                 if (r) {} else { return; }
1124                         }
1125                         var col_pos;
1126                         for (var i = 0; i < obj.columns.length; i++) { 
1127                                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1128                         }
1129                         obj.on_all_fleshed = function() {
1130                                         try {
1131                                                 JSAN.use('util.money');
1132                                                 var rows = [];
1133                                                 var treeitems = obj.treechildren.childNodes;
1134                                                 for (var i = 0; i < treeitems.length; i++) {
1135                                                         var treeitem = treeitems[i];
1136                                                         var treerow = treeitem.firstChild;
1137                                                         var treecell = treerow.childNodes[ col_pos ];
1138                                                         value = ( { 'value' : treecell ? treecell.getAttribute('label') : '', 'node' : treeitem } );
1139                                                         rows.push( value );
1140                                                 }
1141                                                 rows = rows.sort( function(a,b) { 
1142                                                         a = a.value; b = b.value; 
1143                                                         if (col.getAttribute('sort_type')) {
1144                                                                 switch(col.getAttribute('sort_type')) {
1145                                                                         case 'number' :
1146                                                                                 a = Number(a); b = Number(b);
1147                                                                         break;
1148                                                                         case 'money' :
1149                                                                                 a = util.money.dollars_float_to_cents_integer(a);
1150                                                                                 b = util.money.dollars_float_to_cents_integer(b);
1151                                                                         break;
1152                                                                         case 'title' : /* special case for "a" and "the".  doesn't use marc 245 indicator */
1153                                                                                 a = String( a ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1154                                                                                 b = String( b ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1155                                                                         break;
1156                                                                         default:
1157                                                                                 a = String( a ).toUpperCase();
1158                                                                                 b = String( b ).toUpperCase();
1159                                                                         break;
1160                                                                 }
1161                                                         } else {
1162                                                                 if (typeof a == 'string' || typeof b == 'string') {
1163                                                                         a = String( a ).toUpperCase();
1164                                                                         b = String( b ).toUpperCase();
1165                                                                 }
1166                                                         }
1167                                                         if (a < b) return -1; 
1168                                                         if (a > b) return 1; 
1169                                                         return 0; 
1170                                                 } );
1171                                                 if (sortDir == 'asc') rows = rows.reverse();
1172                                                 while(obj.treechildren.lastChild) obj.treechildren.removeChild( obj.treechildren.lastChild );
1173                                                 for (var i = 0; i < rows.length; i++) {
1174                                                         obj.treechildren.appendChild( rows[i].node );
1175                                                 }
1176                                         } catch(E) {
1177                                                 obj.error.standard_unexpected_error_alert('sorting',E); 
1178                                         }
1179                                         setTimeout(function(){ obj.on_all_fleshed = null; },0);
1180                                 }
1181                         obj.full_retrieve();
1182                 } catch(E) {
1183                         obj.error.standard_unexpected_error_alert('pre sorting', E);
1184                 }
1185         },
1186
1187 }
1188 dump('exiting util.list.js\n');