]> git.sur5r.net Git - bacula/bacula/blob - gui/bweb/html/nrs_table.js
ebl Add brestore ajax port.
[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 (k != '') {
203            if (isNaN(k)) {
204              k = k.toLowerCase();
205            } else {
206              k = parseInt(k, 10);
207            }
208         }
209
210         while(j > i)
211         {
212                 if(this.field_asc)
213                 {
214                         while(this.data[i][this.field_to_sort].toLowerCase() < k)
215                                 i++;
216                         while(this.data[j][this.field_to_sort].toLowerCase() > k)
217                                 j--;
218                 }
219                 else
220                 {
221                         while(this.data[i][this.field_to_sort].toLowerCase() > k)
222                                 i++;
223                         while(this.data[j][this.field_to_sort].toLowerCase() < k)
224                                 j--;
225                 }
226                 if(i <= j )
227                 {
228                         //swap both values
229                         //sort data
230                         var temp = this.data[i];
231                         this.data[i] = this.data[j];
232                         this.data[j] = temp;
233                         
234                         //sort contents
235                         var temp = this.data_nodes[i];
236                         this.data_nodes[i] = this.data_nodes[j];
237                         this.data_nodes[j] = temp;
238                         i++;
239                         j--;
240                 }
241         }
242         if(left < j)
243                 this.quickSort(left, j);
244         if(right > i)
245                 this.quickSort(i, right);
246 }
247
248 /**
249  * This is the Javascript natural sort function.  Because of some obscure JavaScript
250  * quirck, we could not do quicsort while calling natcompare to compare, so this
251  * function will so a simple bubble sort using the natural compare algorithm.
252  */
253 nrsTable.prototype.natSort = function()
254 {
255         if(!this.data || this.data.length == 0)
256                 return;
257         var swap;
258         for(i = 0; i < this.data.length - 1; i++)
259         {
260                 for(j = i; j < this.data.length; j++)
261                 {
262                         if(!this.field_asc)
263                         {
264                                 if(natcompare(this.data[i][this.field_to_sort].toLowerCase(), 
265                                         this.data[j][this.field_to_sort].toLowerCase()) == -1)
266                                         swap = true;
267                                 else
268                                         swap = false;
269                         }
270                         else
271                         {
272                                 if(natcompare(this.data[i][this.field_to_sort].toLowerCase(), 
273                                         this.data[j][this.field_to_sort].toLowerCase()) == 1)
274                                         swap = true;
275                                 else
276                                         swap = false;
277                         }
278                         if(swap)
279                         {
280                                 //swap both values
281                                 //sort data
282                                 var temp = this.data[i];
283                                 this.data[i] = this.data[j];
284                                 this.data[j] = temp;
285                                 
286                                 //sort contents
287                                 var temp = this.data_nodes[i];
288                                 this.data_nodes[i] = this.data_nodes[j];
289                                 this.data_nodes[j] = temp;
290                         }
291                 }
292         }
293 }
294
295 /**
296  * This function will recolor all the the nodes to conform to the alternating 
297  * row colors.
298  */
299 nrsTable.prototype.recolorRows = function()
300 {
301         if(this.even_cell_color || this.odd_cell_color)
302         {
303                 DEBUG("Recoloring Rows. length = " + this.data_nodes.length);
304                 for(var i = 0; i < this.data_nodes.length; i++)
305                 {
306                         if(i % 2 == 0)
307                         {
308                                 if(this.even_cell_color)
309                                         this.data_nodes[i].style.backgroundColor = this.even_cell_color;
310                                 this.data_nodes[i].setAttribute("id", "even_row");
311                         }
312                         else
313                         {
314                                 if(this.odd_cell_color)
315                                         this.data_nodes[i].style.backgroundColor = this.odd_cell_color;
316                                 this.data_nodes[i].setAttribute("id", "odd_row");
317                         }
318                 }
319         }
320 }
321
322 /**
323  * This function will create the Data Nodes, which are a reference to the table
324  * rows in the HTML.
325  */
326 nrsTable.prototype.createDataNodes = function()
327 {
328         if(this.data_nodes)
329                 delete this.data_nodes;
330         this.data_nodes = new Array();
331         if(!this.data)
332                 return;
333         for(var i = 0; i < this.data.length; i++)
334         {
335                 var curr_row = document.createElement("TR");
336                 
337                 for(var j = 0; j < this.data[i].length; j++)
338                 {
339                         var curr_cell = document.createElement("TD");
340                         //do we need to create links on every cell?
341                         if(this.cell_links)
342                         {
343                                 var fn = new Function("", this.cell_links[i][j]);
344                                 curr_cell.onclick = fn;
345                                 curr_cell.style.cursor = 'pointer';
346                         }
347                         //workaround for IE
348                         curr_cell.setAttribute("className", "dataTD" + j);
349                         //assign the padding
350                         if(this.padding)
351                         {
352                                 curr_cell.style.paddingLeft = this.padding + "px";
353                                 curr_cell.style.paddingRight = this.padding + "px";
354                         }
355                         
356                         if (typeof this.data[i][j] == "object") {
357                                 curr_cell.appendChild(this.data[i][j]);
358                         } else {
359                                 curr_cell.appendChild(document.createTextNode(this.data[i][j]));
360                         }
361
362                         curr_row.appendChild(curr_cell);
363                 }
364                 //do we need to create links on every row?
365                 if(!this.cell_links && this.row_links)
366                 {
367                         var fn = new Function("", this.row_links[i]);
368                         curr_row.onclick = fn;
369                         curr_row.style.cursor = 'pointer';
370                 }
371                 //sets the id for odd and even rows.
372                 if(i % 2 == 0)
373                 {
374                         curr_row.setAttribute("id", "even_row");
375                         if(this.even_cell_color)
376                                 curr_row.style.backgroundColor = this.even_cell_color;
377                 }
378                 else
379                 {
380                         curr_row.setAttribute("id", "odd_row");
381                         if(this.odd_cell_color)
382                                 curr_row.style.backgroundColor = this.odd_cell_color;
383                 }
384                 if(this.hover_color)
385                 {
386                         curr_row.onmouseover = new Function("", "this.style.backgroundColor='" + this.hover_color + "';");
387                         curr_row.onmouseout = new Function("", "this.style.backgroundColor=(this.id=='even_row')?'" + 
388                                                                 this.even_cell_color + "':'" + this.odd_cell_color + "';");
389                 }
390                 this.data_nodes[i] = curr_row;
391         }
392 }
393
394 /**
395  * This function will update the nav page display.
396  */
397 nrsTable.prototype.updateNav = function()
398 {
399         if(this.page_nav)
400         {
401                 var p = 0;
402                 if(this.foot_headers)
403                         p++;
404                 var t = document.getElementById(this.my_table);
405                 var nav = t.tFoot.childNodes[p];
406                 if(nav)
407                 {
408                         var caption = t.tFoot.childNodes[p].childNodes[0].childNodes[2];
409                         caption.innerHTML = "Page " + (this.current_page + 1) + " of " + this.num_pages;
410                 }
411                 else
412                 {
413                         if(this.num_pages > 1)
414                         {
415                                 this.insertNav();
416                                 nav = t.tFoot.childNodes[p];
417                         }
418                 }
419                 if(nav)
420                 {
421                         if(this.current_page == 0)
422                                 this.hideLeftArrows();
423                         else
424                                 this.showLeftArrows();
425                         
426                         if(this.current_page + 1 == this.num_pages)
427                                 this.hideRightArrows();
428                         else
429                                 this.showRightArrows();
430                 }
431         }
432 }
433
434 /**
435  * This function will flip the sort arrow in place.  If a heading is used in the
436  * footer, then it will flip that one too.
437  */
438 nrsTable.prototype.flipSortArrow = function()
439 {
440         this.field_asc = !this.field_asc;
441         //flip the arrow on the heading.
442         var heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[this.field_to_sort];
443         if(this.field_asc)
444                 heading.getElementsByTagName("IMG")[0].setAttribute("src", this.up_icon);
445         else
446                 heading.getElementsByTagName("IMG")[0].setAttribute("src", this.down_icon);
447         //is there a heading in the footer?
448         if(this.foot_headers)
449         {
450                 //yes, so flip that arrow too.
451                 var footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[this.field_to_sort];
452                 if(this.field_asc)
453                         footer.getElementsByTagName("IMG")[0].setAttribute("src", this.up_icon);
454                 else
455                         footer.getElementsByTagName("IMG")[0].setAttribute("src", this.down_icon);
456         }
457 }
458
459 /**
460  * This function will move the sorting arrow from the place specified in 
461  * this.field_to_sort to the passed parameter.  It will also set 
462  * this.field_to_sort to the new value.  It will also do it in the footers, 
463  * if they exists.
464  * \param field The new field to move it to.
465  */
466 nrsTable.prototype.moveSortArrow = function(field)
467 {
468         var heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[this.field_to_sort];
469         var img = heading.removeChild(heading.getElementsByTagName("IMG")[0]);
470         heading = document.getElementById(this.my_table).tHead.childNodes[0].childNodes[field];
471         heading.appendChild(img);
472         //are there headers in the footers.
473         if(this.foot_headers)
474         {
475                 //yes, so switch them too.
476                 var footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[this.field_to_sort];
477                 var img = footer.removeChild(footer.getElementsByTagName("IMG")[0]);
478                 footer = document.getElementById(this.my_table).tFoot.childNodes[0].childNodes[field];
479                 footer.appendChild(img);
480         }
481         //finally, set the field to sort by.
482         this.field_to_sort = field;
483 }
484
485 /**
486  * This function completely destroys a table.  Should be used only when building
487  * a brand new table (ie, new headers).  Else you should use a function like
488  * buildNewData which only deletes the TBody section.
489  */
490 nrsTable.prototype.emptyTable = function()
491 {
492         var t = document.getElementById(this.my_table);
493         while(t.childNodes.length != 0)
494                 t.removeChild(t.childNodes[0]);
495 };
496
497 /**
498  * This function builds a brand new table from scratch.  This function should
499  * only be called when a brand new table (with headers, footers, etc) needs
500  * to be created.  NOT when refreshing data or changing data.
501  */
502 nrsTable.prototype.buildTable = function()
503 {
504         //reset the sorting information.
505         this.field_to_sort = 0;
506         this.field_asc = true;
507         
508         //remove the nodes links.
509         delete this.data_nodes;
510         
511         //do we have to calculate the number of pages?
512         if(this.data && this.rows_per_page != -1)
513         {
514                 //we do.
515                 this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
516                 this.current_page = 0;
517         }
518         
519         //blank out the table.
520         this.emptyTable();
521         
522         //this is the table that we will be using.
523         var table = document.getElementById(this.my_table);
524         
525         //is there a caption?
526         if(this.caption)
527         {
528                 var caption = document.createElement("CAPTION");
529                 caption.setAttribute("align", "top");
530                 caption.appendChild(document.createTextNode(this.caption));
531                 table.appendChild(caption);
532         }
533         
534         //do the heading first
535         var table_header = document.createElement("THEAD");
536         var table_heading = document.createElement("TR");
537         //since this is a new table the first field is what's being sorted.
538         var curr_cell = document.createElement("TH");
539         var fn = new Function("", "nrsTables['" + this.my_table + "'].fieldSort(" + 0 + ");");
540         if(!this.disable_sorting || this.disable_sorting.indexOf(".0.") == -1)
541                 curr_cell.onclick = fn;
542         if(this.header_color)
543                 curr_cell.style.backgroundColor = this.header_color;
544         curr_cell.style.cursor = 'pointer';
545         var img = document.createElement("IMG");
546         img.setAttribute("src", this.up_icon);
547         img.setAttribute("border", "0");
548         img.setAttribute("height", "8");
549         img.setAttribute("width", "8");
550         curr_cell.appendChild(document.createTextNode(this.heading[0]));
551         curr_cell.appendChild(img);
552         table_heading.appendChild(curr_cell);
553         //now do the rest of the heading.
554         for(var i = 1; i < this.heading.length; i++)
555         {
556                 curr_cell = document.createElement("TH");
557                 var fn = new Function("", "nrsTables['" + this.my_table + "'].fieldSort(" + i + ");");
558                 if(!this.disable_sorting || this.disable_sorting.indexOf("." + i + ".") == -1)
559                         curr_cell.onclick = fn;
560                 if(this.header_color)
561                         curr_cell.style.backgroundColor = this.header_color;
562                 curr_cell.style.cursor = 'pointer';
563                 //build the sorter
564                 curr_cell.appendChild(document.createTextNode(this.heading[i]));
565                 table_heading.appendChild(curr_cell);
566         }
567         table_header.appendChild(table_heading);
568         
569         //now the content
570         var table_body = document.createElement("TBODY");
571         this.createDataNodes();
572         if(this.data)
573         {
574                 if(this.natural_compare)
575                         this.natSort(0, this.data.length - 1);
576                 else
577                         this.quickSort(0, this.data.length - 1);
578                 this.recolorRows();
579         }
580
581         //finally, the footer
582         var table_footer = document.createElement("TFOOT");
583         if(this.foot_headers)
584         {
585                 table_footer.appendChild(table_heading.cloneNode(true));
586         }
587         
588         if(this.page_nav && this.num_pages > 1)
589         {
590                 //print out the page navigation
591                 //first and previous page
592                 var nav = document.createElement("TR");
593                 var nav_cell = document.createElement("TH");
594                 nav_cell.colSpan = this.heading.length;
595                 
596                 var left = document.createElement("DIV");
597                 if(document.attachEvent)
598                         left.style.styleFloat = "left";
599                 else
600                         left.style.cssFloat = "left";
601                 var img = document.createElement("IMG");
602                 img.setAttribute("src", this.rew_icon);
603                 img.setAttribute("border", "0");
604                 img.setAttribute("height", "10");
605                 img.setAttribute("width", "10");
606                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].firstPage();");
607                 img.style.cursor = 'pointer';
608                 left.appendChild(img);
609                 //hack to space the arrows, cause IE is absolute crap
610                 left.appendChild(document.createTextNode(" "));
611                 img = document.createElement("IMG");
612                 img.setAttribute("src", this.prev_icon);
613                 img.setAttribute("border", "0");
614                 img.setAttribute("height", "10");
615                 img.setAttribute("width", "10");
616                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].prevPage();");
617                 img.style.cursor = 'pointer';
618                 left.appendChild(img);
619                 //apend it to the cell
620                 nav_cell.appendChild(left);
621                 
622                 //next and last pages
623                 var right = document.createElement("DIV");
624                 if(document.attachEvent)
625                         right.style.styleFloat = "right";
626                 else
627                         right.style.cssFloat = "right";
628                 img = document.createElement("IMG");
629                 img.setAttribute("src", this.next_icon);
630                 img.setAttribute("border", "0");
631                 img.setAttribute("height", "10");
632                 img.setAttribute("width", "10");
633                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].nextPage();");
634                 img.style.cursor = 'pointer';
635                 right.appendChild(img);
636                 //hack to space the arrows, cause IE is absolute crap
637                 right.appendChild(document.createTextNode(" "));
638                 img = document.createElement("IMG");
639                 img.setAttribute("src", this.fwd_icon);
640                 img.setAttribute("border", "0");
641                 img.setAttribute("height", "10");
642                 img.setAttribute("width", "10");
643                 img.onclick = new Function("", "JavaScript:nrsTables['" + this.my_table + "'].lastPage();");
644                 img.style.cursor = 'pointer';
645                 right.appendChild(img);
646                 //apend it to the cell
647                 nav_cell.appendChild(right);
648                 
649                 //page position
650                 var pos = document.createElement("SPAN");
651                 pos.setAttribute("id", "nav_pos");
652                 pos.appendChild(document.createTextNode("Page " + 
653                                                 (this.current_page + 1) + " of " + this.num_pages));
654                 //append it to the cell.
655                 nav_cell.appendChild(pos);
656                 
657                 nav.appendChild(nav_cell);
658                 //append it to the footer
659                 table_footer.appendChild(nav);
660         }
661         
662         if(this.footer_color)
663         {
664                 for(var i = 0; i < table_footer.childNodes.length; i++)
665                         table_footer.childNodes[i].style.backgroundColor = this.footer_color;
666         }
667         
668         //append the data
669         table.appendChild(table_header);
670         table.appendChild(table_body);
671         table.appendChild(table_footer);
672         if(this.data)
673         {
674                 if(this.natural_compare)
675                         this.natSort(0, this.data.length - 1);
676                 else
677                         this.quickSort(0, this.data.length - 1);
678         }
679         this.refreshTable();
680 };
681
682 /**
683  * This function will remove the elements in teh TBody section of the table and
684  * return an array of references of those elements.  This array can then be 
685  * sorted and re-inserted into the table.
686  * \return An array to references of the TBody contents.
687  */
688 nrsTable.prototype.extractElements = function()
689 {
690         var tbody = document.getElementById(this.my_table).tBodies[0];
691         var nodes = new Array();
692         var i = 0;
693         while(tbody.childNodes.length > 0)
694         {
695                 nodes[i] = tbody.removeChild(tbody.childNodes[0]);
696                 i++;
697         }
698         return nodes;
699 }
700
701 /**
702  * This function will re-insert an array of elements into the TBody of a table.
703  * Note that the array elements are stored in the this.data_nodes reference.
704  */
705 nrsTable.prototype.insertElements = function()
706 {
707         var tbody = document.getElementById(this.my_table).tBodies[0];
708         var start = 0;
709         var num_elements = this.data_nodes.length;
710         if(this.rows_per_page != -1)
711         {
712                 start = this.current_page * this.rows_per_page;
713                 num_elements = (this.data_nodes.length - start) > this.rows_per_page?
714                                                         this.rows_per_page + start:
715                                                         this.data_nodes.length;
716         }
717         DEBUG("start is " + start + " and num_elements is " + num_elements);
718         for(var i = start; i < num_elements; i++)
719         {
720                 tbody.appendChild(this.data_nodes[i]);
721         }
722 }
723
724 /**
725  * This function will sort the table's data by a specific field.  The field 
726  * parameter referes to which field index should be sorted.
727  * \param field The field index which to sort on.
728  */
729 nrsTable.prototype.fieldSort = function(field)
730 {
731         if(this.field_to_sort == field)
732         {
733                 //only need to reverse the array.
734                 if(this.data)
735                 {
736                         this.data.reverse();
737                         this.data_nodes.reverse();
738                 }
739                 //flip the arrow on the heading.
740                 this.flipSortArrow();
741         }
742         else
743         {
744                 //In this case, we need to sort the array.  We'll sort it last, first 
745                 //make sure that the arrow images are set correctly.
746                 this.moveSortArrow(field);
747                 //finally, set the field to sort by.
748                 this.field_to_sort = field;
749                 if(this.data)
750                 {
751                         //we'll be using our implementation of quicksort
752                         if(this.natural_compare)
753                                 this.natSort(0, this.data.length - 1);
754                         else
755                                 this.quickSort(0, this.data.length - 1);
756                 }
757         }
758         //finally, we refresh the table.
759         this.refreshTable();
760 };
761
762 /**
763  * This function will refresh the data in the table.  This function should be
764  * used whenever the nodes have changed, or when chanign pages.  Note that 
765  * this will NOT re-sort.
766  */
767 nrsTable.prototype.refreshTable = function()
768 {
769         this.extractElements();
770         this.recolorRows();
771         this.insertElements();
772         //finally, if there is a nav, upate it.
773         this.updateNav();
774 }
775
776 /**
777  * This function will advance a page.  If we are already at the last page, then 
778  * it will remain there.
779  */
780 nrsTable.prototype.nextPage = function()
781 {
782         DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
783         if(this.current_page + 1 != this.num_pages)
784         {
785                 this.current_page++;
786                 this.refreshTable();
787         }
788         DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
789 }
790
791 /**
792  * This function will go back a page.  If we are already at the first page, then 
793  * it will remain there.
794  */
795 nrsTable.prototype.prevPage = function()
796 {
797         DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
798         if(this.current_page != 0)
799         {
800                 this.current_page--;
801                 this.refreshTable();
802         }
803         DEBUG("current page is " + this.current_page + " and num_pages is " + this.num_pages);
804 }
805
806 /**
807  * This function will go to the first page.
808  */
809 nrsTable.prototype.firstPage = function()
810 {
811         if(this.current_page != 0)
812         {
813                 this.current_page = 0;
814                 this.refreshTable();
815         }
816 }
817
818 /**
819  * This function will go to the last page.
820  */
821 nrsTable.prototype.lastPage = function()
822 {
823         DEBUG("lastPage(), current_page: " + this.current_page + " and num_pages: " + this.num_pages);
824         if(this.current_page != (this.num_pages - 1))
825         {
826                 this.current_page = this.num_pages - 1;
827                 this.refreshTable();
828         }
829 }
830
831 /**
832  * This function will go to a specific page.  valid values are pages 1 to 
833  * however many number of pages there are.
834  * \param page The page number to go to.
835  */
836 nrsTable.prototype.gotoPage = function(page)
837 {
838         page--;
839         if(page >=0 && page < this.num_pages)
840         {
841                 this.current_page = page;
842                 this.refreshTable();
843         }
844 }
845
846 /**
847  * This function can be used to change the number of entries per row displayed
848  * on the fly.
849  * \param entries The number of entries per page.
850  */
851 nrsTable.prototype.changeNumRows = function(entries)
852 {
853         if(entries > 0)
854         {
855                 this.rows_per_page = entries;
856                 //we do.
857                 this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
858                 this.refreshTable();
859         }
860 }
861
862 /**
863  * This function will take in a new data array and , optionally, a new cell_link
864  * array OR a new row_link array.  Only one will be used, with the cell_link
865  * array taking precedence.  It will then re-build the table with the new data
866  * array.
867  * \param new_data This is the new data array.  This is required.
868  * \param cell_links This is the new cell links array, a 2D array for each cell.
869  * \param row_links This is the new row links array, a 1D array for each row.
870  */
871 nrsTable.prototype.newData = function(new_data, cell_links, row_links)
872 {
873         //extract the elements from teh table to clear the table.
874         this.extractElements();
875         //now delete all the data related to this table.  I do this so that 
876         //(hopefully) the memory will be freed.  This is realy needed for IE, whose
877         //memory handling is almost non-existant
878         delete this.data;
879         delete this.data_nodes;
880         delete this.cell_links;
881         delete this.row_links
882         //now re-assign.
883         this.data = new_data;
884         this.cell_links = cell_links;
885         this.row_links = row_links;
886         if(this.rows_per_page != -1)
887         {
888                 //we do.
889                 this.num_pages = Math.ceil(this.data.length / this.rows_per_page);
890                 if(this.num_pages <= 1 && this.page_nav)
891                         this.removeNav();
892                 else if(this.page_nav)
893                         this.insertNav();
894                 this.current_page = 0;
895         }
896         this.createDataNodes();
897         if(this.field_to_sort != 0)
898                 this.moveSortArrow(0);
899         if(!this.field_asc)
900                 this.flipSortArrow();
901         this.insertElements();
902         this.updateNav();
903 }
904
905 /**
906  * This function will remove the NAV bar (if one exists) from the table.
907  */
908 nrsTable.prototype.removeNav = function()
909 {
910         if(this.page_nav)
911         {
912                 //in this case, remove the nav from the existing structure.
913                 var table = document.getElementById(this.my_table);
914                 var p = 0;
915                 if(this.foot_headers)
916                         p++;
917                 var nav = table.tFoot.childNodes[p];
918                 if(nav)
919                 {
920                         table.tFoot.removeChild(nav);
921                         delete nav;
922                 }
923         }
924 }
925
926 /**
927  * This function wil re-insert the nav into the table.
928  */
929 nrsTable.prototype.insertNav = function()
930 {
931         table = document.getElementById(this.my_table);
932         var p = 0;
933         if(this.foot_headers)
934                 p++;
935         if(this.page_nav && !table.tFoot.childNodes[p])
936         {
937                 //this means there should be a nav and there isn't one.
938                 //print out the page navigation
939                 //first and previous page
940                 var nav = document.createElement("TR");
941                 var nav_cell = document.createElement("TH");
942                 nav_cell.colSpan = this.heading.length;
943                 
944                 var left = document.createElement("DIV");
945                 if(document.attachEvent)
946                         left.style.styleFloat = "left";
947                 else
948                         left.style.cssFloat = "left";
949                 var img = document.createElement("IMG");
950                 img.setAttribute("src", this.rew_icon);
951                 img.setAttribute("border", "0");
952                 img.setAttribute("height", "10");
953                 img.setAttribute("width", "10");
954                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].firstPage();");
955                 img.style.cursor = 'pointer';
956                 left.appendChild(img);
957                 //hack to space the arrows, cause IE is absolute crap
958                 left.appendChild(document.createTextNode(" "));
959                 img = document.createElement("IMG");
960                 img.setAttribute("src", this.prev_icon);
961                 img.setAttribute("border", "0");
962                 img.setAttribute("height", "10");
963                 img.setAttribute("width", "10");
964                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].prevPage();");
965                 img.style.cursor = 'pointer';
966                 left.appendChild(img);
967                 //apend it to the cell
968                 nav_cell.appendChild(left);
969                 
970                 //next and last pages
971                 var right = document.createElement("DIV");
972                 if(document.attachEvent)
973                         right.style.styleFloat = "right";
974                 else
975                         right.style.cssFloat = "right";
976                 img = document.createElement("IMG");
977                 img.setAttribute("src", this.next_icon);
978                 img.setAttribute("border", "0");
979                 img.setAttribute("height", "10");
980                 img.setAttribute("width", "10");
981                 img.onclick = new Function("", "nrsTables['" + this.my_table + "'].nextPage();");
982                 img.style.cursor = 'pointer';
983                 right.appendChild(img);
984                 //hack to space the arrows, cause IE is absolute crap
985                 right.appendChild(document.createTextNode(" "));
986                 img = document.createElement("IMG");
987                 img.setAttribute("src", this.fwd_icon);
988                 img.setAttribute("border", "0");
989                 img.setAttribute("height", "10");
990                 img.setAttribute("width", "10");
991                 img.onclick = new Function("", "JavaScript:nrsTables['" + this.my_table + "'].lastPage();");
992                 img.style.cursor = 'pointer';
993                 right.appendChild(img);
994                 //apend it to the cell
995                 nav_cell.appendChild(right);
996                 
997                 //page position
998                 var pos = document.createElement("SPAN");
999                 pos.setAttribute("id", "nav_pos");
1000                 pos.appendChild(document.createTextNode("Page " + 
1001                                                 (this.current_page + 1) + " of " + this.num_pages));
1002                 //append it to the cell.
1003                 nav_cell.appendChild(pos);
1004                 
1005                 nav.appendChild(nav_cell);
1006                 //append it to the footer
1007                 table.tFoot.appendChild(nav);
1008         }
1009 }
1010
1011 /**
1012  * This function will hide the previous arrow and the rewind arrows from the
1013  * nav field.
1014  */
1015 nrsTable.prototype.hideLeftArrows = function()
1016 {
1017         if(!this.page_nav)
1018                 return;
1019         var myTable = document.getElementById(this.my_table);
1020         var p = 0;
1021         if(this.foot_headers)
1022                 p++;
1023         var nav = myTable.tFoot.childNodes[p];
1024         nav.childNodes[0].childNodes[0].style.display = "none";
1025 }
1026
1027 /**
1028  * This function will show the previous arrow and the rewind arrows from the
1029  * nav field.
1030  */
1031 nrsTable.prototype.showLeftArrows = function()
1032 {
1033         if(!this.page_nav)
1034                 return;
1035         table = document.getElementById(this.my_table);
1036         var p = 0;
1037         if(this.foot_headers)
1038                 p++;
1039         var nav = table.tFoot.childNodes[p];
1040         nav.childNodes[0].childNodes[0].style.display = "block";
1041 }
1042
1043 /**
1044  * This function will hide the next arrow and the fast foward arrows from the
1045  * nav field.
1046  */
1047 nrsTable.prototype.hideRightArrows = function()
1048 {
1049         if(!this.page_nav)
1050                 return;
1051         table = document.getElementById(this.my_table);
1052         var p = 0;
1053         if(this.foot_headers)
1054                 p++;
1055         var nav = table.tFoot.childNodes[p];
1056         nav.childNodes[0].childNodes[1].style.display = "none";
1057 }
1058
1059 /**
1060  * This function will show the next arrow and the fast foward arrows from the
1061  * nav field.
1062  */
1063 nrsTable.prototype.showRightArrows = function()
1064 {
1065         if(!this.page_nav)
1066                 return;
1067         table = document.getElementById(this.my_table);
1068         var p = 0;
1069         if(this.foot_headers)
1070                 p++;
1071         var nav = table.tFoot.childNodes[p];
1072         nav.childNodes[0].childNodes[1].style.display = "block";
1073 }