Template loading and switching

Published Mon, Oct 19 2009 21:36

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="BLOCKED SCRIPTSys"> <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.

Filed under: ,

Comments

# william apken said on Monday, October 19, 2009 5:23 PM

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

# luisabreu said on Tuesday, October 20, 2009 3:05 AM

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.

# william apken said on Tuesday, October 20, 2009 4:34 AM

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.

# luisabreu said on Tuesday, October 20, 2009 4:43 AM

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

# william apken said on Tuesday, October 20, 2009 10:50 AM

THIS IS DEFAULT.ASPX

<body xmlns:sys="BLOCKED SCRIPTSys" xmlns:dv="BLOCKED SCRIPTSys.UI.DataView">

   <form id="form1" runat="server">

   <div id="dDivPlaceHolder">

      <div id="importedDataView" >

         <input id="Button1" type="button" onclick="MyloadPage()"                           value="Import Test ASPX page" /> <hr /><hr />

    <br />Employees:<br />

    <div id="employeeView" class="sys-template"

        sys:attach="dv"

    dv:dataprovider="{{myDC}}"

    dv:autofetch="true"

    dv:fetchoperation="Customer">

    {{FirstName}} {{LastName}}<br />

    </div>

 </div>

   </div>

   </form>

</body>

THIS IS ImportedTestPage.aspx

<div id="importedDataView" >

   <br />Plates:<br />

    <ul id="nflList" class="sys-template"

        sys:attach="dv"

      dv:dataprovider="{{myDC}}"

      dv:autofetch="true"

        dv:fetchoperation="Plates">

     <li>{{RawCost}} -- {{PlateName1}}</li>    

    </ul>

</div>

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}}.

    <ul id="nflList" class="sys-template"

        sys:attach="dv"

      dv:dataprovider="{{myDC}}"

      dv:autofetch="true"

        dv:fetchoperation="Plates">

<li>{{RawCost}} -- {{PlateName1}}</li>    

        </ul>

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?

# luisabreu said on Tuesday, October 20, 2009 3:13 PM

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)

# luisabreu said on Wednesday, October 21, 2009 3:29 AM

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

# william apken said on Saturday, October 24, 2009 6:18 PM

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 = "<LI>{{FirstName}}</LI>";

                            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.

# william apken said on Saturday, October 24, 2009 6:40 PM

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 = "<LI>{{FirstName}}</LI>";

                            var template = new Sys.UI.Template(helper);

                            myDV.set_autoFetch(false);

                            myDV.set_fetchOperation("Customer");

                            myDV.set_itemTemplate(template);

                            myDV.set_autoFetch(true);

                        });

# william apken said on Saturday, October 24, 2009 7:38 PM

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?

# luisabreu said on Sunday, October 25, 2009 7:08 AM

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...

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Search

This Blog

Tags

Community

Archives

Syndication

Email Notifications

News




  • View Luis Abreu's profile on LinkedIn


    Follow me at Twitter

    My books

    Portuguese LINQ book cover

    Portuguese ASP.NET 3.5 book cover

    Portuguese ASP.NET AJAX book cover

    Portuguese ASP.NET AJAX book cover