Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
  • 7 commits
  • 6 files changed
  • 0 commit comments
  • 1 contributor
Showing with 226 additions and 42 deletions.
  1. +14 −3 README.md
  2. +118 −38 bind.js
  3. +39 −0 tests/testDijit.js
  4. +1 −1 tests/testForm.js
  5. +21 −0 tests/testStateful.html
  6. +33 −0 tests/testStateful.js
View
17 README.md
@@ -18,13 +18,13 @@ object or property you want to bind to:
bind(anInputElement).to(myObject, "propertyName");
});
-And just like that we have a two-binding. The value from the object will be provided to the
-input. Any changes to the input will cause the object to be modified. We could even
+And just like that we have a two-way binding. The value from the object's property will be provided to the
+input. Any changes to the input will cause the object's property to be modified. We could even
bind another element to this property to easily see the binding in action.
bind(myDiv).to(myObject, 'propertyName');
-And the value of the property would be put in the div, and updated anytime the input
+And the value of the property would be put in the div, and updated any time the input
was changed.
We can also create a property binding that can encapsulate a single property, and
@@ -34,6 +34,17 @@ be directly bound to components:
// now we can bind components to this property
bind(anInputElement).to(myProperty);
+We can bind object properties to inputs, where the property value is synchronized with
+the input's value, and container elements like divs, where the property value is outputted
+to the element's inner text.
+
+In addition we can also bind an object to a form. In this case, dbind will search through
+the form inputs and bind each one to the object's properties based on the input's "name" attribute.
+If you have a form that you wish to create with HTML, this makes it very easy to bind an object to it
+without directly referencing each input:
+
+ bind(myForm).to(myObject);
+
## Dijit Components
With our bindings, we can easily use direct DOM elements or Dijit components interchangeably.
View
156 bind.js
@@ -7,45 +7,72 @@ define([], function(){
}
}
Binding.prototype = {
- then: function(callback){
+ receive: function(callback){
// get the value of this binding, notifying the callback of changes
- (this.callbacks || (this.callbacks = [])).push(callback);
- this.getValue(callback);
+ var callbacks= this.callbacks;
+ (callbacks || (this.callbacks = [])).push(callback);
+ if(callbacks){
+ if("value" in this){
+ callback(this.value);
+ }
+ }else{
+ var self = this;
+ callbacks = this.callbacks;
+ this.getValue(function(value){
+ self.value = value;
+ for(var i = 0; i < callbacks.length; i++){
+ callbacks[i](value);
+ }
+ });
+ }
},
getValue: function(callback){
- callback(this.value);
+ if(this.hasOwnProperty("value")){
+ callback(this.value);
+ }
},
get: function(key, callback){
- var child = this['_' + key] || (this.source ?
- this.source.get(key) :
- (this['_' + key] = this.value && typeof this.value == "object" ?
- new PropertyBinding(this.value, key) :
- new Binding()));
+ // use an existing child/property if it exists
+ var value, child = this['_' + key] ||
+ (this['_' + key] = this.hasOwnProperty("value") && typeof this.value == "object" ?
+ (value = this.value[key]) && typeof value != "object" ? new PropertyBinding(this.value, key) :
+ convertToBindable(value) : new Binding());
if(callback){
- return child.then(callback);
+ return child.receive(callback);
}
return child;
},
put: function(value){
if(this.source){
this.source.put(value);
}
+ value._binding = this;
this.is(value);
},
is: function(value){
- this.value = value;
- if(this.callbacks){
- for(var i = 0; i < this.callbacks.length; i++){
- this.callbacks[i](value);
+ if(value !== this.value){
+ this.value = value;
+ if(typeof this.value == "object"){
+ for(var i in value){
+ if(i.charAt(0) != '_'){
+ this.get(i).is(value[i]);
+ }
+ }
+ }
+ if(this.callbacks){
+ for(var i = 0; i < this.callbacks.length; i++){
+ this.callbacks[i](value);
+ }
}
}
},
keys: function(callback){
- var source = this.source || this;
- for(var i in source){
- if(i.charAt(0) == '_'){
- i = i.slice(1);
- callback(i, source.get(i));
+ if(this.source){
+ this.source.keys(callback);
+ }
+ for(var i in this.value){
+ if(i.charAt(0) != '_'){
+ callback(i, this.get(i));
}
}
},
@@ -55,7 +82,7 @@ define([], function(){
source = source.get(property);
}
var self = this;
- this.source = source;
+ this.source = source;
source.receive(function(value){
self.is(value);
});
@@ -79,7 +106,7 @@ define([], function(){
});
return this;
}
- };
+ };
// StatefulBinding is used for binding to Stateful objects, particularly Dijit widgets
function StatefulBinding(stateful){
this.stateful = stateful;
@@ -89,7 +116,7 @@ define([], function(){
StatefulBinding.prototype.to = function(source){
Binding.prototype.to.apply(this, arguments);
source = this.source;
- var stateful = this.stateful;
+ var stateful = this.stateful;
source.receive(function(value){
stateful.set('value', value);
});
@@ -99,6 +126,45 @@ define([], function(){
}
});
return this;
+ };
+ StatefulBinding.prototype.get = function(key, callback){
+ return this['_' + key] || (this['_' + key] = new StatefulPropertyBinding(this.stateful, key));
+ };
+ StatefulBinding.prototype.keys = function(callback){
+ for(var i in this.stateful){
+ if(i.charAt(0) != '_'){
+ callback(i, this.get(i));
+ }
+ }
+ };
+
+ function StatefulPropertyBinding(stateful, name){
+ this.stateful = stateful;
+ this.name = name;
+ }
+ StatefulPropertyBinding.prototype = new Binding;
+ StatefulPropertyBinding.prototype.getValue = function(callback){
+ // get the value of this property
+ var stateful = this.stateful,
+ name = this.name;
+ // get the current value
+ callback(stateful.get(name));
+ // watch for changes
+ stateful.watch(name, function(name, oldValue, newValue){
+ if(oldValue !== newValue){
+ callback(newValue);
+ }
+ });
+ };
+ StatefulPropertyBinding.prototype.is = function(value){
+ // don't go through setters, it is bubbling up through the source
+ this.stateful._changeAttrValue(this.name, value);
+ //return Binding.prototype.is.call(this, value);
+ }
+ StatefulPropertyBinding.prototype.put = function(value){
+ // put a value, go through setter
+ this.stateful.set(this.name, value);
+ }
function ElementBinding(element, container){
this.element= element;
@@ -125,32 +191,33 @@ define([], function(){
}
}else{
element.innerText = value || "";
+ }
+ this.oldValue = value;
+ };
+ var inputLike = {
+ "INPUT":1,
+ "SELECT":1,
"TEXTAREA":1
};
ElementBinding.prototype.to = function(source){
Binding.prototype.to.apply(this, arguments);
source = this.source;
var element = this.element;
+ if(element.tagName == "FORM"){
var binding = this;
function findInputs(tag){
var inputs = element.getElementsByTagName(tag);
for(var i = 0; i < inputs.length; i++){
var input = inputs[i];
- if(input.name){
+ if(input.name){
bind(input, binding.get(input.name));
}
}
}
findInputs("input");
- findInputs("select");
- }else if(element.tagName == "INPUT" || element.tagName == "SELECT"){
- var value, oldValue, gotOldValue;
- element.onchange = function(){
- if(!gotOldValue){
- gotOldValue = true;
- source.getValue(function(value){
- oldValue = value;
- });
+ findInputs("select");
+ }else if(element.tagName in inputLike){
+ var value, binding = this,
onchange = element.onchange = function(){
if(element.type == "radio"){
if(element.checked){
@@ -160,15 +227,18 @@ define([], function(){
}
}else{
value = element.type == "checkbox" ? element.checked : element.value;
- }
+ }
source.put(typeof binding.oldValue == "number" && !isNaN(value) ? +value : value);
+ };
+ if(element.getAttribute('data-continuous')){
+ element.onkeyup = onchange;
}
}
return this;
- }
+ }
ElementBinding.prototype.receive = function(callback){
var element = this.element;
- if(this.container){
+ if(this.container){
return Binding.prototype.receive.call(this, callback);
}
if("value" in element){
@@ -190,7 +260,7 @@ define([], function(){
length = this.value.length;
// watch all the items, and return a resulting array whenever an item is updated
for(var i = 0; i < length; i++){
- (function(i, source){
+ (function(i, source){
when(source, function(value){
currentValues[i] = value;
updates++;
@@ -217,10 +287,10 @@ define([], function(){
this.func = func;
this.reverseFunc = reverseFunc;
}
- FunctionBinding.prototype = {
+ FunctionBinding.prototype = {
receive: function(callback){
if(callback){
- var func = this.func;
+ var func = this.func;
return this.source.receive(function(value){
callback(value.slice ? func.apply(this, value) : func(value));
});
@@ -303,5 +373,15 @@ define([], function(){
bind.get = get;
bind.Element = ElementBinding;
bind.Container = ContainerBinding;
+ bind.Binding = Binding;
+
+
+ function when(value, callback){
+ if(value && value.receive){
+ return value.receive(callback);
+ }
+ return callback(value);
+ }
+ bind.when = when;
return bind;
View
39 tests/testDijit.js
@@ -0,0 +1,39 @@
+define(['dbind/bind', 'dbind/Validator', 'put-selector/put'], function(bind, Validator, put){
+ var get = bind.get;
+ myObject = {quantity: 3, price: 5};
+ return function(form){
+ // TODO: put this in a model module
+ var quantity = bind(
+ new Validator({type:"number", maximum: 20, minimum: 10})).to
+ (myObject, 'quantity');
+
+ var quantityRow = put(form, 'div');
+
+ quantity.get("title").is("Quantity");
+
+ function ValidationTextBox(){
+ var mainElement = put('div');
+ var binding = new bind.Container(mainElement);
+ // the label
+ bind(put(mainElement, 'label')).to(binding, 'title');
+ // the main value is bound to the input
+ bind(put(mainElement, 'input[type=text]')).to(binding);
+ // any errors go after it
+ bind(put(mainElement, 'span.error-message')).to(binding, 'error');
+ return mainElement;
+ }
+ var quantityTextBox = ValidationTextBox();
+ put(quantityRow, quantityTextBox);
+ bind(quantityTextBox).to(quantity);
+ put(form, "div", "Price", "input[type=text]", {name: "price"});
+ bind(form, myObject);
+
+/* bind(put(quantityRow, 'input[type=text][placeholder=number]'), quantity);
+ bind(put(quantityRow, 'span.error-message'), quantity.get('error'));
+ bind(put(form, 'div label'), quantity.get("title"))
+ bind(put(form, 'div span'), quantity);*/
+ bind(put(form, 'div label', 'Total Price: ', '< span'), bind(function(quantity, price){
+ return "$" + quantity * price;
+ }).to([quantity, bind(myObject, "price")]));
+ }
+});
View
2 tests/testForm.js
@@ -18,7 +18,7 @@ define(['dbind/bind', 'dbind/Validator', 'put-selector/put'], function(bind, Val
// the main value is bound to the input
bind(put(mainElement, 'input[type=text]')).to(binding);
// any errors go after it
- bind(put(mainElement, 'span.error-message')).to(binding.get('error'));
+ bind(put(mainElement, 'span.error-message')).to(binding, 'error');
return mainElement;
}
// create the form elements
View
21 tests/testStateful.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test dbind with a Stateful object and a computed property</title>
+ <style type="text/css">
+ @import "../../dojo/resources/dojo.css";
+ .error-message {
+ color: red;
+ }
+ </style>
+ <script type="text/javascript" src="../../dojo/dojo.js"
+ data-dojo-config="async: true"></script>
+ <script type="text/javascript">
+ require(["./testStateful.js", "dojo/dom", "dojo/domReady!"], function(testForm, dom){
+ });
+ </script>
+ </head>
+ <body>
+ <div id="content"></div>
+ </body>
+</html>
View
33 tests/testStateful.js
@@ -0,0 +1,33 @@
+define(['dbind/bind', 'dojo/Stateful', 'dojo/dom-construct', 'dijit/form/TextBox'], function(bind, Stateful, domConstruct, TextBox){
+ function Model(props) {
+ var stateful = new Stateful(props),
+ first = bind(stateful, 'first'),
+ last = bind(stateful, 'last'),
+ fullName = bind(stateful, 'fullName').to(bind(function (first, last) {
+ console.log('making full name', first, last);
+ return [].join.apply(arguments);
+ }).to([first, last]));
+
+ fullName.then(function(fullName){
+ console.log("The full name is now", fullName);
+ });
+ return stateful;
+ }
+
+ model = Model({
+ first: 'first',
+ last: 'last'
+ });
+ console.log(model);
+ var viewModel = {
+ first: new TextBox().placeAt('content'),
+ last: new TextBox().placeAt('content'),
+ fullName: domConstruct.create('div', null, 'content')
+ };
+ bind(viewModel).to(model);
+
+ model.set("first", "John");
+ model.set("last", "Doe");
+
+ console.log("Full name: ", model.fullName);
+});

No commit comments for this range

Something went wrong with that request. Please try again.