Skip to content

Instantly share code, notes, and snippets.

@lsmith
Created July 19, 2011 07:07
Show Gist options
  • Save lsmith/1091534 to your computer and use it in GitHub Desktop.
Save lsmith/1091534 to your computer and use it in GitHub Desktop.
A POC inline cell editor class extension/view for YUI 3 DataTable
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Test Page</title>
<style>
.yui3-skin-sam .yui3-datatable .yui3-datatable-data .yui3-datatable-editing {
position: relative;
padding: 0;
}
.yui3-datatable-editor {
background: #ffe;
border: 0 none;
font-family: inherit;
font-size: inherit;
left: 0;
line-height: inherit;
margin: 0;
padding: 4px 10px;
position: absolute;
top: 0;
}
</style>
</head>
<body class="yui3-skin-sam">
<div id="x"></div>
<script src="http://yui.yahooapis.com/3.5.0pre5/build/yui/yui.js"></script>
<script>
YUI({ filter: 'raw' }).use('datatable', function (Y) {
var Lang = Y.Lang,
isArray = Lang.isArray,
isObject = Lang.isObject,
isNumber = Lang.isNumber;
// This is just a POC, so it will break, and it's not feature rich. But
// hopefully, it can be useful as a starting point or inspiration for someone
// (maybe even me!) to fill out the functionality.
Y.DataTable.Base.prototype.getColumn = function (name) {
var col, columns, i, len, cols;
if (isObject(name) && name._id) {
// Assume an object with _id passed in is already a column def
col = name;
} else {
col = this.get('columns.' + name);
}
if (col) {
return col;
}
columns = this.get('columns');
if (isNumber(name) || isArray(name)) {
name = toArray(name);
cols = columns;
for (i = 0, len = name.length - 1; cols && i < len; ++i) {
cols = cols[name[i]] && cols[name[i]].children;
}
col = (cols && cols[name[i]]) || null;
} else if (this.body && this.body.getColumn) {
col = this.body.getColumn(name);
}
return col;
};
Y.DataTable.BodyView.prototype.getColumn = function (seed) {
var host = this.get('source'),
column = null,
cell;
if (Y.instanceOf(seed, Y.Node)) {
cell = host.getCell(seed);
if (cell) {
seed = (cell.get('className').match(
new RegExp(host.getClassName('col', '(\\w+)'))) || [])[1];
column = seed && host.get('columns.' + seed);
}
}
return column;
};
Y.DataTable.Editor = {};
Y.DataTable.InlineCellEditorView =
Y.DataTable.Editor['inline-cell'] = Y.Base.create('editor', Y.View, [], {
edit: function (node) {
var cell = this.get('host').getCell(node);
if (cell) {
this.set('cell', cell);
}
},
cancel: function () {
this.set('cell', null);
},
render: function () {
this._editor = Y.Node.create(Y.Lang.sub(this.get('template'), {
className: this.get('host').getClassName('editor')
}));
this._bindUI();
this._uiSetEditorCell(this.get('cell'));
},
_afterCellChange: function (e) {
this.set('model', this.get('host').getRecord(e.newVal));
this._uiSetEditorCell(e.newVal);
},
_afterContainerChange: function (e) {
// TODO
},
_bindUI: function () {
this.get('container').delegate('dblclick', function (e) {
this.edit(e.currentTarget);
}, '.yui3-datatable-cell', this);
this.after('cellChange', this._afterCellChange);
this._editor.on({
keydown: Y.bind(this._onKeydown, this),
blur: Y.bind(this._onBlur, this)
});
},
_getCellValue: function (cell, model, column) {
var value;
if (model && column) {
value = column.key ? model.get(column.key) : cell.get('text');
}
if (value === '' || value == null) {
value = (column && column.defaultValue) || '';
}
return value;
},
_onBlur: function (e) {
if (!this._tabbing) {
this.cancel();
}
},
_onKeydown: function (e) {
var editor = this._editor,
host, cell;
switch (e.keyCode) {
case 13: // enter
editor.getData('model')
.set(editor.getData('column').key,
e.currentTarget.get('value'));
// fall through intentional
case 27: // escape
this.cancel();
break;
case 9: // tab
host = this.get('host');
cell = host.getCell(this.get('cell'),
[ 0, (e.shiftKey ? -1 : 1) ]);
if (cell) {
this._tabbing = true;
this.set('cell', cell);
delete this._tabbing;
} else {
// TODO: navigate to next/previous row
}
// TODO: only prevent if navigating to another cell. Otherwise
// let the native focus tabbing take over.
e.preventDefault();
break;
}
},
_uiSetEditorCell: function (cell) {
var host = this.get('host'),
editor = this._editor,
editClass = host.getClassName('editing'),
model, column;
if (editor.inDoc()) {
editor.get('parentNode').removeClass(editClass);
}
if (cell) {
model = host.getRecord(cell);
column = host.getColumn(cell);
cell.addClass(editClass);
editor.set('value', this._getCellValue(cell, model, column))
.setData('column', column)
.setData('model', model);
editor.setStyles({
height: (cell.get('clientHeight') - 8) + 'px',
width: (cell.get('clientWidth') - 20) + 'px'
});
cell.insert(editor);
editor.focus();
} else {
this._editor.remove();
}
}
}, {
ATTRS: {
host: {},
editable: {},
cell: {},
template: {
value: '<input class="{className}">'
}
}
});
function EditableDataTable() {}
Y.mix(EditableDataTable.prototype, {
edit: function (seed) {
if (this.get('editable') && this.editor && this.editor.edit) {
this.editor.edit.apply(this.editor, arguments);
}
return this;
},
hideEditor: function () {
if (this.editor && this.editor.hide) {
this.editor.hide.apply(this.editor, arguments);
}
return this;
},
_afterEditableChange: function (e) {
this._parseEditable();
if (this._editable.length) {
if (this.editor) {
this.editor.set('editable', this._editable);
} else if (!e._prevVal && !this.get('rendered')){
this._renderEditorHandle =
this.after('renderBody', this._renderEditor);
}
} else if (this.editor) {
this.editor.destroy();
delete this.editor;
} else if (this._renderEditorHandle) {
this._renderEditorHandle.detach();
delete this._renderEditorHandle;
}
},
_afterEditorViewChange: function (e) {
if (this.editor) {
this.editor.destroy();
delete this.editor;
}
if (this.get('rendered') && e.newVal) {
this_renderEditor();
}
},
_defRenderEditorFn: function (e) {
e.view.render();
},
initializer: function () {
this.publish('renderEditorView', {
defaultFn: this._defRenderEditorFn
});
this._parseEditable();
if (this._editable.length) {
this._renderEditorHandle =
this.after('renderBody', this._renderEditor);
}
this.after({
editableChange: this._afterEditableChange,
editorViewChange: this._afterEditorViewChange
});
},
/**
Normalizes the possible input values for the `editable` attribute, storing
the results in the `_editable` property.
@method _parseEditable
@protected
@since 3.6.0
**/
_parseEditable: function () {
var editable = this.get('editable'),
columns = [],
i, len, col;
if (isArray(editable)) {
for (i = 0, len = editable.length; i < len; ++i) {
col = editable[i];
// isArray is called because arrays are objects, but will rely
// on getColumn to nullify them for the subsequent if (col)
if (!isObject(col, true) || isArray(col)) {
col = this.getColumn(col);
}
if (col) {
columns.push(col);
}
}
} else if (editable) {
columns = this._displayColumns.slice();
if (editable === 'auto') {
for (i = columns.length - 1; i >= 0; --i) {
if (!columns[i].editable) {
columns.splice(i, 1);
}
}
}
}
this._editable = columns;
},
_renderEditor: function () {
var View = this.get('editorView'),
config = {};
if (View) {
if (isObject(View, true)) {
config = config.cfg || {};
View = config.fn || config.view;
}
this.fire('renderEditorView', {
view: new View(Y.merge(config, {
host : this,
editable : this._editable,
container: this.get('contentBox'),
modelList: this.data
}))
});
}
}
});
EditableDataTable.ATTRS = {
editorView: {
value: Y.DataTable.InlineCellEditorView
// TODO: validator that accepts View class or
// { fn: ViewClass, cfg: {} } (optional { view: ViewClass }?)
},
editable: {
value: false,
setter: '_setEditable'
}
};
Y.Base.mix(Y.DataTable, [EditableDataTable]);
var table = new Y.DataTable({
columns: ['name', 'age'],
editable: true,
recordType: {
name: {},
age: {
setter: function (val) {
return val == +val ? +val : Y.Attribute.INVALID_VALUE;
}
}
},
data: [
{ name: 'Phil Collins', age: 80 },
{ name: 'Brian Eno', age: 45 },
{ name: 'Phil Collins', age: 80 },
{ name: 'Brian Eno', age: 45 }
]
}).render('#x');
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment