]> git.evergreen-ils.org Git - working/Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/util/list.js
refresh in place function for tree rows, adjustment to code that was expecting nodes...
[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                 return rparams;
331         },
332
333
334         '_append_to_tree' : function (params) {
335
336                 var obj = this;
337
338                 if (typeof params.row == 'undefined') throw('util.list.append: Object must contain a row');
339
340                 var s = ('util.list.append: params = ' + (params) + '\n');
341
342                 var treechildren_node = this.treechildren;
343
344                 if (params.node && params.node.nodeName == 'treeitem') {
345                         params.node.setAttribute('container','true'); /* params.node.setAttribute('open','true'); */
346                         if (params.node.lastChild.nodeName == 'treechildren') {
347                                 treechildren_node = params.node.lastChild;
348                         } else {
349                                 treechildren_node = document.createElement('treechildren');
350                                 params.node.appendChild(treechildren_node);
351                         }
352                 }
353
354                 var treeitem = document.createElement('treeitem');
355                 treeitem.setAttribute('retrieve_id',params.retrieve_id);
356                 if (typeof params.to_bottom != 'undefined') {
357                         treechildren_node.appendChild( treeitem );
358                         if (typeof params.no_auto_select == 'undefined') {
359                                 if (!obj.auto_select_pending) {
360                                         obj.auto_select_pending = true;
361                                         setTimeout(function() {
362                                                 dump('auto-selecting\n');
363                                                 var idx = Number(obj.node.view.rowCount)-1;
364                                                 try { obj.node.view.selection.select(idx); } catch(E) { obj.error.sdump('D_ALERT','tree auto select: ' + E + '\n'); }
365                                                 try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, on_select: ' + E + '\n'); }
366                                                 obj.auto_select_pending = false;
367                                                 try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(idx).firstChild); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, flesh: ' + E + '\n'); }
368                                         }, 1000);
369                                 }
370                         }
371                 } else {
372                         if (treechildren_node.firstChild) {
373                                 treechildren_node.insertBefore( treeitem, treechildren_node.firstChild );
374                         } else {
375                                 treechildren_node.appendChild( treeitem );
376                         }
377                         if (typeof params.no_auto_select == 'undefined') {
378                                 if (!obj.auto_select_pending) {
379                                         obj.auto_select_pending = true;
380                                         setTimeout(function() {
381                                                 try { obj.node.view.selection.select(0); } catch(E) { obj.error.sdump('D_ALERT','tree auto select: ' + E + '\n'); }
382                                                 try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, on_select: ' + E + '\n'); }
383                                                 obj.auto_select_pending = false;
384                                                 try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(0).firstChild); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, flesh: ' + E + '\n'); }
385                                         }, 1000);
386                                 }
387                         }
388                 }
389                 var treerow = document.createElement('treerow');
390                 treeitem.appendChild( treerow );
391                 treerow.setAttribute('retrieve_id',params.retrieve_id);
392
393                 s += ('tree = ' + this.node + '  treechildren = ' + treechildren_node + '\n');
394                 s += ('treeitem = ' + treeitem + '  treerow = ' + treerow + '\n');
395
396                 if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
397
398                         obj.put_retrieving_label(treerow);
399                         treerow.addEventListener(
400                                 'flesh',
401                                 function() {
402
403                                         if (treerow.getAttribute('retrieved') == 'true') return; /* already running */
404
405                                         treerow.setAttribute('retrieved','true');
406
407                                         //dump('fleshing = ' + params.retrieve_id + '\n');
408
409                                         function inc_fleshed() {
410                                                 if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
411                                                 treerow.setAttribute('fleshed','true');
412                                                 obj.row_count.fleshed++;
413                                                 if (obj.row_count.fleshed == obj.row_count.total) {
414                                                         if (typeof obj.on_all_fleshed == 'function') {
415                                                                 setTimeout( function() { obj.on_all_fleshed(); }, 0 );
416                                                         }
417                                                 }
418                                         }
419
420                                         params.row_node = treeitem;
421                                         params.on_retrieve = function(p) {
422                                                 try {
423                                                         p.row = params.row;
424                                                         obj._map_row_to_treecell(p,treerow);
425                                                         inc_fleshed();
426                                                         var idx = obj.node.contentView.getIndexOfItem( params.row_node );
427                                                         dump('idx = ' + idx + '\n');
428                                                         // if current row is selected, send another select event to re-sync data that the client code fetches on selects
429                                                         if ( obj.node.view.selection.isSelected( idx ) ) {
430                                                                 dump('dispatching select event for on_retrieve for idx = ' + idx + '\n');
431                                                                 util.widgets.dispatch('select',params.row_node);
432                                                         }
433                                                 } catch(E) {
434                                                         alert('fixme2: ' + E);
435                                                 }
436                                         }
437
438                                         if (typeof params.retrieve_row == 'function') {
439
440                                                 params.retrieve_row( params );
441
442                                         } else if (typeof obj.retrieve_row == 'function') {
443
444                                                         obj.retrieve_row( params );
445
446                                         } else {
447                                         
448                                                         inc_fleshed();
449                                         }
450                                 },
451                                 false
452                         );
453                         /*
454                         setTimeout(
455                                 function() {
456                                         util.widgets.dispatch('flesh',treerow);
457                                 }, 0
458                         );
459                         */
460                 } else {
461                         obj.put_retrieving_label(treerow);
462                         treerow.addEventListener(
463                                 'flesh',
464                                 function() {
465                                         //dump('fleshing anon\n');
466                                         if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
467                                         obj._map_row_to_treecell(params,treerow);
468                                         treerow.setAttribute('retrieved','true');
469                                         treerow.setAttribute('fleshed','true');
470                                         obj.row_count.fleshed++;
471                                         if (obj.row_count.fleshed == obj.row_count.total) {
472                                                 if (typeof obj.on_all_fleshed == 'function') {
473                                                         setTimeout( function() { obj.on_all_fleshed(); }, 0 );
474                                                 }
475                                         }
476                                 },
477                                 false
478                         );
479                         /*
480                         setTimeout(
481                                 function() {
482                                         util.widgets.dispatch('flesh',treerow);
483                                 }, 0
484                         );
485                         */
486                 }
487                 this.error.sdump('D_LIST',s);
488
489                         try {
490
491                                 if (obj.trim_list && obj.row_count.total >= obj.trim_list) {
492                                         // Remove oldest row
493                                         //if (typeof params.to_bottom != 'undefined') 
494                                         if (typeof params.to_top == 'undefined') {
495                                                 treechildren_node.removeChild( treechildren_node.firstChild );
496                                         } else {
497                                                 treechildren_node.removeChild( treechildren_node.lastChild );
498                                         }
499                                 }
500                         } catch(E) {
501                         }
502
503                 setTimeout( function() { obj.auto_retrieve(); }, 0 );
504
505                 params.my_node = treeitem;
506                 return params;
507         },
508
509         '_refresh_row_in_tree' : function (params) {
510
511                 var obj = this;
512
513                 if (typeof params.row == 'undefined') throw('util.list.refresh_row: Object must contain a row');
514                 if (typeof params.my_node == 'undefined') throw('util.list.refresh_row: Object must contain a my_node');
515                 if (params.my_node.nodeName != 'treeitem') throw('util.list.refresh_rwo: my_node must be a treeitem');
516
517                 var s = ('util.list.refresh_row: params = ' + (params) + '\n');
518
519                 var treeitem = params.my_node;
520                 treeitem.setAttribute('retrieve_id',params.retrieve_id);
521                 if (typeof params.to_bottom != 'undefined') {
522                         if (typeof params.no_auto_select == 'undefined') {
523                                 if (!obj.auto_select_pending) {
524                                         obj.auto_select_pending = true;
525                                         setTimeout(function() {
526                                                 dump('auto-selecting\n');
527                                                 var idx = Number(obj.node.view.rowCount)-1;
528                                                 try { obj.node.view.selection.select(idx); } catch(E) { obj.error.sdump('D_ALERT','tree auto select: ' + E + '\n'); }
529                                                 try { if (typeof params.on_select == 'function') params.on_select(); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, on_select: ' + E + '\n'); }
530                                                 obj.auto_select_pending = false;
531                                                 try { util.widgets.dispatch('flesh',obj.node.contentView.getItemAtIndex(idx).firstChild); } catch(E) { obj.error.sdump('D_ALERT','tree auto select, flesh: ' + E + '\n'); }
532                                         }, 1000);
533                                 }
534                         }
535                 }
536                 var delete_me = [];
537                 for (var i in treeitem.childNodes) if (treeitem.childNodes[i].nodeName == 'treerow') delete_me.push(treeitem.childNodes[i]);
538                 for (var i = 0; i < delete_me.length; i++) treeitem.removeChild(delete_me[i]);
539                 var treerow = document.createElement('treerow');
540                 treeitem.appendChild( treerow );
541                 treerow.setAttribute('retrieve_id',params.retrieve_id);
542
543                 s += ('tree = ' + this.node + '\n');
544                 s += ('treeitem = ' + treeitem + '  treerow = ' + treerow + '\n');
545
546                 if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
547
548                         obj.put_retrieving_label(treerow);
549                         treerow.addEventListener(
550                                 'flesh',
551                                 function() {
552
553                                         if (treerow.getAttribute('retrieved') == 'true') return; /* already running */
554
555                                         treerow.setAttribute('retrieved','true');
556
557                                         //dump('fleshing = ' + params.retrieve_id + '\n');
558
559                                         function inc_fleshed() {
560                                                 if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
561                                                 treerow.setAttribute('fleshed','true');
562                                                 obj.row_count.fleshed++;
563                                                 if (obj.row_count.fleshed == obj.row_count.total) {
564                                                         if (typeof obj.on_all_fleshed == 'function') {
565                                                                 setTimeout( function() { obj.on_all_fleshed(); }, 0 );
566                                                         }
567                                                 }
568                                         }
569
570                                         params.row_node = treeitem;
571                                         params.on_retrieve = function(p) {
572                                                 try {
573                                                         p.row = params.row;
574                                                         obj._map_row_to_treecell(p,treerow);
575                                                         inc_fleshed();
576                                                         var idx = obj.node.contentView.getIndexOfItem( params.row_node );
577                                                         dump('idx = ' + idx + '\n');
578                                                         // if current row is selected, send another select event to re-sync data that the client code fetches on selects
579                                                         if ( obj.node.view.selection.isSelected( idx ) ) {
580                                                                 dump('dispatching select event for on_retrieve for idx = ' + idx + '\n');
581                                                                 util.widgets.dispatch('select',params.row_node);
582                                                         }
583                                                 } catch(E) {
584                                                         alert('fixme2: ' + E);
585                                                 }
586                                         }
587
588                                         if (typeof params.retrieve_row == 'function') {
589
590                                                 params.retrieve_row( params );
591
592                                         } else if (typeof obj.retrieve_row == 'function') {
593
594                                                         obj.retrieve_row( params );
595
596                                         } else {
597                                         
598                                                         inc_fleshed();
599                                         }
600                                 },
601                                 false
602                         );
603                         /*
604                         setTimeout(
605                                 function() {
606                                         util.widgets.dispatch('flesh',treerow);
607                                 }, 0
608                         );
609                         */
610                 } else {
611                         obj.put_retrieving_label(treerow);
612                         treerow.addEventListener(
613                                 'flesh',
614                                 function() {
615                                         //dump('fleshing anon\n');
616                                         if (treerow.getAttribute('fleshed') == 'true') return; /* already fleshed */
617                                         obj._map_row_to_treecell(params,treerow);
618                                         treerow.setAttribute('retrieved','true');
619                                         treerow.setAttribute('fleshed','true');
620                                         obj.row_count.fleshed++;
621                                         if (obj.row_count.fleshed == obj.row_count.total) {
622                                                 if (typeof obj.on_all_fleshed == 'function') {
623                                                         setTimeout( function() { obj.on_all_fleshed(); }, 0 );
624                                                 }
625                                         }
626                                 },
627                                 false
628                         );
629                         /*
630                         setTimeout(
631                                 function() {
632                                         util.widgets.dispatch('flesh',treerow);
633                                 }, 0
634                         );
635                         */
636                 }
637                 this.error.sdump('D_LIST',s);
638
639                         try {
640
641                                 if (obj.trim_list && obj.row_count.total >= obj.trim_list) {
642                                         // Remove oldest row
643                                         //if (typeof params.to_bottom != 'undefined') 
644                                         if (typeof params.to_top == 'undefined') {
645                                                 treechildren_node.removeChild( treechildren_node.firstChild );
646                                         } else {
647                                                 treechildren_node.removeChild( treechildren_node.lastChild );
648                                         }
649                                 }
650                         } catch(E) {
651                         }
652
653                 setTimeout( function() { obj.auto_retrieve(); }, 0 );
654
655                 return params;
656         },
657
658         'put_retrieving_label' : function(treerow) {
659                 var obj = this;
660                 try {
661                         /*
662                         var cols_idx = 0;
663                         dump('put_retrieving_label.  columns = ' + js2JSON(obj.columns) + '\n');
664                         while( obj.columns[cols_idx] && obj.columns[cols_idx].hidden && obj.columns[cols_idx].hidden == 'true') {
665                                 dump('\t' + cols_idx);
666                                 var treecell = document.createElement('treecell');
667                                 treerow.appendChild(treecell);
668                                 cols_idx++;
669                         }
670                         */
671                         for (var i = 0; i < obj.columns.length; i++) {
672                         var treecell = document.createElement('treecell'); treecell.setAttribute('label','Retrieving...');
673                         treerow.appendChild(treecell);
674                         }
675                         /*
676                         dump('\t' + cols_idx + '\n');
677                         */
678                 } catch(E) {
679                         alert(E);
680                 }
681         },
682
683         'detect_visible' : function() {
684                 var obj = this;
685                 try {
686                         //dump('detect_visible  obj.node = ' + obj.node + '\n');
687                         /* FIXME - this is a hack.. if the implementation of tree changes, this could break */
688                         try {
689                                 var scrollbar = document.getAnonymousNodes( document.getAnonymousNodes(obj.node)[1] )[1];
690                                 var curpos = scrollbar.getAttribute('curpos');
691                                 var maxpos = scrollbar.getAttribute('maxpos');
692                                 //alert('curpos = ' + curpos + ' maxpos = ' + maxpos + ' obj.curpos = ' + obj.curpos + ' obj.maxpos = ' + obj.maxpos + '\n');
693                                 if ((curpos != obj.curpos) || (maxpos != obj.maxpos)) {
694                                         if ( obj.auto_retrieve() > 0 ) {
695                                                 obj.curpos = curpos; obj.maxpos = maxpos;
696                                         }
697                                 }
698                         } catch(E) {
699                                 obj.error.sdump('D_XULRUNNER', 'List implementation changed? ' + E);
700                         }
701                 } catch(E) { obj.error.sdump('D_ERROR',E); }
702         },
703
704         'detect_visible_polling' : function() {
705                 try {
706                         //alert('detect_visible_polling');
707                         var obj = this;
708                         obj.detect_visible();
709                         setTimeout(function() { try { obj.detect_visible_polling(); } catch(E) { alert(E); } },2000);
710                 } catch(E) {
711                         alert(E);
712                 }
713         },
714
715
716         'auto_retrieve' : function(params) {
717                 var obj = this;
718                 switch (this.node.nodeName) {
719                         case 'tree' : obj._auto_retrieve_tree(params); break;
720                         default: throw('NYI: Need .auto_retrieve() for ' + obj.node.nodeName); break;
721                 }
722         },
723
724         '_auto_retrieve_tree' : function (params) {
725                 var obj = this;
726                 if (!obj.auto_retrieve_in_progress) {
727                         obj.auto_retrieve_in_progress = true;
728                         setTimeout(
729                                 function() {
730                                         try {
731                                                         //alert('auto_retrieve\n');
732                                                         var count = 0;
733                                                         var startpos = obj.node.treeBoxObject.getFirstVisibleRow();
734                                                         var endpos = obj.node.treeBoxObject.getLastVisibleRow();
735                                                         if (startpos > endpos) endpos = obj.node.treeBoxObject.getPageLength();
736                                                         //dump('startpos = ' + startpos + ' endpos = ' + endpos + '\n');
737                                                         for (var i = startpos; i < endpos + 4; i++) {
738                                                                 try {
739                                                                         //dump('trying index ' + i + '\n');
740                                                                         var item = obj.node.contentView.getItemAtIndex(i).firstChild;
741                                                                         if (item && item.getAttribute('retrieved') != 'true' ) {
742                                                                                 //dump('\tgot an unfleshed item = ' + item + ' = ' + item.nodeName + '\n');
743                                                                                 util.widgets.dispatch('flesh',item); count++;
744                                                                         }
745                                                                 } catch(E) {
746                                                                         //dump(i + ' : ' + E + '\n');
747                                                                 }
748                                                         }
749                                                         obj.auto_retrieve_in_progress = false;
750                                                         return count;
751                                         } catch(E) { alert(E); }
752                                 }, 1
753                         );
754                 }
755         },
756
757         'full_retrieve' : function(params) {
758                 var obj = this;
759                 switch (this.node.nodeName) {
760                         case 'tree' : obj._full_retrieve_tree(params); break;
761                         default: throw('NYI: Need .full_retrieve() for ' + obj.node.nodeName); break;
762                 }
763         },
764
765         '_full_retrieve_tree' : function(params) {
766                 var obj = this;
767                 try {
768                         if (obj.row_count.total == obj.row_count.fleshed) {
769                                 //alert('Full retrieve... tree seems to be in sync\n' + js2JSON(obj.row_count));
770                                 if (typeof obj.on_all_fleshed == 'function') {
771                                         setTimeout( function() { obj.on_all_fleshed(); }, 0 );
772                                 } else {
773                                         alert('.full_retrieve called with no callback?');
774                                 }
775                         } else {
776                                 //alert('Full retrieve... syncing tree' + js2JSON(obj.row_count));
777                                 JSAN.use('util.widgets');
778                                 var nodes = obj.treechildren.childNodes;
779                                 for (var i = 0; i < nodes.length; i++) {
780                                         util.widgets.dispatch('flesh',nodes[i].firstChild);
781                                 }
782                         }
783                 } catch(E) {
784                         obj.error.standard_unexpected_error_alert('_full_retrieve_tree',E);
785                 }
786         },
787
788         '_append_to_listbox' : function (params) {
789
790                 var obj = this;
791
792                 if (typeof params.row == 'undefined') throw('util.list.append: Object must contain a row');
793
794                 var s = ('util.list.append: params = ' + (params) + '\n');
795
796                 var listitem = document.createElement('listitem');
797
798                 s += ('listbox = ' + this.node + '  listitem = ' + listitem + '\n');
799
800                 if (typeof params.retrieve_row == 'function' || typeof this.retrieve_row == 'function') {
801
802                         setTimeout(
803                                 function() {
804                                         listitem.setAttribute('retrieve_id',params.retrieve_id);
805                                         //FIXME//Make async and fire when row is visible in list
806                                         var row;
807
808                                         params.row_node = listitem;
809                                         params.on_retrieve = function(row) {
810                                                 params.row = row;
811                                                 obj._map_row_to_listcell(params,listitem);
812                                                 obj.node.appendChild( listitem );
813                                                 util.widgets.dispatch('select',params.row_node);
814                                         }
815
816                                         if (typeof params.retrieve_row == 'function') {
817
818                                                 row = params.retrieve_row( params );
819
820                                         } else {
821
822                                                 if (typeof obj.retrieve_row == 'function') {
823
824                                                         row = obj.retrieve_row( params );
825
826                                                 }
827                                         }
828                                 }, 0
829                         );
830                 } else {
831                         this._map_row_to_listcell(params,listitem);
832                         this.node.appendChild( listitem );
833                 }
834
835                 this.error.sdump('D_LIST',s);
836                 params.my_node = listitem;
837                 return params;
838
839         },
840
841         '_map_row_to_treecell' : function(params,treerow) {
842                 var obj = this;
843                 var s = '';
844                 util.widgets.remove_children(treerow);
845
846                 if (typeof params.map_row_to_column == 'function' || typeof this.map_row_to_column == 'function') {
847
848                         for (var i = 0; i < this.columns.length; i++) {
849                                 var treecell = document.createElement('treecell');
850                                 var label = '';
851                                 if (params.skip_columns && (params.skip_columns.indexOf(i) != -1)) {
852                                         treecell.setAttribute('label',label);
853                                         treerow.appendChild( treecell );
854                                         s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
855                                         continue;
856                                 }
857                                 if (params.skip_all_columns_except && (params.skip_all_columns_except.indexOf(i) == -1)) {
858                                         treecell.setAttribute('label',label);
859                                         treerow.appendChild( treecell );
860                                         s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
861                                         continue;
862                                 }
863         
864                                 if (typeof params.map_row_to_column == 'function')  {
865         
866                                         label = params.map_row_to_column(params.row,this.columns[i]);
867         
868                                 } else if (typeof this.map_row_to_column == 'function') {
869         
870                                         label = this.map_row_to_column(params.row,this.columns[i]);
871         
872                                 }
873                                 treecell.setAttribute('label',label ? label : '');
874                                 treerow.appendChild( treecell );
875                                 s += ('treecell = ' + treecell + ' with label = ' + label + '\n');
876                         }
877                 } else if (typeof params.map_row_to_columns == 'function' || typeof this.map_row_to_columns == 'function') {
878
879                         var labels = [];
880
881                         if (typeof params.map_row_to_columns == 'function') {
882
883                                 labels = params.map_row_to_columns(params.row,this.columns);
884
885                         } else if (typeof this.map_row_to_columns == 'function') {
886
887                                 labels = this.map_row_to_columns(params.row,this.columns);
888
889                         }
890                         for (var i = 0; i < labels.length; i++) {
891                                 var treecell = document.createElement('treecell');
892                                 treecell.setAttribute('label',typeof labels[i] == 'string' || typeof labels[i] == 'number' ? labels[i] : '');
893                                 treerow.appendChild( treecell );
894                                 s += ('treecell = ' + treecell + ' with label = ' + labels[i] + '\n');
895                         }
896
897                 } else {
898
899                         throw('No row to column mapping function.');
900                 }
901                 this.error.sdump('D_LIST',s);
902         },
903
904         '_map_row_to_listcell' : function(params,listitem) {
905                 var obj = this;
906                 var s = '';
907                 for (var i = 0; i < this.columns.length; i++) {
908                         var value = '';
909                         if (typeof params.map_row_to_column == 'function')  {
910
911                                 value = params.map_row_to_column(params.row,this.columns[i]);
912
913                         } else {
914
915                                 if (typeof this.map_row_to_column == 'function') {
916
917                                         value = this.map_row_to_column(params.row,this.columns[i]);
918                                 }
919                         }
920                         if (typeof value == 'string' || typeof value == 'number') {
921                                 var listcell = document.createElement('listcell');
922                                 listcell.setAttribute('label',value);
923                                 listitem.appendChild(listcell);
924                                 s += ('listcell = ' + listcell + ' with label = ' + value + '\n');
925                         } else {
926                                 listitem.appendChild(value);
927                                 s += ('listcell = ' + value + ' is really a ' + value.nodeName + '\n');
928                         }
929                 }
930                 this.error.sdump('D_LIST',s);
931         },
932
933         'select_all' : function(params) {
934                 var obj = this;
935                 switch(this.node.nodeName) {
936                         case 'tree' : return this._select_all_from_tree(params); break;
937                         default: throw('NYI: Need ._select_all_from_() for ' + this.node.nodeName); break;
938                 }
939         },
940
941         '_select_all_from_tree' : function(params) {
942                 var obj = this;
943                 this.node.view.selection.selectAll();
944         },
945
946         'retrieve_selection' : function(params) {
947                 var obj = this;
948                 switch(this.node.nodeName) {
949                         case 'tree' : return this._retrieve_selection_from_tree(params); break;
950                         default: throw('NYI: Need ._retrieve_selection_from_() for ' + this.node.nodeName); break;
951                 }
952         },
953
954         '_retrieve_selection_from_tree' : function(params) {
955                 var obj = this;
956                 var list = [];
957                 var start = new Object();
958                 var end = new Object();
959                 var numRanges = this.node.view.selection.getRangeCount();
960                 for (var t=0; t<numRanges; t++){
961                         this.node.view.selection.getRangeAt(t,start,end);
962                         for (var v=start.value; v<=end.value; v++){
963                                 var i = this.node.contentView.getItemAtIndex(v);
964                                 list.push( i );
965                         }
966                 }
967                 return list;
968         },
969
970         'dump' : function(params) {
971                 var obj = this;
972                 switch(this.node.nodeName) {
973                         case 'tree' : return this._dump_tree(params); break;
974                         default: throw('NYI: Need .dump() for ' + this.node.nodeName); break;
975                 }
976         },
977
978         '_dump_tree' : function(params) {
979                 var obj = this;
980                 var dump = [];
981                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
982                         var row = [];
983                         var treeitem = this.treechildren.childNodes[i];
984                         var treerow = treeitem.firstChild;
985                         for (var j = 0; j < treerow.childNodes.length; j++) {
986                                 row.push( treerow.childNodes[j].getAttribute('label') );
987                         }
988                         dump.push( row );
989                 }
990                 return dump;
991         },
992
993         'dump_with_keys' : function(params) {
994                 var obj = this;
995                 switch(this.node.nodeName) {
996                         case 'tree' : return this._dump_tree_with_keys(params); break;
997                         default: throw('NYI: Need .dump_with_keys() for ' + this.node.nodeName); break;
998                 }
999
1000         },
1001
1002         '_dump_tree_with_keys' : function(params) {
1003                 var obj = this;
1004                 var dump = [];
1005                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1006                         var row = {};
1007                         var treeitem = this.treechildren.childNodes[i];
1008                         var treerow = treeitem.firstChild;
1009                         for (var j = 0; j < treerow.childNodes.length; j++) {
1010                                 row[ obj.columns[j].id ] = treerow.childNodes[j].getAttribute('label');
1011                         }
1012                         dump.push( row );
1013                 }
1014                 return dump;
1015         },
1016
1017         'dump_csv' : function(params) {
1018                 var obj = this;
1019                 switch(this.node.nodeName) {
1020                         case 'tree' : return this._dump_tree_csv(params); break;
1021                         default: throw('NYI: Need .dump_csv() for ' + this.node.nodeName); break;
1022                 }
1023
1024         },
1025
1026         '_dump_tree_csv' : function(params) {
1027                 var obj = this;
1028                 var dump = '';
1029                 for (var j = 0; j < obj.columns.length; j++) {
1030                         if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1031                                 /* skip */
1032                         } else {
1033                                 if (dump) dump += ',';
1034                                 dump += '"' + obj.columns[j].label.replace(/"/g, '""') + '"';
1035                         }
1036                 }
1037                 dump += '\r\n';
1038                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1039                         var row = '';
1040                         var treeitem = this.treechildren.childNodes[i];
1041                         var treerow = treeitem.firstChild;
1042                         for (var j = 0; j < treerow.childNodes.length; j++) {
1043                                 if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') {
1044                                         /* skip */
1045                                 } else {
1046                                         if (row) row += ',';
1047                                         row += '"' + treerow.childNodes[j].getAttribute('label').replace(/"/g, '""') + '"';
1048                                 }
1049                         }
1050                         dump +=  row + '\r\n';
1051                 }
1052                 return dump;
1053         },
1054
1055         'dump_selected_with_keys' : function(params) {
1056                 var obj = this;
1057                 switch(this.node.nodeName) {
1058                         case 'tree' : return this._dump_tree_selection_with_keys(params); break;
1059                         default: throw('NYI: Need .dump_selection_with_keys() for ' + this.node.nodeName); break;
1060                 }
1061
1062         },
1063
1064         '_dump_tree_selection_with_keys' : function(params) {
1065                 var obj = this;
1066                 var dump = [];
1067                 var list = obj._retrieve_selection_from_tree();
1068                 for (var i = 0; i < list.length; i++) {
1069                         var row = {};
1070                         var treeitem = list[i];
1071                         var treerow = treeitem.firstChild;
1072                         for (var j = 0; j < treerow.childNodes.length; j++) {
1073                                 var value = treerow.childNodes[j].getAttribute('label');
1074                                 if (params.skip_hidden_columns) if (obj.node.treeBoxObject.columns.getColumnAt(j).element.getAttribute('hidden') == 'true') continue;
1075                                 var id = obj.columns[j].id; if (params.labels_instead_of_ids) id = obj.columns[j].label;
1076                                 row[ id ] = value;
1077                         }
1078                         dump.push( row );
1079                 }
1080                 return dump;
1081         },
1082
1083         'clipboard' : function() {
1084                 try {
1085                         var obj = this;
1086                         var dump = obj.dump_selected_with_keys({'skip_hidden_columns':true,'labels_instead_of_ids':true});
1087                         JSAN.use('OpenILS.data'); var data = new OpenILS.data(); data.stash_retrieve();
1088                         data.list_clipboard = dump; data.stash('list_clipboard');
1089                         JSAN.use('util.window'); var win = new util.window();
1090                         win.open(urls.XUL_LIST_CLIPBOARD,'list_clipboard','chrome,resizable,modal');
1091                 } catch(E) {
1092                         this.error.standard_unexpected_error_alert('clipboard',E);
1093                 }
1094         },
1095
1096         'dump_retrieve_ids' : function(params) {
1097                 var obj = this;
1098                 switch(this.node.nodeName) {
1099                         case 'tree' : return this._dump_retrieve_ids_tree(params); break;
1100                         default: throw('NYI: Need .dump_retrieve_ids() for ' + this.node.nodeName); break;
1101                 }
1102         },
1103
1104         '_dump_retrieve_ids_tree' : function(params) {
1105                 var obj = this;
1106                 var dump = [];
1107                 for (var i = 0; i < this.treechildren.childNodes.length; i++) {
1108                         var treeitem = this.treechildren.childNodes[i];
1109                         dump.push( treeitem.getAttribute('retrieve_id') );
1110                 }
1111                 return dump;
1112         },
1113
1114         '_sort_tree' : function(col,sortDir) {
1115                 var obj = this;
1116                 try {
1117                         if (obj.node.getAttribute('no_sort')) {
1118                                 return;
1119                         }
1120                         if (obj.on_all_fleshed) {
1121                                 var r = window.confirm('This list is busy rendering/retrieving data.  Abort current action and proceed?');
1122                                 if (r) {} else { return; }
1123                         }
1124                         var col_pos;
1125                         for (var i = 0; i < obj.columns.length; i++) { 
1126                                 if (obj.columns[i].id == col.id) col_pos = function(a){return a;}(i); 
1127                         }
1128                         obj.on_all_fleshed = function() {
1129                                         try {
1130                                                 JSAN.use('util.money');
1131                                                 var rows = [];
1132                                                 var treeitems = obj.treechildren.childNodes;
1133                                                 for (var i = 0; i < treeitems.length; i++) {
1134                                                         var treeitem = treeitems[i];
1135                                                         var treerow = treeitem.firstChild;
1136                                                         var treecell = treerow.childNodes[ col_pos ];
1137                                                         value = ( { 'value' : treecell ? treecell.getAttribute('label') : '', 'node' : treeitem } );
1138                                                         rows.push( value );
1139                                                 }
1140                                                 rows = rows.sort( function(a,b) { 
1141                                                         a = a.value; b = b.value; 
1142                                                         if (col.getAttribute('sort_type')) {
1143                                                                 switch(col.getAttribute('sort_type')) {
1144                                                                         case 'number' :
1145                                                                                 a = Number(a); b = Number(b);
1146                                                                         break;
1147                                                                         case 'money' :
1148                                                                                 a = util.money.dollars_float_to_cents_integer(a);
1149                                                                                 b = util.money.dollars_float_to_cents_integer(b);
1150                                                                         break;
1151                                                                         case 'title' : /* special case for "a" and "the".  doesn't use marc 245 indicator */
1152                                                                                 a = String( a ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1153                                                                                 b = String( b ).toUpperCase().replace( /^\s*(THE|A|AN)\s+/, '' );
1154                                                                         break;
1155                                                                         default:
1156                                                                                 a = String( a ).toUpperCase();
1157                                                                                 b = String( b ).toUpperCase();
1158                                                                         break;
1159                                                                 }
1160                                                         } else {
1161                                                                 if (typeof a == 'string' || typeof b == 'string') {
1162                                                                         a = String( a ).toUpperCase();
1163                                                                         b = String( b ).toUpperCase();
1164                                                                 }
1165                                                         }
1166                                                         if (a < b) return -1; 
1167                                                         if (a > b) return 1; 
1168                                                         return 0; 
1169                                                 } );
1170                                                 if (sortDir == 'asc') rows = rows.reverse();
1171                                                 while(obj.treechildren.lastChild) obj.treechildren.removeChild( obj.treechildren.lastChild );
1172                                                 for (var i = 0; i < rows.length; i++) {
1173                                                         obj.treechildren.appendChild( rows[i].node );
1174                                                 }
1175                                         } catch(E) {
1176                                                 obj.error.standard_unexpected_error_alert('sorting',E); 
1177                                         }
1178                                         setTimeout(function(){ obj.on_all_fleshed = null; },0);
1179                                 }
1180                         obj.full_retrieve();
1181                 } catch(E) {
1182                         obj.error.standard_unexpected_error_alert('pre sorting', E);
1183                 }
1184         },
1185
1186 }
1187 dump('exiting util.list.js\n');