ASP.NET AJAX Development Approach Part 5
Contents |
· 1 Introduction · 2 Rendering on the client · 3 Binding Data · 4 Working With Tables · 5 Working with Styles · 6 Script Manager Assistance · 7 The Benefit to Client Rendering · 8 Conclusion |
So far we've looked at the way to develop a custom control using the ASP.NET AJAX framework. I want to turn my focus now toward the various ways the UI of a control can be rendered. Server-side rendering isn't the only way this can be done anymore, especially since JavaScript is involved and there is a client lifecycle available.
ASP.NET AJAX framework Series
· Part 1 Overview of the client portion of the ASP.NET AJAX framework.
· Part 2 Overview of the client portion of the ASP.NET AJAX framework.
· Part 3 Overview of the client portion of the ASP.NET AJAX framework.
· Part 4 Overview of the server portion of the ASP.NET AJAX framework.
· Part 5 Overview of rendering the UI of a control with ASP.NET AJAX.
JavaScript has full capability to render the user interface on the client. In some ways, this is a good thing. The challenge with data-intensive web sites often becomes an issue with the volume of data being displayed. With latency always an issue, pages may take a longer time to render and load for the user, which may be unacceptable to them. To offset this issue, it may become a better strategy to push the data down to the client or retrieve the data on the client-side, rather than rendering on the server-side.
Right now, there isn't a built-in way to do this. There are a couple of projects coming on board that implement these features. For instance, future versions of the ASP.NET AJAX framework contain more data-bound controls that work on the client side. The AJAX Data Controls project is another project striving to create data controls that operate on the client side, providing a lot of dynamic client-side features. I'm also working on a project called Nucleo.NET that will contain client-side data binding features; my framework is more geared toward input/list/tabular controls, with some data controls included.
All of these frameworks do some rendering on the client, which pushes the work off to the client rather than on the server. This can be a good thing, as long as the browser settings are supported (meaning that the user can turn off JavaScript, which is a very bad thing in this scenario).
JavaScript supports dynamic rendering of the UI. This can be done in one of two ways: rendering an HTML string, or using the document object model (DOM) to create the HTML tags. Both options have their pros and cons. Let's begin by looking at ways to create objects using the DOM. The document object has a createElement method just for this purpose. Check out the code below that creates a checkbox.
Listing 1: Creating a checkbox dynamically
1.var checkbox = document.createElement("INPUT");
2.checkbox.setAttribute("id", "firstcheck");
3.checkbox.setAttribute("type", "checkbox");
4.checkbox.setAttribute("checked", "checked");
5.someParentControl.appendChild(checkbox);
A checkbox is an INPUT element with a type of "checkbox." The document object model approach uses the document.createElement method to create the object, followed by the setAttribute method to set the actual values. Every DOM element has an appendChild method to append children to its childNodes collection, presenting it in the UI.
The alternative approach to this is using a string statement. Below is the alternative to the previous approach, creating a string using the ASP.NET AJAX StringBuilder class.
Listing 2: Creating a checkbox as a string
1.var builder = new Sys.StringBuilder();
2.builder.append("<input type='checkbox' ");
3.builder.append(String.format("id='{0}'", id));
4.builder.append("checked='checked' ");
5.buider.append(" />");
6.someParentControl.innerHTML = builder.toString();
Both options yield the same results on the screen: showing a checkbox in its checked state.
Let's take this scenario into a client data-binding scenario. Imagine using the following data source for a client data-binding scenario, as in the following:
Listing 3: Sample data source
1.var cities = [];
2.Array.add(cities, { City:"Pittsburgh", State:"PA", ZipCode:15239 });
3.Array.add(cities, { City:"Harrisburg", State:"PA", ZipCode:17110 });
4.Array.add(cities, { City:"New York", State:"NY", ZipCode:10027 });
5.Array.add(cities, { City:"Cleveland", State:"OH", ZipCode:44102 });
6.Array.add(cities, { City:"Washington", State:"DC", ZipCode:20390 });
The above data source can be easily used to create a dynamic table on the client side, as in the following:
Listing 4: Dynamically generating a table based upon a data source
01.var table = document.createElement("TABLE");
02.var header = document.createElement("THEAD");
03.table.appendChild(header);
04.var body = document.createElement("TBODY");
05.table.appendChild(body);
06.var row = document.createElement("TR");
07.header.appendChild(row);
08.var cell =document.createElement("TH");
09.row.appendChild(cell);
10.cell.innerHTML = "<strong>City</strong>";
11.cell =document.createElement("TH");
12.row.appendChild(cell);
13.cell.innerHTML = "<strong>State</strong>";
14.cell =document.createElement("TH");
15.row.appendChild(cell);
16.cell.innerHTML = "<strong>Zip Code</strong>";
17.for (var index = 0; index < cities.length; index++)
18.{
19. var city = cities[index];
20. row = document.createElement("TR");
21. body.appendChild(row);
22. cell = document.createElement("TD");
23. row.appendChild(cell);
24. cell.innerHTML = city.City;
25. cell = document.createElement("TD");
26. row.appendChild(cell);
27. cell.innerHTML = city.State;
28. cell = document.createElement("TD");
29. row.appendChild(cell);
30. cell.innerHTML = city.ZipCode;
31.}
As you can see, because JavaScript can work with objects, an interface can be rendered via JavaScript. In this sample, this code is embedded in a page, but this could easily exist in the initialize method of an AJAX component as well. Conceptually, the code and the approach is the same.
Previously, when creating a dynamic table, I used the childNodes collections to work with the table. But that isn't the only approach. Instead, I've outlined an alternative approach in this section. The TABLE, THEAD, and TBODY elements have specific properties and methods for working with the table structure, like the rows collection of TR elements and an insertRow method, all shown in Figure 5.
Listing 5: Creating a table using an alternative approach
01.<body>
02.<span id="placeholder"></span>
03.<script language="javascript">
04.var table = document.createElement("TABLE");
05.document.getElementById("placeholder").appendChild(table);
06.var thead = document.createElement("THEAD");
07.table.appendChild(thead);
08.tbody = document.createElement("TBODY");
09.table.appendChild(tbody);
10.var headerRow = thead.insertRow(0);
11.headerRow.appendChild(document.createElement("TH"));
12.headerRow.appendChild(document.createElement("TH"));
13.headerRow.appendChild(document.createElement("TH"));
14.headerRow.cells[0].innerHTML = "City";
15.headerRow.cells[1].innerHTML = "State";
16.headerRow.cells[2].innerHTML = "Zip";
17.var row = tbody.insertRow(0);
18.row.appendChild(document.createElement("TD"));
19.row.appendChild(document.createElement("TD"));
20.row.appendChild(document.createElement("TD"));
21.row.cells[0].innerHTML = "Pittsburgh";
22.row.cells[1].innerHTML = "PA";
23.row.cells[2].innerHTML = "15239";
24.</script>
25.</body>
The TR element has some useful features too. It has a cells collection, representing each TH or TD cell in that row. The rows and cells collection is nice because these collections only contain TR or TH/TD elements, and not additional elements that may be in the HTML markup (or if you use FireFox's FireBug utility, you'll see that childNode also contains literals too).
So these collections make it handy to work with table. There are other handy methods, like insertRow above, or deleteRow for deleting rows, or even deleteTHead, deleteTFoot or insertCell.
The Style object is a convenient way to apply styles to a region of the underlying HTML output. A style can be transformed into a CSSStyleCollection collection, which is subsequently passed as a string to the client using the following code in the description process. Obviously this is used in an AJAX component, because of the use of the descriptor.
Listing 6: Converting a Style to a string
1.CssStyleCollection cssList = style.GetStyleAttributes(control);
2.descriptor.AddProperty(name, cssList.Value);
On the client, this style is a string that is in the format of attribute:value pair separated by semi-colons. These styles can be applied to most HTML elements through its style property. The style property can't be set directly; the style property is an object that doesn't directly assign to strings. The two approaches I've seen to assign a style string to an object via:
Listing 7: Setting a style on the client side
1.var element = document.getElementById("test");
2.element.setAttribute("style", "background-color:blue;color:white;");
3.element.style.cssText = "background-color:blue;color:white;";
I ran a quick test against the latest versions of the internet explorer, firefox, safari, and opera browsers. Internet Explorer is the only browser that doesn't support both approaches to setting the style, not supporting the setAttribute approach of passing a string. In the other three browsers, both approaches work fine. I did a little research and found that setAttribute was a recommended approach for older browsers, as I believe cssText was introduced later.
The script manager contains some additional useful features for rendering on the client. It can register hidden fields using the ScriptManager.RegisterHiddenField static method. It can register some scripts registered on the server-side, and pass data to the client. I've used this approach to pass data to JavaScript variables through an assignment in the script. This can be easily done by adding a client variable and doing the following on the server:
Listing 8: Passing a querystring value to the client
1.string key = Request.ServerVariables.Get("Key");
2.if (!string.IsNullOrEmpty(key))
3. ScriptManager.RegisterStartupScript(this, this.GetType(), "assignment",
4. "_key = " + key + ";", true);
Of course this could be anything, as long as the statement can be rendered easily. This is equivalent to the <%= %> approach (like $get(<%= TextBox1.ClientID %>);) you see to get the real ID of a control. In this case, we're passing serialized data to the client.
The thing to watch out is where the _key variable is used, because placement is important. If the variable used in the ASP.NET AJAX client load event, the variable will contain the correct value. This is because load runs after the script initializes. The load event is delayed in a sense meaning that load runs after the script have been initialized. If the _key variable is used immediately after declaration, this approach won't work because the assignment with RegisterStartupScript happens near the end of the page, and thus _key would be null. So placement is key, and JavaScript can be tricky in that sense.
In the following example, the value "1" returned to me.
Listing 9: Sample Code
01.<script language="javascript">
02.var _key = null;
03.function pageLoad() {
04. alert(_key);
05.} </script>
06.public partial class TestPage : System.Web.UI.Page
07.{
08. protected void Page_Load(object sender, EventArgs e)
09. {
10. string key = "1"; //Request.ServerVariables.Get("Key");
11. if (!string.IsNullOrEmpty(key))
12. ScriptManager.RegisterStartupScript(this,
13. this.GetType(), "assignment",
14. "_key = " + key + ";", true);
15. }
16.}
The Benefit to Client Rendering
So why even mention rendering on the client? There isn't always a need to render on the client, but picture this situation: as applications get more complex, the desire to add more and more information increases. What this means is that the size of HTML content posted from server to client in a typical ASP.NET application increases. This can be problematic because .NET controls render a lot of content, sometimes excessively, and that content is what gets sent over the wire.
It's not too long before the content gets so big that the users of an application complain of its speed. By rendering HTML content via JavaScript, this minimizes the total payload over the wire because there isn't as much HTML content when the page renders and served up from the server. The data can be passed via web services, so only the raw data itself, and not HTML markup gets passed to the client, and at a different time than the rendering.
The challenge becomes avoiding unnecessary postbacks because served-up content rendered on the client isn't remembered in ViewState, since ViewState is a server-based mechanism. There are some alternatives that can be used (using hidden fields to store data or trying to give it an ID so the control posts back to the LoadPostData method), but overall if a postback can be prevented, that would be the best approach.
Content can be rendered on the client, rather than the server, and this approach is sometimes a good one to take. As applications grow, they tend to render and consume a lot of data that increases performance issues. An alternative can be to render the content on the client, while only passing the data via web services to the client, reducing the overall payload.
Rendering on the client may be a challenge, but I've included some steps to understand how it works, how styles can be applied. Look for more in the future with ASP.NET AJAX 4, which will support expressions, client-side templates, and more features.