LA.NET [EN]

Oct 19

In this post, we’ll start applying all the things we’ve met in previous posts and, at the same time, we’ll be making reader William Apken really happy :,,)

William commented a previous post right before preview 6 was released. He was interested in seeing how he could switch templates dynamically. At the time, there wasn’t nothing in the code samples that would help him with that. Things changed with preview 6 and it already has one sample that shows how to do this.

The problem: it defines templates in a declarative way and he’s interested in getting the second template’s HTML from  a web service call. So simplify things, I won’t be showing how to get the HTML: that should be the easy part if you understand the rest of the things we’ll be discussing in this post. Ok, now it’s time to get started…

The idea is to have two templates, one for browsing and another for editing, and switch them at runtime when the user clicks on a button. The preview 6 already has a somewhat similar sample , but we’ll change it so that both templates are created on the fly (ie, they aren’t previously defined in the markup). Let’s look at the code and then we’ll analyze it block by block:

<head runat="server">
    <title></title>
    <script src="Scripts/MicrosoftAjax/start.debug.js" 
type="text/javascript">
</
script> <script type="text/javascript"> Sys.require([Sys.components.dataView]); var templates = {}; Sys.onReady(function() { var items = [ { name: "luis", address: "fx" }, { name: "john", address: "london" }, { name: "rita", address: "lx" } ]; function createHtml(html) { var aux = document.createElement("div"); aux.innerHTML = html; return aux.childNodes[0]; } var browseHtml = "<ul id=''browse''>”+
”<li>{{name}}</li></ul>"
; var editHtml = "<div id=''edit''>”+
”<input type=''text'' sys:value=''{binding name, mode=twoWay}'' />”+
”<input type=''text'' sys:value=''{binding address, mode=twoWay}'' />”+
”</div>"
; templates.browseTemplate = new Sys.UI.Template(createHtml(browseHtml)); templates.editTemplate = new Sys.UI.Template(createHtml(editHtml)); Sys.create.dataView(Sys.get("#view"), { data: items, itemTemplate: templates.browseTemplate }); $addHandler( Sys.get("#change"), "click", function(){ var dv = Sys.get("$view"); dv.set_itemTemplate( dv.get_itemTemplate() === templates.editTemplate ? templates.browseTemplate : templates.editTemplate); }); }); Sys.converters.updateText = function(val) {
return (val === templates.editTemplate ? ''browse'' : ''edit'');
}; </script> </head> <body xmlns:sys="javascript:Sys"> <input type="button" id="change" sys:value="{binding itemTemplate, source={{Sys.get(''$view'')}}, convert=updateText}" /> <div id="view"></div> </body>

There are several interesting things going on here:

  • we’re using the Sys.onReady “event” for running all the code necessary for creating the templates and hooking up the click event which lead to the template switch;
  • we need two templates, so we start by creating the HTML for those templates (notice that these are just simple strings that simulate the HTML obtained from a possible server side call). Notice that we’re also specifying binding expressions. The astute reader will also notice that we’re not using the sys-template class. This makes sense because that CSS class is used for “finding” templates during the DOM parsing and in this case we’re explicitly indicating the HTML element that should be used for the template;
  • the HTML strings end up being passed into a helper method named createHtml. This method creates new DIV node so that it can parse the HTML string by setting its innerHTML property. This is (probably) the easiest way to parse an HTML string (into a DOM node);
  • as we’ve seen, templates don’t require a reference to a page’s DOM element. That means that we can simply create the nodes and pass them to the constructor of the Sys.UI.Template.
  • since we’ll be using these templates for the entire life cycle of the page, I’ve opted for saving them in a global object named templates. This means that we’ll only be creating each template once and then we’ll simply reuse them while the user clicks in the button;
  • we’ve used a binding for ensuring proper update of the button’s text.

Notice that you can also use a slightly different approach if you don’t mind “embedding” the templates in the DOM. For instance, take a look at the following modified JavaScript which embeds the HTML in the page’s DOM:

Sys.require([Sys.components.dataView]);
Sys.onReady(function() {
    var items = [
        { name: "luis", address: "fx" },
        { name: "john", address: "london" },
        { name: "rita", address: "lx" }
    ];

    function createHtml(html) {
        var aux = document.createElement("div");
        aux.innerHTML = html;
        return aux.childNodes[0];
    }
    var browseHtml = "...";//same as before
    var editHtml = "...";//same as before
    document.body.appendChild(createHtml(browseHtml));
    document.body.appendChild(createHtml(editHtml));
    Sys.create.dataView(Sys.get("#view"),
                            {
                                data: items,
                                itemTemplate: "#browse"
                            });
    $addHandler(Sys.get("#change"),
                 "click",
                 function() {
                     var dv = Sys.get("$view");
                     dv.set_itemTemplate(
                        dv.get_itemTemplate() === "#browse"
                          ? "#edit" : "#browse" );
                 });
    Sys.converters.updateText = function(val) {
        return (val === "#edit" ? ''browse'' : ''edit'');
    }

});

We start by creating the nodes and appending them to the page’s DOM. Notice that in this case, we don’t create a template explicitly; we rely, instead, on passing a string (on the form expected by Sys.get helper) to the itemTemplate property. The DataView control is smart enough for checking the type of the property and for instantiating a template when it finds a string.

This might be a good approach for those getting HTML through the jquery’s load method. Stay tuned for more in MS AJAX.

11 comments so far

  1. william apken
    10:23 pm - 10-19-2009

    Thank you very much.

    When you say ” The DataView control is smart enough for checking the type of the property and for instantiating a template when it finds a string.”

    Do you mean that it will detect when that property changes (the one that is set when you call dv.set_itemTemplate() ) and then will instantiate a new template based on the change in property?

    So I take it if you choose the declarative option, you can not force it to re parse the code.

    Then how do you do paging if you code the dataView with the declarative option?

    Once again,
    thank for you time and effort.

    William Apken

  2. luisabreu
    8:05 am - 10-20-2009

    checking property type: internally, it will see that you”ve passed a string and it will try to find that element by id. changing the template results in a refresh of the grid.

    when i talk about declarative, i”m thinking in having the template defined directly on the markup (ie, you put it in the page with your html at startup)

    paging: there”s no direct support for that right now. you”ll have to write JS code for doing that and the easiest option is refreshing the data shown by the view when someone clicks on the next or previous page button.

  3. william apken
    9:34 am - 10-20-2009

    That is what I mean also with declarative.

    That was going back to the .load() topic you have been helping me out on.

    I want to declare my dataview in my html. And the code that I .load() will also be declared within it”s html.

    That is why I needed to re parse the page and create a new template that is refreshed with the data. This where I create a new dataView based on the loaded html. That causes the imported html to be parsed and create a new template reflecting the new

    Keep information coming.

    Are you going to write a book on MS AJAX?

    I”m currently reading (chapters 1,2,3,8) ASP.NET AJAX IN ACTION 2nd Edition.

    Thank you very much.

  4. luisabreu
    9:43 am - 10-20-2009

    I”m not following. I think you”re saying that you”ve got the template defined in an external file, right? If that is the case and you”re using the jquery”s .load method, then things are easy too: instead of using the template approach i”ve shown in the first example, you can use the second approach (uses # + id and the dataview will do that for you). what you can”t do is place the download html for the template inside the control you”ve attached to the dataview

  5. william apken
    3:50 pm - 10-20-2009

    THIS IS DEFAULT.ASPX

    Employees:

    {{FirstName}} {{LastName}}

    THIS IS ImportedTestPage.aspx

    Plates:

    {{RawCost}} — {{PlateName1}}

    From the default page when the user clicks the button the code below is executed.
    It pulls the #importedDataView div from ImportedTestPage.aspx and places it in the #dDivPlaceHolder.

    function MyloadPage() {

    loadContent(“ImportedTestPage.aspx”, “#importedDataView”, “#dDivPlaceHolder”, cbLoadContent);

    };

    this is the callback function after the load is completed.

    the commented code below is how I get it to work.

    But for this example the cbLoadContent is not being executed. I have commented it out. This is what I mean when I say I can get it to work this by creating a new dataview.

    function cbLoadContent() {
    /// alert(“in callback from loadContent”);
    /// $create(Sys.UI.DataView, { dataProvider: myDC, autoFetch: true, fetchOperation: “PlateName” }, null, null, $get(“nflList”));
    };

    Heart of my question:

    after the html is loaded from ImportedTestPage and placed into dDivPlaceHolder. I want that code to be re-parsed (this may not be the correct terminology ).

    I want to declare the dv as below. Load it up. Put it into the default.aspx.
    And have it parsed. I”m using the same dataprovider just changing the fetchOperation and have different bindings. {{RawCost}} {{PlateName}}.

    {{RawCost}} — {{PlateName1}}

    After you gave me your last example and how we can swap out templates and have them repopulate base on the new template. I started thinking why not apply that same logic to this problem.

    Your example uses the same datasource. I need it to be able to swap templates and use the same dataprovider but base the query upon a different entity.

    My WebService is setup using AdoNet DataService and myDC is a AdoNetDataContext. Both entities(customers,Plates) are defined in my EF Model. So if I”m thinking correctly, myDC should be aware and have access to both entities.

    What method is used to parse the template when the page originally loads.

    Why can”t I just call that method and pass in the new template. And have it evaulate it again?

  6. luisabreu
    8:13 pm - 10-20-2009

    regarding MS AJAX, i”ve written one back in 2006, but it”s in Portuguese. Btw, you”re well server with ASP.NET AJAX in Action (I”ve only read the first version and it was good)

  7. luisabreu
    8:29 am - 10-21-2009

    William, build the simples sample you can that reproduces your problem (something which just spits HTML and doesn”t even use a db). then send it to my mail so that I can cehck it: labreu at gmail.com

  8. william apken
    11:18 pm - 10-24-2009

    Luis,

    I gave up on the declarative way. Period. I have tried to get the dataview object that is created from the declarative way and treat like it as it was created using the $create() helper. Either I don”t understand this well enough or internally they are treated like two different objects.

    I tried this.

    $addHandler(Sys.get(“#changeFetch”),
    “click”,
    function() {

    var helper = document.createElement(“UL”);
    helper.innerHTML = “{{FirstName}}”;
    var template = new Sys.UI.Template(helper);
    myDV.beginUpdate();
    myDV.set_fetchOperation(“Customer”);
    myDV.set_itemTemplate(template);
    myDV.endUpdate();

    });

    the problem is once you call .set_fetchOperation() it goes and renders the new operation.
    Also, when you call .set_itemTemplate() it does the same thing.

    I thought if I used the .beginUpdate() and .endUpdate() that this would delay the rendering until I changed operation and Template.

    It does not. As soon as it calls .set_fetchOperation it renders.

    Since I”m changing the fetchOperation the existing Template does not have the correct field names. That makes sense since I”m querying from a different entity.

    I can not believe I”m having this much trouble trying to query a different entity with a different template.

  9. william apken
    11:40 pm - 10-24-2009

    follow up: got the fetchOperation to work or I should say not to render when the .set_fetchOperation() is called.

    by setting the .set_autoFetch(false); will mark it as being stale but does not try to render.

    Ok all good.

    BUT.
    when the .set_itemTemplate() is called it in turn calls .refresh() without checking any “should I update” values.

    $addHandler(Sys.get(“#changeFetch”),
    “click”,
    function() {

    var helper = document.createElement(“UL”);
    helper.innerHTML = “{{FirstName}}”;
    var template = new Sys.UI.Template(helper);
    myDV.set_autoFetch(false);
    myDV.set_fetchOperation(“Customer”);
    myDV.set_itemTemplate(template);
    myDV.set_autoFetch(true);

    });

  10. william apken
    12:38 am - 10-25-2009

    Is this a bug?

    I have my AdoNetDataContext “wired up” everything works fine.

    Close the solution.

    Come back the next day, open the same solution and the webService.svc is not working any more.

    I goto the folder:
    c:/windows/Miscrosoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files

    delete the temp file that matches the solution.

    Open the soluction, run it and it works.

    Have you heard of this before?

  11. luisabreu
    12:08 pm - 10-25-2009

    William, setting the template means refreshing the view even if there”s no new data on it. and i think it makes sense because the template controls the UI of the DataView control

    regarding your other problem, i”ve had to do something similar in the past too…