Sunday, March 21, 2010

Client Side AJAX Applications in SharePoint 2010 - Part 3

Note: This is one of a multi-part series exploring building client side AJAX applications with ASP.Net AJAX 4 Templating and the WCF Data Services (aka ADO.Net Data Services, aka oData, aka Astoria) in SharePoint 2010.

Part 1 – Exploring WCF Data Services in SharePoint 2010
Part 2 – Creating a read-only page with ASP.Net AJAX 4.0 Templates
Part 3 – Writing data back to SharePoint with ASP.Net AJAX 4.0 Templates
Part 4 - jQuery and Manual JSON Object Manipulation

In the previous two posts in this series I’ve shown how you can access SharePoint data using WCF Data Services and consume that data using ASP.Net AJAX 4 templates. In this post I’ll update the previous example in order to show how to write data back to SharePoint.

Templates and Linking

Before we go into writing data back let’s modify our example to link each item back to the SharePoint DispForm.aspx page. A na├»ve approach would look like this:

<ul id="userStoriesList" class="sys-template">
       <li><a href="../../Lists/User%20Stories/DispForm.aspx?ID={{ID}}">{{ Title }}</a></li>
</
ul>

It turns out this approach doesn’t work because the DataView won’t look for the data binding syntax inside of attributes. The next approach that might make sense would now be to set href to a data binding syntax and inside there do some string concatenation like so:

<li><a href="{{ '../../Lists/User%20Stories/DispForm.aspx?ID=' + ID }}">{{ Title }}</a></li>

It turns out this doesn’t work either, but for a different reason. Apparently the DataView can’t natively convert some HTML attributes like href and src for reasons Bertrand La Roy describes. The solution is to prefix the attribute with the ASP.Net AJAX system namespace. Typically you would define the system namespace on the HTML body element. But with SharePoint you don’t have access to this element. Fortunately you can declare this namespace on any element so the following code works:

<div xmlns:sys="javascript:Sys">
    <
ul id="userStoriesList" class="sys-template">
        <li><a sys:href="{{ '../../Lists/User%20Stories/DispForm.aspx?ID=' + ID }}">{{ Title }}</a></li>
    </
ul>
</
div>

Great. Let’s move on to updating data.

Live Binding Syntax

The {{ }} template syntax we’ve seen so far is what’s known as one time binding. When you use this syntax the expression is evaluated only once when the template is rendered. What we want is one way live binding and/or two way live binding.

In order to understand the live bindings you need to understand that behind the scenes the DataView keeps an in memory copy of all of the JSON objects it downloads. It's possible for the data in these objects to change. And that's where one way live bindings come in. When you use the syntax { binding [FieldName] }, the DataView will automatically update the binding when the underlying JSON object changes.

Two way live binding happens automatically when you use the one way live binding syntax on HTML INPUT elements. For these scenarios the DataView automatically updates the in memory JSON objects. Then the DataView updates any one way live binding's.

So let’s throw two DataViews onto the page, one with one way live bound hyperlinks, one with two way bound HTML INPUT textbox elements and see what happens:

<script type="text/javascript">
    function pageInit() {
        var dataContext = $create(Sys.Data.AdoNetDataContext, { serviceUri: "/demoprep/_vti_bin/ListData.svc" });

        $create(Sys.UI.DataView,
                {
                    autoFetch: true,
                    dataProvider: dataContext,
                    fetchOperation: "UserStories",
                    fetchParameters: { $top: 20 }
                },
                null,
                null,
                $get("userStoriesList")
            );

        $create(Sys.UI.DataView,
                {
                    autoFetch: true,
                    dataProvider: dataContext,
                    fetchOperation: "UserStories",
                    fetchParameters: { $top: 20 }
                },
                null,
                null,
                $get("userStoriesTable")
            );
    }
    Sys.Application.add_init(pageInit);
</script>

<div xmlns:sys="javascript:Sys">
    <ul id="userStoriesList" class="sys-template">
        <li><a sys:href="{{ '../../Lists/User%20Stories/DispForm.aspx?ID=' + ID }}">{ binding Title }</a></li>
    </ul>

    <table>
        <thead>
            <tr>
                <td>Title</td>
            </tr>
        </thead>
        <tbody id="userStoriesTable" class="sys-template">
            <tr>
                <td><input type="text" sys:value="{binding Title}" /></td>
            </tr>
        </tbody>
    </table>
</div>

Notice the input element with the sys:value bound to title and the new binding syntax in the text of the a href. The result looks like this:

It may not be all that pretty yet, but if you change the text in any of the text boxes the result is immediately updated in the hyperlinks on mouse out. But if you hop back to SharePoint or refresh the page you’ll see that our data hasn’t been updated.

Updating SharePoint, For Real This Time

Just because the DataView updated the in memory JSON objects it’s bound to doesn’t mean it sent a POST request back to ListData.svc. To accomplish that we need to call dataContext.saveChanges(). If you toss in a button below the table like this:

<button onclick="dataContext.saveChanges()">Save Changes</button>

And move the dataContext variable to a higher scope like this:

var dataContext;

function onLoad() {
       dataContext = $create(Sys.Data.AdoNetDataContext, { serviceUri: "/demoprep/_vti_bin/ListData.svc" });

Then when you click the button the AdoNetDataContext figures out which JSON objects changed and sends just those objects back to ListData.svc. How do you know it only sends the rows that changed? If you look at Fiddler there will be a POST to ListData.svc/$batch with something like the following:

--batch_d425-6668-6b05
Content-Type: multipart/mixed;boundary=changeset_8a05-bdcd-0b3e
--changeset_8a05-bdcd-0b3e
Content-Type: application/http
Content-Transfer-Encoding: binary
MERGE http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(3) HTTP/1.1
If-Match: W/"3"
Host: localhost
Accept: application/json
Accept-Charset: utf-8
Content-Type: application/json;charset=utf-8

{
"__metadata":{"uri":"http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(3)",
"etag":"W/\"3\"",
"type":"Microsoft.SharePoint.DataService.UserStoriesItem"},
"ContentTypeID":"0x01080070E2807D369BD94FBD6C057D3110E6D3",
"Title":"Renamed Task",
"Predecessors":{"__deferred":{"uri":"http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(3)/Predecessors"}},
"Priority":{"__deferred":{"uri":"http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(3)/Priority"}},
"PriorityValue":"(2) Normal",
"Status":{"__deferred":{"uri":"http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(3)/Status"}},
"StatusValue":"Not Started",
"Complete":null,
"AssignedToID":null,
"TaskGroupID":null,
"Description":"<div></div>",
"StartDate":"\/Date(1266624000000)\/",
"DueDate":null,
"Points":8,
"Iteration":{"__deferred":{"uri":"http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(3)/Iteration"}},
"IterationID":1,
"ID":3,
"ContentType":"Task",
"Modified":"\/Date(1269128451000)\/",
"Created":"\/Date(1266679964000)\/",
"CreatedByID":1,
"ModifiedByID":1,
"Owshiddenversion":3,
"Version":"3.0",
"Attachments":{"__deferred":{"uri":"http://localhost/demoprep/_vti_bin/ListData.svc/UserStories(3)/Attachments"}},
"Path":"/demoprep/Lists/User Stories"
}
--changeset_8a05-bdcd-0b3e--
--batch_d425-6668-6b05—

And that, as you may have noticed, is the JSON for just a single of the rows that we were displaying.

But how did the AdoNetDataContext know which objects were changed? Jonathan Carter explains this better than I could but basically when the data context downloads JSON objects it injects them with the ability to notify itself when they change. As Jonathan points out it’s a neat trick that would not be possible in a statically typed language.

Summary

We’ve finally gotten to some of the core benefits of using ASP.Net AJAX 4 Templating. With very little JavaScript we were able to cleanly separate our UI from our data access code and write data back to the server. And the amount of HTTP traffic and server operations was kept to a bare minimum, keeping things as fast and responsive as possible. So all we’re missing at this point is how to use it to make something real out of these technologies. And that will be the topic for the next post.

No comments: