Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

added basic functionality to binding class

pertains to siq/gloss#20
  • Loading branch information...
commit 728a7bc4359be848af7f347e8899d39eaecfa055 1 parent f084908
Aaron Stacy aaronj1335 authored
138 src/util/binding.js
View
@@ -1,6 +1,13 @@
define([
- 'bedrock/class'
-], function(Class) {
+ 'vendor/underscore',
+ 'vendor/t',
+ 'bedrock/class',
+ 'bedrock/settable',
+ './../widgets/widget',
+ './../widgets/formwidget'
+], function(_, t, Class, Settable, Widget, FormWidget) {
+ var textContent = document.createElement('span').textContent?
+ 'textContent' : 'innerText';
// # Binding
//
@@ -80,8 +87,133 @@ define([
// widget and the model field corresponding to the widget's element's
// 'name' attribute
//
+ // ### stuff that `Binding` does not support
+ //
+ // - changing the UI (either the `widget` or the `el`): i mean.. this
+ // would be kind of silly, right?
+ //
// [formwidget]: https://github.com/siq/gloss/blob/master/src/widgets/formwidget.js
//
return Class.extend({
- });
+ _settableOnChange: '_onOptionsChange',
+
+ init: function(options) {
+ var explicitBindings = options.bindings;
+
+ _.bindAll(this, '_onModelChange');
+
+ delete (options = _.extend({}, options)).bindings;
+
+ this.set(options);
+
+ this._setBindings(explicitBindings);
+ },
+
+ _onModelChange: function(eventName, model, changed) {
+ var self = this, bindings = self.get('bindings');
+
+ if (!bindings) {
+ return;
+ }
+
+ _.each(changed, function(___, prop) {
+ if (bindings[prop]) {
+ self._setUIFromModelForBinding(prop);
+ }
+ });
+ },
+
+ _onOptionsChange: function(changed, opts) {
+ if (changed.model) {
+ this.get('model').on('change', this._onModelChange);
+ if (this.previous('model')) {
+ this.previous('model').off('change', this._onModelChange);
+ }
+ this._setUIFromModel();
+ }
+
+ if (changed.el || changed.widget) {
+ if (this.get('el') && this.get('widget')) {
+ throw Error(
+ 'Binding object has either `widget` or `el`, not both');
+ }
+
+ }
+ },
+
+ // this walks through the DOM element (either from `widgt` or `el`) and
+ // sets up bindings for everything it finds. this is where we
+ // implement the algorithm for 'automatic binding'
+ _setBindings: function(explicitBindings) {
+ var el, widget, bindings = {},
+ root = (widget = this.get('widget'))? widget.node :
+ (el = this.get('el'))? el.jquery && el[0] :
+ el,
+
+ // set up the binding, overriding any automatically discovered
+ // settings w/ the explicit settings
+ setUpBinding = function(bindings, name, newBinding, explicit) {
+ bindings[name] = explicit && explicit[name]?
+ _.extend(newBinding, explicit[name]) : newBinding;
+ };
+
+ t.dfs(root, function(el, parentEl, ctrl) {
+ var id, widget, name, newBinding,
+ dataBind = el.getAttribute('data-bind');
+
+ if (dataBind) {
+ setUpBinding(bindings, dataBind, {el: el},
+ explicitBindings);
+
+ // dont traverse any further into this DOM node
+ ctrl.cutoff = true;
+
+ } else if (
+ (widget = Widget.registry.get(el.getAttribute('id'))) &&
+ widget instanceof FormWidget) {
+
+ setUpBinding(bindings, widget.$node.attr('name'),
+ {widget: widget}, explicitBindings);
+ }
+ });
+
+ // add any bindings that were in the explicitBindings, but not
+ // discovered during the automatic binding
+ bindings = _.reduce(explicitBindings, function(bindings, binding, name) {
+ if (!bindings[name]) {
+ if (binding.el && binding.el.jquery) {
+ binding.el = binding.el[0];
+ }
+ bindings[name] = binding;
+ }
+ return bindings;
+ }, bindings);
+
+ this.set('bindings', bindings);
+
+ this._setUIFromModel();
+ },
+
+ // update the UI with all of the values from the model
+ _setUIFromModel: function() {
+ var self = this;
+ _.each(self.get('bindings') || [], function(___, prop) {
+ self._setUIFromModelForBinding(prop);
+ });
+ },
+
+ // update the UI with the value from the model for a specific binding
+ // (i.e. just update one field)
+ _setUIFromModelForBinding: function(binding) {
+ var bindings = this.get('bindings');
+ if (bindings[binding].el) {
+ bindings[binding].el[textContent] =
+ this.get('model').get(binding) || '';
+ } else if (bindings[binding].widget) {
+ bindings[binding].widget.$el.text(
+ this.get('model').get(binding) || '');
+ }
+ }
+
+ }, {mixins: [Settable]});
});
104 src/util/binding/test.js
View
@@ -1,10 +1,106 @@
/*global test, asyncTest, ok, equal, deepEqual, start, module, strictEqual, notStrictEqual, raises*/
define([
- './../binding'
-], function(Binding) {
+ 'vendor/jquery',
+ 'vendor/underscore',
+ 'mesh/model',
+ './../binding',
+ 'text!./testDataBindingAttribute.html'
+], function($, _, Model, Binding, testDataBindHtml) {
- test('instantiation', function() {
- ok(Binding);
+ var assertThatModelMatchesUI = function(binding) {
+ var model = binding.get('model');
+ _.each(binding.get('bindings'), function(b, name) {
+ if (b.el) {
+ equal($(b.el).text(), model.get(name) || '',
+ 'element with `data-bind="'+name+'"` text is "'+model.get(name)+'"');
+ }
+ });
+ };
+
+ module('use cases');
+
+ test('explicit binding', function() {
+ var $el = $('<span class=bound-field></span>').appendTo('#qunit-fixture'),
+ myModel = Model.Model({myField: 'foo'}),
+ binding = Binding({
+ model: myModel,
+ bindings: {
+
+ // this is the model's field name, '.' is expanded to
+ // nested model fields
+ 'myField': {
+
+ // any HTML snippet, either jQuery collection or
+ // bare HTMLElement
+ el: $el
+
+ }
+ }
+ });
+
+ $('#qunit-fixture').css({position: 'static'});
+
+ assertThatModelMatchesUI(binding);
+
+ equal(_.keys(binding.get('bindings')).length, 1);
+
+ equal($el.text(), 'foo');
+
+ });
+
+ test('automatic binding to some fields in an HTML snippet', function() {
+ var $el = $(testDataBindHtml).appendTo('#qunit-fixture'),
+ myModel = Model.Model({field1: 'before set'}),
+ binding = Binding({
+ el: $el,
+ model: myModel
+ });
+
+ $('#qunit-fixture').css({position: 'static'});
+
+ ok(binding);
+
+ assertThatModelMatchesUI(binding);
+ equal(_.keys(binding.get('bindings')).length, 2);
+
+ myModel.set('field1', 'foo1');
+ myModel.set('field2', 'foo2');
+
+ assertThatModelMatchesUI(binding);
+ });
+
+ module('corner cases');
+
+ test('nested attribute', function() {
+ var $el = $('<span class=bound-field></span>').appendTo('#qunit-fixture'),
+ myModel = Model.Model({
+ myModelField: {
+ subField: 'foo'
+ }
+ }),
+ binding = Binding({
+ model: myModel,
+ bindings: {
+
+ // this is the model's field name, '.' is expanded to
+ // nested model fields
+ 'myModelField.subField': {
+
+ // any HTML snippet, either jQuery collection or
+ // bare HTMLElement
+ el: $el
+
+ }
+ }
+ });
+
+ $('#qunit-fixture').css({position: 'static'});
+
+ assertThatModelMatchesUI(binding);
+
+ equal(_.keys(binding.get('bindings')).length, 1);
+
+ equal($el.text(), 'foo');
});
start();
4 src/util/binding/testDataBindingAttribute.html
View
@@ -0,0 +1,4 @@
+<div>
+ <span data-bind=field1></span>
+ <span data-bind=field2></span>
+</div>
Please sign in to comment.
Something went wrong with that request. Please try again.