]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/util/list.js
changed on_retrieve callback for list.full_retrieve so that it can handle an array...
[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',obj.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',obj.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         JSAN.use('util.widgets'); util.widgets.dispatch('select',obj.node);
657
658                 return params;
659         },
660
661         'put_retrieving_label' : function(treerow) {
662                 var obj = this;
663                 try {
664                         /*
665                         var cols_idx = 0;
666                         dump('put_retrieving_label.  columns = ' + js2JSON(obj.columns) + '\n');
667                         while( obj.columns[cols_idx] && obj.columns[cols_idx].hidden && obj.columns[cols_idx].hidden == 'true') {
668                                 dump('\t' + cols_idx);
669                                 var treecell = document.createElement('treecell');
670                                 treerow.appendChild(treecell);
671                                 cols_idx++;
672                         }
673                         */
674                         for (var i = 0; i < obj.columns.length; i++) {
675                         var treecell = document.createElement('treecell'); treecell.setAttribute('label','Retrieving...');
676                         treerow.appendChild(treecell);
677                         }
678                         /*
679                         dump('\t' + cols_idx + '\n');
680                         */
681                 } catch(E) {
682                         alert(E);
683                 }
684         },
685
686         'detect_visible' : function() {
687                 var obj = this;
688                 try {
689                         //dump('detect_visible  obj.node = ' + obj.node + '\n');
690                         /* FIXME - this is a hack.. if the implementation of tree changes, this could break */
691                         try {
692                                 var scrollbar = document.getAnonymousNodes( document.getAnonymousNodes(obj.node)[1] )[1];
693                                 var curpos = scrollbar.getAttribute('curpos');
694                                 var maxpos = scrollbar.getAttribute('maxpos');
695                                 //alert('curpos = ' + curpos + ' maxpos = ' + maxpos + ' obj.curpos = ' + obj.curpos + ' obj.maxpos = ' + obj.maxpos + '\n');
696                                 if ((curpos != obj.curpos) || (maxpos != obj.maxpos)) {
697                                         if ( obj.auto_retrieve() > 0 ) {
698                                                 obj.curpos = curpos; obj.maxpos = maxpos;
699                                         }
700                                 }
701                         } catch(E) {
702                                 obj.error.sdump('D_XULRUNNER', 'List implementation changed? ' + E);
703                         }
704                 } catch(E) { obj.error.sdump('D_ERROR',E); }
705         },
706
707         'detect_visible_polling' : function() {
708                 try {
709                         //alert('detect_visible_polling');
710                         var obj = this;
711                         obj.detect_visible();
712                         setTimeout(function() { try { obj.detect_visible_polling(); } catch(E) { alert(E); } },2000);
713                 } catch(E) {
714                         alert(E);
715                 }
716         },
717
718
719         'auto_retrieve' : function(params) {
720                 var obj = this;
721                 switch (this.node.nodeName) {
722                         case 'tree' : obj._auto_retrieve_tree(params); break;
723                         default: throw('NYI: Need .auto_retrieve() for ' + obj.node.nodeName); break;
724                 }
725         },
726
727         '_auto_retrieve_tree' : function (params) {
728                 var obj = this;
729                 if (!obj.auto_retrieve_in_progress) {
730                         obj.auto_retrieve_in_progress = true;
731                         setTimeout(
732                                 function() {
733                                         try {
734                                                         //alert('auto_retrieve\n');
735                                                         var count = 0;
736                                                         var startpos = obj.node.treeBoxObject.getFirstVisibleRow();
737                                                         var endpos = obj.node.treeBoxObject.getLastVisibleRow();
738                                                         if (startpos > endpos) endpos = obj.node.treeBoxObject.getPageLength();
739                                                         //dump('startpos = ' + startpos + ' endpos = ' + endpos + '\n');
740                                                         for (var i = startpos; i < endpos + 4; i++) {
741                                                                 try {
742                                                                         //dump('trying index ' + i + '\n');
743                                                                         var item = obj.node.contentView.getItemAtIndex(i).firstChild;
744                                                                         if (item && item.getAttribute('retrieved') != 'true' ) {
745                                                                                 //dump('\tgot an unfleshed item = ' + item + ' = ' + item.nodeName + '\n');
746                                                                                 util.widgets.dispatch('flesh',item); count++;
747                                                                         }
748                                                                 } catch(E) {
749                                                                         //dump(i + ' : ' + E + '\n');
750                                                                 }
751                                                         }
752                                                         obj.auto_retrieve_in_progress = false;
753                                                         return count;
754                                         } catch(E) { alert(E); }
755                                 }, 1
756                         );
757                 }
758         },
759
760         'full_retrieve' : function(params) {
761                 var obj = this;
762                 switch (this.node.nodeName) {
763                         case 'tree' : obj._full_retrieve_tree(params); break;
764                         default: throw('NYI: Need .full_retrieve() for ' + obj.node.nodeName); break;
765                 }
766         },
767
768         '_full_retrieve_tree' : function(params) {
769                 var obj = this;
770                 try {
771                         if (obj.row_count.fleshed >= obj.row_count.total) {
772                                 dump('Full retrieve... tree seems to be in sync\n' + js2JSON(obj.row_count) + '\n');
773                                 if (typeof obj.on_all_fleshed == 'function') {
774                                         setTimeout( 
775                         function() { 
776                             try { obj.on_all_fleshed(); } catch(E) { obj.error.standard_unexpected_error_alert('_full_retrieve_tree callback',obj.on_all_fleshed); }
777                         }, 0 
778                     );
779                                 } else if (typeof obj.on_all_fleshed.length != 'undefined') {
780                     setTimeout(
781                         function() {
782                             var f = obj.on_all_fleshed.pop();
783                             if (typeof f == 'function') { 
784                                 try { f(); } catch(E) { obj.error.standard_unexpected_error_alert('_full_retrieve_tree callback',f); } 
785                             } else { 
786                                 obj.error.standard_unexpected_error_alert('_full_retrieve_tree callback: expected a function',f);
787                             }
788                             if (obj.on_all_fleshed.length > 0) arguments.callee(); 
789                         }, 0
790                     ); 
791                 } else {
792                                         dump('.full_retrieve called with no callback?' + '\n');
793                                 }
794                         } else {
795                                 dump('Full retrieve... syncing tree' + js2JSON(obj.row_count) + '\n');
796                                 JSAN.use('util.widgets');
797                                 var nodes = obj.treechildren.childNodes;
798                                 for (var i = 0; i < nodes.length; i++) {
799                                         util.widgets.dispatch('flesh',nodes[i].firstChild);
800                                 }
801                         }
802                 } catch(E) {
803                         obj.error.standard_unexpected_error_alert('_full_retrieve_tree',E);
804                 }
805         },
806
807         '_append_to_listbox' : function (params) {
808
809                 var obj = this;
810
811                 if (typeof params.row == 'undefined') throw('util.list.append: Object must contain a row');
812
813                 var s = ('util.list.append: params = ' + (params) + '\n');
814
815                 var listitem = document.createElement('listitem');
816
817                 s += ('listbox = ' + this.node + '  listitem = ' + listitem + '\n');
818
819                 if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
820
821                         setTimeout(
822                                 function() {
823                                         listitem.setAttribute('retrieve_id',params.retrieve_id);
824                                         //FIXME//Make async and fire when row is visible in list
825                                         var row;
826
827                                         params.row_node = listitem;
828                                         params.on_retrieve = function(row) {
829                                                 params.row = row;
830                                                 obj._map_row_to_listcell(params,listitem);
831                                                 obj.node.appendChild( listitem );
832                                                 util.widgets.dispatch('select',obj.node);
833                                         }
834
835                                         if (typeof params.retrieve_row == 'function') {
836
837                                                 row = params.retrieve_row( params );
838
839                                         } else {
840
841                                                 if (typeof obj.retrieve_row == 'function') {
842
843                                                         row = obj.retrieve_row( params );
844
845                                                 }
846                                         }
847                                 }, 0
848                         );
849                 } else {
850                         this._map_row_to_listcell(params,listitem);
851                         this.node.appendChild( listitem );
852                 }
853
854                 this.error.sdump('D_LIST',s);
855                 params.my_node = listitem;
856                 return params;
857
858         },
859
860         '_map_row_to_treecell' : function(params,treerow) {
861                 var obj = this;
862                 var s = '';
863                 util.widgets.remove_children(treerow);
864
865                 if (typeof params.map_row_to_column == 'function' || typeof this.map_row_to_column == 'function') {
866
867                         for (var i = 0; i < this.columns.length; i++) {
868                                 var treecell = document.createElement('treecell');
869                                 var label = '';
870                                 if (params.skip_columns && (params.skip_columns.indexOf(i) != -1)) {
871                                         treecell.setAttribute('label',label);
872                                         treerow.appendChild( treecell );
873                                         s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
874                                         continue;
875                                 }
876                                 if (params.skip_all_columns_except && (params.skip_all_columns_except.indexOf(i) == -1)) {
877                                         treecell.setAttribute('label',label);
878                                         treerow.appendChild( treecell );
879                                         s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
880                                         continue;
881                                 }
882         
883                                 if (typeof params.map_row_to_column == 'function')  {
884         
885                                         label = params.map_row_to_column(params.row,this.columns[i]);
886         
887                                 } else if (typeof this.map_row_to_column == 'function') {
888         
889                                         label = this.map_row_to_column(params.row,this.columns[i]);
890         
891                                 }
892                                 treecell.setAttribute('label',label ? label : '');
893                                 treerow.appendChild( treecell );
894                                 s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
895                         }
896                 } else if (typeof params.map_row_to_columns == 'function' || typeof this.map_row_to_columns == 'function') {
897
898                         var labels = [];
899
900                         if (typeof params.map_row_to_columns == 'function') {
901
902                                 labels = params.map_row_to_columns(params.row,this.columns);
903
904                         } else if (typeof this.map_row_to_columns == 'function') {
905
906                                 labels = this.map_row_to_columns(params.row,this.columns);
907
908                         }
909                         for (var i = 0; i < labels.length; i++) {
910                                 var treecell = document.createElement('treecell');
911                                 treecell.setAttribute('label',typeof labels[i] == 'string' || typeof labels[i] == 'number' ? labels[i] : '');
912                                 treerow.appendChild( treecell );
913                                 s += ('treecell = ' + treecell + ' with label = ' + labels[i] + '\n');
914                         }
915
916                 } else {
917
918                         throw('No row to column mapping function.');
919                 }
920                 this.error.sdump('D_LIST',s);
921         },
922
923         '_map_row_to_listcell' : function(params,listitem) {
924                 var obj = this;
925                 var s = '';
926                 for (var i = 0; i < this.columns.length; i++) {
927                         var value = '';
928                         if (typeof params.map_row_to_column == 'function')  {
929
930                                 value = params.map_row_to_column(params.row,this.columns[i]);
931
932                         } else {
933
934                                 if (typeof this.map_row_to_column == 'function') {
935
936                                         value = this.map_row_to_column(params.row,this.columns[i]);
937                                 }
938                         }
939                         if (typeof value == 'string' || typeof value == 'number') {
940                                 var listcell = document.createElement('listcell');
941                                 listcell.setAttribute('label',value);
942                                 listitem.appendChild(listcell);
943                                 s += ('listcell = ' + listcell + ' with label = ' + value + '\n');
944                         } else {
945                                 listitem.appendChild(value);
946                                 s += ('listcell = ' + value + ' is really a ' + value.nodeName + '\n');
947                         }
948                 }
949                 this.error.sdump('D_LIST',s);
950         },
951
952         'select_all' : function(params) {
953                 var obj = this;
954                 switch(this.node.nodeName) {
955                         case 'tree' : return this._select_all_from_tree(params); break;
956                         default: throw('NYI: Need ._select_all_from_() for ' + this.node.nodeName); break;
957                 }
958         },
959
960         '_select_all_from_tree' : function(params) {
961                 var obj = this;
962                 this.node.view.selection.selectAll();
963         },
964
965         'retrieve_selection' : function(params) {
966                 var obj = this;
967                 switch(this.node.nodeName) {
968                         case 'tree' : return this._retrieve_selection_from_tree(params); break;
969                         default: throw('NYI: Need ._retrieve_selection_from_() for ' + this.node.nodeName); break;
970                 }
971         },
972
973         '_retrieve_selection_from_tree' : function(params) {
974                 var obj = this;
975                 var list = [];
976                 var start = new Object();
977                 var end = new Object();
978                 var numRanges = this.node.view.selection.getRangeCount();
979                 for (var t=0; t<numRanges; t++){
980                         this.node.view.selection.getRangeAt(t,start,end);
981                         for (var v=start.value; v<=end.value; v++){
982                                 var i = this.node.contentView.getItemAtIndex(v);
983                                 list.push( i );
984                         }
985                 }
986                 return list;
987         },
988
989         'dump' : function(params) {
990                 var obj = this;
991                 switch(this.node.nodeName) {
992                         case 'tree' : return this._dump_tree(params); break;
993                         default: throw('NYI: Need .dump() for ' + this.node.nodeName); break;
994                 }
995         },
996
997         '_dump_tree' : function(params) {
998                 var obj = this;
999                 var dump = [];
1000                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1001                         var row = [];
1002                         var treeitem = this.treechildren.childNodes[i];
1003                         var treerow = treeitem.firstChild;
1004                         for (var j = 0; j < treerow.childNodes.length; j++) {
1005                                 row.push( treerow.childNodes[j].getAttribute('label') );
1006                         }
1007                         dump.push( row );
1008                 }
1009                 return dump;
1010         },
1011
1012         'dump_with_keys' : function(params) {
1013                 var obj = this;
1014                 switch(this.node.nodeName) {
1015                         case 'tree' : return this._dump_tree_with_keys(params); break;
1016                         default: throw('NYI: Need .dump_with_keys() for ' + this.node.nodeName); break;
1017                 }
1018
1019         },
1020
1021         '_dump_tree_with_keys' : function(params) {
1022                 var obj = this;
1023                 var dump = [];
1024                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1025                         var row = {};
1026                         var treeitem = this.treechildren.childNodes[i];
1027                         var treerow = treeitem.firstChild;
1028                         for (var j = 0; j < treerow.childNodes.length; j++) {
1029                                 row[ obj.columns[j].id ] = treerow.childNodes[j].getAttribute('label');
1030                         }
1031                         dump.push( row );
1032                 }
1033                 return dump;
1034         },
1035
1036         'dump_csv' : function(params) {
1037                 var obj = this;
1038                 switch(this.node.nodeName) {
1039                         case 'tree' : return this._dump_tree_csv(params); break;
1040                         default: throw('NYI: Need .dump_csv() for ' + this.node.nodeName); break;
1041                 }
1042
1043         },
1044
1045         '_dump_tree_csv' : function(params) {
1046                 var obj = this;
1047                 var dump = '';
1048                 for (var j = 0; j < obj.columns.length; j++) {
1049                         if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1050                                 /* skip */
1051                         } else {
1052                                 if (dump) dump += ',';
1053                                 dump += '"' + obj.columns[j].label.replace(/"/g, '""') + '"';
1054                         }
1055                 }
1056                 dump += '\r\n';
1057                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1058                         var row = '';
1059                         var treeitem = this.treechildren.childNodes[i];
1060                         var treerow = treeitem.firstChild;
1061                         for (var j = 0; j < treerow.childNodes.length; j++) {
1062                                 if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1063                                         /* skip */
1064                                 } else {
1065                                         if (row) row += ',';
1066                                         row += '"' + treerow.childNodes[j].getAttribute('label').replace(/"/g, '""') + '"';
1067                                 }
1068                         }
1069                         dump +=  row + '\r\n';
1070                 }
1071                 return dump;
1072         },
1073
1074     'dump_csv_to_clipboard' : function(params) {
1075         var obj = this;
1076         if (params && params.no_full_retrieve) {
1077             copy_to_clipboard( obj.dump_csv( params ) );
1078         } else {
1079             obj.wrap_in_full_retrieve( function() { copy_to_clipboard( obj.dump_csv( params ) ); } );
1080         }
1081     },
1082
1083         'dump_selected_with_keys' : function(params) {
1084                 var obj = this;
1085                 switch(this.node.nodeName) {
1086                         case 'tree' : return this._dump_tree_selection_with_keys(params); break;
1087                         default: throw('NYI: Need .dump_selection_with_keys() for ' + this.node.nodeName); break;
1088                 }
1089
1090         },
1091
1092         '_dump_tree_selection_with_keys' : function(params) {
1093                 var obj = this;
1094                 var dump = [];
1095                 var list = obj._retrieve_selection_from_tree();
1096                 for (var i = 0; i < list.length; i++) {
1097                         var row = {};
1098                         var treeitem = list[i];
1099                         var treerow = treeitem.firstChild;
1100                         for (var j = 0; j < treerow.childNodes.length; j++) {
1101                                 var value = treerow.childNodes[j].getAttribute('label');
1102                                 if (params.skip_hidden_columns) if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') continue;
1103                                 var id = obj.columns[j].id; if (params.labels_instead_of_ids) id = obj.columns[j].label;
1104                                 row[ id ] = value;
1105                         }
1106                         dump.push( row );
1107                 }
1108                 return dump;
1109         },
1110
1111         'clipboard' : function() {
1112                 try {
1113                         var obj = this;
1114                         var dump = obj.dump_selected_with_keys({'skip_hidden_columns':true,'labels_instead_of_ids':true});
1115                         JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
1116                         data.list_clipboard = dump; data.stash('list_clipboard');
1117                         JSAN.use('util.window'); var win = new util.window();
1118                         win.open(urls.XUL_LIST_CLIPBOARD,'list_clipboard','chrome,resizable,modal');
1119                 } catch(E) {
1120                         this.error.standard_unexpected_error_alert('clipboard',E);
1121                 }
1122         },
1123
1124         'dump_retrieve_ids' : function(params) {
1125                 var obj = this;
1126                 switch(this.node.nodeName) {
1127                         case 'tree' : return this._dump_retrieve_ids_tree(params); break;
1128                         default: throw('NYI: Need .dump_retrieve_ids() for ' + this.node.nodeName); break;
1129                 }
1130         },
1131
1132         '_dump_retrieve_ids_tree' : function(params) {
1133                 var obj = this;
1134                 var dump = [];
1135                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1136                         var treeitem = this.treechildren.childNodes[i];
1137                         dump.push( treeitem.getAttribute('retrieve_id') );
1138                 }
1139                 return dump;
1140         },
1141
1142     'wrap_in_full_retrieve' : function(f) {
1143         var obj = this;
1144                 if (typeof obj.on_all_fleshed == 'function') { // legacy
1145             obj.on_all_fleshed = [ obj.on_all_fleshed ];
1146                 }
1147         if (! obj.on_all_fleshed) obj.on_all_fleshed = [];
1148         obj.on_all_fleshed.push(f);
1149         obj.full_retrieve();
1150     },
1151
1152         '_sort_tree' : function(col,sortDir) {
1153                 var obj = this;
1154                 try {
1155                         if (obj.node.getAttribute('no_sort')) {
1156                                 return;
1157                         }
1158                         if (obj.on_all_fleshed) {
1159                                 var r = window.confirm('This list is busy rendering/retrieving data.  Abort current action and proceed?');
1160                                 if (r) {} else { return; }
1161                         }
1162                         var col_pos;
1163                         for (var i = 0; i < obj.columns.length; i++) { 
1164                                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1165                         }
1166                         obj.on_all_fleshed = function() {
1167                                         try {
1168                                                 JSAN.use('util.money');
1169                                                 var rows = [];
1170                                                 var treeitems = obj.treechildren.childNodes;
1171                                                 for (var i = 0; i < treeitems.length; i++) {
1172                                                         var treeitem = treeitems[i];
1173                                                         var treerow = treeitem.firstChild;
1174                                                         var treecell = treerow.childNodes[ col_pos ];
1175                                                         value = ( { 'value' : treecell ? treecell.getAttribute('label') : '', 'node' : treeitem } );
1176                                                         rows.push( value );
1177                                                 }
1178                                                 rows = rows.sort( function(a,b) { 
1179                                                         a = a.value; b = b.value; 
1180                                                         if (col.getAttribute('sort_type')) {
1181                                                                 switch(col.getAttribute('sort_type')) {
1182                                                                         case 'number' :
1183                                                                                 a = Number(a); b = Number(b);
1184                                                                         break;
1185                                                                         case 'money' :
1186                                                                                 a = util.money.dollars_float_to_cents_integer(a);
1187                                                                                 b = util.money.dollars_float_to_cents_integer(b);
1188                                                                         break;
1189                                                                         case 'title' : /* special case for "a" and "the".  doesn't use marc 245 indicator */
1190                                                                                 a = String( a ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1191                                                                                 b = String( b ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1192                                                                         break;
1193                                                                         default:
1194                                                                                 a = String( a ).toUpperCase();
1195                                                                                 b = String( b ).toUpperCase();
1196                                                                         break;
1197                                                                 }
1198                                                         } else {
1199                                                                 if (typeof a == 'string' || typeof b == 'string') {
1200                                                                         a = String( a ).toUpperCase();
1201                                                                         b = String( b ).toUpperCase();
1202                                                                 }
1203                                                         }
1204                                                         if (a < b) return -1; 
1205                                                         if (a > b) return 1; 
1206                                                         return 0; 
1207                                                 } );
1208                                                 if (sortDir == 'asc') rows = rows.reverse();
1209                                                 while(obj.treechildren.lastChild) obj.treechildren.removeChild( obj.treechildren.lastChild );
1210                                                 for (var i = 0; i < rows.length; i++) {
1211                                                         obj.treechildren.appendChild( rows[i].node );
1212                                                 }
1213                                         } catch(E) {
1214                                                 obj.error.standard_unexpected_error_alert('sorting',E); 
1215                                         }
1216                                         setTimeout(function(){ obj.on_all_fleshed = null; },0);
1217                                 }
1218                         obj.full_retrieve();
1219                 } catch(E) {
1220                         obj.error.standard_unexpected_error_alert('pre sorting', E);
1221                 }
1222         },
1223
1224 }
1225 dump('exiting util.list.js\n');