ractive-ez-table
Version:
Ractive Ez UI Table
302 lines (242 loc) • 9.39 kB
JavaScript
/*
-----------------------------------------------
| Header 1 | Header 2 | Header 3 | Header 4 |
| [ ] | [ ] | [filter] | [ ] |
|-----------------------------------------------|
| Grouping 1a |
| Grouping 2a |
| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
| Grouping 2b |
| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
| Cell 1 | Cell 2 | Cell 3 | Cell 4 |
-----------------------------------------------
Interactions:
1. Header => drag/drop column to change column order
2. Header => click column to change sort direction
3. Header => enter filter text to filter on a property (search type depends on first character? < | > | ^ | $ | = )
4. Header => drag handle to resize
4. Grouping => click to collapse
5. Row => drag/drop
selections!
editing?
Components:
EzTableHeader
EzTableGroup
EzTableRow
*/
import Ractive from 'ractive';
import 'ractive-ez-core';
import 'ractive-ez-resize';
import utils from './utils.js';
import settings from './settings.js';
import EzTableHeader from './EzTableHeader.js';
import EzTableContent from './EzTableContent.js';
import groupBy from './groupBy.js';
import Virtualization from './Virtualization.js';
import NoCursor from './cursors/SelectionCursor.None.js';
import SingleCursor from './cursors/SelectionCursor.Single.js';
import MultiCursor from './cursors/SelectionCursor.Multi.js';
import './EzTable.less';
const EzTable = Ractive.components.EzTable = Ractive.extend({
template: require("./EzTable.html"),
_scrollHandler: null,
_isRendered: false,
components: {
EzTableHeader,
EzTableContent
},
data() {
return {
items: null,
selectedItems: [],
columns: null,
groups: null,
sortColumn: null,
itemHeight: 24,
labelHeight: 24,
virtualBuffer: 144,
_root: null,
_selectionCursor: null,
_isValidDropTarget: false,
_viewport: {
top: 0,
height: 0
},
visible(columns, groups = []) {
return columns.filter(column => column.isVisible && groups.indexOf(column) == -1);
},
columnContentType: settings.columnContentType,
selectionMode: settings.selectionMode,
enableDrag: settings.enableDrag,
enableDrop: settings.enableDrop,
enableGrouping: settings.enableGrouping,
enableSorting: settings.enableSorting,
enableVirtualization: settings.enableVirtualization,
showHeaderTop: settings.showHeaderTop,
showHeaderBottom: settings.showHeaderBottom,
dataMapper: settings.dataMapper
}
},
oninit() {
// TODO: this seems to trigger a re-render more than necessary?
this.observe("items groups columns.*.sortDirection _viewport", () => this._refreshView());
this.observe("items", () => {
const items = this.get("items");
const selectedItems = this.get("selectedItems");
selectedItems
.filter(item => items.indexOf(item) == -1)
.forEach(item => this.splice("selectedItems", selectedItems.indexOf(item), 1));
});
this.observe("selectionMode", () => {
switch (this.getSelectionMode()) {
case "single": return this.set("_selectionCursor", new SingleCursor(this));
case "multi": return this.set("_selectionCursor", new MultiCursor(this));
default: return this.set("_selectionCursor", new NoCursor(this));
}
});
// TODO: handle resize
this._scrollHandler = utils.createScrollHandler(() => {
if (this._isRendered) {
const virtualBuffer = this.get("virtualBuffer");
this.set("_viewport.top", this.find(".body").scrollTop - virtualBuffer);
this.set("_viewport.height", this.find(".body").offsetHeight + virtualBuffer * 2);
}
});
},
onrender() {
this._isRendered = true;
this._scrollHandler();
},
onunrender() {
this._isRendered = false;
},
_refreshView() {
const groupings = groupBy(this.get("items"), this.get("groups"), this.get("sortColumn"));
const virtualizationEnabled = this.get("enableVirtualization");
const virtualization = new Virtualization(
this.get("_viewport"),
this.get("labelHeight"),
this.get("itemHeight"));
const root = virtualization.virtualize(groupings, virtualizationEnabled);
this.set("_root", root);
},
getConfiguration() {
const columns = this.get("columns") || [];
const groups = this.get("groups") || [];
const sortColumn = this.get("sortColumn") || {};
const colData = Object.create(null);
const grpData = groups.map(column => column.name);
columns.forEach((column, index) => {
colData[column.name] = {
isVisible: column.isVisible,
sortDirection: !!column.sortDirection,
sequence: index
};
});
return {
groups: grpData,
columns: colData,
sortColumn: sortColumn.name
};
},
setConfiguration(config) {
const columns = this.get("columns") || [];
// Load columns
if (config.columns) {
columns.forEach(col => {
const setting = config.columns[col.name] || {};
if ("isVisible" in setting) col.isVisible = setting.isVisible;
if ("sortDirection" in setting) col.sortDirection = setting.sortDirection;
});
columns.sort((a, b) => config.columns[a.name].sequence - config.columns[b.name].sequence);
this.set("columns", columns);
}
// Load sort column
if (config.sortColumn) {
const sortColumn = columns.find(col => col.name == config.sortColumn);
this.set("sortColumn", sortColumn);
}
// Load groups
if (config.groups && config.groups.length) {
const groups = config.groups
.map(name => columns.find(col => col.name == name))
.filter(col => !!col);
this.set("groups", groups);
}
},
getSelectionMode() {
return this.get("selectionMode").toLowerCase();
},
isItemSelected(item) {
return this.get("selectedItems").indexOf(item) != -1;
},
selectItem(item) {
if (!this.isItemSelected(item)) {
this.push("selectedItems", item);
}
},
unselectItem(item) {
const index = this.get("selectedItems").indexOf(item);
if (index != -1) {
this.splice("selectedItems", index, 1);
}
},
toggleItem(item) {
this.isItemSelected(item)
? this.unselectItem(item)
: this.selectItem(item);
},
clearSelection() {
this.splice("selectedItems", 0, this.get("selectedItems").length);
},
preventSelection(event) {
event.preventDefault();
return false;
},
handleScroll(event) {
this._scrollHandler(event);
},
_handleDragStart(event, items) {
const mapping = this.get("dataMapper");
const contentTypes = Object.keys(mapping);
contentTypes.forEach(contentType => {
const map = mapping[contentType].stringify;
if (map) {
const data = map(items);
event.dataTransfer.setData(contentType, data);
}
});
this.fire("dragitem", items);
},
_handleDragOver(event) {
const mapping = this.get("dataMapper");
if (!mapping) {
console.warn("EzTable - dataMapper is undefined");
return;
}
const contentTypes = Object.keys(mapping);
const allowDrop = event.dataTransfer.types.some(type => mapping[type] && mapping[type].parse);
this.set("_isValidDropTarget", allowDrop);
if (allowDrop) {
event.preventDefault();
event.dropEffect = "move";
}
},
_handleDrop(event, targetItem) {
const mapping = this.get("dataMapper");
const contentTypes = Object.keys(mapping);
const data = Object.create(null);
if (event.cancelBubble) return;
event.cancelBubble = true;
event.dataTransfer.types.forEach(type => {
if (contentTypes.indexOf(type) != -1) {
const map = mapping[type].parse;
data[type] = map(event.dataTransfer.getData(type));
}
});
this.fire("dropitem", data, targetItem);
}
});
EzTable.settings = settings;
export default EzTable;