]> git.sur5r.net Git - bacula/bacula/blob - gui/bweb/html/nrs_table.js
ebl update
[bacula/bacula] / gui / bweb / html / nrs_table.js
1 /**
2  * Copyright 2005 New Roads School
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 /**
20  * \class nrsTable
21  * This describes the nrsTable, which is a table created in JavaScript that is
22  * able to be sorted and displayed in different ways based on teh configuration
23  * parameters passed to it. 
24  * to create a new table one only needs to call the setup function like so:
25  * <pre>
26  * nrsTable.setup(
27  * {
28  *      table_name: "table_container",
29  *      table_data: d,
30  *      table_header: h
31  * }
32  * );
33  * </pre>
34  * Where table_name is the name of the table to build.  THis must be defined in
35  * your HTML by putting a table declaration, such as <table id=table_name>
36  * </table>.  This will declare where your table will be shown.
37  * All sorts of parameters can be customized here.  For details look at the
38  * function setup.
39  * \see setup
40  */
41
42
43 /**
44  * Debug function.  Set debug to tru to view messages.
45  * \param msg Message to display in an alert.
46  */
47 debug = false;
48 function DEBUG(msg)
49 {
50         if(debug)
51                 alert(msg);
52 }
53
54 /**
55  * There is a memory leak problem that I can't seem to fix.  I'm attching 
56  * something that I found from Aaron Boodman, which will clean up all the
57  * memory leaks in the page (this can be found at http://youngpup.net/2005/0221010713
58  * ). This is a little clunky, but it will do till I track this problem.
59  * Oh, and this problem only occurrs is IE.
60  */
61 if (window.attachEvent) {
62     var clearElementProps = [
63                 'data',
64         'onmouseover',
65         'onmouseout',
66         'onmousedown',
67         'onmouseup',
68         'ondblclick',
69         'onclick',
70         'onselectstart',
71         'oncontextmenu'
72     ];
73
74     window.attachEvent("onunload", function() {
75         var el;
76         for(var d = document.all.length;d--;){
77             el = document.all[d];
78             for(var c = clearElementProps.length;c--;){
79                 el[clearElementProps[c]] = null;
80             }
81         }
82     });
83 }
84
85
86 /**
87  * This is the constructor.
88  * It only needs the name of the table.  This should never be called directly, 
89  * instead use setup function.
90  * \param table The name of the table to create.
91  * \see setup
92  */
93 function nrsTable(table)
94 {
95         this.my_table = table;
96         this.field_to_sort = 0;
97         this.field_asc = true;
98 }
99
100 new nrsTable('');
101 /**
102  * This function is responsible for setting up an nrsTable.  All the parameters
103  * can be configured directly from this function.  The params array of this 
104  * function is a class (or a associative array, depending on how you want to
105  * look at it) with the following possible parameters:
106  *      - table_name: required.  The id of the table tag.
107  *      - table_header: required.  An array containing the header names.
108  *      - table_data: optional.  A 2D array of strings containing the cell contents.
109  *      - caption: optional.  A caption to include on the table.
110  *      - row_links: optional.  An array with hyperlinks per row.  Must be a javascript function.
111  *      - cell_links: optional.  A 2D array with links on every cell.  Must be a javascript function
112  *      - up_icon: optional.  A path to the ascending sort arrow.
113  *      - down_icon: optional.  A path to the descending sort arrow.
114  *      - prev_icon: optional.  A path to the previous page icon in the navigation.
115  *      - next_icon: optional.  A path to the next page icon in the navigation.
116  *      - rew_icon: optional.  A path to the first page icon in the navigation.
117  *      - fwd_icon: optional.  A path to the last page icon in the navigation.
118  *      - rows_per_page: optional.  The number of rows per page to display at any one time.
119  *      - display_nav: optional.  Displays navigation (prev, next, first, last)
120  *      - foot_headers: optional.  Whether to display th eheaders at the foot of the table.
121  *      - header_color: optional.  The color of the header cells.  Will default to whatever is defined in CSS.
122  *      - even_cell_color: optional.  The color of the even data cells.  Will default to whatever is defined in CSS.
123  *      - odd_cell_color: optional.  The color of the odd data cells.  Will default to whatever is defined in CSS.
124  *      - footer_color: optional.  The color of the footer cells.  Will default to whatever is defined in CSS.
125  *      - hover_color: optional.  The color tha a row should turn when the mouse is over it.
126  *      - padding: optional.  Individual cell padding, in pixels.
127  *      - natural_compare: optional.  Uses the natural compare algorithm (separate from this program) to sort.
128  *      - disable_sorting: optional.  An array specifying the columns top disable sorting on (0 is the first column).
129  *
130  * \params params An array as described above.
131  */
132 nrsTable.setup = function(params)
133 {
134         //here we assign all the veriables that we are passed, or the defaults if 
135         //they are not defined.
136         //Note that the only requirements are a table name and a header.
137         if(typeof params['table_name'] == "undefined")
138         {
139                 alert("Error! You must supply a table name!");
140                 return null;
141         }
142         if(typeof params['table_header'] == "undefined")
143         {
144                 alert("Error! You must supply a table header!");
145                 return null;
146         }
147         
148         //check if the global array exists, else create it.
149         if(typeof(nrsTables) == "undefined")
150         {
151                 eval("nrsTables = new Array();");
152         }
153         nrsTables[params['table_name']] = new nrsTable(params['table_name']);
154         nrsTables[params['table_name']].heading = params['table_header'].concat();
155         
156         //now the non-required elements.  Data elements first
157         nrsTables[params['table_name']].data = (typeof params['table_data'] == "undefined" || !params['table_data'])? null: params['table_data'].concat();
158         nrsTables[params['table_name']].caption = (typeof params['caption'] == "undefined")? null: params['caption'];
159         nrsTables[params['table_name']].row_links = (typeof params['row_links'] == "undefined" || !params['row_links'])? null: params['row_links'].concat();
160         nrsTables[params['table_name']].cell_links = (typeof params['cell_links'] == "undefined" || !params['row_links'])? null: params['cell_links'].concat();
161         
162         //these are the icons.
163         nrsTables[params['table_name']].up_icon = (typeof params['up_icon'] == "undefined")? "up.gif": params['up_icon'];
164         nrsTables[params['table_name']].down_icon = (typeof params['down_icon'] == "undefined")? "down.gif": params['down_icon'];
165         nrsTables[params['table_name']].prev_icon = (typeof params['prev_icon'] == "undefined")? "left.gif": params['prev_icon'];
166         nrsTables[params['table_name']].next_icon = (typeof params['next_icon'] == "undefined")? "right.gif": params['next_icon'];
167         nrsTables[params['table_name']].rew_icon = (typeof params['rew_icon'] == "undefined")? "first.gif": params['rew_icon'];
168         nrsTables[params['table_name']].fwd_icon = (typeof params['fwd_icon'] == "undefined")? "last.gif": params['fwd_icon'];
169         
170         //now the look and feel options.
171         nrsTables[params['table_name']].rows_per_page = (typeof params['rows_per_page'] == "undefined")? -1: params['rows_per_page'];
172         nrsTables[params['table_name']].page_nav = (typeof params['page_nav'] == "undefined")? false: params['page_nav'];
173         nrsTables[params['table_name']].foot_headers = (typeof params['foot_headers'] == "undefined")? false: params['foot_headers'];
174         nrsTables[params['table_name']].header_color = (typeof params['header_color'] == "undefined")? null: params['header_color'];
175         nrsTables[params['table_name']].even_cell_color = (typeof params['even_cell_color'] == "undefined")? null: params['even_cell_color'];
176         nrsTables[params['table_name']].odd_cell_color = (typeof params['odd_cell_color'] == "undefined")? null: params['odd_cell_color'];
177         nrsTables[params['table_name']].footer_color = (typeof params['footer_color'] == "undefined")? null: params['footer_color'];
178         nrsTables[params['table_name']].hover_color = (typeof params['hover_color'] == "undefined")? null: params['hover_color'];
179         nrsTables[params['table_name']].padding = (typeof params['padding'] == "undefined")? null: params['padding'];
180         nrsTables[params['table_name']].natural_compare = (typeof params['natural_compare'] == "undefined")? false: true;
181         nrsTables[params['table_name']].disable_sorting = 
182                 (typeof params['disable_sorting'] == "undefined")? false: "." + params['disable_sorting'].join(".") + ".";
183         //finally, build the table
184         nrsTables[params['table_name']].buildTable();
185 };
186
187
188 /**
189  * This is the Javascript quicksort implementation.  This will sort the 
190  * this.data and the this.data_nodes based on the this.field_to_sort parameter.
191  * \param left The left index of the array.
192  * \param right The right index of the array
193  */
194 nrsTable.prototype.quickSort = function(left, right)
195 {
196         if(!this.data || this.data.length == 0)
197                 return;
198 //      alert("left = " + left + " right = " + right);
199         var i = left;
200         var j = right;
201         var k = this.data[Math.round((left + right) / 2)][this.field_to_sort];
202         if (isNaN(k)) {
203           k = k.toLowerCase();
204         } else {
205           k = parseInt(k, 10);
206         }
207         
208         while(j > i)
209         {
210                 if(this.field_asc)
211                 {
212                         while(this.data[i][this.field_to_sort].toLowerCase() < k)
213                                 i++;
214                         while(this.data[j][this.field_to_sort].toLowerCase() > k)
215                                 j--;
216                 }
217                 else
218                 {
219                         while(this.data[i][this.field_to_sort].toLowerCase() > k)
220                                 i++;
221                         while(this.data[j][this.field_to_sort].toLowerCase() < k)
222                                 j--;
223                 }
224                 if(i <= j )
225                 {
226                         //swap both values
227                         //sort data
228                         var temp = this.data[i];
229                         this.data[i] = this.data[j];
230                         this.data[j] = temp;
231                         
232                         //sort contents
233                         var temp = this.data_nodes[i];
234                         this.data_nodes[i] = this.data_nodes[j];
235                         this.data_nodes[j] = temp;
236                         i++;
237                         j--;
238                 }
239         }
240         if(left < j)
241                 this.quickSort(left, j);
242         if(right > i)
243                 this.quickSort(i, right);
244 }
245
246 /**
247  * This is the Javascript natural sort function.  Because of some obscure JavaScript
248  * quirck, we could not do quicsort while calling natcompare to compare, so this
249  * function will so a simple bubble sort using the natural compare algorithm.
250  */
251 nrsTable.prototype.natSort = function()
252 {
253         if(!this.data || this.data.length == 0)
254                 return;
255         var swap;
256         for(i = 0; i < this.data.length - 1; i++)
257         {
258                 for(j = i; j < this.data.length; j++)
259                 {
260                         if(!this.field_asc)
261                         {
262                                 if(natcompare(this.data[i][this.field_to_sort].toLowerCase(), 
263                                         this.data[j][this.field_to_sort].toLowerCase()) == -1)
264                                         swap = true;
265                                 else
266                                         swap = false;
267                         }
268                         else
269                         {
270                                 if(natcompare(this.data[i][this.field_to_sort].toLowerCase(), 
271                                         this.data[j][this.field_to_sort].toLowerCase()) == 1)
272                                         swap = true;
273                                 else
274                                         swap = false;
275                         }
276                         if(swap)
277                         {
278                                 //swap both values
279                                 //sort data
280                                 var temp = this.data[i];
281                                 this.data[i] = this.data[j];
282                                 this.data[j] = temp;
283                                 
284                                 //sort contents
285                                 var temp = this.data_nodes[i];
286                                 this.data_nodes[i] = this.data_nodes[j];
287                                 this.data_nodes[j] = temp;
288                         }
289                 }
290         }
291 }
292
293 /**
294  * This function will recolor all the the nodes to conform to the alternating 
295  * row colors.
296  */
297 nrsTable.prototype.recolorRows = function()
298 {
299         if(this.even_cell_color || this.odd_cell_color)
300         {
301                 DEBUG("Recoloring Rows. length = " + this.data_nodes.length);
302                 for(var i = 0; i < this.data_nodes.length; i++)
303                 {
304                         if(i % 2 == 0)
305                         {
306                                 if(this.even_cell_color)
307                                         this.data_nodes[i].style.backgroundColor = this.even_cell_color;
308                                 this.data_nodes[i].setAttribute("id", "even_row");
309                         }
310                         else
311                         {
312                                 if(this.odd_cell_color)
313                                         this.data_nodes[i].style.backgroundColor = this.odd_cell_color;
314                                 this.data_nodes[i].setAttribute("id", "odd_row");
315                         }
316                 }
317         }
318 }
319
320 /**
321  * This function will create the Data Nodes, which are a reference to the table
322  * rows in the HTML.
323  */
324 nrsTable.prototype.createDataNodes = function()
325 {
326         if(this.data_nodes)
327                 delete this.data_nodes;
328         this.data_nodes = new Array();
329         if(!this.data)
330                 return;
331         for(var i = 0; i < this.data.length; i++)
332         {
333                 var curr_row = document.createElement("TR");
334                 
335                 for(var j = 0; j < this.data[i].length; j++)
336                 {
337                         var curr_cell = document.createElement("TD");
338                         //do we need to create links on every cell?
339                         if(this.cell_links)
340                         {
341                                 var fn = new Function("", this.cell_links[i][j]);
342                                 curr_cell.onclick = fn;
343                                 curr_cell.style.cursor = 'pointer';
344                         }
345                         //workaround for IE
346                         curr_cell.setAttribute("className", "dataTD" + j);
347                         //assign the padding
348                         if(this.padding)
349                         {
350                                 curr_cell.style.paddingLeft = this.padding + "px";
351                                 curr_cell.style.paddingRight = this.padding + "px";
352                         }
353                         
354                         if (typeof this.data[i][j] == "object") {
355                                 curr_cell.appendChild(this.data[i][j]);
356                         } else {
357                                 curr_cell.appendChild(document.createTextNode(this.data[i][j]));
358                         }
359
360                         curr_row.appendChild(curr_cell);
361                 }
362                 //do we need to create links on every row?
363                 if(!this.cell_links && this.row_links)
364                 {
365                         var fn = new Function("", this.row_links[i]);
366                         curr_row.onclick = fn;
367                         curr_row.style.cursor = 'pointer';
368                 }
369                 //sets the id for odd and even rows.
370                 if(i % 2 == 0)
371                 {
372                         curr_row.setAttribute("id", "even_row");
373                         if(this.even_cell_color)
374                                 curr_row.style.backgroundColor = this.even_cell_color;
375                 }
376                 else
377                 {
378                         curr_row.setAttribute("id", "odd_row");
379                         if(this.odd_cell_color)
380                                 curr_row.style.backgroundColor = this.odd_cell_color;
381                 }
382                 if(this.hover_color)
383                 {
384                         curr_row.onmouseover = new Function("", "this.style.backgroundColor='" + this.hover_color + "';");
385                         curr_row.onmouseout = new Function("", "this.style.backgroundColor=(this.id=='even_row')?'" + 
386                                                                 this.even_cell_color + "':'" + this.odd_cell_color + "';");
387                 }
388                 this.data_nodes[i] = curr_row;
389         }
390 }
391
392 /**
393  * This function will update the nav page display.
394  */
395 nrsTable.prototype.updateNav = function()
396 {
397         if(this.page_nav)
398         {
399                 var p = 0;
400                 if(this.foot_headers)
401                         p++;
402                 var t = document.getElementById(this.my_table);
403                 var nav = t.tFoot.childNodes[p];
404                 if(nav)
405                 {
406                         var caption = t.tFoot.childNodes[p].childNodes[0].childNodes[2];
407                         caption.innerHTML = "Page " + (this.current_page + 1) + " of " + this.num_pages;
408                 }
409                 else
410                 {
411                         if(this.num_pages > 1)
412                         {
413                                 this.insertNav();
414                                 nav = t.tFoot.childNodes[p];
415                         }
416                 }
417                 if(nav)
418                 {
419                         if(this.current_page == 0)
420                                 this.hideLeftArrows();
421                         else
422                                 this.showLeftArrows();
423                         
424                         if(this.current_page + 1 == this.num_pages)
425                                 this.hideRightArrows();
426                         else
427                                 this.showRightArrows();
428                 }
429         }
430 }
431
432 /**
433  * This function will flip the sort arrow in place.  If a heading is used in the
434  * footer, then it will flip that one too.
435  */
436 nrsTable.prototype.flipSortArrow = function()
437 {
438         this.field_asc = !this.field_asc;
439         //flip the arrow on the heading.
440         var heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[this.field_to_sort];
441         if(this.field_asc)
442                 heading.getElementsByTagName("IMG")[0].setAttribute("src", this.up_icon);
443         else
444                 heading.getElementsByTagName("IMG")[0].setAttribute("src", this.down_icon);
445         //is there a heading in the footer?
446         if(this.foot_headers)
447         {
448                 //yes, so flip that arrow too.
449                 var footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[this.field_to_sort];
450                 if(this.field_asc)
451                         footer.getElementsByTagName("IMG")[0].setAttribute("src", this.up_icon);
452                 else
453                         footer.getElementsByTagName("IMG")[0].setAttribute("src", this.down_icon);
454         }
455 }
456
457 /**
458  * This function will move the sorting arrow from the place specified in 
459  * this.field_to_sort to the passed parameter.  It will also set 
460  * this.field_to_sort to the new value.  It will also do it in the footers, 
461  * if they exists.
462  * \param field The new field to move it to.
463  */
464 nrsTable.prototype.moveSortArrow = function(field)
465 {
466         var heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[this.field_to_sort];
467         var img = heading.removeChild(heading.getElementsByTagName("IMG")[0]);
468         heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[field];
469         heading.appendChild(img);
470         //are there headers in the footers.
471         if(this.foot_headers)
472         {
473                 //yes, so switch them too.
474                 var footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[this.field_to_sort];
475                 var img = footer.removeChild(footer.getElementsByTagName("IMG")[0]);
476                 footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[field];
477                 footer.appendChild(img);
478         }
479         //finally, set the field to sort by.
480         this.field_to_sort = field;
481 }
482
483 /**
484  * This function completely destroys a table.  Should be used only when building
485  * a brand new table (ie, new headers).  Else you should use a function like
486  * buildNewData which only deletes the TBody section.
487  */
488 nrsTable.prototype.emptyTable = function()
489 {
490         var t = document.getElementById(this.my_table);
491         while(t.childNodes.length != 0)
492                 t.removeChild(t.childNodes[0]);
493 };
494
495 /**
496  * This function builds a brand new table from scratch.  This function should
497  * only be called when a brand new table (with headers, footers, etc) needs
498  * to be created.  NOT when refreshing data or changing data.
499  */
500 nrsTable.prototype.buildTable = function()
501 {
502         //reset the sorting information.
503         this.field_to_sort = 0;
504         this.field_asc = true;
505         
506         //remove the nodes links.
507         delete this.data_nodes;
508         
509         //do we have to calculate the number of pages?
510         if(this.data && this.rows_per_page != -1)
511         {
512                 //we do.
513                 this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
514                 this.current_page = 0;
515         }
516         
517         //blank out the table.
518         this.emptyTable();
519         
520         //this is the table that we will be using.
521         var table = document.getElementById(this.my_table);
522         
523         //is there a caption?
524         if(this.caption)
525         {
526                 var caption = document.createElement("CAPTION");
527                 caption.setAttribute("align", "top");
528                 caption.appendChild(document.createTextNode(this.caption));
529                 table.appendChild(caption);
530         }
531         
532         //do the heading first
533         var table_header = document.createElement("THEAD");
534         var table_heading = document.createElement("TR");
535         //since this is a new table the first field is what's being sorted.
536         var curr_cell = document.createElement("TH");
537         var fn = new Function("", "nrsTables['" + this.my_table + "'].fieldSort(" + 0 + ");");
538         if(!this.disable_sorting || this.disable_sorting.indexOf(".0.") == -1)
539                 curr_cell.onclick = fn;
540         if(this.header_color)
541                 curr_cell.style.backgroundColor = this.header_color;
542         curr_cell.style.cursor = 'pointer';
543         var img = document.createElement("IMG");
544         img.setAttribute("src", this.up_icon);
545         img.setAttribute("border", "0");
546         img.setAttribute("height", "8");
547         img.setAttribute("width", "8");
548         curr_cell.appendChild(document.createTextNode(this.heading[0]));
549         curr_cell.appendChild(img);
550         table_heading.appendChild(curr_cell);
551         //now do the rest of the heading.
552         for(var i = 1; i < this.heading.length; i++)
553         {
554                 curr_cell = document.createElement("TH");
555                 var fn = new Function("", "nrsTables['" + this.my_table + "'].fieldSort(" + i + ");");
556                 if(!this.disable_sorting || this.disable_sorting.indexOf("." + i + ".") == -1)
557                         curr_cell.onclick = fn;
558                 if(this.header_color)
559                         curr_cell.style.backgroundColor = this.header_color;
560                 curr_cell.style.cursor = 'pointer';
561                 //build the sorter
562                 curr_cell.appendChild(document.createTextNode(this.heading[i]));
563                 table_heading.appendChild(curr_cell);
564         }
565         table_header.appendChild(table_heading);
566         
567         //now the content
568         var table_body = document.createElement("TBODY");
569         this.createDataNodes();
570         if(this.data)
571         {
572                 if(this.natural_compare)
573                         this.natSort(0, this.data.length - 1);
574                 else
575                         this.quickSort(0, this.data.length - 1);
576                 this.recolorRows();
577         }
578
579         //finally, the footer
580         var table_footer = document.createElement("TFOOT");
581         if(this.foot_headers)
582         {
583                 table_footer.appendChild(table_heading.cloneNode(true));
584         }
585         
586         if(this.page_nav && this.num_pages > 1)
587         {
588                 //print out the page navigation
589                 //first and previous page
590                 var nav = document.createElement("TR");
591                 var nav_cell = document.createElement("TH");
592                 nav_cell.colSpan = this.heading.length;
593                 
594                 var left = document.createElement("DIV");
595                 if(document.attachEvent)
596                         left.style.styleFloat = "left";
597                 else
598                         left.style.cssFloat = "left";
599                 var img = document.createElement("IMG");
600                 img.setAttribute("src", this.rew_icon);
601                 img.setAttribute("border", "0");
602                 img.setAttribute("height", "10");
603                 img.setAttribute("width", "10");
604                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].firstPage();");
605                 img.style.cursor = 'pointer';
606                 left.appendChild(img);
607                 //hack to space the arrows, cause IE is absolute crap
608                 left.appendChild(document.createTextNode(" "));
609                 img = document.createElement("IMG");
610                 img.setAttribute("src", this.prev_icon);
611                 img.setAttribute("border", "0");
612                 img.setAttribute("height", "10");
613                 img.setAttribute("width", "10");
614                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].prevPage();");
615                 img.style.cursor = 'pointer';
616                 left.appendChild(img);
617                 //apend it to the cell
618                 nav_cell.appendChild(left);
619                 
620                 //next and last pages
621                 var right = document.createElement("DIV");
622                 if(document.attachEvent)
623                         right.style.styleFloat = "right";
624                 else
625                         right.style.cssFloat = "right";
626                 img = document.createElement("IMG");
627                 img.setAttribute("src", this.next_icon);
628                 img.setAttribute("border", "0");
629                 img.setAttribute("height", "10");
630                 img.setAttribute("width", "10");
631                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].nextPage();");
632                 img.style.cursor = 'pointer';
633                 right.appendChild(img);
634                 //hack to space the arrows, cause IE is absolute crap
635                 right.appendChild(document.createTextNode(" "));
636                 img = document.createElement("IMG");
637                 img.setAttribute("src", this.fwd_icon);
638                 img.setAttribute("border", "0");
639                 img.setAttribute("height", "10");
640                 img.setAttribute("width", "10");
641                 img.onclick = new Function("", "JavaScript:nrsTables['" + this.my_table + "'].lastPage();");
642                 img.style.cursor = 'pointer';
643                 right.appendChild(img);
644                 //apend it to the cell
645                 nav_cell.appendChild(right);
646                 
647                 //page position
648                 var pos = document.createElement("SPAN");
649                 pos.setAttribute("id", "nav_pos");
650                 pos.appendChild(document.createTextNode("Page " + 
651                                                 (this.current_page + 1) + " of " + this.num_pages));
652                 //append it to the cell.
653                 nav_cell.appendChild(pos);
654                 
655                 nav.appendChild(nav_cell);
656                 //append it to the footer
657                 table_footer.appendChild(nav);
658         }
659         
660         if(this.footer_color)
661         {
662                 for(var i = 0; i < table_footer.childNodes.length; i++)
663                         table_footer.childNodes[i].style.backgroundColor = this.footer_color;
664         }
665         
666         //append the data
667         table.appendChild(table_header);
668         table.appendChild(table_body);
669         table.appendChild(table_footer);
670         if(this.data)
671         {
672                 if(this.natural_compare)
673                         this.natSort(0, this.data.length - 1);
674                 else
675                         this.quickSort(0, this.data.length - 1);
676         }
677         this.refreshTable();
678 };
679
680 /**
681  * This function will remove the elements in teh TBody section of the table and
682  * return an array of references of those elements.  This array can then be 
683  * sorted and re-inserted into the table.
684  * \return An array to references of the TBody contents.
685  */
686 nrsTable.prototype.extractElements = function()
687 {
688         var tbody = document.getElementById(this.my_table).tBodies[0];
689         var nodes = new Array();
690         var i = 0;
691         while(tbody.childNodes.length > 0)
692         {
693                 nodes[i] = tbody.removeChild(tbody.childNodes[0]);
694                 i++;
695         }
696         return nodes;
697 }
698
699 /**
700  * This function will re-insert an array of elements into the TBody of a table.
701  * Note that the array elements are stored in the this.data_nodes reference.
702  */
703 nrsTable.prototype.insertElements = function()
704 {
705         var tbody = document.getElementById(this.my_table).tBodies[0];
706         var start = 0;
707         var num_elements = this.data_nodes.length;
708         if(this.rows_per_page != -1)
709         {
710                 start = this.current_page * this.rows_per_page;
711                 num_elements = (this.data_nodes.length - start) > this.rows_per_page?
712                                                         this.rows_per_page + start:
713                                                         this.data_nodes.length;
714         }
715         DEBUG("start is " + start + " and num_elements is " + num_elements);
716         for(var i = start; i < num_elements; i++)
717         {
718                 tbody.appendChild(this.data_nodes[i]);
719         }
720 }
721
722 /**
723  * This function will sort the table's data by a specific field.  The field 
724  * parameter referes to which field index should be sorted.
725  * \param field The field index which to sort on.
726  */
727 nrsTable.prototype.fieldSort = function(field)
728 {
729         if(this.field_to_sort == field)
730         {
731                 //only need to reverse the array.
732                 if(this.data)
733                 {
734                         this.data.reverse();
735                         this.data_nodes.reverse();
736                 }
737                 //flip the arrow on the heading.
738                 this.flipSortArrow();
739         }
740         else
741         {
742                 //In this case, we need to sort the array.  We'll sort it last, first 
743                 //make sure that the arrow images are set correctly.
744                 this.moveSortArrow(field);
745                 //finally, set the field to sort by.
746                 this.field_to_sort = field;
747                 if(this.data)
748                 {
749                         //we'll be using our implementation of quicksort
750                         if(this.natural_compare)
751                                 this.natSort(0, this.data.length - 1);
752                         else
753                                 this.quickSort(0, this.data.length - 1);
754                 }
755         }
756         //finally, we refresh the table.
757         this.refreshTable();
758 };
759
760 /**
761  * This function will refresh the data in the table.  This function should be
762  * used whenever the nodes have changed, or when chanign pages.  Note that 
763  * this will NOT re-sort.
764  */
765 nrsTable.prototype.refreshTable = function()
766 {
767         this.extractElements();
768         this.recolorRows();
769         this.insertElements();
770         //finally, if there is a nav, upate it.
771         this.updateNav();
772 }
773
774 /**
775  * This function will advance a page.  If we are already at the last page, then 
776  * it will remain there.
777  */
778 nrsTable.prototype.nextPage = function()
779 {
780         DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
781         if(this.current_page + 1 != this.num_pages)
782         {
783                 this.current_page++;
784                 this.refreshTable();
785         }
786         DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
787 }
788
789 /**
790  * This function will go back a page.  If we are already at the first page, then 
791  * it will remain there.
792  */
793 nrsTable.prototype.prevPage = function()
794 {
795         DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
796         if(this.current_page != 0)
797         {
798                 this.current_page--;
799                 this.refreshTable();
800         }
801         DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
802 }
803
804 /**
805  * This function will go to the first page.
806  */
807 nrsTable.prototype.firstPage = function()
808 {
809         if(this.current_page != 0)
810         {
811                 this.current_page = 0;
812                 this.refreshTable();
813         }
814 }
815
816 /**
817  * This function will go to the last page.
818  */
819 nrsTable.prototype.lastPage = function()
820 {
821         DEBUG("lastPage(), current_page: " + this.current_page + " and num_pages: " + this.num_pages);
822         if(this.current_page != (this.num_pages - 1))
823         {
824                 this.current_page = this.num_pages - 1;
825                 this.refreshTable();
826         }
827 }
828
829 /**
830  * This function will go to a specific page.  valid values are pages 1 to 
831  * however many number of pages there are.
832  * \param page The page number to go to.
833  */
834 nrsTable.prototype.gotoPage = function(page)
835 {
836         page--;
837         if(page >=0 && page < this.num_pages)
838         {
839                 this.current_page = page;
840                 this.refreshTable();
841         }
842 }
843
844 /**
845  * This function can be used to change the number of entries per row displayed
846  * on the fly.
847  * \param entries The number of entries per page.
848  */
849 nrsTable.prototype.changeNumRows = function(entries)
850 {
851         if(entries > 0)
852         {
853                 this.rows_per_page = entries;
854                 //we do.
855                 this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
856                 this.refreshTable();
857         }
858 }
859
860 /**
861  * This function will take in a new data array and , optionally, a new cell_link
862  * array OR a new row_link array.  Only one will be used, with the cell_link
863  * array taking precedence.  It will then re-build the table with the new data
864  * array.
865  * \param new_data This is the new data array.  This is required.
866  * \param cell_links This is the new cell links array, a 2D array for each cell.
867  * \param row_links This is the new row links array, a 1D array for each row.
868  */
869 nrsTable.prototype.newData = function(new_data, cell_links, row_links)
870 {
871         //extract the elements from teh table to clear the table.
872         this.extractElements();
873         //now delete all the data related to this table.  I do this so that 
874         //(hopefully) the memory will be freed.  This is realy needed for IE, whose
875         //memory handling is almost non-existant
876         delete this.data;
877         delete this.data_nodes;
878         delete this.cell_links;
879         delete this.row_links
880         //now re-assign.
881         this.data = new_data;
882         this.cell_links = cell_links;
883         this.row_links = row_links;
884         if(this.rows_per_page != -1)
885         {
886                 //we do.
887                 this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
888                 if(this.num_pages <= 1 && this.page_nav)
889                         this.removeNav();
890                 else if(this.page_nav)
891                         this.insertNav();
892                 this.current_page = 0;
893         }
894         this.createDataNodes();
895         if(this.field_to_sort != 0)
896                 this.moveSortArrow(0);
897         if(!this.field_asc)
898                 this.flipSortArrow();
899         this.insertElements();
900         this.updateNav();
901 }
902
903 /**
904  * This function will remove the NAV bar (if one exists) from the table.
905  */
906 nrsTable.prototype.removeNav = function()
907 {
908         if(this.page_nav)
909         {
910                 //in this case, remove the nav from the existing structure.
911                 var table = document.getElementById(this.my_table);
912                 var p = 0;
913                 if(this.foot_headers)
914                         p++;
915                 var nav = table.tFoot.childNodes[p];
916                 if(nav)
917                 {
918                         table.tFoot.removeChild(nav);
919                         delete nav;
920                 }
921         }
922 }
923
924 /**
925  * This function wil re-insert the nav into the table.
926  */
927 nrsTable.prototype.insertNav = function()
928 {
929         table = document.getElementById(this.my_table);
930         var p = 0;
931         if(this.foot_headers)
932                 p++;
933         if(this.page_nav && !table.tFoot.childNodes[p])
934         {
935                 //this means there should be a nav and there isn't one.
936                 //print out the page navigation
937                 //first and previous page
938                 var nav = document.createElement("TR");
939                 var nav_cell = document.createElement("TH");
940                 nav_cell.colSpan = this.heading.length;
941                 
942                 var left = document.createElement("DIV");
943                 if(document.attachEvent)
944                         left.style.styleFloat = "left";
945                 else
946                         left.style.cssFloat = "left";
947                 var img = document.createElement("IMG");
948                 img.setAttribute("src", this.rew_icon);
949                 img.setAttribute("border", "0");
950                 img.setAttribute("height", "10");
951                 img.setAttribute("width", "10");
952                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].firstPage();");
953                 img.style.cursor = 'pointer';
954                 left.appendChild(img);
955                 //hack to space the arrows, cause IE is absolute crap
956                 left.appendChild(document.createTextNode(" "));
957                 img = document.createElement("IMG");
958                 img.setAttribute("src", this.prev_icon);
959                 img.setAttribute("border", "0");
960                 img.setAttribute("height", "10");
961                 img.setAttribute("width", "10");
962                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].prevPage();");
963                 img.style.cursor = 'pointer';
964                 left.appendChild(img);
965                 //apend it to the cell
966                 nav_cell.appendChild(left);
967                 
968                 //next and last pages
969                 var right = document.createElement("DIV");
970                 if(document.attachEvent)
971                         right.style.styleFloat = "right";
972                 else
973                         right.style.cssFloat = "right";
974                 img = document.createElement("IMG");
975                 img.setAttribute("src", this.next_icon);
976                 img.setAttribute("border", "0");
977                 img.setAttribute("height", "10");
978                 img.setAttribute("width", "10");
979                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].nextPage();");
980                 img.style.cursor = 'pointer';
981                 right.appendChild(img);
982                 //hack to space the arrows, cause IE is absolute crap
983                 right.appendChild(document.createTextNode(" "));
984                 img = document.createElement("IMG");
985                 img.setAttribute("src", this.fwd_icon);
986                 img.setAttribute("border", "0");
987                 img.setAttribute("height", "10");
988                 img.setAttribute("width", "10");
989                 img.onclick = new Function("", "JavaScript:nrsTables['" + this.my_table + "'].lastPage();");
990                 img.style.cursor = 'pointer';
991                 right.appendChild(img);
992                 //apend it to the cell
993                 nav_cell.appendChild(right);
994                 
995                 //page position
996                 var pos = document.createElement("SPAN");
997                 pos.setAttribute("id", "nav_pos");
998                 pos.appendChild(document.createTextNode("Page " + 
999                                                 (this.current_page + 1) + " of " + this.num_pages));
1000                 //append it to the cell.
1001                 nav_cell.appendChild(pos);
1002                 
1003                 nav.appendChild(nav_cell);
1004                 //append it to the footer
1005                 table.tFoot.appendChild(nav);
1006         }
1007 }
1008
1009 /**
1010  * This function will hide the previous arrow and the rewind arrows from the
1011  * nav field.
1012  */
1013 nrsTable.prototype.hideLeftArrows = function()
1014 {
1015         if(!this.page_nav)
1016                 return;
1017         var myTable = document.getElementById(this.my_table);
1018         var p = 0;
1019         if(this.foot_headers)
1020                 p++;
1021         var nav = myTable.tFoot.childNodes[p];
1022         nav.childNodes[0].childNodes[0].style.display = "none";
1023 }
1024
1025 /**
1026  * This function will show the previous arrow and the rewind arrows from the
1027  * nav field.
1028  */
1029 nrsTable.prototype.showLeftArrows = function()
1030 {
1031         if(!this.page_nav)
1032                 return;
1033         table = document.getElementById(this.my_table);
1034         var p = 0;
1035         if(this.foot_headers)
1036                 p++;
1037         var nav = table.tFoot.childNodes[p];
1038         nav.childNodes[0].childNodes[0].style.display = "block";
1039 }
1040
1041 /**
1042  * This function will hide the next arrow and the fast foward arrows from the
1043  * nav field.
1044  */
1045 nrsTable.prototype.hideRightArrows = function()
1046 {
1047         if(!this.page_nav)
1048                 return;
1049         table = document.getElementById(this.my_table);
1050         var p = 0;
1051         if(this.foot_headers)
1052                 p++;
1053         var nav = table.tFoot.childNodes[p];
1054         nav.childNodes[0].childNodes[1].style.display = "none";
1055 }
1056
1057 /**
1058  * This function will show the next arrow and the fast foward arrows from the
1059  * nav field.
1060  */
1061 nrsTable.prototype.showRightArrows = function()
1062 {
1063         if(!this.page_nav)
1064                 return;
1065         table = document.getElementById(this.my_table);
1066         var p = 0;
1067         if(this.foot_headers)
1068                 p++;
1069         var nav = table.tFoot.childNodes[p];
1070         nav.childNodes[0].childNodes[1].style.display = "block";
1071 }