Created
July 19, 2011 07:07
-
-
Save lsmith/1091534 to your computer and use it in GitHub Desktop.
A POC inline cell editor class extension/view for YUI 3 DataTable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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