Subversion Repositories wimsdev

Rev

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
})();