My ASP.NET 4.0 is already available on the FCA web site
It’s still not for sale, but you can be notified when it’s out. Btw, here’s its cover:
Ramblings about C#, .NET and Programming
It’s still not for sale, but you can be notified when it’s out. Btw, here’s its cover:
I’d like to apologize for the lack of posts lately…no, I haven’t given up in my Silverlight series, but I’ve been busy updating my existing ASP.NET book to the 4.0 version. It seems like I will have a lot of work (much more than I had anticipated, but the truth is that I really enjoy writing about technical stuff) so the Silverlight series won’t really go as fast as the one on MS AJAX…sorry for that…now back to business :,,)
I really didn’t gave it any thoughts until I spoke with a friend who he asked me about the options we have for requiring a script (when using MS AJAX, of course!). So, lets see…we can specify it by:
All of them requires that you “define” your scripts in a JavaScript file which must be included in the page after the start.js file. And the best thing is that you can mix them all when using the Sys.required method. Fantastic, right?
In my last post, I’ve talked a little bit about the new plugins introduced by the latest release of MS AJAX (beta, at the time of writing). One of the things that didn’t enjoy much was that I had to call the JQuery’s get method in order to get a reference to the DOM element array. What I was forgetting was that MS AJAX has really good integration with JQuery (something that Dave Ward was kind enough to reminded me).
In practice, this means that I can simply pass a string with the selector I want and I’ll automatically get the correct DOM element array. here’s the updated code:
<script type="text/javascript"> Sys.require( [Sys.scripts.jQuery, Sys.scripts.ComponentModel], function () { $.addHandler( "input", "click", function (evt) { alert("JQuery: " + evt.target.id); }); } ); </script>
Notice that you can use more complex selectors too. All the selectors which can’t be interpreted by MS AJAX will be delegated to JQuery. As you can see, MS AJAX is becoming a really cool lib, don’t you think?
The MS AJAX beta release adds some new plugins to the previous ones. Besides the existing setCommand and bind, we now have access to the following plugins:
I’m not sure if you recall it, but plugins get promoted to Sys helper methods. Take a look at the following code which shows how to use the addHandler plugin to hook up the click event over all the existing buttons:
<head runat="server"> <script type="text/javascript"
src="Scripts/MicrosoftAjax/Start.debug.js">
</script> <script type="text/javascript"> Sys.require([Sys.scripts.ComponentModel], function () { var buttons = document.getElementsByTagName("input"); Sys.addHandler(buttons, "click",function (evt) { alert(evt.target.id); }); }); </script> </head> <body> <input type="button" id="bt1" value="Button 1" /> <input type="button" id="bt2" value="Button 2" /> <input type="button" id="bt3" value="Button 3" /> </body>
Whenever you click over a button,you should get an alert message with the ID of the button. Now, since we’re talking about handlers, here’s how you’d use the addHanders plugin:
Sys.addHandlers(Sys.get("#bt1"), { click: function (evt) { alert("bt-1"); } } );
As you can see, I’ve used a literal JS object for specifying the events I want to handle. You should only use this plugin when you want to pass several handlers to different events. Notice that both plugins (addHandler and addHandlers) expect a reference to one or more DOM elements (you can see how to pass several references in the first snippet; the second passes a single DOM element reference). That’s why you should only use the second (addHandlers) when you need to setup several events at once.
Here’s what you get when you click “Button 1” after adding the last snippet to the page:
Notice that if you add this last snippet to the previous page, you should see two alert messages because you’re effectively adding two handlers to “button 1” click event.
One of the nicest things about plugins is their integration with JQuery. For instance, take a look at the following code:
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <script type="text/javascript"
src="Scripts/MicrosoftAjax/Start.debug.js"></script> <script type="text/javascript"> Sys.require(
[Sys.scripts.jQuery,Sys.scripts.ComponentModel], function () { $.addHandler( $("input").get(), "click", function (evt) { alert("JQuery: " + evt.target.id); }); } ); </script> </head> <body> <input type="button" id="bt1" value="Button 1" /> <input type="button" id="bt2" value="Button 2" /> <input type="button" id="bt3" value="Button 3" /> </body> </html>
As you can see, I’ve started by requiring JQuery and the MS AJAX JavaScript files needed for using the addHandler method (oh, btw, I could have specified the dependency through the Sys.plugins.addHandler too). After getting all the files, my anonymous callback function will be called and that’s where everything interesting is happening.
There’s an addHandler method which got added to the jQuery object (aliased to the $) during JQuery loading. Notice that the addHandler plugin still expects the same arguments and that’s why we need to use the JQuery’s get method to get a reference to an array with all the DOM elements of the internal wrapped set.
This behavior is pretty cool and also works with other plugins. The only thing you should keep in mind is that non-behavior/control plugins always get added to the $ object; on the other hand, behaviors/controls get added to the $.fn object (and that means that these plugins are “transformed” into “instance” methods of the JQuery object – take a look at this post to see what I mean).
And that’s it”. Stay tuned for more on MS AJAX.
In the last week, MS AJAX beta was released during PDC 2009. One of the things that this released added is ACT script loader support. If you go into the samples, you’ll see that there is an Extended folder which holds all the client files for the behaviors and controls exposed by the AjaxControlToolkit.
The code files have all been updated so that its” definitions are wrapped into an execute function (which is also placed inside an anonymous function). As you probably recall from previous posts, this function is called when all the dependencies have been loaded. Notice that with the previous CTP you could download the ACT from codeplex and make the changes present in all the files that come with this release. In other words, wrapping all the code within functions is the easiest part when you want to adapt an existing script so that it can be used with the new script loader component.
To me, the most important script file is the ExtendedControl Javascript file because it contains all the definitions (and dependencies) required by all the ACT controls. In practice, this means that you can simply drop this file on a page (plus the base start.js Javascript file) and use any of the ACT controls. Don’t believe me? Here’s some demo code which uses the calendar behavior:
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <link rel="stylesheet" type="text/css" href=
"Scripts/MicrosoftAjax/Extended/Calendar/Calendar.css" /> <script type="text/javascript" src="Scripts/MicrosoftAjax/Start.debug.js"></script> <script type="text/javascript" src="Scripts/MicrosoftAjax/Extended/ExtendedControls.debug.js"></script> <script type="text/javascript"> Sys.require([Sys.components.calendar], function () { Sys.create.calendar(Sys.get("#date")); }); </script> </head> <body> <input type="text" id="date" /> </body> </html>
And that’s it: I’ve just added two scripts and that’s all I needed to use the CalendarBehavior. Btw, here’s the results I got on my machine after loading the page:
Nice, right?
You can get it from here. I’m curious to see if it contains new features…
At least, when the RTM version is released!
Ok, I guess you need some context: in the previous post, I talked about a problem which was mentioned to me by Andy in the comments of an existing post. The problem was that the __msajaxBindings field ended up being added to an object used in a live binding and that really means trouble when you try to serialize it (because you end up with a cyclic reference which generates a runtime exception).
In the previous post, I suggested a hack for it. Fortunately, Dave saw it and emailed me assuring that the hack won’t be necessary and that the internal __msajax… fields should only be added to HTML elements. As you’re probably expecting by now, the problem I mentioned in the previous post is the result of a bug (which will be solved before the RTM is out).
After being alerted by Dave, I’ve decided to take a closer look at the code…And yes, there’s a problem and it’s on the Sys.UI.DomElement.isDomElement method. This method ends up invoking the _isDomElement method which has a check that returns a “false positive”:
Sys._isDomElement = function Sys$_isDomElement(obj) { var val = false; if (typeof (obj.nodeType) !== ''number'') { var doc = obj.ownerDocument || obj.document || obj; if (doc != obj) { var w = doc.defaultView || doc.parentWindow; val = (w != obj); } else { val = (typeof (doc.body) === ''undefined''); } } return !val; }
Can you spot the problem? Yep, the val = (typeof(doc.body)… line is responsible for considering the object a DOM element (remember: section had a property called body and if you look at the code, you’ll see that it ends up messing everything). While the good guys at MS don’t publish a new release which solves this bug, you really shouldn’t use any property named body in an object which is used in a binding relationship (at least,if you intend to serialize that object). Ok,to be fair, you can always use the previous awful hack, but If I were you, I’d renamed the property 🙂
And that’s it for now. Stay tuned for more.
In my “goodbye MS AJAX post”, reader Andy asked a really interesting question: how to serialize an object which is used in a binding relationship? The main problem is that the JavaScriptSerializer doesn’t support circular references. Unfortunately, it doesn’t also provide a way for you to specify which properties should be serialized. However, the problem is still there: how do you serialize the objects used in binding relationships?
I’m still hopping that the team will fix the serializer before MS AJAX RTMs is released, but meanwhile, we’re still left with the problem. I’ve thought a little bit about it and it seems like the best option is to “suspend” the binding, serialize it, and then “resume” it again. Notice that the code you’re about to see has not been tested (it was written during my bus trip home, so it might have one or two bugs 🙂 ,,). Let’s take a look at the example (I’ve reused most of Andy’s example and I’d like you to concentrate on the save method):
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title></title> <script src="Scripts/MicrosoftAjax/start.debug.js" type="text/javascript"> </script> <script type="text/javascript"> var instance = [ { title: ''New document'', content: { sections: [ { body: ''Enter text here'' }, { body: ''other text'' } ] } } ]; Sys.require([Sys.components.dataView], function () { var view = Sys.create.dataView( "#maincontent", { itemTemplate: ''#edit'', data: instance}); });
function onSave(){ var data = Sys.get("$maincontent").get_data(); var backup = {}; function cleanupMetadata(obj, propName, key) { if (obj.hasOwnProperty(propName)) { backup[key] = {}; backup[key][propName] = obj[propName]; delete obj[propName]; } } function restoreMetadata(obj, propName, key) { if (backup[key] !== undefined) { obj[propName] = backup[key][propName]; delete backup[key]; } } for (var i = 0; i < data.length; i++) { var sections = data[i].content.sections; for (var j = 0; j < sections.length; j++) { cleanupMetadata(sections[j], "_msajaxBindings",
i+"-"+j); } } var serialized =
Sys.Serialization.JavaScriptSerializer.serialize(data); for (var i = 0; i < data.length; i++) { var sections = data[i].content.sections; for (var j = 0; j < sections.length; j++) { restoreMetadata(
sections[i], "_msajaxBindings", i + "-" + j); } } alert(serialized); } </script> </head> <body xmlns:sys="javascript:Sys" xmlns:dataview="javascript:Sys.UI.DataView"> <div id="edit" class="sys-template"> <label>Title</label> <input sys:value="{binding title}"/> <div sys:attach="dataview" dataview:data="{{ content.sections }}" class="sys-template"> <label>Body</label> <textarea>{binding body}</textarea> </div> </div> <div id="maincontent"></div> <button onclick="onSave()">Save</button> </body> </html>
We’re using two helper methods: cleanupMetadata and restoreMetadata. In the cleanupMetadata, we store the _msajaxBinding info in the backup object. As you can see, we’re building a key by combining the positions of the objects so that we can recover that metadata later. Yes, this isn’t really reusable code and I’d really prefer to have the serializer filter the properties which shouldn’t be serialized. However, this approach will probably be enough for now if you need to serialize an object used in a binding relationship.
And that’s it for now…
Well, this really doesn’t deserve any special attention. Ok, let me put it another way: there’s a lot of work going on behind the scenes, but you shouldn’t have to worry with any of it.
I’ve thought about writing a post on the objects that end up being used by the AdoNetDataContext to propagate changes back to the server, but after some thought, I’ve give up on that idea because I’m not seeing anyone use any of these classes directly (yes, the AdoNetDataContext should do it all for you).
And that’s why I’m wrapping up this series on MS AJAX preview 6. I hope you’ve enjoyed it as much as did…Now, I do need to find another topic to keep me busy looking at new stuff…any ideas?
Here we are again, for more on the MS AJAX library. In the previous post, we’ve seen that we can interact with ADO.NET Data Services by using the AdoNetDataContext instances. Today, we’ll be looking at more advanced features of the ADO.NET Data Services which are also available in the MS AJAX library.
Before going on, I must confess that I’m not really an ADO.NET Data Service user (I guess this has made me curious enough for taking a peek in the future). However, since the MS AJAX library offers such great support for it, I couldn’t resist doing some digging and seeing which options are available for interacting with an existing data service.
According to the docs, ADO.NET Data Services can “be surfaced as a REST-style resource collection that is addressable with URIs and that agents can interact with using standard HTTP verbs such as GET, POST, PUT or DELETE”. In practice, and if we go back to our previous example, we can get all the images by using the uri http://:…/Services/ImageDataService.svc/Images. Besides this simple option,which returns all the images contained in the object,there are still other interesting options. For instance:
All these operations are specified through uris. We can easily do them by building the uris by hand. For instance, we can modify the previous example so that it also displays the associated :
<body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView"> <div id="myView" class="sys-template" sys:attach="dv" dv:autofetch="true" dv:dataprovider="{{ _ctx }}" dv:fetchoperation="Images" dv:fetchparameters="{{ {$expand: ''Tags''} }}"> <h2>{{Name}}</h2> <img sys:src="{{Uri}}" sys:alt="{{Name}}" /> <div class="sys-template" sys:attach="dv" dv:data="{{ Tags }}"> <span>{{Name}}</span>, </div> </div> </body>
By using the $expand parameter, we’ll end up getting each image’s associated tags and we can display them by using a nested DataView control. The next image shows what happens when we load the previous page:
We could specify all the “special” parameters this way. However, there is another way: we can rely on the Sys.Data.AdoNetQueryBuilder class for doing building the uri for us.
The AdoNetQueryBuilder’s constructor expects the base uri and exposes several properties which we can use to build the uri which performs a specific operation against the existing ADO.NET Data Service. You’ll probably end up using the ones which are present in the following list:
I must confess that I really didn’t find the use of this object intuitive at first. However, after running some experiences, everything started making sense. In order to show you how you might end up using this object, suppose we want to get all the images and associated tags and that we want to order them by name. Here’s a possible solution:
<head runat="server"> <style type="text/css"> .sys-template{ display: none; } img{ width:320px; height:212px; } </style> <script type="text/javascript" src="../Scripts/MicrosoftAjax/start.debug.js"> </script> <script type="text/javascript"> var _ctx = null; Sys.require([Sys.components.adoNetDataContext, Sys.components.dataView], function () { var uriBuilder =
new Sys.Data.AdoNetQueryBuilder("Images"); uriBuilder.set_expand("Tags");//$expand uriBuilder.set_orderby("Name");//$orderby _ctx = Sys.create.adoNetDataContext({ serviceUri: "../Services/ImagesDataService.svc" }); Sys.create.dataView( Sys.get("#myView"), { dataProvider: _ctx, autoFetch: true, fetchOperation: uriBuilder.toString() }); }); </script> </head> <body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView"> <div id="myView" class="sys-template"> <h2>{{Name}}</h2> <img sys:src="{{Uri}}" sys:alt="{{Name}}" /> <div class="sys-template" sys:attach="dv" dv:data="{{ Tags }}"> <span>{{Name}}</span>, </div> </div> </body>
This time, I’ve opted for using an imperative approach. Things are really similar to what we had before; however, notice that now we’re using an AdoNetQueryBuilder object to set the fetchOperation property of the DataView object.
We start by instantiating the AdoNetQueryBuilder object. If you recall it, I said that this wasn’t really intuitive…it was only looking at the source code that I’ve understood that I was supposed to pass the name of the resource I wanted to access (instead of passing the base uri for the service – not sure on why the constructor’s only parameter is called uri, when resource would have make things so much easier!). Since I was interested in retrieving images, I only had to pass the string “Images” to the constructor.
After having an instance and setting the required properties (recall from above that we wanted to get images+tags and to order everything by name), we can simply call the toString method to get the string which represents the operation we need to perform. And that’s it. We’ll still be speaking about MS AJAX in future posts, so stay tuned for more!
Today we’re going to start looking at the AdoNetDataContext. This class expands the DataContext object and uses several specific methods for ensuring the correct interaction with an existing ADO.NET Data Service.
These internal methods added by the AdoNetDataContext are essentially used in identity related operations. In other words, it extends the base DataContext class by introducing several methods which turn on identity support (btw, I’m talking about the stuff we’ve seen in several of the previous posts).
In this post, I’ll be reusing the sample ADO.NET Data Service which is included in MS AJAX preview 6. Here’s the model used internally by the ImageDataService.svc ADO.NET service:
In this case, and since this is just an intro post, we’ll build a simple page which shows all the images returned by this ADO.NET service:
<head runat="server"> <style type="text/css"> .sys-template{ display: none; } img{ width:320px; height:212px; } </style> <script type="text/javascript" src="../Scripts/MicrosoftAjax/start.debug.js"> </script> <script type="text/javascript"> var _ctx = null; Sys.require([Sys.components.adoNetDataContext, Sys.components.dataView], function () { _ctx = Sys.create.adoNetDataContext({ serviceUri: "../Services/ImagesDataService.svc", mergeOption: Sys.Data.MergeOption.appendOnly }); }); </script> </head> <body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView"> <div id="myView" class="sys-template" sys:attach="dv" dv:autofetch="true" dv:dataprovider="{{ _ctx }}" dv:fetchoperation="Images"> <h2>{{Name}}</h2> <img sys:src="{{Uri}}" sys:alt="{{Name}}" /> </div> </body>
By now, you shouldn’t really be surprised by anything that is shown in the previous snippet. As you can see, the AdoNetDataContext class protects us from all those nasty details associated with the communication with the ImageDataService.svc server side service. I’m not going to show you how to add, edit or remove items because the samples released with preview 6 already have code which shows how to do those things.
On the next post,we’ll see how to do more interesting operations by using the AdoNetQueryBuilder object. Stay tuned for more on MS AJAX.
Before we can move on and take a look at the AdoNetDataContext component, we need to talk about one more topic: creating entities and how it might be integrated with DataContext component. The DataContext exposes a createEntity method which can be used for creating new objects of a specific type. Internally, that method will always delegate the creation of a new entity to the function to which the createEntityMethod property points to.
As you’re probably wondering, this is something you’ll only need to configure if you need to use the DataContext features associated with identity (and even in that case, setting the createEntityMethod is optional if you’ve set the getNewIdentityMethod property).
The createEntityMethod property should reference a function which receives two parameters (a DataContext reference and a string with the “type” of the object that should be created) and initializes the metadata necessary for tracking that object. If you recall the previous posts, you’ll surely remember that we’ve added an internal __meta property to all the objects stored by the DataContext instance.
We could improve our DataContext used at the time by setting the createEntityMethod to a function which initializes the metadata associated to that object. Here’s how I’d augment the entityManager we’ve been using with a new method used for creating new entities:
createNewEntity: function (dataContext, entitySet) { var obj = {}; entityManager.getNewIdentity(dataContext, obj, entitySet); return obj; }
As you can see, we’re simply relying in the getNewIdentity method because it is responsible for setting the __meta object used for that purpose. Referencing entityManager instead of this might seem little strange at first, but that’s needed because the DataContext won’t call the reference through the entityManager instance (it will simply call the function through the reference it got to it during initialization:
var ctx = Sys.create.dataContext( { serviceUri: "PeopleService.svc", getIdentityMethod: entityManager.getEntityId,getNewIdentityMethod: entityManager.getNewIdentity,isDeferredPropertyMethod: entityManager.isDeferredProperty, getDeferredPropertyFetchOperationMethod:
entityManager.getDeferredProperty, createEntityMethod: entityManager.createNewEntity } );
And that’s it. There’s really not much to say about the createEntity method. Stay tuned for more on MS AJAX.
Today we’ll keep looking at the DataContext component and we’ll see how it supports deferred properties. Deferred properties are properties which are lazy loaded as “needed”. Using deferred properties means that the component will try to make a remote call for getting the value of that property. As you’re probably expecting, this means that you’ll have to write code on the client and server side. On the client, we’ll need some JavaScript functions which are responsible for:
After creating the functions and configuring the DataContext component’s isDeferredPropertyMethod and getDeferredPropertyFetchOperationMethod , we can get the deferred value of the property by using the DataContext’s fetchDeferredProperty method. Let’s take a look at the client side code (which augments the code we introduced in previous posts):
var entityManager = { metaCounter: {}, getEntityId: function (dataContext, entity) { //removed: check previous posts }, getNewIdentity: function (dataContext, entity, entitySet) { //removed: check previous posts }, isDeferredProperty: function (dataContext, entity, propertyName) { var id = dataContext.getIdentity(entity); return propertyName === "contacts" && id !== null && /$new/.test(id); },getDeferredProperty: function (
dataContext,entity, propName, parameters, userContext) { //returns web service operation var id = dataContext.getIdentity(entity); return new Sys.Net.WebServiceOperation( "GetContactsFor", {id: parseInt(/(d+)/.exec(id)[0]) }, "get" ) ; } };
It goes without saying (I think!) that this is just demo code (you’ll need better code for real world scenarios). As you can see, the contacts property will only be lazily loaded when we’re trying to get it from an object which has been previously persisted in the server side (once again, you’ll need to go back to the previous posts to understand what’s going on here). In the real world, I’d probably check that property against undefined too so that you’d only ask for the data when the property hasn’t been initialized.
The getDeferredProperty tries to find the id of the current person’s instance (don’t forget that person instances which have been previously persisted are on the form people$xxx where xxx is an integer which identifies the current instance) and uses it to build a JS literal object which is used to pass parameters to the server side code. Notice that the Sys.Net.WebService’s “constructor” expects three parameters: the first identifies the name of the operation; the second is a literal object which represent the parameters that will be passed to the server side method. Finally, the third parameter is a string which identifies the HTTP method that should be used in the server side call.
Now, we’re ready to create a new DataContext instance:
var ctx = Sys.create.dataContext( { serviceUri: "PeopleService.svc", getIdentityMethod: entityManager.getEntityId, getNewIdentityMethod: entityManager.getNewIdentity, isDeferredPropertyMethod: entityManager.isDeferredProperty, getDeferredPropertyFetchOperationMethod:
entityManager.getDeferredProperty } );
One additional note before looking at the server side code: we *really* need to set the serviceUri property when we intend to use deferred property fetching. If you don’t set it, you’ll end up receiving an exception when you try to get the value of the property from the server side:
ctx.fetchDeferredProperty( people[0], "contacts", null, null, function(){ var contacts = people[0].contacts; //contacts already filled here! });
The fetchDeferredProperty method receives several parameters:
In order for the previous client code work, we need to update our server side service’s code by adding the GetContactsFor method. Once again, and since is just demo code, I’ll implement in the easiest way possible: I’ll make it return a collection with a single predefined contact:
[OperationContract] public IEnumerable<Contact> GetContactsFor(Int32 id) { return new List<Contact>{ new Contact{ Type = ContactType.Phone, Ct = "123123123" } }; }
And there you go: you don’t need anything else for making the DataContext component get the value of the contacts property lazily. And that’s it for now. Stay tuned for more on MS AJAX.
Today we’ll keep going in our study of the DataContext component and we’ll see how we can establish relationships between objects through links. I guess you’re probably asking something like: “Hum…establishing relationships between objects…how about using properties?” For instance, suppose we’re working with person and contact instances from the previous post…We can say that a person has one contact and that is easily expressed through JavaScript which looks like this:
var personWithContact = { id: 6, name: "dumb", contact: { value: 1, contact: "123123123" } };
Now, this is perfectly good code…really…so, why would I want to use links then? Glad you asked :,,)
Before going on, I’d like to point out that this is a feature which was added in order to support ADO.NET Data Services. Fortunately, the team wrote it in a generic way so that you could use it even if you’re not using ADO.NET Data Services (I said fortunately because you can reuse this feature if you want). But I’m divagating…why would we use links? For bookkeeping, of course! Whenever you create a link between two objects, you end up setting a property in the source and you’ll also get an entry in the changes array (internal _changelist array).
When working with links, you’ve got several methods which you can use:
As I’ve said above, you should keep in mind that these methods end up changing the internal changes list maintained by the DataContext control. Establishing and cancelling links produces different results when you’re working with single properties and collections. For instance, take a look at the following code (based in code presented in the previous post):
var newPerson = { name: "joseph", address: "fx",
contacts: [] }; var ct1 = { value: 1, contact: "123123123" }; var ct2 = { value: 2, contact: "222333444" }; ctx.addLink(newPerson, "contacts", ct1);//collection ctx.setLink(newPerson, "contact", ct2);//single prop ctx.setLink(newPerson, "contact", null);//removing single ctx.removeLink(newPerson, "contacts", ct1);//removing coll.
In the previous snippet, we’ve introduced a new person instance and several contacts in order to show you how to establish links between the objects. If you stop the code in the addLink method, you’ll see something like this:
As you can see, establishing the link through the add method ended up adding the contact to the contacts array of the person object and inserting a new entry in the changes list. If you stop the code’s execution after the setLink line, you’ll see something similar (depending on what changes were made to the DataContext):
As you can see, the main difference is that setLink ends up setting an item instead of adding it to a collection. Canceling a relationship established through the setLink method involves using that method again, but passing null instead of a valid value. On the other hand, removing an item from the collection should always be done with the removeLink method. Interestingly, these methods produce different results in this scenario (don’t forget that we still haven’t committed the changes, ie, we still haven’t committed our setLink and addLink changes). Take a look at the following picture to see what I mean:
As you can see, the removeLink call ended up deleting the previous addLink entry (while also removing the entry from the contacts array). However, the setLink method doesn’t affect the changes array in the same way; instead, it will simply update the existing entry so that it points to the new target that has been passed to it. Notice that this means that you’ll have to pay attention to this if you’re writing custom code in the server side which is responsible for persisting the changes.
And I guess this sums it up quite nicely. but there’s still more to discuss…so stay tuned for more on MS AJAX.
In the previous post, we’ve started looking at identity and how it’s supported by the DataContext component. Today we’ll improve the initial algorithm so that we’re able to support more advanced scenarios.
Even though the first algorithm for identity management was really simple, it ended up showing that identity management ends up adding (at least) two important problems:
One possible solution for these problems relies on adding some sort of metadata to our objects. Going back to our initial example, we’ll start by adding an internal __meta object which will hold two important pieces of data:
Notice that the identity of the object must be enough for identifying that object (even amongst objects of different “types”). Another important detail to keep in mind is that we need some way to identify the current type of an object (if we were using a strongly typed language, this wouldn’t be necessary, but with dynamic languages, it’s something that must also be tracked). It’s also important to understand that the objects that will be passed to the trackData method should have this info correctly filled up (unlike objects which will be inserted, where the metadata is supposed to be generated by the identity methods):
var people = [ { id: 1, name: "luis", address: "fx",
__meta: { set: "people", id: "people$1"} },{ id: 2,name: "john", address: "fx",
__meta: { set: "people", id: "people$2"} }, { id: 3, name: "peter", address: "fx",
__meta: { set: "people", id: "people$3"} } ];
There’s not much we can say about the previous snippet. It represents a collection of person instances which have their __meta properties set up. Notice that from now on, the identity of each of these objects is given by the __meta.id value (instead of relying in the id field of the object).
Our implementation of the identity related methods need to handle more details. Due to that, I’ve decided to create a helper object which encapsulates all the work needed for getting and creating new identities:
var entityManager = { metaCounter: {}, getEntityId: function (dataContext, entity) { return entity.__meta && entity.__meta.id ? entity.__meta.id : null; }, getNewIdentity: function (dataContext, entity, entitySet) { if (!entitySet) { Error.invalidOperation("entity set must be defined"); } var _this =entityManager; function createMetaInfo(entity, entitySet) { var generatedId = _this.metaCounter[entitySet] = _this.metaCounter[entitySet] !== undefined ? _this.metaCounter[entitySet] + 1 : 0; return { set: entitySet, id: entitySet + "$new:" + generatedId }; } if (!(entity.__meta && _entity.__meta.id)) { entity.__meta = createMetaInfo(entity, entitySet); } return entity.__meta.id; } };
A couple of observations regarding the previous helper:
Now, we can start adding several kinds of objects without any problem. For instance, take a look at the following code:
ctx.insertEntity({ name: "joseph", address: "fx" },
"people"); ctx.insertEntity({ name: "joseph", address: "fx" },
"people"); ctx.insertEntity({ value: 1, contact: "123123123" },
"contacts");
And the next image shows the final result:
As you can see, with this approach we no longer set the id property of a new person object (that will be done by the server side, when it process an eventual commit performed by the DataContext instance).
Notice that until now we’ve seen the client code needed for supporting addition of new items. A complete solution would still need more work in the server side in order to ensure that items are correctly persisted. On the next post, we’ll start looking at links between objects. Stay tuned for more on MS AJAX.
We’ve already met several interesting features of the DataContext component. Today we’ll keep going and we’ll start discussing identity. I’m not sure if you’ve noticed, but the DataContext exposes several identity related methods. For instance, the getIdentity method is used by the control to get the identity of an object.
In order for you to activate identity related features, you need to (at least) configure the component so that it knows how to get or create a new identity for an object. This is done by setting two properties:
Setting these properties is all that is need for activating identity support for a DataContext component. Notice that using identities means that you’ll end up using the internal _items object for storing all the objects maintained in the current DataContext component. This object is used as a dictionary, where the identity of each object is used as the key of a specific entry. In practice, this means that you can mix object of different types in the same _items dictionary (we’ll talk about this in a future post).
As you’ve probably guessed, identity is one of those features which exists (mainly) for supporting ADO.NET data service. However, nothing prevents us from exploring it, right? So let’s get starting…and we’ll start simple…Suppose you want to track changes to the array presented in the next snippet:
var people = [ { id: 1, name: "luis", address: "fx" },{ id: 2,name: "john", address: "fx" }, { id: 3, name: "peter", address: "fx" } ];
In this case, things are pretty simple, right? We’re only interested in managing simple objects (lets call them person instances) and that means that we can use the id value as the identity of each object. There’s still one little problem: what to do with new items?
In this case, and since we’re supposed to create a function for calculating new identities, we can write it so that it also sets the id of any new person object. Here’s the code needed for instantiating a DataContext component which does exactly that:
var ctx = Sys.create.dataContext( { getIdentityMethod: function (dataContext, entity) { return entity.id; }, getNewIdentityMethod: function (
dataContext, entity, entitySet) { if (entity.id === undefined) { var contextItems = ctx.get_items(); var max = 0; for (var id in contextItems) { if (parseInt(id) > max) { max = parseInt(id); } } entity.id = max + 1; } return entity.id; } } );
ctx.trackData(people);
With these properties sets, we can add new objects without having to worry with the ID of a new person instance:
ctx.insertEntity({ name: "joseph", address: "fx" });
The next image shows the changes made to the DataContext instance after the previous insert:
As you can see, identity activation leads to several changes:
The example we’ve seen is really intended to get you started with identities. Unfortunately, things aren’t so easy in the real world. there will be times where you can’t really set the ID of an item (ex.: suppose it gets mapped to an auto number field of a table). There will also be other times where you need to support several “types” of objects…in these scenarios, you’ll need a more advanced way to identifying each instance.
In the next post we’ll take a look at more advanced ways of generating IDs for these scenarios. Stay tuned for more on MS AJAX.
In the previous post, we’ve seen how easy it is to commit the changes that are buffered by a DataContext instance. At the time, I said that there were still a couple of things related with the saveChanges method that needed further discussion…and that’s what we’ll do in this post.
To illustrate one scenario you might end up facing in the real world, lets assume that our person instances have a piece of information which can only be set in the server side. For instance, lets say that all created instances need to have a creation date that can only be set in the server side. Going back to our initial example, that means that each person instance in the initial items array needs to be updated with this extra info (keep in mind that items is supposed to have info about previously saved elements):
var items = [ { name: "luis", address: "fx", date: new Date() }, { name: "john", address: "fx", date: new Date() }, { name: "peter",address: "fx",date: new Date() } ];
To make the code more real, we’ll be adding a DataView control which allows us to edit/add new items to this array. Take a look at the following image:
The code used for generating this page looks like this:
<head runat="server"> <style type="text/css"> .sys-template{ display: none; } </style> <script type="text/javascript" src="Scripts/MicrosoftAjax/start.debug.js"> </script> <script type="text/javascript"> var items = [ { name: "luis", address: "fx", date: new Date() }, { name: "john", address: "fx", date: new Date() }, { name: "peter", address: "fx", date: new Date() } ]; var aux = { name: "charles", address: "lx" }; var ctx = null; Sys.require([Sys.components.dataContext, Sys.components.dataView], function () { Sys.Observer.makeObservable(items); ctx = Sys.create.dataContext({ serviceUri: "PeopleService.svc", saveOperation: "SavePeople", saveHttpVerb: "POST" }); ctx.trackData(items); $addHandler( Sys.get("#add"), "click", function () { var item = { name: "", address: "" }; ctx.insertEntity(item); items.add(item); }); $addHandler( Sys.get("#save"), "click", function () { ctx.saveChanges(); }); }); </script> </head> <body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView"> <div id="view" class="sys-template" sys:attach="dv" dv:data="{{items}}"> <div> <input type="text" sys:value="{binding name}" /> <input type="text" sys:value="{binding address}" /> <span>{binding date}</span> </div> </div> <input type="button" value="add new" id="add" /> <input type="button" value="save changes" id="save" /> </body>
As you can see, the items array is used in two places: it feeds the data shown by the DataView control and it is tracked by the DataContext component. We’ve transformed the items array into an observable array so that the DataView is able to automatically pick up insertions and deletions.
Even though this works, there’s still a small problem here: the date of each new person instance can only be set in the server side. The previous web service didn’t return anything from the server side and that is why you’ll always get null for the date property.
If you look at the code of the DataContext component, you’ll notice that it checks for values that are returned from the server for a save operation. Take a look at the _processResults method (edited for improved readability):
function Sys$Data$DataContext$_processResults(
dataContext, changes, results) { if (results && results.length === changes.length) { dataContext._ignoreChange = true; try { for (var i = 0, l = results.length; i < l; i++) { var result = results[i],
change = changes[i],
item = change.item; if (result && typeof(result) === "object") { dataContext._fixAfterSave(
change, item, result); } } } finally { dataContext._ignoreChange = false; } } }
As you can see, if the web service method used for saving returns a collection, the component will try to merge the client information with the one returned from the server side. It’s imperative that the returned collection has the same size as the array of changes (_changelist property) that was sent to the server and that each returned item’s position matches the “correct” position in the changes array. With this info, we can easily solve our initial problem by updating the server side save method:
[OperationContract] public IEnumerable<Person> SavePeople(
IEnumerable<PersonOperation> changeSet) { var changed = new List<Person>(); foreach (var change in changeSet) { change.Person.Date = DateTime.Now;//bad!!!demo only _executors[change.OperationType](change.Person); changed.Add(change.Person); } return changed; }
We’ve updated the web service save method so that it returns the collection of person instances it received from the client. Notice that in this case I’ve forced an update of the Date property and that info will be propagated back to the client and picked up by the internal _processResults method (you shouldn’t really do this for all items – after all, we only need to set the date for insertions). And that’s why you’ll get dates after you click the save changes button:
And I guess that’s all for now. Stay tuned for more on MS AJAX.
Ok, we’ve already seen one option for fetching data from a remote web service. In this post, we’ll see how to commit changes tracked by the DataContext component. Committing buffered changes means that you’ll need to set (at least) two DataContext instance properties: serviceUri and saveOperation. serviceUri identifies the URI of the web service that is responsible for persisting the changes; saveOperation is used for identifying the server side method which persists the changes that were buffered by the DataContext instance.
To illustrate committing, we’ll be updating the PeopleService we’ve introduced in a previous post. For now, we’ll start by writing some JavaScript code:
var items = [ { name: "luis", address: "fx" }, { name: "john", address: "fx" }, { name: "peter", address: "fx" } ]; Sys.require([Sys.components.dataContext], function () { var ctx = Sys.create.dataContext({ serviceUri: "PeopleService.svc", saveOperation: "SavePeople",saveHttpVerb: "POST" }); ctx.trackData(items); var aux = { name: "charles",address: "lx" }; ctx.insertEntity(aux); ctx.saveChanges(); });
As you can see, we’re configuring three properties for our DataContext object:
Btw, notice that we’re using the trackData method for initiating data tracking. I’ve opted for doing this to show you that saving does not depend on fetching data from the same web service. You should also keep in mind that the saveChanges method might receive three parameters:
Before showing the server side code, we need to know what is passed to the server side when someone calls the saveChanges method. The answer shouldn’t be too hard (at least, after thinking a little bit about it): we’ve already seen that the DataContext component saves all changes by adding entries to the internal _changelist field. And that is the information which gets send back to the server when the component tries to persist the buffered changes.
With this information, we can start updating the server side code. The first thing we need to do is create a new C# class which represents an instance of a change. In other words, we need to mimic the Sys.Data.ChangeOperation in the server side. Now, if I was building a reusable class, I’d use templates and I’d duplicate all the properties exposed by the ChangeOperation type.
However, since this is demo code and I know that the buffered operations will only involve insertions, editions and removals of Person objects (after all, we still haven’t talked about links), I’ve opted for only replicating the properties that are used in this scenario:
[DataContract] public class PersonOperation { [DataMember(Name="item")] public Person Person{ get; set; } [DataMember(Name="action")] public OperationType OperationType { get; set; } } public enum OperationType { Insert, Update, Remove }
As you can see, we’ve added a new enum too. That was necessary because the client side ChangeOperation instance relies in using an enum for specifying the operation that is being performed (the only thing we need to ensure is that the enum entries have the same values as the ones specified in the client side).
The PersonOperation class mimics the Sys.Data.ChangeOperation “type” we have in the client side. As you can see, we’re using the DataMemberAttribute for mapping each of the server properties to the ones used in the client side. Oh, and just for completion, here’s the code for the Person class:
[DataContract] public class Person { [DataMember(Name="name")] public String Name { get; set; } [DataMember(Name="address")] public String Address { get; set; } }
Now, we’re still missing the web service’s save method. We’ve already established that it receives a collection of changes (ie, an array of Sys.Data.ChangeOperation objects). However, there’s still one important piece of information missing: we need to know the name of the parameter used for sending that information. In this case, the DataContext object uses the changeSet name. And now we’re read for taking a peek at the web service code:
[ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(
RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)] public class PeopleService { static List<Person> _people = new List<Person> { new Person{ Name = "luis", Address = "fx"}, new Person{ Name = "john", Address = "lx"}, new Person{ Name = "peter", Address = "pt"} }; private readonly
IDictionary<OperationType, Action<Person>> _executors; public PeopleService() { _executors =
new Dictionary<OperationType, Action<Person>> { {OperationType.Insert, InsertItem}, {OperationType.Update, UpdateItem}, {OperationType.Remove, DeleteItem} }; } [OperationContract] [WebGet] public IEnumerable<Person> GetPeople(SortOrder sort){ var ordered = _people.OrderBy(
p => (sort == SortOrder.Name ? p.Name : p.Address) ); return ordered; } [OperationContract] public void SavePeople(
IEnumerable<PersonOperation> changeSet) { foreach (var change in changeSet) { _executors[change.OperationType](change.Person); } } private void InsertItem(Person person) { //do something } private void UpdateItem(Person person) { //update person } private void DeleteItem(Person person) { //delete item } }
We rely on the OperationType property of each PersonOperation instance to decide if we’re inserting, updating or removing an item (I’ve opted for using a dictionary for eliminating the switch that you’ll see in most cases). It’s just that simple, really! The only thing to watch out for are the mappings: you need to ensure proper mapping between the client and side objects (and this includes method parameter names too!).
This has become a rather large post, so I guess this is it for now. There are still a couple of interesting observations that can be made about the server side save method, but we’ll leave it for a future post. Stay tuned for more!
Until now, we’ve met some of the basic features introduced by the DataContext component. As I’ve said, you can think of this control as a buffer which keeps track of changes performed to several items it keeps track of. We’ve already seen that we can use the trackData method for initiating a tracking operation. What I haven’t mentioned yet is that you can also configure the control so that it fetches data automatically from a web service (or even from an ADO.NET data service).
Getting data from a web service means that you’ll have to (at a minimum!)configure the URI of the web service. Currently, you can do that by setting the DataContext’s serviceUri property. After setting the URI, you can get the data by calling the fetchData method. Here’s an example (btw, I’m reusing the web service presented here):
<script type="text/javascript"> Sys.require([Sys.components.dataContext], function () { var ctx = Sys.create.dataContext( { serviceUri: "PeopleService.svc" } ); ctx.fetchData( "GetPeople", //method {sort: 0 }, //method parameters Sys.Data.MergeOption.overwriteChanges, //merge options "GET", //http verb function () { //succeded //could use parameters, but wil use //lastFetchedResults for demo purpose var items = ctx.get_lastFetchDataResults(); for (var i = 0; i < items.length; i++) { alert(items[i].name); } }); }); </script>
After setting the serviceUri property,we end up calling the fetchData method (while passing it several parameters which influence the remote web service call) for getting the data from the web service. As you can see,we’re only passing the succeeded callback function to ensure that the lastFetchDataResults property is correctly filled up (in this case, we’re just iterating over each item and printing the value of its name property).
The fetchData exposed by the DataContext component matches the one defined by the IDataProvider interface. This isn’t just coincidence… it happens because the DataContext implements this interface and needs to provide an implementation for its fetchData method:
Sys.Data.IDataProvider.prototype = {
fetchData: Sys$Data$IDataProvider$fetchData
}
Btw, here’s the current signature for the fetchData method:
function Sys$Data$IDataProvider$fetchData(operation,
parameters,
mergeOption,
httpVerb,
succeededCallback,
failedCallback,
timeout,
userContext) {
I guess that the parameters are self-explanatory, so I won’t be wasting your time with additional details on them. If you’ve been following along, then you’re probably read one of the posts on how to configure the DataView control to fetch data from a remote web service.
At the time, I’ve said that one of the options is configuring the DataView’s dataProvider property with a valida IDataProvider instance. Since the DataContext implements this interface, then you can also use an object of this type for getting the data that is rendered by the DataView control. Here’s a quick example that shows how to configure the DataView control so that it gets the data from a DataContext:
<head runat="server"> <style type="text/css"> .sys-template{ display: none; } </style> <script type="text/javascript" src="Scripts/MicrosoftAjax/start.debug.js"> </script> <script type="text/javascript"> Sys.require([Sys.components.dataContext, Sys.components.dataView], function () { var ctx = Sys.create.dataContext({ serviceUri: "PeopleService.svc" }); var view = Sys.create.dataView( Sys.get("#view"), { itemTemplate: "#template", autoFetch: true, dataProvider: ctx, httpVerb: "GET", fetchOperation: "GetPeople", fetchParameters: { sort: Sys.Data.MergeOption.overwriteChanges } }); }); </script> </head> <body> <div id="view"> </div> <div class="sys-template" id="template"> <span>{{name}}</span>- <span>{{address}}</span> <br /> </div> </body>
The code is pretty straightforward (I think): we create a new DataContext instance and set its serviceUri property. After that, we initialize a DataView control and pass it the remaining info that is needed to perform the remote call. And that’s it. There’s nothing more to it than that…
The next step in your study guide is see how to configure the DataContext so that it propagates changes from the client to the server side. Stay tuned for more!
In the previous post, we’ve see how to execute some basic actions over a DataContext instance. In this post we’ll keep looking at the it and we’ll see the available options for working with changes made over the data tracked by the control. Before getting started, we’ll assume that _ctx references an existing DataContext object created previously with the code shown in the previous post.
The first thing you can do is cancel all the changes that have been made to the data encapsulated by the control. You can easily do this by calling the clearChanges method. This method ends up doing several things:
Here’s some demo code that shows how to use this method:
var aux = { name: "charles", address: "lx" }; _ctx.insertEntity(aux); Sys.Observer.setValue(aux, "address", "pt"); alert(_ctx.get_changes().length); //at least 1 change here! _ctx.clearChanges(); alert(_ctx.get_changes().length);//no changes here
As you can see, inserting a new entity ensures that the _changelist is not empty. After calling the clearChanges method, all pending changes are deleted. Even though pending changes are cancelled (ie, cleared), you still have access to the initial data that was tracked by the control (that is,if the control encapsulated any data that was being tracked).
If you also need to clear the tracked data,then you’ll need to resort to the clearData method. Besides clearing pending changes, this method will also clear the internal arrays it uses (including _items and _lastResults) ensuring proper clean up of the internal state of the control. Here’s some demo code that shows how to use this method:
_ctx.clearData(); alert(_ctx.get_lastFetchDataResults() === null);//true!
As you can see, calling the clearData method means that you’ll end up “nulling” the private _getchanges field of the DataContext instance.
Besides cancelling changes, you can also “commit” changes. But before we go into this topic, we need to talk about interacting with remote web services. And that’s what we’ll start doing in the next post. Stay tuned for more.
Yesterday, I’ve started looking at the DataContext object. I haven’t still tested all its features (to do that, I still need to take a look at ADO.NET Data Services and study the AdoNetDataContext type), but I’ve already picked up some ideas about it.
I guess it’s fair to say that you can think of the DataContext as a buffer which keeps track of the changes you do along the time. Besides tracking basic changes, it does also support several more advanced concepts like links (which are just “fancy relations” between different objects – I think that most of this stuff is there to support interactions with the ADO.NET Data Services).
Since this is just an intro post, we’ll only be looking at its basic features. Creating a new DataContext instance can be easily accomplished through the Sys.create object helper:
<script type="text/javascript"> var items = [ { name: "luis", address: "fx" }, { name: "john", address: "fx" }, { name: "peter", address: "fx" } ]; var _ctx = null; Sys.require([Sys.components.dataContext]) Sys.onReady(function () { _ctx = Sys.create.dataContext(); }); </script>
Once again, we rely on the script loader for getting the necessary JavaScript files and populating the helper objects with new methods (ex.: Sys.create.dataContext). The previous DataContext instance is “empty”. However,I’m positive that in most scenarios you’re really interested in populating it with some data that will be tracked over time. The DataContext lets you do that through trackData method:
_ctx.trackData(items);
If you’ve looked at the DataContext’s prototype object,then you might think that calling trackData ends up initializing the items property. However, that will only happen if the DataContext object has been previously configured with identity related methods (we’ll be talking about this feature in future posts). Since we’re not using these advanced features, we’ll end up getting the data saved in the _lastResults property:
Now that the DataContext has been “initialized” with data, we can use it to, say, add a new item to that internal array:
_ctx.insertEntity({ name: "charles", address: "lx" });
You won’t be surprised by knowing that the previous line ends up “adding” a new item to the list of objects tracked by the _ctx DataContext instance. However, the new item won’t be added to the _lastResults array; instead, it gets added to the private _inserts property:
I’ve expanded the _lastResults, _items and _inserts fields of the _ctx instance. As you can see, _lastResults remains unchanged. The inserted item is appended to the _inserts array and you’ll also get a new entry on the _changeslist property. Even though I’m mentioning the _lastResults and _inserts fields, the truth is that you shouldn’t use them in your code (they’re considered private details and should only be managed by the DataContext instance). You can, however, get a reference to the _changeslist field through the get_changes public property:
var changes = _ctx.get_changes();
The local variable changes is just a collection of Sys.Data.ChangeOperation instances, which expose the following properties:
The last three properties displayed in the previous list are only used for link operations (which we’ll talk about in future posts).
Since changes is an array, we can print all the changes by using a simple for loop. Notice that this will only show changes which haven’t been “persisted” by the DataContext instance (more about this topic at the end of this post):
for (var i = 0; i < changes.length; i++) { alert(changes[i].action + "-" + changes[i].item.name + "-" + changes[i].linkSource + "-" + changes[i].linkTarget + "-" + changes[i].linkSourceField); }
To wrap up the data context’s insertEntity method topic, I’d like to add two things:
Now that we’ve seen how insertion works, it’s time to see how we can remove items from the DataContext instance. In this case, we must use the removeEntity method:
_ctx.removeEntity(items[0]);
Removing an item ends up adding a new ChangeOperation to the internal _changelist field. If you run the previous for loop, you’ll see that it has two changes: one for the insertion and another one for the removal of the item. And yes, the private _deletes array will end up with one entry (which references the element that has been removed).
Now, lets make things a little more interesting…Take a look at the following snippet:
var aux = { name: "charles", address: "lx" }; _ctx.insertEntity(aux); _ctx.removeEntity(aux);
What do you expect to happen now? If you’re thinking that you’ll end up with _insert and _delete empty arrays and with no changes (ie, with an empty _changeslist array), then you’re absolutely right. That happens because you’ve inserted and removed the same item without “committing” the insertion. The next image shows these results:
From the three basic operations, we’re only missing one: editing an item. To understand editing, we need to go back and talk a little more about tracking data. Besides initializing the _lastResults field, the trackData ends up hooking all the array items’ property changed events so that the DataContext instance is notified of any change made to any of the objects that it’s tracking.
In practice, this means that you’ll need to use the observable methods to change an item that is being tracked by a DataContext instance. For instance, the next snippet changes the address of the first item that is being tracked:
Sys.Observer.setValue(items[0], "address", "pt");
Which produces the following internal changes in the DataContext instance:
Btw, do notice that in editing scenarios, the item ends up being modified (notice the _lastResult’s first item address). As you might expect, the edit operation ended up leading to new entries in the private _changelist and _edits fields too.
Notice that if you’re updating an item which was added after the DataContext has started tracking data, then that edit operation will be performed, but it won’t be registered in the _changeslist collection (because there’s still a pending insert operation which, when performed in the back office, ends up doing everything with a single insertion instruction).
And I guess this covers the basic operations related with inserting, editing and removing items that are being tracked by a DataContext object. In the next posts, we’ll keep looking at this object. Stay tuned for more in MS AJAX.
Ok, I think this title might have caught your attention! (at least, that’s what I’m hopping for!). This post is a direct consequence of another discussion with a friend regarding the use of the DataView control. He told me something like this: “well, DataViews are cool…can’t deny it…but it’s only for listing items and that means it’s only usable for grids…”
Aha! That is not correct. And to show him that he was wrong, we’ve settled in a challenge (not as interesting as top gear’s challenges, but still…). After some discussion, we’ve settle in simple scenario: an edit form for a complex JavaScript object. Here’s the UI:
The print button you see is there to give feedback on the values of the properties of the person object. And here’s the JavaScript for the objects used in the sample:
var contactTypes = [ { id: 1, description: "phone" }, { id: 2, description: "email"}]; var person = { name: "luis", address: "some place", contacts: [ { contact: "123123123",type: contactTypes[0]},{ contact: "labreu@somewhere.pt", type: contactTypes[1]} ] };
As you can see, we’ve got a collection of literal contact types objects which are used for identifying the types of each of the contacts saved in the person object. To solve this problem, I’ve ended up defining three DataView controls:
Here’s the final markup I’ve ended up using:
<div id="mainView" class="sys-template" sys:attach="dv" dv:data="{{person}}"> <label for="name">Name:</label> <input type="text" name="name"
sys:value="{binding name}" /> <br /> <label for="name">Address:</label> <input type="text" name="name"
sys:value="{binding address}" /> <table> <thead> <tr> <td>Contact</td> <td>type</td> </tr> </thead> <tbody sys:attach="dv" class="sys-template" dv:data="{binding contacts}"> <tr> <td> <input type="text"
sys:value="{binding contact}" /> </td> <td> <select id="contactTypes" sys:attach="dv" class="sys-template" sys:value=
"{binding type, convert=getId, convertBack= getContactType}" dv:data="{{ contactTypes }}"> <option sys:value="{{id}}">
{{description}}
</option> </select> </td> </tr> </tbody> </table> </div> <input type="button" value="print info" id="print" />
There are a couple of interesting things going on here:
Btw, we do need to define two convert functions for ensuring proper setting of each of objects that participate in the binding relationship. Here’s the code for those functions:
Sys.converters.getId = function (value, binding) { return value.id; }; Sys.converters.getContactType = function (val, binding) { for (var i = 0; i < contactTypes.length; i++) { if (contactTypes[i].id == val) { return contactTypes[i]; } } //probably should throw here? return null; };
The getId conversion function is invoked when the value is being propagated from the person’s contact object to the dropdown. In this case, we only need the ID in order to select the correct OPTION; on the other hand, when the value is being propagated from the dropdown to the “current” contact, we do need to convert the OPTION’s value into an object (and that’s why we’re using a for loop in the getContactType method). Btw, here’s another picture which shows that alterations are in fact propagated back to the object:
Even though I’m not showing the code for print button, I can assure you that it’s the longest method of this demo page. As you can see, the DataView is really a useful control. Combining it with templates and bindings does really cut down the code needed for several real world scenarios.
And that’s it for now. Stay tuned for more!
Yesterday I’ve ended up receiving an interesting question regarding the code I’ve used in the posts which explain how to get data from a remote web service. The question was: how do we adapt the data returned from the web service before it is used by the DataView for rendering its HTML?
This is an interesting question. Fortunately, the answer is not really complicated. To illustrate this problem and its solution, we’ll start by going back to the Person class we’ve introduced in the server side:
[DataContract] public class Person { [DataMember(Name="name")] public String Name { get; set; } [DataMember(Name="address")] public String Address { get; set; } }
As you can see, we’re using the DataMemberAttribute for influencing the name of the properties used in the JavaScript objects that will be returned from the service. Now, let’s assume that we don’t own the service and that the Person class looks like this:
[DataContract] public class Person { [DataMember()] public String Name { get; set; } [DataMember()] public String Address { get; set; } }
Now the problem: we want to adapt the data received from the server so that we don’t have to change the template used by the view. Btw, here’s how the template is currently defined:
<tr> <td>{{name}}</td> <td>{{address}}</td> </tr>
If we leave the code as is, we’ll end up getting errors because *now* each JavaScript Person instance has two properties named Name and Address (instead of name and address). Ok, I guess you could say that it’s a lot easier to just update the names of the properties used in the templates than to change the data; however, changing the data gives me the chance to introduce another technique which might be useful for you in other less contrived scenarios…
When I thought about intercepting and changing the data, I thought that the fetchSucceeded event would be a good place to do that. Unfortunately, this event is only fired after the DataView control has refreshed itself: by then, you’d already received an exception saying that name is undefined!
Ok,time to go back to the prototype definition of the control…and to read a previous post on the topic of DataView events. And yes,the answer to our problem is right there, on the first event described on this post:
rendering: this event is generated when the DataView control is about to start instantiating its template to get the rendered HTML. Any function which handles this event receives two parameters: the first, references the DataView control which generated the event; the second is an instance of the Sys.Data.DataEventArgs type;
Hooking up the rendering event is all we need to adapt the data. here’s some code that adapts the JavaScript objects returned by the server (as you can see, we’re replacing each instance with a new one that has the correct names):
function updateData(sender, e) { var data = e.get_data(); for (var i = 0; i < data.length; i++) { data[i] = { name: data[i].Name,
address: data[i].Address } } }
Since we’re using a declarative approach, hooking up the event can be done by using the on rendering attribute:
dv:onrendering="{{ updateData }}"
And that’s it for now. Keep tuned for more on MS AJAX.
In the previous posts, we’ve seen how to configure the DataView control so that it gets the data from a remote web service. In this post, we’ll see how we can take advantage of the fetchParameters to pass info to the web service. The idea is simple: we’ll update the previous sample so that it renders a table with clickable headers used for sorting the displayed data. Sorting the data will be done on the server. This means we need to start updating the server side code so that the web service method gets the info used for sorting (notice I’m only showing the changes made to the initial code):
public class PeopleService { static List<Person> _people = new List<Person> { new Person{ Name = "luis", Address = "fx"}, new Person{ Name = "john", Address = "lx"}, new Person{ Name = "peter", Address = "pt"} }; [OperationContract] [WebGet] public IEnumerable<Person> GetPeople(SortOrder sort) { var ordered = _people.OrderBy(
p => (sort == SortOrder.Name ?
p.Name : p.Address) ); return ordered; } } public enum SortOrder { Name, Address }
As you can see, changes are really minimal: we’ve added an enumeration used for indicating the sort order that should be applied to the collection and we’ve updated the GetPeople method so that it orders the collection accordingly. Now, let’s see the client code:
<head runat="server"> <title></title> <style type="text/css"> .sys-template{ display: none; } </style> <script src="Scripts/MicrosoftAjax/start.debug.js"
type="text/javascript">
</script> <script type="text/javascript"> Sys.require([Sys.scripts.jQuery, Sys.scripts.WebServices,Sys.components.dataView],function () { Sys.loadScripts(
["PeopleService.svc/jsdebug"], function () { Sys.Application.activateElement(
Sys.get("#view")); $("thead td").click(function () { var view = Sys.get("$view"); view.get_fetchParameters()
.sort =
($(this).text() === "Name" ?
SortOrder.Name :
SortOrder.Address); view.fetchData(); }) }); }); </script> </head> <body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView"> <table> <thead> <tr> <td>Name</td> <td>Address</td> </tr> </thead> <tbody id="view" class="sys-template" sys:attach="dv" dv:autofetch="true" dv:httpverb="GET" dv:dataprovider="{{ PeopleService }}" dv:fetchoperation="GetPeople" dv:fetchparameters="{{ { sort: SortOrder.Name } }}"> <tr> <td>{{name}}</td> <td>{{address}}</td> </tr> </tbody> </table> </body>
Most of the code is similar to what we had before. There are, however, some differences:
And there you go: quick and easy, right? Stay tuned for more on MS AJAX.
In the previous post, we’ve seen how we can configure the DataView control to get data returned from a web service. The previous approach is a good one if you don’t intend to use a proxy to a web service. However, suppose you’ve already have a web service proxy and that you want to reuse it for getting the data that is needed by the DataView…in that case, the best option might be reusing that proxy.
To show you the necessary steps for configuring the DataView control to do that, we’ll reuse the previous server side code and we’ll only update the client side:
<head runat="server"> <title></title> <style type="text/css"> .sys-template{ display: none; } </style> <script src="Scripts/MicrosoftAjax/start.debug.js"
type="text/javascript">
</script> <script type="text/javascript"> Sys.require([Sys.scripts.WebServices, Sys.components.dataView], function () { Sys.loadScripts(["PeopleService.svc/jsdebug"], function () { Sys.Application.activateElement(
Sys.get("#view")); }); }); </script> </head> <body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView"> <div id="view" class="sys-template" sys:attach="dv" dv:autofetch="true" dv:httpverb="GET" dv:dataprovider="{{ PeopleService }}" dv:fetchoperation="GetPeople"> <div> <span>{{name}}</span>- <span>{{address}}</span> </div> </div> </body>
Ok, so there’s lot of things going on here:
And I guess this sums it up quite nicely. But there’s still lots of things to talk about so stay tuned for more on MS AJAX.
Until now, we’ve been using static data to feed all the DataView controls we’ve been using in the MS AJAX series. Today we’ll see how we can configure the control so that it automatically gets data returned from a web service call. To get things started, we’ll start by introducing the server side code for the WCF service:
[ServiceContract(Namespace = "")] [AspNetCompatibilityRequirements(
RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)] public class PeopleService { [OperationContract] [WebGet] public IEnumerable<Person> GetPeople() { var people = new List<Person> { new Person{ Name = "luis", Address = "fx"}, new Person{ Name = "john", Address = "lx"}, new Person{ Name = "peter", Address = "pt"} }; return people; } } [DataContract] public class Person { [DataMember(Name="name")] public String Name { get; set; } [DataMember(Name="address")] public String Address { get; set; } }
Yes, not the best code in the world, but don’t forget that I’m not interested in creating web service! What I want is to show you how to properly configure the DataView control to get data from a web service…
As you can see, PeopleService is a WCF service which has a single operation: GetPeople. This method returns a collection of Person instances (since I wanted to preserve JS naming conventions, I’ve used the DataMemberAttribute to customize the name of the properties used in JSON returned from the server).
And now we can proceed with the most important part: configuring the DataView for getting the data automatically from the server side. In this case,I’ve opted for using the declarative approach:
<head runat="server"> <title></title> <script src="Scripts/MicrosoftAjax/start.debug.js"
type="text/javascript">
</script> <script type="text/javascript"> Sys.require([Sys.scripts.WebService,
Sys.components.dataView]); </script> </head> <body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView"> <div id="view" class="sys-template" sys:attach="dv" dv:autofetch="true" dv:httpverb="GET" dv:dataprovider="PeopleService.svc" dv:fetchoperation="GetPeople"> <div> <span>{{name}}</span>- <span>{{address}}</span> </div> </div>
We start by loading the JS files needed for communicating with the server and using the DataView control. Since we need to communicate with the server side through a remote web service method call, we need to get the base classes defined in the MicrosoftAjaxWebService.js file.
Configuring the DataView isn’t really hard since we only need to set a couple of properties:
Besides these properties, there are a still a couple of properties related with getting data from a remote provider:
When the DataView control is fetching data from a remote provider, you can abort that operation by invoking the abortFetch method. On the other hand, you can force a remote call by invoking the fetchData method. Calling this method starts a remote call and will automatically refresh the control with the new data that is returned from the provider.
Besides properties and methods, the control has a couple of events which you can handle:
A fetchfailed handler receives three parameters:
A fetchSucceeded handler also expects the same number of parameters as the ones that are passed to the fetchFailed handler: the main difference is that the first parameter references the data returned from the service.
And this should be enough for getting you started with getting data through remote calls. There’s still more to come, so stay tuned!
In these last posts, we’ve already met several of the events generated by the DataView control. I thought it would be a good idea to write a small post which documents many of the events we’ve seen in the past:
command: fired when a user clicks over an element which has been “marked” with the sys:command system attribute;
The Sys.Data.DataEventArgs object used by the rendering and rendered events introduces some interesting properties:
During the rendering event, you can set all of these properties and influence the HTML generated by the DataView control.
Besides these events, there are still others, but we’ll leave them for the next posts because they’re related with the integration of the DataView control with data contexts. Stay tuned for more on MS AJAX.
In one of the previous posts of the series, we’ve seen how easy it is to build a master-detail scenario with the help of the new DataView control. Today, we’ll keep looking at it and we’ll see how we can build an editable grid. The next two images explain the objective of our post. The first shows the grid in browse mode while the second illustrates what it looks like in edit mode:
As you can see, the idea is simple: any row can be in editing or browsing mode (notice that we can only edit one row at the time). While in browsing mode, you can move it to edit mode or you can simply delete it. In edit mode, you can only confirm the changes or cancel them.
If you’ve been playing with the DataView control, then you’ll know that you’ll need two templates: one used for displaying the row in browse mode and another to display it in edit mode. Here’s the HTML I’ve used for the control and for each of the templates:
<body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView"> <div id="view" sys:attach="dv" dv:data="{{items}}" dv:onitemrendering="{{handleItemRendering}}" dv:itemtemplate="#browse" dv:oncommand="{{handleCommand}}"> </div> <div id="browse" class="sys-template"> <div> <span>{{name}}</span> - <span>{{address}}</span> <a sys:command="edit" href=""
sys:commandargument="{{$index}}"
onclick="return false;">Edit</a> <a sys:command="delete" href=""
sys:commandargument="{{$index}}"
onclick="return false;">Delete</a> </div> </div> <div id="edit" class="sys-template"> <div> <input type="text" id="name"
sys:value="{binding name, mode = oneWay}"/> <input type="text" id="address"
sys:value="{binding address, mode = oneWay}"/> <a sys:command="update"
sys:commandargument="{{$index}}" href=""
onclick="return false">Update</a> <a sys:command="cancel" href=""
onclick="return false">Cancel</a> </div> </div> </body>
In this case, I’ve opted for using two external templates: the first one uses spans for showing the name and address of each object + the anchors needed for raising edit and delete commands. The second one has some textboxes plus update and cancel “buttons”. By default, the DataView control uses the browsing template (notice the dv:itemtemplate=”#browse” attribute usage).
As you can see, we’re using commands for signaling the operations. We’re also relying on command arguments for getting the current position of the element that raises a command (the only command which doesn’t require knowing the position is the cancel command).
One thing that might have caught your attention is the use of one-way bindings between the INPUT controls and the objects. Unfortunately,that’s the only way to guarantee that the changes made in the textboxes won’t be propagated automatically to the associated objects (in other words,two way bindings won’t help here because they will automatically propagate all the changes to the associated objects and that means the cancel button wouldn’t really work).
Without further ado, lets take a look at the JavaScript:
<script type="text/javascript"> var items = [ { name: "luis", address: "fx" }, { name: "john", address: "lx" }, { name: "rita", address: "pt" } ]; Sys.require([Sys.components.dataView], function () {
Sys.Observer.makeObservable(items); Sys.UI.DataView.prototype.editElement =
function (pos, addingNewItem) { this.__editIndex = pos; this._isNewItem = addingNewItem; } Sys.UI.DataView.prototype.cancelEditElement =
function () { this.__editIndex = -1; this.__isNewItem = false; } }); function handleCommand(sender, e) { function delItem(sender, e) { items.removeAt(e.get_commandArgument()); } function updateItem(sender, e) { var name = sender.get_contexts()[ e.get_commandArgument()] .get("#name") .value; var address = sender.get_contexts()[ e.get_commandArgument()] .get("#address") .value; //probably do some validation here var item = items[e.get_commandArgument()]; item.name = name; item.address = address;
sender.cancelEditElement(); sender.refresh(); } function editItem(sender, e) { sender.editElement(e.get_commandArgument(), false); sender.refresh(); } function cancelItem(sender, e) { sender.cancelEditElement(); sender.refresh(); } switch (e.get_commandName()) { case "delete": delItem(sender, e); break; case "update": updateItem(sender, e); break; case "cancel": cancelItem(sender, e); break; case "edit": editItem(sender, e); break; default: return; } } function handleItemRendering(sender, e) { function find(item, arr) { for (var i = 0; i < arr.length; i++) { if (arr[i] === item) { return i; } } return -1; } if (sender.__editIndex === undefined || sender.__editIndex === -1 || find(e.get_dataItem(), items) !==
sender.__editIndex)
{ return; } e.set_itemTemplate("#edit"); } </script>
There are a couple of interesting things going on here:
And that’s it. With a little JS code, we’ve ended with a cool template which lets us add edit in place to a simple grid. We could improve the previous code for supporting new items, but I’ll leave that for you. And that’s it for now. Stay tuned for more on MS AJAX.
One of the cool features MS AJAX preview 6 introduces is better JQuery integration. From now on, MS AJAX components (which have been defined through a script info object registered through a defineScript(s) method call) are exposed as jQuery plugins. This means that you can write something like this:
<head runat="server"> <style type="text/css"> .sys-template{ display:none; } </style> <script src="Scripts/MicrosoftAjax/start.debug.js"
type="text/javascript">
</script> <script type="text/javascript"> Sys.require([Sys.components.dataView,
Sys.scripts.jQuery]); var items = [ { name: "luis", address: "fx" }, { name: "john", address: "lx" }, { name: "rita", address: "fx" } ]; Sys.onReady(function () { $("#myDv").dataView({
data: items,
itemTemplate: "#template" }) .css("background-color", "lightgray"); }); </script> </head> <body> <div id="myDv"> </div> <div id="template" class="sys-template"> <div>{{name}}</div> </div> </body>
Pretty cool, right? Instead of using the traditional MS AJAX Sys.create helper methods, we’re using a JQuery object for wrapping the div so that we can attach it to a MS AJAX DataView control and set its background color.
Notice that the dataView method (added to the jQuery.fn object) is built on the fly when the JQuery script is listed on the depedencies list passed to the Sys.require method. This is one of those small details that makes life better for everyone…Stay tuned for more.