Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
12405 | guerimand | 1 | ;(function() { |
2 | function Tablesort(el, options) { |
||
3 | if (!(this instanceof Tablesort)) return new Tablesort(el, options); |
||
4 | |||
5 | if (!el || el.tagName !== 'TABLE') { |
||
6 | throw new Error('Element must be a table'); |
||
7 | } |
||
8 | this.init(el, options || {}); |
||
9 | } |
||
10 | |||
11 | var sortOptions = []; |
||
12 | |||
13 | var createEvent = function(name) { |
||
14 | var evt; |
||
15 | |||
16 | if (!window.CustomEvent || typeof window.CustomEvent !== 'function') { |
||
17 | evt = document.createEvent('CustomEvent'); |
||
18 | evt.initCustomEvent(name, false, false, undefined); |
||
19 | } else { |
||
20 | evt = new CustomEvent(name); |
||
21 | } |
||
22 | |||
23 | return evt; |
||
24 | }; |
||
25 | |||
26 | var getInnerText = function(el) { |
||
27 | return el.getAttribute('data-sort') || el.textContent || el.innerText || ''; |
||
28 | }; |
||
29 | |||
30 | // Default sort method if no better sort method is found |
||
31 | var caseInsensitiveSort = function(a, b) { |
||
32 | a = a.trim().toLowerCase(); |
||
33 | b = b.trim().toLowerCase(); |
||
34 | |||
35 | if (a === b) return 0; |
||
36 | if (a < b) return 1; |
||
37 | |||
38 | return -1; |
||
39 | }; |
||
40 | |||
41 | // Stable sort function |
||
42 | // If two elements are equal under the original sort function, |
||
43 | // then there relative order is reversed |
||
44 | var stabilize = function(sort, antiStabilize) { |
||
45 | return function(a, b) { |
||
46 | var unstableResult = sort(a.td, b.td); |
||
47 | |||
48 | if (unstableResult === 0) { |
||
49 | if (antiStabilize) return b.index - a.index; |
||
50 | return a.index - b.index; |
||
51 | } |
||
52 | |||
53 | return unstableResult; |
||
54 | }; |
||
55 | }; |
||
56 | |||
57 | Tablesort.extend = function(name, pattern, sort) { |
||
58 | if (typeof pattern !== 'function' || typeof sort !== 'function') { |
||
59 | throw new Error('Pattern and sort must be a function'); |
||
60 | } |
||
61 | |||
62 | sortOptions.push({ |
||
63 | name: name, |
||
64 | pattern: pattern, |
||
65 | sort: sort |
||
66 | }); |
||
67 | }; |
||
68 | |||
69 | Tablesort.prototype = { |
||
70 | |||
71 | init: function(el, options) { |
||
72 | var that = this, |
||
73 | firstRow, |
||
74 | defaultSort, |
||
75 | i, |
||
76 | cell; |
||
77 | |||
78 | that.table = el; |
||
79 | that.thead = false; |
||
80 | that.options = options; |
||
81 | |||
82 | if (el.rows && el.rows.length > 0) { |
||
83 | if (el.tHead && el.tHead.rows.length > 0) { |
||
84 | for (i = 0; i < el.tHead.rows.length; i++) { |
||
85 | if (el.tHead.rows[i].getAttribute('data-sort-method') === 'thead') { |
||
86 | firstRow = el.tHead.rows[i]; |
||
87 | break; |
||
88 | } |
||
89 | } |
||
90 | if (!firstRow) { |
||
91 | firstRow = el.tHead.rows[el.tHead.rows.length - 1]; |
||
92 | } |
||
93 | that.thead = true; |
||
94 | } else { |
||
95 | firstRow = el.rows[0]; |
||
96 | } |
||
97 | } |
||
98 | |||
99 | if (!firstRow) return; |
||
100 | |||
101 | var onClick = function() { |
||
102 | if (that.current && that.current !== this) { |
||
103 | that.current.removeAttribute('aria-sort'); |
||
104 | } |
||
105 | |||
106 | that.current = this; |
||
107 | that.sortTable(this); |
||
108 | }; |
||
109 | |||
110 | // Assume first row is the header and attach a click handler to each. |
||
111 | for (i = 0; i < firstRow.cells.length; i++) { |
||
112 | cell = firstRow.cells[i]; |
||
113 | cell.setAttribute('role','columnheader'); |
||
114 | if (cell.getAttribute('data-sort-method') !== 'none') { |
||
115 | cell.tabindex = 0; |
||
116 | cell.addEventListener('click', onClick, false); |
||
117 | |||
118 | if (cell.getAttribute('data-sort-default') !== null) { |
||
119 | defaultSort = cell; |
||
120 | } |
||
121 | } |
||
122 | } |
||
123 | |||
124 | if (defaultSort) { |
||
125 | that.current = defaultSort; |
||
126 | that.sortTable(defaultSort); |
||
127 | } |
||
128 | }, |
||
129 | |||
130 | sortTable: function(header, update) { |
||
131 | var that = this, |
||
132 | column = header.cellIndex, |
||
133 | sortFunction = caseInsensitiveSort, |
||
134 | item = '', |
||
135 | items = [], |
||
136 | i = that.thead ? 0 : 1, |
||
137 | sortMethod = header.getAttribute('data-sort-method'), |
||
138 | sortOrder = header.getAttribute('aria-sort'); |
||
139 | |||
140 | that.table.dispatchEvent(createEvent('beforeSort')); |
||
141 | |||
142 | // If updating an existing sort, direction should remain unchanged. |
||
143 | if (!update) { |
||
144 | if (sortOrder === 'ascending') { |
||
145 | sortOrder = 'descending'; |
||
146 | } else if (sortOrder === 'descending') { |
||
147 | sortOrder = 'ascending'; |
||
148 | } else { |
||
149 | sortOrder = that.options.descending ? 'descending' : 'ascending'; |
||
150 | } |
||
151 | |||
152 | header.setAttribute('aria-sort', sortOrder); |
||
153 | } |
||
154 | |||
155 | if (that.table.rows.length < 2) return; |
||
156 | |||
157 | // If we force a sort method, it is not necessary to check rows |
||
158 | if (!sortMethod) { |
||
159 | while (items.length < 3 && i < that.table.tBodies[0].rows.length) { |
||
160 | item = getInnerText(that.table.tBodies[0].rows[i].cells[column]); |
||
161 | item = item.trim(); |
||
162 | |||
163 | if (item.length > 0) { |
||
164 | items.push(item); |
||
165 | } |
||
166 | |||
167 | i++; |
||
168 | } |
||
169 | |||
170 | if (!items) return; |
||
171 | } |
||
172 | |||
173 | for (i = 0; i < sortOptions.length; i++) { |
||
174 | item = sortOptions[i]; |
||
175 | |||
176 | if (sortMethod) { |
||
177 | if (item.name === sortMethod) { |
||
178 | sortFunction = item.sort; |
||
179 | break; |
||
180 | } |
||
181 | } else if (items.every(item.pattern)) { |
||
182 | sortFunction = item.sort; |
||
183 | break; |
||
184 | } |
||
185 | } |
||
186 | |||
187 | that.col = column; |
||
188 | |||
189 | for (i = 0; i < that.table.tBodies.length; i++) { |
||
190 | var newRows = [], |
||
191 | noSorts = {}, |
||
192 | j, |
||
193 | totalRows = 0, |
||
194 | noSortsSoFar = 0; |
||
195 | |||
196 | if (that.table.tBodies[i].rows.length < 2) continue; |
||
197 | |||
198 | for (j = 0; j < that.table.tBodies[i].rows.length; j++) { |
||
199 | item = that.table.tBodies[i].rows[j]; |
||
200 | if (item.getAttribute('data-sort-method') === 'none') { |
||
201 | // keep no-sorts in separate list to be able to insert |
||
202 | // them back at their original position later |
||
203 | noSorts[totalRows] = item; |
||
204 | } else { |
||
205 | // Save the index for stable sorting |
||
206 | newRows.push({ |
||
207 | tr: item, |
||
208 | td: getInnerText(item.cells[that.col]), |
||
209 | index: totalRows |
||
210 | }); |
||
211 | } |
||
212 | totalRows++; |
||
213 | } |
||
214 | // Before we append should we reverse the new array or not? |
||
215 | // If we reverse, the sort needs to be `anti-stable` so that |
||
216 | // the double negatives cancel out |
||
217 | if (sortOrder === 'descending') { |
||
218 | newRows.sort(stabilize(sortFunction, true)); |
||
219 | } else { |
||
220 | newRows.sort(stabilize(sortFunction, false)); |
||
221 | newRows.reverse(); |
||
222 | } |
||
223 | |||
224 | // append rows that already exist rather than creating new ones |
||
225 | for (j = 0; j < totalRows; j++) { |
||
226 | if (noSorts[j]) { |
||
227 | // We have a no-sort row for this position, insert it here. |
||
228 | item = noSorts[j]; |
||
229 | noSortsSoFar++; |
||
230 | } else { |
||
231 | item = newRows[j - noSortsSoFar].tr; |
||
232 | } |
||
233 | |||
234 | // appendChild(x) moves x if already present somewhere else in the DOM |
||
235 | that.table.tBodies[i].appendChild(item); |
||
236 | } |
||
237 | } |
||
238 | |||
239 | that.table.dispatchEvent(createEvent('afterSort')); |
||
240 | }, |
||
241 | |||
242 | refresh: function() { |
||
243 | if (this.current !== undefined) { |
||
244 | this.sortTable(this.current, true); |
||
245 | } |
||
246 | } |
||
247 | }; |
||
248 | |||
249 | if (typeof module !== 'undefined' && module.exports) { |
||
250 | module.exports = Tablesort; |
||
251 | } else { |
||
252 | window.Tablesort = Tablesort; |
||
253 | } |
||
254 | })(); |