otsdaq_utilities  v2_05_02_indev
sort.js
1 var Sort = function(table){
2  this.table = table; //hold Tabulator object
3  this.sortList = []; //holder current sort
4  this.changed = false; //has the sort changed since last render
5  };
6 
7 //initialize column header for sorting
8 Sort.prototype.initializeColumn = function(column, content){
9  var self = this,
10  sorter = false,
11  colEl,
12  arrowEl;
13 
14 
15  switch(typeof column.definition.sorter){
16  case "string":
17  if(self.sorters[column.definition.sorter]){
18  sorter = self.sorters[column.definition.sorter];
19  }else{
20  console.warn("Sort Error - No such sorter found: ", column.definition.sorter);
21  }
22  break;
23 
24  case "function":
25  sorter = column.definition.sorter;
26  break;
27  }
28 
29  column.modules.sort = {
30  sorter:sorter, dir:"none",
31  params:column.definition.sorterParams || {},
32  startingDir:column.definition.headerSortStartingDir || "asc",
33  tristate: typeof column.definition.headerSortTristate !== "undefined" ? column.definition.headerSortTristate : this.table.options.headerSortTristate,
34  };
35 
36  if(typeof column.definition.headerSort === "undefined" ? (this.table.options.headerSort !== false) : column.definition.headerSort !== false){
37 
38  colEl = column.getElement();
39 
40  colEl.classList.add("tabulator-sortable");
41 
42 
43  arrowEl = document.createElement("div");
44  arrowEl.classList.add("tabulator-arrow");
45  //create sorter arrow
46  content.appendChild(arrowEl);
47 
48  //sort on click
49  colEl.addEventListener("click", function(e){
50  var dir = "",
51  sorters=[],
52  match = false;
53 
54  if(column.modules.sort){
55  if(column.modules.sort.tristate){
56  if(column.modules.sort.dir == "none"){
57  dir = column.modules.sort.startingDir;
58  }else{
59  if(column.modules.sort.dir == column.modules.sort.startingDir){
60  dir = column.modules.sort.dir == "asc" ? "desc" : "asc";
61  }else{
62  dir = "none";
63  }
64  }
65  }else{
66  switch(column.modules.sort.dir){
67  case "asc":
68  dir = "desc";
69  break;
70 
71  case "desc":
72  dir = "asc";
73  break;
74 
75  default:
76  dir = column.modules.sort.startingDir;
77  }
78  }
79 
80 
81  if (self.table.options.columnHeaderSortMulti && (e.shiftKey || e.ctrlKey)) {
82  sorters = self.getSort();
83 
84  match = sorters.findIndex(function(sorter){
85  return sorter.field === column.getField();
86  });
87 
88  if(match > -1){
89  sorters[match].dir = dir;
90 
91  if(match != sorters.length -1){
92  match = sorters.splice(match, 1)[0];
93  if(dir != "none"){
94  sorters.push(match);
95  }
96  }
97  }else{
98  if(dir != "none"){
99  sorters.push({column:column, dir:dir});
100  }
101  }
102 
103  //add to existing sort
104  self.setSort(sorters);
105  }else{
106  if(dir == "none"){
107  self.clear();
108  }else{
109  //sort by column only
110  self.setSort(column, dir);
111  }
112 
113  }
114 
115  self.table.rowManager.sorterRefresh(!self.sortList.length);
116  }
117  });
118  }
119 };
120 
121 //check if the sorters have changed since last use
122 Sort.prototype.hasChanged = function(){
123  var changed = this.changed;
124  this.changed = false;
125  return changed;
126 };
127 
128 //return current sorters
129 Sort.prototype.getSort = function(){
130  var self = this,
131  sorters = [];
132 
133  self.sortList.forEach(function(item){
134  if(item.column){
135  sorters.push({column:item.column.getComponent(), field:item.column.getField(), dir:item.dir});
136  }
137  });
138 
139  return sorters;
140 };
141 
142 //change sort list and trigger sort
143 Sort.prototype.setSort = function(sortList, dir){
144  var self = this,
145  newSortList = [];
146 
147  if(!Array.isArray(sortList)){
148  sortList = [{column: sortList, dir:dir}];
149  }
150 
151  sortList.forEach(function(item){
152  var column;
153 
154  column = self.table.columnManager.findColumn(item.column);
155 
156  if(column){
157  item.column = column;
158  newSortList.push(item);
159  self.changed = true;
160  }else{
161  console.warn("Sort Warning - Sort field does not exist and is being ignored: ", item.column);
162  }
163 
164  });
165 
166  self.sortList = newSortList;
167 
168  if(this.table.options.persistence && this.table.modExists("persistence", true) && this.table.modules.persistence.config.sort){
169  this.table.modules.persistence.save("sort");
170  }
171 };
172 
173 //clear sorters
174 Sort.prototype.clear = function(){
175  this.setSort([]);
176 };
177 
178 //find appropriate sorter for column
179 Sort.prototype.findSorter = function(column){
180  var row = this.table.rowManager.activeRows[0],
181  sorter = "string",
182  field, value;
183 
184  if(row){
185  row = row.getData();
186  field = column.getField();
187 
188  if(field){
189 
190  value = column.getFieldValue(row);
191 
192  switch(typeof value){
193  case "undefined":
194  sorter = "string";
195  break;
196 
197  case "boolean":
198  sorter = "boolean";
199  break;
200 
201  default:
202  if(!isNaN(value) && value !== ""){
203  sorter = "number";
204  }else{
205  if(value.match(/((^[0-9]+[a-z]+)|(^[a-z]+[0-9]+))+$/i)){
206  sorter = "alphanum";
207  }
208  }
209  break;
210  }
211  }
212  }
213 
214  return this.sorters[sorter];
215 };
216 
217 //work through sort list sorting data
218 Sort.prototype.sort = function(data){
219  var self = this, lastSort, sortList;
220 
221  sortList = this.table.options.sortOrderReverse ? self.sortList.slice().reverse() : self.sortList;
222 
223  if(self.table.options.dataSorting){
224  self.table.options.dataSorting.call(self.table, self.getSort());
225  }
226 
227  self.clearColumnHeaders();
228 
229  if(!self.table.options.ajaxSorting){
230 
231  sortList.forEach(function(item, i){
232 
233  if(item.column && item.column.modules.sort){
234 
235  //if no sorter has been defined, take a guess
236  if(!item.column.modules.sort.sorter){
237  item.column.modules.sort.sorter = self.findSorter(item.column);
238  }
239 
240  self._sortItem(data, item.column, item.dir, sortList, i);
241  }
242 
243  self.setColumnHeader(item.column, item.dir);
244  });
245  }else{
246  sortList.forEach(function(item, i){
247  self.setColumnHeader(item.column, item.dir);
248  });
249  }
250 
251  if(self.table.options.dataSorted){
252  self.table.options.dataSorted.call(self.table, self.getSort(), self.table.rowManager.getComponents("active"));
253  }
254 
255 };
256 
257 //clear sort arrows on columns
258 Sort.prototype.clearColumnHeaders = function(){
259  this.table.columnManager.getRealColumns().forEach(function(column){
260  if(column.modules.sort){
261  column.modules.sort.dir = "none";
262  column.getElement().setAttribute("aria-sort", "none");
263  }
264  });
265 };
266 
267 //set the column header sort direction
268 Sort.prototype.setColumnHeader = function(column, dir){
269  column.modules.sort.dir = dir;
270  column.getElement().setAttribute("aria-sort", dir);
271 };
272 
273 //sort each item in sort list
274 Sort.prototype._sortItem = function(data, column, dir, sortList, i){
275  var self = this;
276 
277  var params = typeof column.modules.sort.params === "function" ? column.modules.sort.params(column.getComponent(), dir) : column.modules.sort.params;
278 
279  data.sort(function(a, b){
280 
281  var result = self._sortRow(a, b, column, dir, params);
282 
283  //if results match recurse through previous searchs to be sure
284  if(result === 0 && i){
285  for(var j = i-1; j>= 0; j--){
286  result = self._sortRow(a, b, sortList[j].column, sortList[j].dir, params);
287 
288  if(result !== 0){
289  break;
290  }
291  }
292  }
293 
294  return result;
295  });
296 };
297 
298 //process individual rows for a sort function on active data
299 Sort.prototype._sortRow = function(a, b, column, dir, params){
300  var el1Comp, el2Comp, colComp;
301 
302  //switch elements depending on search direction
303  var el1 = dir == "asc" ? a : b;
304  var el2 = dir == "asc" ? b : a;
305 
306  a = column.getFieldValue(el1.getData());
307  b = column.getFieldValue(el2.getData());
308 
309  a = typeof a !== "undefined" ? a : "";
310  b = typeof b !== "undefined" ? b : "";
311 
312  el1Comp = el1.getComponent();
313  el2Comp = el2.getComponent();
314 
315  return column.modules.sort.sorter.call(this, a, b, el1Comp, el2Comp, column.getComponent(), dir, params);
316 };
317 
318 
319 //default data sorters
320 Sort.prototype.sorters = {
321 
322  //sort numbers
323  number:function(a, b, aRow, bRow, column, dir, params){
324  var alignEmptyValues = params.alignEmptyValues;
325  var decimal = params.decimalSeparator || ".";
326  var thousand = params.thousandSeparator || ",";
327  var emptyAlign = 0;
328 
329  a = parseFloat(String(a).split(thousand).join("").split(decimal).join("."));
330  b = parseFloat(String(b).split(thousand).join("").split(decimal).join("."));
331 
332  //handle non numeric values
333  if(isNaN(a)){
334  emptyAlign = isNaN(b) ? 0 : -1;
335  }else if(isNaN(b)){
336  emptyAlign = 1;
337  }else{
338  //compare valid values
339  return a - b;
340  }
341 
342  //fix empty values in position
343  if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
344  emptyAlign *= -1;
345  }
346 
347  return emptyAlign;
348  },
349 
350  //sort strings
351  string:function(a, b, aRow, bRow, column, dir, params){
352  var alignEmptyValues = params.alignEmptyValues;
353  var emptyAlign = 0;
354  var locale;
355 
356  //handle empty values
357  if(!a){
358  emptyAlign = !b ? 0 : -1;
359  }else if(!b){
360  emptyAlign = 1;
361  }else{
362  //compare valid values
363  switch(typeof params.locale){
364  case "boolean":
365  if(params.locale){
366  locale = this.table.modules.localize.getLocale();
367  }
368  break;
369  case "string":
370  locale = params.locale;
371  break;
372  }
373 
374  return String(a).toLowerCase().localeCompare(String(b).toLowerCase(), locale);
375  }
376 
377  //fix empty values in position
378  if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
379  emptyAlign *= -1;
380  }
381 
382  return emptyAlign;
383  },
384 
385  //sort date
386  date:function(a, b, aRow, bRow, column, dir, params){
387  if(!params.format){
388  params.format = "DD/MM/YYYY";
389  }
390 
391  return this.sorters.datetime.call(this, a, b, aRow, bRow, column, dir, params);
392  },
393 
394  //sort hh:mm formatted times
395  time:function(a, b, aRow, bRow, column, dir, params){
396  if(!params.format){
397  params.format = "hh:mm";
398  }
399 
400  return this.sorters.datetime.call(this, a, b, aRow, bRow, column, dir, params);
401  },
402 
403  //sort datetime
404  datetime:function(a, b, aRow, bRow, column, dir, params){
405  var format = params.format || "DD/MM/YYYY hh:mm:ss",
406  alignEmptyValues = params.alignEmptyValues,
407  emptyAlign = 0;
408 
409  if(typeof moment != "undefined"){
410  a = moment(a, format);
411  b = moment(b, format);
412 
413  if(!a.isValid()){
414  emptyAlign = !b.isValid() ? 0 : -1;
415  }else if(!b.isValid()){
416  emptyAlign = 1;
417  }else{
418  //compare valid values
419  return a - b;
420  }
421 
422  //fix empty values in position
423  if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
424  emptyAlign *= -1;
425  }
426 
427  return emptyAlign;
428 
429  }else{
430  console.error("Sort Error - 'datetime' sorter is dependant on moment.js");
431  }
432  },
433 
434  //sort booleans
435  boolean:function(a, b, aRow, bRow, column, dir, params){
436  var el1 = a === true || a === "true" || a === "True" || a === 1 ? 1 : 0;
437  var el2 = b === true || b === "true" || b === "True" || b === 1 ? 1 : 0;
438 
439  return el1 - el2;
440  },
441 
442  //sort if element contains any data
443  array:function(a, b, aRow, bRow, column, dir, params){
444  var el1 = 0;
445  var el2 = 0;
446  var type = params.type || "length";
447  var alignEmptyValues = params.alignEmptyValues;
448  var emptyAlign = 0;
449 
450  function calc(value){
451 
452  switch(type){
453  case "length":
454  return value.length;
455  break;
456 
457  case "sum":
458  return value.reduce(function(c, d){
459  return c + d;
460  });
461  break;
462 
463  case "max":
464  return Math.max.apply(null, value) ;
465  break;
466 
467  case "min":
468  return Math.min.apply(null, value) ;
469  break;
470 
471  case "avg":
472  return value.reduce(function(c, d){
473  return c + d;
474  }) / value.length;
475  break;
476  }
477  }
478 
479  //handle non array values
480  if(!Array.isArray(a)){
481  alignEmptyValues = !Array.isArray(b) ? 0 : -1;
482  }else if(!Array.isArray(b)){
483  alignEmptyValues = 1;
484  }else{
485 
486  //compare valid values
487  el1 = a ? calc(a) : 0;
488  el2 = b ? calc(b) : 0;
489 
490  return el1 - el2;
491  }
492 
493  //fix empty values in position
494  if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
495  emptyAlign *= -1;
496  }
497 
498  return emptyAlign;
499  },
500 
501 
502  //sort if element contains any data
503  exists:function(a, b, aRow, bRow, column, dir, params){
504  var el1 = typeof a == "undefined" ? 0 : 1;
505  var el2 = typeof b == "undefined" ? 0 : 1;
506 
507  return el1 - el2;
508  },
509 
510  //sort alpha numeric strings
511  alphanum:function(as, bs, aRow, bRow, column, dir, params){
512  var a, b, a1, b1, i= 0, L, rx = /(\d+)|(\D+)/g, rd = /\d/;
513  var alignEmptyValues = params.alignEmptyValues;
514  var emptyAlign = 0;
515 
516  //handle empty values
517  if(!as && as!== 0){
518  emptyAlign = !bs && bs!== 0 ? 0 : -1;
519  }else if(!bs && bs!== 0){
520  emptyAlign = 1;
521  }else{
522 
523  if(isFinite(as) && isFinite(bs)) return as - bs;
524  a = String(as).toLowerCase();
525  b = String(bs).toLowerCase();
526  if(a === b) return 0;
527  if(!(rd.test(a) && rd.test(b))) return a > b ? 1 : -1;
528  a = a.match(rx);
529  b = b.match(rx);
530  L = a.length > b.length ? b.length : a.length;
531  while(i < L){
532  a1= a[i];
533  b1= b[i++];
534  if(a1 !== b1){
535  if(isFinite(a1) && isFinite(b1)){
536  if(a1.charAt(0) === "0") a1 = "." + a1;
537  if(b1.charAt(0) === "0") b1 = "." + b1;
538  return a1 - b1;
539  }
540  else return a1 > b1 ? 1 : -1;
541  }
542  }
543 
544  return a.length > b.length;
545  }
546 
547  //fix empty values in position
548  if((alignEmptyValues === "top" && dir === "desc") || (alignEmptyValues === "bottom" && dir === "asc")){
549  emptyAlign *= -1;
550  }
551 
552  return emptyAlign;
553  },
554 };
555 
556 Tabulator.prototype.registerModule("sort", Sort);