Chapter 7: Manipulating Client-Side HTML
Introduction | The Roles and Responsibilities of Client-Side Languages - Structure, Appearance, and Behavior, Adding Interactive Behavior to an Application | Selecting and Manipulating Existing HTML Elements - Selecting Elements, Using Context, Using Data- Attributes, Modifying Existing Elements, Showing and Hiding Content, Updating Element Attributes | Generating and Loading New HTML in a Page - Generating HTML Dynamically with JavaScript and jQuery, Retrieving HTML Fragments from the Server, Content Retrieval Decisions in Mileage Stats, Using Templates to Retrieve and Generate HTML, Anatomy of a jQuery Template, Server-Side Rendering and jQuery Templates | Summary | Further Reading |
Introduction
Both hybrid and single-page interface (SPI) web applications that do not perform full-page post backs in order to change the rendered user interface (UI) must use client-side techniques for manipulating the document object model (DOM). This DOM manipulation could include updating the content of the page, displaying new UI elements, or loading entire pages in response to user gestures and other events. The three client-side technologies you can use when implementing these techniques are HTML, cascading style sheets (CSS) and JavaScript.
This chapter discusses how you can clearly define the roles of HTML, CSS, and JavaScript on the client to ensure that developers and designers can work together without affecting each other's code. You will also see how to use JavaScript to manipulate HTML effectively in the browser.
In this chapter you will learn:
- The roles and responsibilities of HTML, CSS, and JavaScript within a client application.
- How to use jQuery to select and manipulate HTML elements.
- How to use jQuery to generate new HTML within the client.
- How to use jQuery Templates to generate new HTML within the client.
- How to choose the most appropriate option when manipulating HTML.
Technologies discussed in this chapter are HTML, CSS, and JavaScript.
The Roles and Responsibilities of Client-Side Languages
When building the client-side portion of a web application, you generally use three technologies that can be viewed as separate development languages: HTML, CSS, and JavaScript. You may not be accustomed to thinking of these technologies as development languages. However, considering them in this way can help to clarify the role that each one plays in your application, and can help you keep their responsibilities separate.
As with any development language, it is possible to use these languages in a way that makes the application harder to maintain and extend. This can be avoided by taking advantage of the strengths of each language, and by keeping their responsibilities clear and distinct.
Structure, Appearance, and Behavior
There are three areas of responsibility within client applications, and each client-side language is associated with one of those areas. The three areas are:
- The structure of the UI. HTML is used to define the logical structure of the UI. Structure refers to the hierarchy of objects that make up the UI, not the visual appearance or layout of the UI. Today, HTML is ideally used only for the logical, non-visual structure.
- The appearance of the UI. This is the responsibility of CSS, which applies styling to elements and manages visual layout (positioning and flow) for the UI. CSS allows designers and developers to change the layout, styling, and appearance of the UI elements without requiring changes to the structure defined by the HTML.
- The behavior of the UI. JavaScript is responsible for the behavior of the application. It is used to define or re-define how UI elements respond to user interactions and gestures, as well as other events not directly initiated by the user.
HTML also has an additional responsibility that might not be immediately obvious. It is frequently used to transport the data that the client requires from the server. For example, when the Mileage Stats Reference Implementation (Mileage Stats) renders a page, the information about the vehicles is stored as metadata in attributes within the HTML markup. Using metadata in this way is discussed in "Using Data- Attributes" later in this chapter.
It is surprisingly easy to blur the lines of responsibility among the client-side languages. For example, you might be tempted to use a CSS class to associate semantic or structural meaning to an HTML element. In fact, this approach was commonly used in the past to identify elements in order to attach some behavior.
An example of this approach would be to add an attribute such as class="vehicleLink" to relevant hyperlinks so that you can select these elements in your JavaScript code. However, using a CSS class in this way is problematic because it muddles the responsibilities of CSS as a language. In particular, if you use a CSS class to specify the style and the behavior of elements, your designer might unintentionally break your application by changing a class name or relocating a CSS class to another element when updating the layout and style of the page.
Adding Interactive Behavior to an Application
When you have a simple web application, you can rely on the web server to handle changes to the HTML by posting back to the server so that it can update the page, or by loading a different page that contains the required HTML. However, this approach limits the level of interactivity in the application.
There are several ways that JavaScript code can add interactivity to a page through manipulation of the HTML structure and content without requiring a full-page reload. Some typical approaches are:
- Using the existing page structure and simply hiding and showing elements as necessary. This is the simplest approach and is appropriate in some scenarios. For example, you might use JavaScript code to change the CSS properties that specify the visibility of elements within the page as it is loading, or in response to user actions.
- Using JavaScript to modify, add, or remove elements within the browser's DOM. The code can also add, remove, or update the attributes and style classes applied to elements. For small applications that do not require the appearance, structure, and behavior to change very much, this may be an acceptable solution. For example, you might want to add a new UI element to the page, enable or disable controls in the page, change the text displayed in elements, or show a different image.
- Using JavaScript to retrieve HTML fragments from the server and add them to the DOM. This is an ideal approach when the server-generated HTML fragments can be reused directly within the client. The HTML fragments may be generated dynamically on the server to include data relevant to the current state of the application, or may be static content, perhaps read directly from disk files.
- Using templates to generate HTML fragments dynamically on the client, and adding or replacing elements in the DOM with the generated HTML. This approach is ideal when there is a lot of content to be generated on the client. It can be used to combine HTML in the template with data provided by the server. This is the solution used in Mileage Stats.
Using a JavaScript Library to Manipulate the DOM
For a rich user experience, where your application can change the UI while avoiding full page reloads, it is obviously necessary to have a mechanism for manipulating the DOM. JavaScript is the primary way to do this. However, due to different DOM and JavaScript implementations within different browsers, using a JavaScript library to shield you from cross-browser compatibility issues is the only practical approach for anything more than very simple interactions.
There are several libraries that do this very well, but this chapter discusses only the jQuery library. Mileage Stats uses the jQuery library because it provides many powerful methods for selecting and changing elements in the DOM, modifying element attributes, wrapping existing elements, and creating new elements while remaining compatible with almost all modern browsers.
Note
For more information about jQuery, see "jQuery Home Page" in the "Further Reading" section of this chapter. For more information about browser support for jQuery, see "jQuery Browser Compatibility" in the "Further Reading" section of this chapter.
jQuery uses an element selection syntax that is very similar to CSS, and extends it to provide additional ways to select elements within the HTML. This means that the same syntax you use in style sheets to select elements can be used to attach JavaScript behaviors to elements. Once the elements have been selected, the code can modify them, remove them, or insert new elements within them. Modifying these elements could be as simple as adding or removing a CSS style, or changing the contents of the element. The remainder of this chapter focuses on how you can select elements and modify them, and how you can add new HTML elements to the page rather than changing the behavior of existing elements.
Selecting and Manipulating Existing HTML Elements
Many scenarios for interactive web applications only require client-side languages such as CSS and JavaScript to modify existing elements. Modification usually involves adding, removing, or updating the attributes and style classes applied to elements. In some cases, CSS alone may be capable of performing the required changes, such as changing the style of hyperlink elements as the mouse pointer passes over them. In other cases, JavaScript code is required to manipulate the elements directly.
Before you can perform actions on elements, you must specify how the CSS or JavaScript will select them. You can select one or more elements, as described in the following section, and then use a range of techniques to manipulate the elements. These techniques are described in "Modifying Existing Elements," later in this chapter.
Selecting Elements
There are several ways you can use selectors within an application to select elements you want to modify. Each approach has its own benefits and drawbacks:
- Using data- attributes. data- attributes (pronounced "data dash" attributes) provide a simple and efficient way to select elements based on the presence and value of attributes whose name starts with "data-". jQuery provides the data method for working with data- attributes, as you will see in the next section of this chapter. This is the preferred approach for most element selection scenarios.
- Using the id attribute. This works well when there is a single element in the DOM with the specified value for its id attribute. However, this approach often causes jQuery to search the entire DOM to find the element, which can have significant impact on the client-side performance of your application. In addition, using the id as a selector is not a valid approach when you must support multiple elements on the page that may have the same id attribute values.
- Using element name selectors. This is a useful technique when you want to apply an action to all elements of a specific type (for example, all of the <tr> elements in a table). However, it can cause problems because it is easy to select more elements than intended if the element occurs elsewhere in the page. It also means that changing the type of elements in the HTML in the future will require changes to the JavaScript code.
- Using class name selectors. This is a useful technique for selecting multiple elements across the element hierarchy, but (as described in the introduction to this chapter) it can cause confusion between class names used for applying CSS styles and class names used as selectors to apply JavaScript behavior. It can also lead to subtle bugs when the JavaScript adds and removes classes on elements; suddenly the class name you use to find an element is no longer there.
- Using relative location. You can select one or more elements by relative position within the DOM with respect to a known element. For example, you might select all of the elements that are the children of a target element. This can be an efficient way to locate elements because it is usually much faster than searching from the root element of the DOM. It can be combined with the previous selection methods. However, selectors that rely on this approach are fragile because they depend on the specific hierarchy of the HTML structure.
Note
All of these selection methods run the risk of impacting client-side performance. It is important to ensure that you provide additional context with the selectors to prevent searching the entire DOM. See "Using Context" below for more information.
For more information on jQuery selectors, see "jQuery Selectors" in the "Further Reading" section of this chapter.
It is important to note that your HTML structure becomes an unofficial contract between the server and the client. The JavaScript code will expect elements with specific ids to exist as well as expecting the presence of certain data- attributes. The more assumptions that the JavaScript makes about the HTML, the more fragile the application will become.
Writing unit tests to verify that the HTML provides the expected metadata can save significant effort if changes to the HTML structure are required at a later time. For more information on how the server can provide the correct initial HTML to the client, see "Providing HTML Structure" in Chapter 11, "Server-Side Implementation."
Using Context
One of the ways to achieve maximum performance when selecting elements is to specify a context to search within. This context allows searches based on a known and referenced element. There are two ways to provide this context. The jQuery method can take a second parameter, a known element, to scope the search through the DOM. Alternatively, the find method of an element can be used to obtain a collection of all or specific child elements. Both techniques are shown here.
// Example: select the child element(s) with id="dashboard-link"
// Both of the following lines return the same elements.
var myLinkElements =$('#dashboard-link', someKnownElement);
var myLinkElementsByFind = someKnownElement.find('#dashboard-link');
Using Data- Attributes
HTML5 provides data- attributes as a mechanism for applying metadata to elements. It turns out that this metadata is also useful when selecting elements. The browser does not use data- attributes when rendering the page. This means that they can be used to select elements independently of the element hierarchy and styling.
In addition, data- attributes can store other arbitrary metadata, such as the state of a UI element, because they are merely attributes on the element. For example, the attribute data-action="reminders" can represent the action="reminders" name/value pair.
In Mileage Stats, data- attributes are used to provide the client with useful information. The client will make Ajax requests to the server to load and save data, so the client must know the URLs corresponding to the endpoints for those data requests. Rather than hard-coding the URLs within the client-side script, the server can provide the URLs in the HTML by using data- attributes, and the code can extract this value when it needs to make a request to the server. This means that the code will continue to work if the URLs change, such as when changing the routing in a Model-View-Controller (MVC) application or when moving the application to a different virtual directory on the server.
The following code shows the CSHTML code that Mileage Stats uses to render the vehicle list on the dashboard. Both the id attribute and CSS class are set so that they can be used for styling the vehicle list with CSS. There are also two data- attributes that contain URL metadata for use within the JavaScript that controls the behavior of the vehicle list.
<!-- Contained in_VehicleList.cshtml -->
<div id="vehicles" class="article @compactClass"
data-list-url="@Url.Action("JsonList","Vehicle")"
data-sort-url="@Url.Action("UpdateSortOrder", "Vehicle")">
...
</div>
<!-- result of _VehicleList.cshtml -->
<div id="vehicles" class="article"
data-list-url="/Vehicle/JsonList"
data-sort-url="/Vehicle/UpdateSortOrder">
...
</div>
Note
Only the first hyphen in the attribute name is significant. Everything in the attribute name after the first hyphen is treated as a simple string value. For example, data-list-url="/Vehicle/JsonList" is a data- attribute containing the metadata list-url="/Vehicle/JsonList".
To select all of the elements that carry the data-list-url attribute shown in the previous HTML listing, you could use the following JavaScript code.
// Example: select the element(s) with a data-list-url attribute
var myVehicleElement = myVehicleList.find('[data-list-url]');
// Example: get the value of a data-list-url attribute
var myUrl = myVehicleElement.data('list-url');
Because data- attributes are ideal for selecting elements, and for getting or setting data associated with those elements in the DOM, you should use them wherever possible. If your designer changes the layout and style information in the HTML, your JavaScript won't break. In addition, data- attributes allow the server to easily provide the client with useful data without the client having to parse UI-specific HTML. Use data- attributes wherever it is appropriate to specify metadata in the server-generated HTML or with JavaScript on the client.
Keep in mind that the HTML represents the logical structure of the UI, not the visible layout and style. The use of data- attributes provides you with a mechanism for annotating the structure and adding metadata without using style class names or element identifiers.
Modifying Existing Elements
After you select your target element or set of elements, you can modify them as required. The following sections provide some examples of scenarios and solutions for modifying existing elements within the HTML structure of the page.
Showing and Hiding Content
When the user first navigates to your application, the server will return the HTML for the page. This HTML includes the locations (URLs) for the resources such as images, scripts, and HTML fragments that the browser will download as secondary requests in order to create the complete page. However, there will be a noticeable delay as the content and scripts are downloaded, the HTML and CSS is parsed, and any startup scripts run. While usually short, this delay can affect the user experience, especially if there are a large number of secondary requests for scripts, images, or other assets, or if the user is on a slow Internet connection.
One solution is to include in the HTML sent from the server a progress indicator that shows that the application is loading. Then, once the rest of the content is available, the application can use JavaScript to change the UI by hiding the progress indicator and replacing it with the desired HTML (which was also part of the server response, but hidden using CSS styles). The code needs only to change the CSS styles applied to the relevant elements. This technique means that the user perceives a reduced waiting time and better responsiveness, even though there is no actual increase in performance.
Note
Many applications require JavaScript. One approach to communicating this requirement to the user is to include a notice in the initial HTML that the site requires JavaScript. The first action the application script takes is to hide the notice, so that it remains visible only when the client does not support JavaScript. A more common approach is to require that the application run on a recent browser and check that features such as JavaScript are enabled. This is often easier for users, who are immediately aware that the application requires JavaScript or other features, and it simplifies the work of technical support teams who do not encounter errors caused by the use of older browsers.
Updating Element Attributes
Interactivity in the page can be implemented using JavaScript code that runs in response to user actions, or when events such as completion of page load occur. Typically, the HTML sent from the server will contain all of the structure (the HTML element hierarchy) required to render the page. Unless you actually need to build all or part of the UI dynamically on the client, having the initial structure in place is much faster than creating the same structure in the script each time. However, you may need to modify the HTML elements after the page has loaded in the browser.
For example, the Mileage Stats main page contains a header with links to the dashboard and chart views (The HTML is shown here with some content removed for clarity).
<!-- Rendered from _Layout.cshtml -->
<div class="nav">
<span id="welcome">Welcome <span data-display-name>Sample User</span></span>
[ <a href="/Dashboard" id="dashboard-link">Dashboard</a>
| <a href="/Chart/List" id="charts-link">Charts</a>
...
</div>
These hyperlinks must be updated when the page loads to include in the URL any subfolder name (if the application is installed on the server in a subfolder of the default website). If this is not done, the links will not work correctly. When the page load is complete, the following JavaScript runs in the browser to update the href attributes of these hyperlink elements to include the correct URL.
// Contained in mstats.header.js
_adjustNavigation: function () {
var state = $.bbq.getState() || { },
url = this.element.data('url'),
newUrlBase = mstats.getRelativeEndpointUrl(url);
state.layout = 'dashboard';
this.element.find('#dashboard-link')
.attr('href', $.param.fragment(newUrlBase, state));
state.layout = 'charts';
this.element.find('#charts-link')
.attr('href', $.param.fragment(newUrlBase, state));
},
The first part of this code uses the jQuery Back Button and Query (BBQ) plug-in to create a state object and get the actual base URL of the page. This value is discovered on the server and can be obtained from the getRelativeEndpointUrl method of the global mstats object defined in the JavaScript of the page. Chapter 9, "Navigation," discusses in more detail the BBQ plug-in, the use of a state object, and how this section of code works.
After the base URL has been established, the code changes the name/value pair named layout within the state object instance so that it specifies the correct layout style (dashboard or charts) for each hyperlink. It then uses the find method to select the appropriate hyperlink element based on the value of its id attribute, and sets the value of the href parameter to the correct URL. This URL is a combination of the base URL of the application and the relevant layout property held in the state object instance.
Mileage Stats uses data- attributes both to locate elements and to store data values in the DOM. As another example, the _bindNavigation method of the fill ups widget (part of which is shown below) relies on the presence of a data-action attribute to locate navigation buttons. The method also uses the data-action and data-fillup-id attributes to determine which action to take and which fill up to target.
// Contained in mstats.fillups.js
_bindNavigation: function () {
var that = this;
this.element.delegate('[data-action]', 'click.' + this.name, function (event) {
if ($(this).data('action') === 'select-fillup') {
that._setOption('selectedFillupId', $(this).data('fillup-id'));
event.preventDefault();
}
});
},
Generating and Loading New HTML in a Page
In the previous section, you saw how you can select HTML elements and manipulate them. This obviously requires existing elements to work with. However, you will often need to create new elements and content within the existing HTML structure. There are several ways to generate new HTML and modify the existing HTML structure on the client. Typical techniques, which are discussed in this chapter, are:
- Generating the HTML structure dynamically using JavaScript and jQuery
- Retrieving HTML fragments from the server and injecting them into the page
- Using templates both to retrieve and to generate the HTML structure to insert into the page
Generating HTML Dynamically with JavaScript and jQuery
When developing a hybrid or SPI application, you often need to modify the existing page by inserting into it fragments of new HTML. You can, of course, remove elements from the page as well, but it is usually easier to change the CSS styles that control visibility.
Mileage Stats generates and inserts fragments of HTML into its pages in several places. For example, it uses JavaScript to add new elements to the page structure as the user navigates through the application. The following code (it is part of the info pane widget) creates the HTML structure that displays a vehicle's details.
// Contained in mstats.info-pane.js
_setupDetailsPane: function () {
var elem = this.element,
options = this.options;
if (!elem.find('#details-pane').length) {
elem.find('div:first')
.append('<div id="details-pane" class="tab opened section" />');
}
this.vehicleDetails = elem.find('#details-pane').vehicleDetails({
// options for the widget
});
},
This code uses the find method to check if the Details pane is already present in the structure. If it does not exist, the code locates the first div element within the current context and uses the append method to insert a new div element with id="details-pane" into the page.
Generally, the amount of HTML generated with script should be small. Script that creates large amounts of HTML is difficult to read and can easily create a structure that is invalid because of syntax errors in the code. If you need to generate a large amount of HTML, you should reevaluate the design or consider using one of the other methods described in this section to retrieve or generate the HTML you require.
Retrieving HTML Fragments from the Server
In some situations, it may make sense to retrieve an HTML fragment from another source (for example, a URL on the web server) and inject it directly into the page, perhaps after manipulating it, rather than dynamically generating the content on the client. For example, it may make sense to reuse an HTML form generated on the server and dynamically embed it in a web page, rather than generating the entire form and its contents with JavaScript. This approach can simplify the client-side code by moving some of the complexity to the server.
Typically, you will use Ajax methods, including the load method of existing elements, to load the new content asynchronously, directly into the target element. An alternative is to retrieve the new content in the background, perhaps in a hidden element or as a string variable, and then insert it into the page at the appropriate location using JavaScript and jQuery. The final version of Mileage Stats does not retrieve HTML fragments from the server. However, this technique was investigated during development.
Content Retrieval Decisions in Mileage Stats
Now that we've arrived at the topic of using HTML fragments and templates, let's take a moment to talk about the design process our team followed while looking for the optimal solution. Since we knew that we wanted to support both a full-page reload and a hybrid experience for most of the features in Mileage Stats, we realized that we needed to generate HTML on the server as well as manipulate the HTML on the client. Our goal was to reuse as much HTML as possible, following the Don't Repeat Yourself (DRY) principle. Unfortunately, this was more difficult than expected. The markup necessary for the full-page reload scenarios was rendered as large chunks of HTML combined with data, while the markup needed for the SPI scenarios was focused and generally independent of the data.
Our strategy was to break the server-side HTML views into small, granular, partial views (small fragments of the UI). The client-side code could request the partial views using an Ajax call, and use the result to replace a small portion of the rendered UI. When we needed to reload a full page, the server could assemble the requested page from the granular partial views and send the complete page to the client. In this way, none of the HTML would be duplicated. We envisioned the final result as a set of composable blocks that could be used to build the UI structure for both scenarios.
Once we began implementing this strategy, we found that the code quickly became very complicated. Both the controller and the view code became intertwined and confusing. Many conditional blocks were required to share the numerous partial views between the server-side and client-side code.
Back on the client side, widgets were given URLs to request the partial views they needed. This had a number of drawbacks. First, it increased the surface area of the server by exposing the additional endpoints the widgets needed to request the necessary HTML fragments. Second, the number of Ajax calls made by the client increased because each widget needed to retrieve both the required data and the partial views. The increase in calls also created additional failure points and made the error handling code for the widgets more complex.
As an alternative, we had considered using a client-side templating engine such as the jQuery Templates plug-in. That would have involved using the server-generated HTML for the full-page reload experience, and creating a second set of client HTML templates for use only on the client. We initially decided against this approach because we thought that we would be duplicating all of the HTML code that was generated on the server.
However, after working through the difficulties of managing the small partial views, we decided that it would actually be easier to maintain the application if we used two methods to generate the HTML—one on the server side and one on the client side—so we chose to rewrite the application using the jQuery Template plug-in. Ensuring that the HTML that was duplicated in both the server and client code remained synchronized was a bit of a challenge. However, we were able to mitigate this issue through developer diligence when changing HTML or HTML templates, through test automation, and through either paired programming or code reviews (a practice we already followed to mitigate other risks). There were also several cases where we intentionally generated different HTML on the client than we generated on the server in order to accommodate the different UIs for the JavaScript-enabled experience and the non-JavaScript experience. These situations were handled on a case-by-case basis and communicated to the entire team.
Using Templates to Retrieve and Generate HTML
As mentioned in the previous section, another option for generating HTML is to use HTML templates. The idea behind templates is fairly simple: given a set of data and a template with placeholders for the data, you merge the HTML in the template with the data to generate an HTML fragment that can be inserted into the DOM. Templates provide the opportunity for generating HTML dynamically without the need for a lot of JavaScript code. This approach also allows you to generate the HTML using standard design tools, and to insert the placeholders for the data afterwards.
There are a number of HTML templating engines that can be used. As you saw, Mileage Stats uses the jQuery Templates plug-in because it was a natural fit after the team decided to use jQuery and jQuery UI.
Anatomy of a jQuery Template
jQuery templates consist of HTML and placeholders for data. By convention, jQuery templates usually reside in a script element that has a unique value for its id attribute, and a type attribute set to type="text/x-jquery-tmpl". The template script element can be located within any HTML element in the structure; it does not have to be located where the result will be displayed.
The id attribute allows the jQuery template plug-in to locate the template. The type attribute is the Internet media type (also known as the MIME Type or Content Type), and the value shown above is unknown to browsers. When the browser parses the HTML, it will not parse the content of any section with the unknown type, which is the desired result.
The following example shows part of the template used in Mileage Stats to generate the Statistics panel located on the Dashboard screen. The ${…} expression is a placeholder within the template that the jQuery Template plug-in uses to render a JavaScript value or expression when the data is applied to the template. The generated HTML fragment will contain several values, including the value of the AverageFuelEfficiency property of the data applied to the template. Only the section containing the first of these placeholders is shown in this listing.
// Contained in _TemplateFleetStatistics.cshtml
<script id="fleet-statistics-template" type="text/x-jquery-tmpl">
<h1>Summary Statistics</h1>
<div class="statistic mile-per-gallon">
${mstats.makeMPGDisplay(AverageFuelEfficiency)}<span
class="units">mpg</span></div>
...
</script>
The template in this example runs other JavaScript code to create a string representing the value that is inserted into the placeholder. It calls the mstats.makeMPGDisplay method, which formats the AverageFuelEfficiency value of the original data to ensure that it displays correctly.
The example below shows the _applyTemplate method, which uses the previous template to generate an HTML fragment, and places this fragment in the element with the id of statistics-content.
// Contained in mstats.statistics.js
_applyTemplate: function (data) {
var options = this.options,
$template = $(options.templateId);
...
this.element.find('#statistics-content')
.html($template.tmpl(data));
}
When the user runs the application, the following HTML is generated:
// Run-time HTML
<div id="statistics-content">
<h1>Summary Statistics</h1>
<div class="statistic mile-per-gallon">24<span
class="units">mpg</span></div>
...
</div>
For more information on the jQuery Template plug-in and authoring templates, see "jQuery Templates" in the "Further Reading" section.
Server-Side Rendering and jQuery Templates
jQuery Templates are a powerful feature of the jQuery library. Developers can use their chosen server-side rendering engine to dynamically create or inject values into jQuery templates on the server instead of, or in addition to, having the templates combined with data on the client. This dynamic server-side rendering of the template provides many additional options when developing your application.
In one respect, the approach of populating a template on the server instead of on the client resembles the approach described earlier of retrieving HTML fragments that contain the final HTML for display. There is no requirement to merge the data and template on the client. However, there is no reason why you cannot combine both approaches. Your server code could populate some of the dynamic content in the template, but leave other placeholders in the content that will be populated by merging the template with data on the client.
Mileage Stats uses server-side code to generate jQuery templates in several places. For example, templates were used in cases where the URLs for buttons and links in the rendered page depended on where the application was deployed. For example, if the application is deployed in a subfolder instead of in the root of the website on the server, the URL must include the folder name.
Mileage Stats jQuery templates are in the Views\Shared folder of the application and use the "_TemplateNAME.cshtml" naming convention so that they will be processed by the Razor rendering engine. This allows the server-side code to inject values into the template HTML before it is sent to the client. For example, the @Url.Action view helper method can render the correct URL of the add action of the vehicle controller, whether or not the application is deployed in a subfolder on the server, as shown here.
<-- Contained in _TemplateVehicleList.cshtml -->
<script id="mstats-add-vehicle-button-template" type="text/x-jquery-tmpl">
<div class="framed command section">
<div>
<a data-action="vehicle-add-selected"
href="@Url.Action("add", "vehicle")">+ Add Vehicle</a>
</div>
</div>
</script>
The _TemplateVehicleList template shown above is processed by the Razor rendering engine using the @Html.Partial view helper method in _Layout.cshtml, as shown below.
<!-- Contained in _Layout.cshtml -->
@Html.Partial("_TemplateVehicleList");
The HTML fragment shown below is the rendered _TemplateVehicleList template when the web application is deployed to the root web site on the server.
<!-- Rendered to the browser at runtime -->
<script id="mstats-add-vehicle-button-template" type="text/x-jquery-tmpl">
<div class="framed command section">
<div>
<a data-action="vehicle-add-selected"
href="/vehicle/add">+ Add Vehicle</a>
</div>
</div>
</script>
If, instead, the web application is deployed to the MileageStats folder in the root website, the rendered HTML will be the code shown below. You can see the folder name in the value of the href attribute.
<!-- Rendered to the browser at runtime -->
<script id="mstats-add-vehicle-button-template" type="text/x-jquery-tmpl">
<div class="framed command section">
<div>
<a data-action="vehicle-add-selected"
href="/MileageStats/vehicle/add">+ Add Vehicle</a>
</div>
</div>
</script>
Now the template can be used within the client application, and will provide the correct URLs for the current deployment on the web server.
Summary
There are three key technologies that work together in the browser to enable interactive applications: HTML, CSS, and JavaScript. HTML specifies the structure of the elements available within the UI, CSS controls the styling and positioning of those elements, and JavaScript allows the elements to respond to user gestures and other events such as timers and animations.
Within an application, there is often a requirement to manipulate the HTML that generates the page the user sees, and there are a number of options for selecting the HTML elements whose appearance or behavior you wish to change. Of these, the use of relative position provides the best performance, while using data- attributes can significantly help with both the selection of HTML elements and the transmission of metadata to the client code. Data- attributes also help to ensure separation between the HTML and the metadata the application requires.
Finally, you saw how you can create new HTML structure within a page by using JavaScript and jQuery, by retrieving fragments of HTML from the server, or by using jQuery Templates on the client to combine data retrieved from the server with HTML included in the templates.
Further Reading
jQuery Home Page: http://jquery.com
jQuery Browser Compatibility: http://docs.jquery.com/Browser_Compatibility
jQuery Selectors: http://api.jquery.com/category/selectors
jQuery Templates: http://api.jquery.com/category/plugins/templates
For more information on how the server can provide the correct initial HTML to the client, see "Providing HTML Structure" in Chapter 11, "Server-Side Implementation."
For more detail on the BBQ plug-in and the use of a state object, see Chapter 9, "Navigation."