|
1 /* Adapted from MochiKit's example to use custom cubicweb attribute |
|
2 and a stable sort (merge sort) instead of default's js array sort |
|
3 |
|
4 merge sort JS implementation was found here : |
|
5 http://en.literateprograms.org/Merge_sort_(JavaScript) |
|
6 |
|
7 |
|
8 On page load, the SortableManager: |
|
9 |
|
10 - Finds the table by its id (sortable_table). |
|
11 - Parses its thead for columns with a "mochi:format" attribute. |
|
12 - Parses the data out of the tbody based upon information given in the |
|
13 "cubicweb:sorvalue" attribute, and clones the tr elements for later re-use. |
|
14 - Clones the column header th elements for use as a template when drawing |
|
15 sort arrow columns. |
|
16 - Stores away a reference to the tbody, as it will be replaced on each sort. |
|
17 |
|
18 On sort request: |
|
19 |
|
20 - Sorts the data based on the given key and direction |
|
21 - Creates a new tbody from the rows in the new ordering |
|
22 - Replaces the column header th elements with clickable versions, adding an |
|
23 indicator (↑ or ↓) to the most recently sorted column. |
|
24 |
|
25 */ |
|
26 |
|
27 //************** merge sort implementation ***************// |
|
28 Sortable = {} |
|
29 |
|
30 Sortable.msort = function(array, begin, end, cmpfunc) { |
|
31 var size=end-begin; |
|
32 if(size<2) return; |
|
33 |
|
34 var begin_right=begin+Math.floor(size/2); |
|
35 |
|
36 Sortable.msort(array, begin, begin_right, cmpfunc); |
|
37 Sortable.msort(array, begin_right, end, cmpfunc); |
|
38 Sortable.merge(array, begin, begin_right, end, cmpfunc); |
|
39 } |
|
40 |
|
41 Sortable.merge_sort = function(array, cmpfunc) { |
|
42 Sortable.msort(array, 0, array.length, cmpfunc); |
|
43 } |
|
44 |
|
45 Sortable.merge = function(array, begin, begin_right, end, cmpfunc) { |
|
46 for(;begin<begin_right; ++begin) { |
|
47 // if array[begin] > array[begin_right] |
|
48 if(cmpfunc(array[begin], array[begin_right]) == 1) { |
|
49 var v = array[begin]; |
|
50 array[begin] = array[begin_right]; |
|
51 Sortable.insert(array, begin_right, end, v, cmpfunc); |
|
52 } |
|
53 } |
|
54 } |
|
55 |
|
56 Array.prototype.swap=function(a, b) { |
|
57 var tmp = this[a]; |
|
58 this[a] = this[b]; |
|
59 this[b] = tmp; |
|
60 } |
|
61 |
|
62 |
|
63 Sortable.insert = function(array, begin, end, v, cmpfunc) { |
|
64 // while(begin+1<end && array[begin+1]<v) { |
|
65 while(begin+1<end && cmpfunc(array[begin+1], v) == -1) { |
|
66 array.swap(begin, begin+1); |
|
67 ++begin; |
|
68 } |
|
69 array[begin]=v; |
|
70 } |
|
71 |
|
72 //************** auto-sortable tables ***************// |
|
73 |
|
74 Sortable.SortableManager = function () { |
|
75 this.thead = null; |
|
76 this.tbody = null; |
|
77 this.columns = []; |
|
78 this.rows = []; |
|
79 this.sortState = {}; |
|
80 this.sortkey = 0; |
|
81 }; |
|
82 |
|
83 mouseOverFunc = function () { |
|
84 addElementClass(this, "over"); |
|
85 }; |
|
86 |
|
87 mouseOutFunc = function () { |
|
88 removeElementClass(this, "over"); |
|
89 }; |
|
90 |
|
91 Sortable.ignoreEvent = function (ev) { |
|
92 if (ev && ev.preventDefault) { |
|
93 ev.preventDefault(); |
|
94 ev.stopPropagation(); |
|
95 } else if (typeof(event) != 'undefined') { |
|
96 event.cancelBubble = false; |
|
97 event.returnValue = false; |
|
98 } |
|
99 }; |
|
100 |
|
101 |
|
102 Sortable.getTableHead = function(table) { |
|
103 var thead = table.getElementsByTagName('thead')[0]; |
|
104 if ( !thead ) { |
|
105 thead = table.getElementsByTagName('tr')[0]; |
|
106 } |
|
107 return thead; |
|
108 } |
|
109 |
|
110 Sortable.getTableBody = function(table) { |
|
111 var tbody = table.getElementsByTagName('tbody')[0]; |
|
112 if ( !tbody ) { |
|
113 tobdy = table; // XXX |
|
114 } |
|
115 return tbody; |
|
116 } |
|
117 |
|
118 jQuery.extend(Sortable.SortableManager.prototype, { |
|
119 |
|
120 "initWithTable" : function (table) { |
|
121 /*** Initialize the SortableManager with a table object ***/ |
|
122 // Find the thead |
|
123 this.thead = Sortable.getTableHead(table); |
|
124 // get the mochi:format key and contents for each column header |
|
125 var cols = this.thead.getElementsByTagName('th'); |
|
126 for (var i = 0; i < cols.length; i++) { |
|
127 var node = cols[i]; |
|
128 var o = node.childNodes; |
|
129 node.onclick = this.onSortClick(i); |
|
130 node.onmousedown = Sortable.ignoreEvent; |
|
131 node.onmouseover = mouseOverFunc; |
|
132 node.onmouseout = mouseOutFunc; |
|
133 this.columns.push({ |
|
134 "element": node, |
|
135 "proto": node.cloneNode(true) |
|
136 }); |
|
137 } |
|
138 // scrape the tbody for data |
|
139 this.tbody = Sortable.getTableBody(table); |
|
140 // every row |
|
141 var rows = this.tbody.getElementsByTagName('tr'); |
|
142 for (var i = 0; i < rows.length; i++) { |
|
143 // every cell |
|
144 var row = rows[i]; |
|
145 var cols = row.getElementsByTagName('td'); |
|
146 var rowData = []; |
|
147 for (var j = 0; j < cols.length; j++) { |
|
148 // scrape the text and build the appropriate object out of it |
|
149 var cell = cols[j]; |
|
150 rowData.push([evalJSON(cell.getAttribute('cubicweb:sortvalue'))]); |
|
151 } |
|
152 // stow away a reference to the TR and save it |
|
153 rowData.row = row.cloneNode(true); |
|
154 this.rows.push(rowData); |
|
155 } |
|
156 // do initial sort on first column |
|
157 // this.drawSortedRows(null, true, false); |
|
158 |
|
159 }, |
|
160 |
|
161 "onSortClick" : function (name) { |
|
162 /*** Return a sort function for click events ***/ |
|
163 return method(this, function () { |
|
164 var order = this.sortState[name]; |
|
165 if (order == null) { |
|
166 order = true; |
|
167 } else if (name == this.sortkey) { |
|
168 order = !order; |
|
169 } |
|
170 this.drawSortedRows(name, order, true); |
|
171 }); |
|
172 }, |
|
173 |
|
174 "drawSortedRows" : function (key, forward, clicked) { |
|
175 /*** Draw the new sorted table body, and modify the column headers |
|
176 if appropriate |
|
177 ***/ |
|
178 this.sortkey = key; |
|
179 // sort based on the state given (forward or reverse) |
|
180 var cmp = (forward ? keyComparator : reverseKeyComparator); |
|
181 Sortable.merge_sort(this.rows, cmp(key)); |
|
182 |
|
183 // save it so we can flip next time |
|
184 this.sortState[key] = forward; |
|
185 // get every "row" element from this.rows and make a new tbody |
|
186 var newRows = []; |
|
187 for (var i=0; i < this.rows.length; i++){ |
|
188 var row = this.rows[i].row; |
|
189 if (i%2) { |
|
190 removeElementClass(row, 'even'); |
|
191 addElementClass(row, 'odd'); |
|
192 } else { |
|
193 removeElementClass(row, 'odd'); |
|
194 addElementClass(row, 'even'); |
|
195 } |
|
196 newRows.push(row); |
|
197 } |
|
198 // var newBody = TBODY(null, map(itemgetter("row"), this.rows)); |
|
199 var newBody = TBODY(null, newRows); |
|
200 // swap in the new tbody |
|
201 this.tbody = swapDOM(this.tbody, newBody); |
|
202 for (var i = 0; i < this.columns.length; i++) { |
|
203 var col = this.columns[i]; |
|
204 var node = col.proto.cloneNode(true); |
|
205 // remove the existing events to minimize IE leaks |
|
206 col.element.onclick = null; |
|
207 col.element.onmousedown = null; |
|
208 col.element.onmouseover = null; |
|
209 col.element.onmouseout = null; |
|
210 // set new events for the new node |
|
211 node.onclick = this.onSortClick(i); |
|
212 node.onmousedown = Sortable.ignoreEvent; |
|
213 node.onmouseover = mouseOverFunc; |
|
214 node.onmouseout = mouseOutFunc; |
|
215 // if this is the sorted column |
|
216 if (key == i) { |
|
217 // \u2193 is down arrow, \u2191 is up arrow |
|
218 // forward sorts mean the rows get bigger going down |
|
219 var arrow = (forward ? "\u2193" : "\u2191"); |
|
220 // add the character to the column header |
|
221 node.appendChild(SPAN(null, arrow)); |
|
222 if (clicked) { |
|
223 node.onmouseover(); |
|
224 } |
|
225 } |
|
226 |
|
227 // swap in the new th |
|
228 col.element = swapDOM(col.element, node); |
|
229 } |
|
230 } |
|
231 }); |
|
232 |
|
233 var sortableManagers = []; |
|
234 |
|
235 /* |
|
236 * Find each table under `rootNode` and make them sortable |
|
237 */ |
|
238 Sortable.makeTablesSortable = function(rootNode) { |
|
239 var tables = getElementsByTagAndClassName('table', 'listing', rootNode); |
|
240 for(var i=0; i < tables.length; i++) { |
|
241 var sortableManager = new Sortable.SortableManager(); |
|
242 sortableManager.initWithTable(tables[i]); |
|
243 sortableManagers.push(sortableManagers); |
|
244 } |
|
245 } |
|
246 |
|
247 jQuery(document).ready(Sortable.makeTablesSortable); |
|
248 |
|
249 CubicWeb.provide('sortable.js'); |