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