I’ve recently come across a situation in an application I’m working on where I’d like to bind a client side business object that is defined by a JSON statement to a series of labels. Out of the box, Atlas provides a way to bind one attribute of a component to an attribute of another component by defining a Sys.Binding object in JavaScript or a <binding> tag in xml-script and adding it to a component’s binding collection. For example, I can bind the text of a label to the text of a textbox or the cssClass of a button to a drop down list’s selected value. There's also a way to bind a list of items to a ListView or other list type object using an Atlas Datasource, but it only works with DataTables and Arrays. (There are complete code examples in the Controls and UI Quickstart on the Atlas site.) However, my business object isn’t a Component, it’s just a server side state bag (entity object, domain object, whatever) that’s been serialized into JSON format so the browser can access it. So how do I make it participate in binding?
In my research I realized that to accomplish something like this, the client side business object must be a Component itself as components can be bound to other components. (A good explanation of how binding works in Atlas can be found at Wilco Bauwer’s Blog about 1/2 way down.) So I went about the path of creating a business object that derives from Sys.Component so it can participate in bindings.
(BTW, binding comes in handy when you can't use an UpdatePanel to refresh a control’s values such as when you're doing custom control development and you’re well past the InitComplete event when the control is added to the page.)
So, following the pattern defined by other bindable controls in the Atlas framework such as the label and textbox, I created a client-side object that inherits from Sys.Component. Inheriting from Sys.Component provides my business object with the capabilities to raise propertyChanged events and participate in bindings. (Actually, the INotifyPropertyChanged interface implemented by Sys.Component provides the propertyChanged ability.)
On to the code …
<script language="javascript" type="text/javascript">
// pageLoad fires automatically because it's tied to the application load event in Atlas
function pageLoad()
{
// define a new bindable business object
BindableKeyword = function ()
{
// execute the initializeBase method
BindableKeyword.initializeBase(this, [true]);
// create some private variables.
var _createDate;
var _description;
var _name;
// initialize function
this.initialize = function() {
BindableKeyword.callBaseMethod(this, 'initialize');
}
// create date getter
this.get_createDate = function()
{
return _createDate;
}
// create date setter
this.set_createDate = function (value)
{
// if the current createdate value != the new value
if (_createDate != value) {
// update the createDate's value
_createDate = value;
// raise the propertyChanged event
this.raisePropertyChanged('createDate');
}
}
// description getter
this.get_description = function ()
{
return _description;
}
// description setter
this.set_description = function (value)
{
if (_description != value)
{
_description = value;
this.raisePropertyChanged('description');
}
}
// name getter
this.get_name = function ()
{
return _name;
}
// name setter
this.set_name = function (value)
{
if (_name != value)
{
_name = value;
this.raisePropertyChanged('name');
}
}
// override the getDescriptor of the base class.
// This is where we define the bindable properties.
this.getDescriptor = function() {
// execute the base class's getDescriptor to get the TypeDescription
var td = BindableKeyword.callBaseMethod(this, 'getDescriptor');
// add the name property
td.addProperty('name', String);
// add the description property
td.addProperty('description', String);
// add the createDate property
td.addProperty('createDate', String);
return td;
}
// register the override.
BindableKeyword.registerBaseMethod(this, 'getDescriptor');
} // end keyword class
// Set the BindableKeyword's base class to Sys.Component. This is important as Sys.Component provides the framework
// for binding to take place.
BindableKeyword.registerClass('BindableKeyword', Sys.Component);
Sys.TypeDescriptor.addType('script', 'BindableKeyword', BindableKeyword);
// Some test code to play with my new BindableKeyword object.
// create a new BindableKeyword.
var bindableKeyword = new BindableKeyword();
// set the name
bindableKeyword.set_name ("Test Name 1");
// Create an Atlas label that refers to one of the spans I declared in the HTML.
var lblName = new Sys.UI.Label($('lblName'));
// Create the binding on the label
var binding_1 = new Sys.Binding();
// set the dataContext (really the dataSource) to be the BindableKeyword
binding_1.set_dataContext(bindableKeyword);
// set the property path within the BindableKeyword to bind to.
binding_1.set_dataPath('name');
// set the property within the label to update with the data from the BindableKeyword
binding_1.set_property('text');
// setup the binding direction.
binding_1.set_direction(Sys.BindingDirection.In);
// Add the binding to the label
lblName.get_bindings().add(binding_1);
// Initialize the label
lblName.initialize();
bindableKeyword.set_name ("Test Name 2");
alert ("pause");
bindableKeyword.set_name ("After Set Name 3");
}
</script>
</head>
<body>
<form id="form1" runat="server">
<atlas:ScriptManager ID="sc1" runat="server"></atlas:ScriptManager>
<div>
<span id="lblName">Unbound Name</span> <span id="lblCreateDate">Unbound Create Date</span>
<span id="lblDescription">Unbound Description</span>
</div>
</form>
</body>
</html>
Basically what I’m doing here is defining a business object that when properties are set, the raisePropertyChanged (propertyName) method provided by the base class is executed. This notifies any bindings bound to this property to update themselves. Once I’ve defined my business object in this manner, it can participate in bindings just like other Atlas components. By changing the binding’s direction to InOut, I could even update my business object with a user entered value. This would be useful for capturing data and placing it in a client side business object for validation.
While this works, defining all of my business objects that need to participate in clientside binding is a bit tedious. Also, if I am able to serialize my objects to JSON on the server and then emit the necessary JavaScript to place them on the client then defining them on the client isn’t really necessary as the deserialized JSON statement is already an object. All I need to know is the makeup of my server object to access the data on the client. But, to make them bindable I have to go through the definition process on the client. Ugh! Furthermore, it kills maintainability as if I add or remove a property from the server object I have to update the client object too. Double Ugh! If I can figure out a way to do something like this … “var bindableObject = new BindableObject (keyword);” and then have all the pieces of keyword become bindable, that would be most excellent.
That will be the subject of my next blog. If I can figure it out…
Comments??