Skip to content

Instantly share code, notes, and snippets.

@lsmith
Created December 29, 2012 01:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lsmith/4403715 to your computer and use it in GitHub Desktop.
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?
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