Created
December 29, 2012 01:14
-
-
Save lsmith/4403715 to your computer and use it in GitHub Desktop.
YUI 3 data binding. First pass. Supports lots of stuff that I don't have time to document at this very moment. Maybe you can figure it out from the code?
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
YUI.add('data-bind', function (Y) { | |
Y.DataBind = function (config) { | |
if (!Y.Base || !(this instanceof Y.Base)) { | |
// Being used as a standalone class | |
this.initializer(config); | |
// alias instance.destroy() to destructor for convenience | |
this.destroy = this.destructor; | |
} | |
}; | |
Y.DataBind.FIELD_EVENTS = { | |
radio: 'click', | |
checkbox: 'click' | |
}; | |
Y.DataBind._buildCfg = { statics: ['FIELD_EVENTS'] }; | |
Y.mix(Y.DataBind.prototype, { | |
getUIValue: function (attrName) { | |
var fieldConfig = this._attrMap[attrName], | |
type, field, method, value; | |
if (fieldConfig) { | |
type = fieldConfig.type, | |
field = fieldConfig.field; | |
method = fieldConfig.getter || | |
this['_get' + type.charAt(0).toUpperCase() + type.slice(1) + 'Value'] || | |
this._getSimpleValue; | |
// e.g. this._getRadioValue(field) | |
value = method.call(this, field); | |
return (fieldConfig.parser) ? fieldConfig.parser.call(this, value) : value; | |
} | |
// TODO: Better default return value? | |
return null; | |
}, | |
setUIValue: function (attrName, value) { | |
var fieldConfig = this._attrMap[attrName], | |
type, field, method; | |
if (fieldConfig) { | |
type = fieldConfig.type, | |
field = fieldConfig.field; | |
method = fieldConfig.setter || | |
this['_set' + type.charAt(0).toUpperCase() + type.slice(1) + 'Value'] || | |
this._setSimpleValue; | |
// e.g. this._setRadioValue(field, value) | |
method.call(this, field, (fieldConfig.formatter) ? fieldConfig.formatter.call(this, value) : value); | |
} | |
}, | |
syncToUI: function () { | |
var model = this._dataModel, | |
values, attr; | |
if (model) { | |
values = model.getAttrs(); | |
for (attr in this._attrMap) { | |
this.setUIValue(attr, values[attr]); | |
} | |
} | |
}, | |
syncToModel: function () { | |
var values = {}, | |
model = this._dataModel, | |
attr; | |
if (model) { | |
for (attr in this._attrMap) { | |
values[attr] = this.getUIValue(attr); | |
} | |
model.setAttrs(values); | |
} | |
}, | |
initializer: function (config) { | |
this._initDataModel(); | |
this._initDataBinding(config); | |
}, | |
_initDataModel: function (config) { | |
this._dataModel = this.get ? (this.get('model') || this) : config.model; | |
}, | |
_initDataBinding: function (config) { | |
if (this.get) { | |
this._bindContainer = this.attrAdded('contentBox') ? | |
this.get('contentBox') : | |
this.get('container'); | |
} else { | |
this._bindContainer = Y.one(config.container); | |
} | |
if (this._bindContainer) { | |
this._initBindMaps(config); | |
this._bindEvents(config); | |
} | |
}, | |
_initBindMaps: function (config) { | |
var fields = Y.merge(config.fields), | |
container = this._bindContainer, | |
name, allFields, field, nodes, type, i, len, fieldConfig; | |
this._fieldMap = {}; | |
this._attrMap = {}; | |
if (container) { | |
if (!config.fields) { | |
allFields = container.all('[data-bind-attr]'); | |
if (!allFields.size()) { | |
allFields = container.all('input,select,textarea'); | |
} | |
allFields = allFields.getDOMNodes(); | |
for (i = 0, len = allFields.length; i < len; ++i) { | |
field = allFields[i]; | |
fields[field.name] = { attr: field.getAttribute('data-bind-attr') || field.name }; | |
} | |
} | |
for (name in fields) { | |
fieldConfig = typeof fields[name] === 'string' ? { attr: fields[name] } : Y.merge(fields[name]); | |
nodes = container.all('[name=' + name + ']'); | |
field = nodes.item(0); | |
fieldConfig.type = field.get('nodeName').toLowerCase(); | |
if (fieldConfig.type === 'input') { | |
fieldConfig.type = field.get('type').toLowerCase(); | |
} | |
fieldConfig.field = (fieldConfig.type === 'radio') ? nodes : nodes.item(0); | |
this._fieldMap[name] = this._attrMap[fieldConfig.attr] = fieldConfig; | |
} | |
} | |
}, | |
_bindEvents: function (config) { | |
var eventMap = this.constructor.FIELD_EVENTS || Y.DataBind.FIELD_EVENTS, | |
model = this._dataModel, | |
eventNames = [], | |
name, fieldConfig, field, event, method; | |
this._dataBindEvents = []; | |
for (name in this._fieldMap) { | |
if (this._fieldMap.hasOwnProperty(name)) { | |
fieldConfig = this._fieldMap[name]; | |
field = fieldConfig.field; | |
event = eventMap[fieldConfig.type] || 'change'; | |
eventNames.push(fieldConfig.attr + 'Change'); | |
// Subscribing to the individual nodes rather than delegating on purpose to avoid cross browser issues | |
// with events. This might be a mistake. | |
this._dataBindEvents.push(field.on(event, this._onUIChange, this, fieldConfig.attr)); | |
} | |
} | |
if (model) { | |
this._dataBindEvents.push(model.after(eventNames, this._afterAttrChange, this)); | |
} | |
}, | |
destructor: function () { | |
if (this._dataBindEvents.length && Y.EventHandle) { | |
new Y.EventHandle(this._dataBindEvents).detach(); | |
} | |
}, | |
_afterAttrChange: function (e) { | |
if (!e.src) { | |
this.setUIValue(e.attrName, e.newVal); | |
} | |
}, | |
_onUIChange: function (e, attr) { | |
var model = this._dataModel, | |
fieldConfig = this._attrMap[attr], | |
type = fieldConfig.type, | |
method = '_get' + type.charAt(0).toUpperCase() + type.slice(0) + 'Value', | |
value = (this[method] || this._getSimpleValue)(e.target); | |
if (model) { | |
model.set(attr, this.getUIValue(attr), { src: 'UI' }); | |
} | |
}, | |
_setRadioValue: function (radioGroup, value) { | |
var radios = radioGroup.getDOMNodes(), | |
i, len; | |
for (i = 0, len = radios.length; i < len; ++i) { | |
// allowing for type coercion on purpose | |
if (radios[i].value == value) { | |
break; | |
} | |
} | |
if (i < len) { | |
radios[i].checked = true; | |
} | |
}, | |
_setCheckboxValue: function (checkbox, value) { | |
checkbox.set('checked', (value === true || value === checkbox.get('value'))); | |
}, | |
_setSelectValue: function (select, value) { | |
var options = select.getDOMNode().options, | |
i, len; | |
for (i = 0, len = options.length; i < len; ++i) { | |
// allowing for type coercion on purpose | |
if (options[i].value == value) { | |
break; | |
} | |
} | |
if (i < len) { | |
options[i].selected = true; | |
} | |
}, | |
_setSimpleValue: function (field, value) { | |
field.set('value', value); | |
}, | |
_getRadioValue: function (radioGroup) { | |
var radios = radioGroup.getDOMNodes(), | |
i, len, radio; | |
for (i = 0, len = radios.length; i < len; ++i) { | |
radio = radios[i]; | |
if (radio.checked) { | |
return radio.value; | |
} | |
} | |
// TODO: better default return value? | |
return null; | |
}, | |
_getCheckboxValue: function (checkbox) { | |
// TODO: better default value for unchecked box? | |
return checkbox.get('checked') ? checkbox.get('value') : null; | |
}, | |
_getSelectValue: function (select) { | |
select = select.getDOMNode(); | |
return (select.selectedIndex >= 0) ? select.options[select.selectedIndex].value : null; | |
}, | |
_getSimpleValue: function (field) { | |
return field.get('value'); | |
} | |
}, true); | |
}, 'ERMAGERD', { requires: ['node-base'] }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment