Builds a stripy, sortable table with methods to add rows.
HtmlTable = Class.refactor(HtmlTable, {
options: {/*
onSort: function(){}, */
sortIndex: 0,
sortReverse: false,
parsers: [],
defaultParser: 'string',
classSortable: 'table-sortable',
classHeadSort: 'table-th-sort',
classHeadSortRev: 'table-th-sort-rev',
classNoSort: 'table-th-nosort',
classGroupHead: 'table-tr-group-head',
classGroup: 'table-tr-group',
classCellSort: 'table-td-sort',
classSortSpan: 'table-th-sort-span',
sortable: false
},
initialize: function (){
this.previous.apply(this, arguments);
if (this.occluded) return this.occluded;
this.sorted = {index: null, dir: 1};
this.bound = {
headClick: this.headClick.bind(this)
};
this.sortSpans = new Elements();
if (this.options.sortable){
this.enableSort();
if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
}
},
attachSorts: function(attach){
this.element.removeEvents('click:relay(th)');
this.element[attach !== false ? 'addEvent' : 'removeEvent']('click:relay(th)', this.bound.headClick);
},
setHeaders: function(){
this.previous.apply(this, arguments);
if (this.sortEnabled) this.detectParsers();
},
detectParsers: function(force){
if (!this.head) return;
var parsers = this.options.parsers,
rows = this.body.rows;
auto-detect
this.parsers = $$(this.head.cells).map(function(cell, index){
if (!force && (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser'))) return cell.retrieve('htmltable-parser');
var thDiv = new Element('div');
Array.each(cell.childNodes, function(node){
thDiv.adopt(node);
});
thDiv.inject(cell);
var sortSpan = new Element('span', {'html': ' ', 'class': this.options.classSortSpan}).inject(thDiv, 'top');
this.sortSpans.push(sortSpan);
var parser = parsers[index],
cancel;
switch (typeOf(parser)){
case 'function': parser = {convert: parser}; cancel = true; break;
case 'string': parser = parser; cancel = true; break;
}
if (!cancel){
Object.some(HtmlTable.Parsers, function(current) {
var match = current.match;
if (!match) return false;
for (var i = 0, j = rows.length; i < j; i++){
var cell = document.id(rows[i].cells[index]);
var text = cell ? cell.get('html').clean() : '';
if (text && match.test(text)){
parser = current;
return true;
}
}
});
}
if (!parser) parser = this.options.defaultParser;
cell.store('htmltable-parser', parser);
return parser;
}, this);
},
headClick: function(event, el){
if (!this.head || el.hasClass(this.options.classNoSort)) return;
var index = Array.indexOf(this.head.cells, el);
this.sort(index);
return false;
},
sort: function(index, reverse, pre){
if (!this.head) return;
var classCellSort = this.options.classCellSort;
var classGroup = this.options.classGroup,
classGroupHead = this.options.classGroupHead;
if (!pre){
if (index != null){
if (this.sorted.index == index){
this.sorted.reverse = !(this.sorted.reverse);
} else {
if (this.sorted.index != null){
this.sorted.reverse = false;
this.head.cells[this.sorted.index].removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
} else {
this.sorted.reverse = true;
}
this.sorted.index = index;
}
} else {
index = this.sorted.index;
}
if (reverse != null) this.sorted.reverse = reverse;
var head = document.id(this.head.cells[index]);
if (head){
head.addClass(this.options.classHeadSort);
if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
else head.removeClass(this.options.classHeadSortRev);
}
this.body.getElements('td').removeClass(this.options.classCellSort);
}
var parser = this.parsers[index];
if (typeOf(parser) == 'string') parser = HtmlTable.Parsers[parser];
if (!parser) return;
if (!Browser.ie){
var rel = this.body.getParent();
this.body.dispose();
}
var data = Array.map(this.body.rows, function(row, i){
var value = parser.convert.call(document.id(row.cells[index]));
return {
position: i,
value: value,
toString: function(){
return value.toString();
}
};
}, this);
data.reverse(true);
data.sort(function(a, b){
if (a.value === b.value) return 0;
return a.value > b.value ? 1 : -1;
});
if (!this.sorted.reverse) data.reverse(true);
var i = data.length, body = this.body;
var j, position, entry, group;
while (i){
var item = data[--i];
position = item.position;
var row = body.rows[position];
if (row.disabled) continue;
if (!pre){
if (group === item.value){
row.removeClass(classGroupHead).addClass(classGroup);
} else {
group = item.value;
row.removeClass(classGroup).addClass(classGroupHead);
}
if (this.options.zebra) this.zebra(row, i);
row.cells[index].addClass(classCellSort);
}
body.appendChild(row);
for (j = 0; j < i; j++){
if (data[j].position > position) data[j].position--;
}
};
data = null;
if (rel) rel.grab(body);
return this.fireEvent('sort', [body, index]);
},
reSort: function(){
if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
return this;
},
enableSort: function(){
this.element.addClass(this.options.classSortable);
this.attachSorts(true);
this.detectParsers();
this.sortEnabled = true;
return this;
},
disableSort: function(){
this.element.removeClass(this.options.classSortable);
this.attachSorts(false);
this.sortSpans.each(function(span){ span.destroy(); });
this.sortSpans.empty();
this.sortEnabled = false;
return this;
}
});
HtmlTable.Parsers = {
'date': {
match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
convert: function(){
return Date.parse(this.get('text').stripTags()).format('db');
},
type: 'date'
},
'input-checked': {
match: / type="(radio|checkbox)" /,
convert: function(){
return this.getElement('input').checked;
}
},
'input-value': {
match: /<input/,
convert: function(){
return this.getElement('input').value;
}
},
'number': {
match: /^\d+[^\d.,]*$/,
convert: function(){
return this.get('text').stripTags().toInt();
},
number: true
},
'numberLax': {
match: /^[^\d]+\d+$/,
convert: function(){
return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt();
},
number: true
},
'float': {
match: /^[\d]+\.[\d]+/,
convert: function(){
return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat();
},
number: true
},
'floatLax': {
match: /^[^\d]+[\d]+\.[\d]+$/,
convert: function(){
return this.get('text').replace(/[^-?^\d.]/, '').stripTags();
},
number: true
},
'string': {
match: null,
convert: function(){
return this.get('text').stripTags();
}
},
'title': {
match: null,
convert: function(){
return this.title;
}
}
};
<1.2compat>
HtmlTable.Parsers = new Hash(HtmlTable.Parsers);
</1.2compat>
HtmlTable.defineParsers = function(parsers){
HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers);
};