Commit | Line | Data |
---|---|---|
6a1aa64f DV |
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 'str': | |
99 | break; | |
100 | case 'istr': | |
101 | obj = obj.toLowerCase(); | |
102 | break; | |
103 | // cases for numbers, etc. could be here | |
104 | default: | |
105 | break; | |
106 | } | |
107 | rowData.push(obj); | |
108 | } | |
109 | // stow away a reference to the TR and save it | |
110 | rowData.row = row.cloneNode(true); | |
111 | this.rows.push(rowData); | |
112 | ||
113 | } | |
114 | ||
115 | // do initial sort on first column | |
116 | this.drawSortedRows(this.sortkey, true, false); | |
117 | ||
118 | }, | |
119 | ||
120 | "onSortClick": function (name) { | |
121 | /*** | |
122 | ||
123 | Return a sort function for click events | |
124 | ||
125 | ***/ | |
126 | return method(this, function () { | |
127 | log('onSortClick', name); | |
128 | var order = this.sortState[name]; | |
129 | if (order == null) { | |
130 | order = true; | |
131 | } else if (name == this.sortkey) { | |
132 | order = !order; | |
133 | } | |
134 | this.drawSortedRows(name, order, true); | |
135 | }); | |
136 | }, | |
137 | ||
138 | "drawSortedRows": function (key, forward, clicked) { | |
139 | /*** | |
140 | ||
141 | Draw the new sorted table body, and modify the column headers | |
142 | if appropriate | |
143 | ||
144 | ***/ | |
145 | log('drawSortedRows', key, forward); | |
146 | this.sortkey = key; | |
147 | // sort based on the state given (forward or reverse) | |
148 | var cmp = (forward ? keyComparator : reverseKeyComparator); | |
149 | this.rows.sort(cmp(key)); | |
150 | // save it so we can flip next time | |
151 | this.sortState[key] = forward; | |
152 | // get every "row" element from this.rows and make a new tbody | |
153 | var newBody = TBODY(null, map(itemgetter("row"), this.rows)); | |
154 | // swap in the new tbody | |
155 | this.tbody = swapDOM(this.tbody, newBody); | |
156 | for (var i = 0; i < this.columns.length; i++) { | |
157 | var col = this.columns[i]; | |
158 | var node = col.proto.cloneNode(true); | |
159 | // remove the existing events to minimize IE leaks | |
160 | col.element.onclick = null; | |
161 | col.element.onmousedown = null; | |
162 | col.element.onmouseover = null; | |
163 | col.element.onmouseout = null; | |
164 | // set new events for the new node | |
165 | node.onclick = this.onSortClick(i); | |
166 | node.onmousedown = ignoreEvent; | |
167 | node.onmouseover = mouseOverFunc; | |
168 | node.onmouseout = mouseOutFunc; | |
169 | // if this is the sorted column | |
170 | if (key == i) { | |
171 | // \u2193 is down arrow, \u2191 is up arrow | |
172 | // forward sorts mean the rows get bigger going down | |
173 | var arrow = (forward ? "\u2193" : "\u2191"); | |
174 | // add the character to the column header | |
175 | node.appendChild(SPAN(null, arrow)); | |
176 | if (clicked) { | |
177 | node.onmouseover(); | |
178 | } | |
179 | } | |
180 | ||
181 | // swap in the new th | |
182 | col.element = swapDOM(col.element, node); | |
183 | } | |
184 | } | |
185 | }); | |
186 | ||
187 | sortableManager = new SortableManager(); | |
188 | ||
189 | addLoadEvent(function () { | |
190 | sortableManager.initWithTable('sortable_table'); | |
191 | }); | |
192 | ||
193 | // rewrite the view-source links | |
194 | addLoadEvent(function () { | |
195 | var elems = getElementsByTagAndClassName("A", "view-source"); | |
196 | var page = "sortable_tables/"; | |
197 | for (var i = 0; i < elems.length; i++) { | |
198 | var elem = elems[i]; | |
199 | var href = elem.href.split(/\//).pop(); | |
200 | elem.target = "_blank"; | |
201 | elem.href = "../view-source/view-source.html#" + page + href; | |
202 | } | |
203 | }); |