Atlas provides a binding model which differs from the data-binding model in ASP.NET. It is a flexible binding mechanism, somewhat similar to the one provided by WPF (Windows Presentation Foundation), that basically lets you bind any property of 1 component to any property of another object. Therefore we call them bindings, instead of data-bindings, since we can for example even bind the style of 1 control to a property of another control.
Let's first look at a basic example that binds the value of a textbox to a label. Whenever we change the text in the textbox, we want to update the label's text property accordingly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
HTML:
[...]
<input id="nameTextBox" type="textbox" /><br />
<span id="nameLabel"></span>
[...]
Atlas:
<script type="text/xml-script">
<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
<components>
<!-- 'Upgrade' the HTML textbox to an Atlas textbox. -->
<textBox id="nameTextBox" />
<!-- 'Upgrade' the HTML span to an Atlas label. -->
<label id="nameLabel">
<bindings>
<binding dataContext="nameTextBox" dataPath="text" property="text" />
</bindings>
</label>
</components>
</page>
</script>
|
So, what does Atlas do to make this this possible? First of all, Atlas needs a way to listen to property value changes (unless we don't use automatic bindings). There is an INotifyPropertyChanged interface, which is similar to the one provided by .NET. Objects can implement this interface to let other objects listen to property value changes. The Atlas component class (base class for components such as controls and behaviors) implements this interface. Additionally, the component class has a function 'raisePropertyChanged(propertyName)' which you should call in every property's setter, to raise the INotifyPropertyChanged.propertyChanged event.
With this knowledge, lets look at a few parts of the TextBox implementation to learn how we should raise the propertyChanged event when an event of the HTML element was raised.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
[...]
var _text;
var _changeHandler;
this.get_text = function() {
return this.element.value;
}
this.set_text = function(value) {
if (this.element.value != value) {
_text = value;
this.element.value = value;
this.raisePropertyChanged('text');
}
}
this.initialize = function() {
Web.UI.TextBox.callBaseMethod(this, 'initialize');
_text = this.element.value;
_changeHandler = Function.createDelegate(this, this._onChanged);
this.element.attachEvent('onchange', _changeHandler);
[...]
}
// Handles changes in the underlying HTML control.
this._onChanged = function() {
if (this.element.value != _text) {
_text = this.element.value;
this.raisePropertyChanged('text');
}
}
[...]
|
As you can see, we raise the propertyChanged event whenever the text in our textbox changes. This makes it possible to actually bind against this property.
When we create a binding, the binding will look at both the 'source' object (the data context) and the target object (on which you defined the binding) to see if it implements INotifyPropertyChanged. If the source object does, the binding will handle its propertyChanged event by evaluating the 'incoming' binding, if the property you specified in the dataPath was the property that changed and the direction is set to In or InOut.
If the target object implements INotifyPropertyChanged, its propertyChanged event will be handled by evaluating the 'outgoing' binding, if it is the property you defined on the binding and if the direction is set to Out or InOut.
Lastly, lets have a look at the public properties and methods of a binding, and see what they are good for. I got this list by looking at the getDescriptor implementation of Web.Binding in AtlasCore.js.
- Property 'automatic': Specifies whether the binding should automatically be evaluated whenever the property of the source object's property was changed (In, InOut), or the target object's property was changed (Out, InOut). Sometimes you want to control when the binding should be evaluated. For example, you only want to evaluate a binding when a ServiceMethodRequest completed the request so it actually has data. You can do this by setting automatic to false, and explictly invoke the evaluateIn method of the binding. The default value is 'true'.
- Property 'dataContext': Specifies the source object which has a property that you want to bind to. You can set this to another object, such as a TextBox control. If you don't set this, the binding will call its owner's dataContext property. Controls implement this property by either returning the dataContext that was set, or by returning its parent control's dataContext. Controls such as the ListView take care of setting the dataContext to for example a DataRow object when it creates ListView items, which makes it possible to bind against data items.
- Property 'dataPath': The source object's property that you want to bind to. You can bind against 'nested properties' as well, using the following syntax: 'sourceObjectProperty.nestedProperty.anotherNestedProperty'. The binding will split this path and run code similar to:
'var value = mySourceObject.getProperty('sourceObjectProperty')
.getProperty('nestedProperty')
.getProperty(anotherNestedProperty)'
The default value is nothing, which means you will actually bind to the source object itself.
- Property 'direction': Lets you specify whether you want to listen for changes (In), propagate changes (Out), or both (InOut). The default value is 'In'.
- Property 'property': The target object's property that you want to bind to. You should always set this property.
- Property 'propertyKey': Sometimes you want to bind to a member of the property you specified in the 'property' property. For example, you may want to bind to the style's property 'width'. You can do so by setting the property 'property' to style, and set the property 'propertyKey' to 'width'.
- Property 'transformerArgument': An argument that will be passed to a transformer.
- Event 'transform': This event lets you apply a transformation during the evaluation of a binding. This can be useful if you for example want to display a bound value differently in the target object. There are several built-in transformers, such as Add, which can be useful to add a value (specified using the property 'transformerArgument') to the bound value. This is useful if you are binding to an index, which generally start at 0, and you want to display them starting at 1.
- Method 'evaluateIn': Evaluates the 'incoming' binding, if the direction was set to In or InOut. It does so by actually getting the value of the source object's property (based on the dataContext and dataPath values), and call the target object property's setter with this value.
- Method 'evaluateOut': Similar to evaluateIn, except that it works the other way round, and only works when the direction is set to Out or InOut.
Hopefully this answers some questions with relation to bindings. Please leave a comment with any questions that you may have, or leave behind a suggestion for a different aspect of Atlas that you want me to zoom in on.