add sorting tables to the pcu view.
[monitor.git] / web / MonitorWeb / monitorweb / static / javascript / sortable_tables.js
1 /*
2
3 On page load, the SortableManager:
4
5 - Finds the table by its id (sortable_table).
6 - Parses its thead for columns with a "mochi:format" attribute.
7 - Parses the data out of the tbody based upon information given in the
8   "mochi:format" attribute, and clones the tr elements for later re-use.
9 - Clones the column header th elements for use as a template when drawing 
10   sort arrow columns.
11 - Stores away a reference to the tbody, as it will be replaced on each sort.
12 - Performs the first sort.
13
14
15 On sort request:
16
17 - Sorts the data based on the given key and direction
18 - Creates a new tbody from the rows in the new ordering
19 - Replaces the column header th elements with clickable versions, adding an
20    indicator (↑ or ↓) to the most recently sorted column.
21
22 */
23
24 SortableManager = function () {
25     this.thead = null;
26     this.tbody = null;
27     this.columns = [];
28     this.rows = [];
29     this.sortState = {};
30     this.sortkey = 0;
31 };
32
33 mouseOverFunc = function () {
34     addElementClass(this, "over");
35 };
36
37 mouseOutFunc = function () {
38     removeElementClass(this, "over");
39 };
40
41 ignoreEvent = function (ev) {
42     if (ev && ev.preventDefault) {
43         ev.preventDefault();
44         ev.stopPropagation();
45     } else if (typeof(event) != 'undefined') {
46         event.cancelBubble = false;
47         event.returnValue = false;
48     }
49 };
50
51
52 update(SortableManager.prototype, {
53
54     "initWithTable": function (table) {
55         /***
56
57             Initialize the SortableManager with a table object
58         
59         ***/
60         // Ensure that it's a DOM element
61         table = getElement(table);
62         // Find the thead
63         this.thead = table.getElementsByTagName('thead')[0];
64         // get the mochi:format key and contents for each column header
65         var cols = this.thead.getElementsByTagName('th');
66         for (var i = 0; i < cols.length; i++) {
67             var node = cols[i];
68             var attr = null;
69             try {
70                 attr = node.getAttribute("mochi:format");
71             } catch (err) {
72                 // pass
73             }
74             var o = node.childNodes;
75             this.columns.push({
76                 "format": attr,
77                 "element": node,
78                 "proto": node.cloneNode(true)
79             });
80         }
81         // scrape the tbody for data
82         this.tbody = table.getElementsByTagName('tbody')[0];
83         // every row
84         var rows = this.tbody.getElementsByTagName('tr');
85         for (var i = 0; i < rows.length; i++) {
86             // every cell
87             var row = rows[i];
88             var cols = row.getElementsByTagName('td');
89             var rowData = [];
90             for (var j = 0; j < cols.length; j++) {
91                 // scrape the text and build the appropriate object out of it
92                 var cell = cols[j];
93                 var obj = scrapeText(cell);
94                 switch (this.columns[j].format) {
95                     case 'isodate':
96                         obj = isoDate(obj);
97                         break;
98                     case 'int':
99                         obj = parseInt(obj);
100                         break;
101                     case 'str':
102                         break;
103                     case 'istr':
104                         obj = obj.toLowerCase();
105                         break;
106                     // cases for numbers, etc. could be here
107                     default:
108                         break;
109                 }
110                 rowData.push(obj);
111             }
112             // stow away a reference to the TR and save it
113             rowData.row = row.cloneNode(true);
114             this.rows.push(rowData);
115
116         }
117
118         // do initial sort on first column
119         this.drawSortedRows(this.sortkey, true, false);
120
121     },
122
123     "onSortClick": function (name) {
124         /***
125
126             Return a sort function for click events
127
128         ***/
129         return method(this, function () {
130             log('onSortClick', name);
131             var order = this.sortState[name];
132             if (order == null) {
133                 order = true;
134             } else if (name == this.sortkey) {
135                 order = !order;
136             }
137             this.drawSortedRows(name, order, true);
138         });
139     },
140
141     "drawSortedRows": function (key, forward, clicked) {
142         /***
143
144             Draw the new sorted table body, and modify the column headers
145             if appropriate
146
147         ***/
148         log('drawSortedRows', key, forward);
149         this.sortkey = key;
150         // sort based on the state given (forward or reverse)
151         var cmp = (forward ? keyComparator : reverseKeyComparator);
152         this.rows.sort(cmp(key));
153         // save it so we can flip next time
154         this.sortState[key] = forward;
155         // get every "row" element from this.rows and make a new tbody
156         var newBody = TBODY(null, map(itemgetter("row"), this.rows));
157         // swap in the new tbody
158         this.tbody = swapDOM(this.tbody, newBody);
159                 // replace first column with row count.
160                 var rows = this.tbody.getElementsByTagName('tr');
161         for (var i=0; i < rows.length; i++) {
162                         var row = rows[i];
163             var cols = row.getElementsByTagName('td');
164                         // var cell = cols[0];
165                         cols[0].innerHTML = String(i);
166                 }
167         for (var i = 0; i < this.columns.length; i++) {
168             var col = this.columns[i];
169             var node = col.proto.cloneNode(true);
170             // remove the existing events to minimize IE leaks
171             col.element.onclick = null;
172             col.element.onmousedown = null;
173             col.element.onmouseover = null;
174             col.element.onmouseout = null;
175             // set new events for the new node
176             node.onclick = this.onSortClick(i);
177             node.onmousedown = ignoreEvent;
178             node.onmouseover = mouseOverFunc;
179             node.onmouseout = mouseOutFunc;
180             // if this is the sorted column
181             if (key == i) {
182                 // \u2193 is down arrow, \u2191 is up arrow
183                 // forward sorts mean the rows get bigger going down
184                 var arrow = (forward ? "\u2193" : "\u2191");
185                 // add the character to the column header
186                 node.appendChild(SPAN(null, arrow));
187                 if (clicked) {
188                     node.onmouseover();
189                 }
190             }
191  
192             // swap in the new th
193             col.element = swapDOM(col.element, node);
194         }
195     }
196 });
197
198 sortableManager = new SortableManager();
199
200 addLoadEvent(function () {
201     sortableManager.initWithTable('sortable_table');
202 });
203
204 // rewrite the view-source links
205 addLoadEvent(function () {
206     var elems = getElementsByTagAndClassName("A", "view-source");
207     var page = "sortable_tables/";
208     for (var i = 0; i < elems.length; i++) {
209         var elem = elems[i];
210         var href = elem.href.split(/\//).pop();
211         elem.target = "_blank";
212         elem.href = "../view-source/view-source.html#" + page + href;
213     }
214 });