Skip to content

Instantly share code, notes, and snippets.

@gifnksm gifnksm/datagrid.js
Created Dec 6, 2010

Embed
What would you like to do?
簡易的な DataGrid
(function() {
const NAMESPACE = 'mfp.xrea.jp';
if (!(NAMESPACE in window))
window[NAMESPACE] = {};
if (window[NAMESPACE].DataGrid !== undefined)
return;
const DataGrid = function(property, namePrefix) {
this._property = new DataGrid.Property(property);
this._namePrefix = namePrefix || '';
this._table = document.createElement('table');
this._table.id = namePrefix + 'table';
this._table.appendChild(this._createCols());
this._thead = new DataGrid.Header(this, this._property, namePrefix);
this._table.appendChild(this._thead.element);
this._tbody = document.createElement('tbody');
this._table.appendChild(this._tbody);
};
DataGrid.prototype = {
_property: null, _namePrefix: null,
_table: null, get element() this._table,
_thead: null, _tbody: null,
_rows: null, _filteredRows: null, _visibleRows: null,
_rowPerPage: 0, get rowPerPage() this._rowPerPage,
set rowPerPage(value) this._rowPerPage = value,
get length() this._rows ? this._rows.length : 0,
get availableLength() this._filteredRows ? this._filteredRows.length : 0,
get visibleLength() this._visibleRows ? this._visibleRows.length : 0,
_page: 0, get page() this._page,
set page(value) this._page = value,
get pageLength() this.rowPerPage > 0
? Math.ceil(this.availableLength / this.rowPerPage)
: 1,
_filter: null, get filter() this._filter,
set filter(value) this._filter = value,
_createCols: function() {
let cols = document.createDocumentFragment();
let prefix = this._namePrefix;
this._property.forEach(function(prop) {
let col = document.createElement('col');
col.className = prefix + 'col-' + prop.id;
cols.appendChild(col);
});
return cols;
},
setData: function(data) {
let prop = this._property, prefix = this._namePrefix;
this._rows = data.map(function(d) new DataGrid.Row(d, prop, prefix));
this._thead.setSortOrder(-1);
},
sort: function(colId, reverse) {
let prop = this._property.getPropertyById(colId);
if (prop === null) return;
let order;
if (reverse === undefined) {
let cur = this._thead.getSortOrder(colId, order);
if (cur !== 0) order = -cur;
else order = prop.sortDefaultOrder;
} else {
order = reverse ? -1 : 1;
}
this._thead.setSortOrder(colId, order);
let comparer = prop.comparer;
this._rows.sort(function(r1, r2) order * comparer(r1.data, r2.data));
},
update: function() {
let filter = this.filter;
this._filteredRows = filter == null
? this._rows
: this._rows.filter(function(r) filter(r.data));
let rowPerPage = this.rowPerPage;
this._visibleRows = rowPerPage === 0
? this._filteredRows
: this._filteredRows.slice(
this.page * rowPerPage, (this.page + 1) * rowPerPage - 1);
let df = document.createDocumentFragment();
this._visibleRows.forEach(function(d) df.appendChild(d.element));
this._tbody.textContent = '';
this._tbody.appendChild(df);
},
setCaption: function(content) {
let caption = this._table.querySelector('caption');
if (content === null) {
if (caption !== null)
caption.parentNode.removeChild(caption);
return;
}
if (caption === null)
caption = document.createElement('caption');
caption.textContent = '';
caption.appendChild(content);
this._table.insertBefore(caption, this._table.firstChild);
},
getRowByData: function(data) {
for (let i = 0, len = this.length; i < len; i++) {
if (this._rows[i].data === data)
return this._rows[i];
}
return null;
}
};
DataGrid.Property = function(property) {
let result = [];
result.__proto__ = DataGrid.Property.prototype;
for (let i = 0, len = property.length; i < len; i++)
result[i] = new DataGrid.Property.Item(property[i]);
return result;
};
DataGrid.Property.prototype = {
getPropertyById: function(id) {
for (let i = 0, len = this.length; i < len; i++)
if (this[i].id === id)
return this[i];
return null;
},
getIndexById: function(id) {
for (let i = 0, len = this.length; i < len; i++)
if (this[i].id === id)
return i;
return -1;
}
};
DataGrid.Property.prototype.__proto__ = Array.prototype;
DataGrid.Property.Item = function(prop) {
this._id = String(prop.id);
this._hidden = Boolean(prop.hidden);
this._title = prop.title;
this._content = prop.content;
this._sortable = Boolean(prop.sortable);
this._sortDefaultOrder = Number(prop.sortDefaultOrder) || 1;
this._updateLabel(prop.label);
this._updateComparer(prop.comparer);
};
DataGrid.Property.Item.prototype = {
_id: null, get id() this._id,
_hidden: null, get hidden() this._hidden,
_label: null, get label() this._label,
_title: null, get title() this._title,
_content: null,
_sortable: null, get sortable() this._sortable,
_sortDefaultOrder: null, get sortDefaultOrder() this._sortDefaultOrder,
_comparer: null, get comparer() this._comparer,
_comparerType: null, get comparerType() this._comparerType,
_updateLabel: function(label) {
if (label && label.nodeType !== undefined) {
this._label = label;
return;
}
this._label = document.createTextNode(
label === undefined ? String(this.id) : String(label));
},
_updateComparer: function(comp) {
if (typeof comp === 'function') {
this._comparerType = 'function';
this._comparer = comp;
return;
}
let cmp;
switch (comp) {
case 'date':
case 'number':
case 'bool':
cmp = function(a1, a2) a1 - a2;
this._comparerType = comp;
break;
case 'string':
default:
cmp = function(s1, s2) s1 === s2 ? 0 : s1 > s2 ? 1 : -1;
this._comparerType = 'string';
break;
}
let id = this.id;
this._comparer = function(d1, d2) cmp(d1[id], d2[id]);
},
createContent: function(data) {
if (this._content === undefined) {
let text = data[this.id] !== undefined ? String(data[this.id]) : '';
return document.createTextNode(text);;
}
if (typeof this._content === 'function') {
let content = this._content(data);
if (content && content.nodeType !== undefined)
return content;
return document.createTextNode(content);
}
return null;
}
};
DataGrid.Header = function(grid, property, namePrefix) {
this._grid = grid;
this._property = property;
this._namePrefix = namePrefix;
};
DataGrid.Header.prototype = {
_grid: null, _property: null, _namePrefix: null,
_created: false,
_thead: null, get element() {
if (!this._created) this.update();
return this._thead;
},
__ths: null, get _ths() {
if (!this._created) this.update();
return this.__ths;
},
update: function() {
this._created = true;
let row = document.createElement('tr');
let cols = null;
let prefix = this._namePrefix;
let ths = [];
this._property.forEach(function(prop) {
if (prop.hidden) return;
let th = document.createElement('th');
th.className = prefix + 'head-' + prop.id;
ths.push(th);
if (prop.title !== undefined)
th.title = prop.title;
if (prop.sortable) {
let link = document.createElement('a');
link.href = '#' + prop.id;
link.appendChild(prop.label);
th.appendChild(link);
} else {
th.appendChild(prop.label);
}
row.appendChild(th);
});
this.__ths = ths;
this._thead = document.createElement('thead');
this._thead.appendChild(row);
this._thead.addEventListener('click', this, false);
},
get _ascending() this._namePrefix + 'sort-ascending',
get _descending() this._namePrefix + 'sort-descending',
getSortOrder: function(col, order) {
if (typeof col === 'string' || col instanceof String)
col = this._property.getIndexById(col);
if (col === -1) return 0;
let list = this._ths[col].classList;
if (list.contains(this._ascending)) return 1;
if (list.contains(this._descending)) return -1;
return 0;
},
setSortOrder: function(col, order) {
if (typeof col === 'string' || col instanceof String)
col = this._property.getIndexById(col);
const ASCENDING = this._ascending;
const DESCENDING = this._descending;
this._ths.forEach(function(th) {
th.classList.remove(ASCENDING);
th.classList.remove(DESCENDING);
});
if (col !== -1)
this._ths[col].classList.add((order > 0 ? ASCENDING : DESCENDING));
},
handleEvent: function(e) {
let { target } = e;
do {
if (target === this.element) return;
if (target.nodeName === 'A') break;
target = target.parentNode;
} while (target !== null);
e.preventDefault();
let colname = target.getAttribute('href').slice('1');
this._grid.sort(colname);
this._grid.update();
}
};
DataGrid.Row = function(data, property, namePrefix) {
this._data = data;
this._property = property;
this._namePrefix = namePrefix;
this._tr = document.createElement('tr');
};
DataGrid.Row.prototype = {
_data: null, get data() this._data,
_property: null, _namePrefix: null,
_created: false,
_tr: null, get element() {
if (!this._created) this.update();
return this._tr ;
},
update: function() {
this._created = true;
let df = document.createDocumentFragment();
let data = this._data;
let prefix = this._namePrefix + 'cell-';
this._property.forEach(function(prop) {
if (prop.hidden) return;
let td = document.createElement('td');
td.className = prefix + prop.id;
td.appendChild(prop.createContent(data));
df.appendChild(td);
});
this._tr.textContent = '';
this._tr.appendChild(df);
}
};
window[NAMESPACE].DataGrid = DataGrid;
})();
const DataGrid = window['mfp.xrea.jp'].DataGrid;
let grid = new DataGrid([
{ id: 'first_name', sortable: true },
{ id: 'last_name', sortable: true },
{ id: 'age', sortable: true, comparer: 'number' },
{ id: 'sex', content: function(data) data.isMale ? 'Male' : 'Female' }
], 'grid');
grid.setData([
{ first_name: 'Taro', last_name: 'Yamada', age: 30, isMale: true },
{ first_name: 'Hanako', last_name: 'Sato', age: 0, isMake: false }
]);
grid.update();
document.body.appendChild(grid.element);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.