Templates, snippets, and views

Một phần của tài liệu Manning lift in action the simply functional web framework for scala (Trang 131 - 151)

Any web framework, irrespective of language or implementation style, will ultimately need to generate markup to pass back to the browser. With this in mind, the templat- ing system must be flexible but also easy to use because it’s highly likely that develop- ers and designers will spend a lot of time using the template system. Lift takes this to heart and recognizes that templating should not just be an afterthought to flush markup to the client side.

In the following subsections, we look at how you can implement different tem- plating strategies in Lift, and we highlight some of the best practices for designing your snippets.

6.1.1 Templates

At its core, you can think of Lift’s entire template system as a mechanism for replacing XML nodes. The markup templates are essentially simple indicators to Lift that it ought to replace the markers with something dynamic from a particular snippet, a view computation, or another template. This kind of system allows for a high degree of reuse within templates, and Lift provides a selection of additional utilities to further reduce the amount of repetition in your applications.

This section covers the core components and strategies you’ll need to wield Lift’s template system in the vast majority of situations: from building common tem- plates to avoiding repeating regularly used elements through to handling Lift’s HTML5 support.

SURROUNDS

When dealing with presentation markup and templates, the same rules apply, and it’s unfortunately all too easy to repeat yourself and create a maintenance nightmare.

Developers by their very nature hate to repeat themselves; we’re always looking for ways to reuse this, that, or the other. To that end, Lift provides a mechanism called sur- rounds that allow you to build hierarchies of templates, with grandparent, parent, and child relationships, where the child inherits from both the parent and the grandpar- ent. You can have as many levels in this hierarchy as you require. The most common use case for such a technique is to save repeating the <head> elements and other bits of page furniture that are common over all your pages.

To give this concept a little more substance, suppose the following listing shows the contents of a parent template called default.html found in webapp/templates-hidden.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"

"http://www.w3.org/TR/html4/strict.dtd">

<html xmlns:lift="http://liftweb.net" xmlns="http://www.w3.org/1999/xhtml">

<head>

Listing 6.1 Example of a parent template

109 Templates, snippets, and views

<title>My Application</title>

</head>

<body>

<lift:bind name="content" />

</body>

</html>

For the most part, this is pretty standard markup. But you’ll notice that there’s a spe- cial Lift element called bind B. This marks a location in your template where child templates will be merged in place. The name attribute must be unique, but otherwise you’re free to name these bind locations whatever you like.

You might be wondering how you can actually get content into this placeholder.

That’s simple. For child templates binding to a single placeholder, the surround ele- ment is your friend. Consider this example of using surround:

<lift:surround with="default" at="content">

<p>Will be displayed in the placeholder position</p>

</lift:surround>

As you can see, the at attribute denotes the name defined in the parent template.

When the page containing the surround is loaded, Lift wraps the content defined in the page template with that of the parent, defined by the with attribute. By default, it’s assumed that parent templates are located in the templates-hidden directory in the webapp folder, but if you’d rather place the template somewhere else, you simply have to do something like this:

<lift:surround with="/path/from/webapp_dir/template" at="content">

Be sure to not place .html at the end of the with attribute value, though.

This simple system covers a lot of use cases, but what if you need to bind to multi- ple placeholders in the parent template? In that case, there’s a handy helper called

<bind-at> that’s perfect for such a situation. Lift templates can only have a single root element because they must all be well-formed XML markup, so when you want to use bind-at, your implementing template would look like the next listing.

<lift:surround with="default" at="content">

<lift:bind-at name="another_placeholder">

<p>Wow, I am going elsewhere!</p>

</lift:bind-at>

<p>Will be displayed in the placeholder position</p>

</lift:surround>

The <bind-at> element sits within the surround, and the content defined therein is placed at the named bind point, irrespective of where that lives on the page.

As a side note, if you have a template that’s only updating fragments of pages utiliz- ing <lift:bind-at>, but you attempt to supply a template that effectively has more than one root node, the markup parser will explode with an error because well- formed XML must have a single root node. In this situation, you may not want to wrap

Listing 6.2 Using multiple bind placeholders in a child template Bind placeholder

B

your fragments with a <div> or other erroneous markup as it would impact your CSS implementation, so there’s a helpful wrapper called children. Simply wrap the group of nodes with <lift:children> and the template will pass markup validation—this won’t cause any adverse impact on the ultimate rendering in the browser.

NOTE You may be wondering if all this processing of markup and templates has a performance impact. To a degree, it does, but Lift is clever enough to realize that when you’re running in production mode you’re unlikely to be changing your templates, so by default Lift caches the templates. If, for what- ever reason, you want to alter this behavior or stop the caching, just look at LiftRules.templateCache.

Using layers of surrounds can be an extremely effective technique for reducing markup duplication. But there are some use cases that would be somewhat cumber- some with surrounds, and an embedding strategy would be more effective for directly reusing content.

CONTENT EMBEDDING AND PARTIAL TEMPLATES

One of the things that <lift:surround> doesn’t give you is the ability to easily reuse a segment of markup in multiple places in an ad hoc fashion. It’s doable, but it wouldn’t be overly elegant, and you’d certainly be better served by an alternative solution. This is exactly what content embedding was designed to do.

Let’s assume that you have the following markup, and it represents something that you want to repeatedly use in your application:

<div style="amazing">

<p>Did you realize quite how amazing this is</p>

</div>

If you wanted to embed that markup in an ad hoc fashion in any template, all you need to do is place it in the templates-hidden directory and then call <lift:embed> in the template where you’d like that content to be imported. Here’s an example:

<lift:embed what="_amazing" />

As a general convention, we recommend denoting your partial templates in some way so that it’s immediately obvious that they’re different from your main surround tem- plates. Typically, I prefix their names with an underscore, but this is just a suggested convention; it’s not a hard and fast rule required to make the system work.

HEAD RESOURCES

It’s very common to want to externalize the surrounding <head> content and other global resources into a single template. Doing this can really cut down on mainte- nance, but it does, like most things, have a downside too. Let’s assume that you have a single page that requires some extra CSS and JavaScript that deviates from the stan- dard <head> you have elsewhere, and that it’s an unacceptable weight to include in the main parent surround. Lift provides the solution in a feature called a head merge.

111 Templates, snippets, and views

In essence, head merge looks in your child (page) templates for the <head> ele- ment, and, upon finding it, will merge that together with the <head> from the sur- round template, giving you a rendered page that’s an amalgamation of the resources from child and parent templates. It couldn’t be simpler.

Lift also provides the reverse mechanism so you can insert content just before the closing </body> tag, which a lot of frontend developers like to do to boost page- loading speeds. Unsurprisingly, being the opposite of <head>, this mechanism is called tail, and it can be used like any other Lift tag:

<lift:tail>

<!-- stuff you want before the closing body -->

</lift:tail>

Both during the development cycle and after moving an application into production, you can get strange results when the client-side browser has cached the CSS, Java- Script, and perhaps even image files. Lift provides a simple helper to prevent the browser from using an incorrectly cached resource: <lift:with-resource-id>. Sim- ply wrap the element you want to have a specialized ID appended to with the with- resource-id tag like this:

<lift:with-resource-id>

<script type="text/javascript" src="js/example.js"><script>

</lift:with-resource-id>

Then when Lift renders your page, you’ll get a consistent URL that will only change when the application is rebooted. If you were wondering, the URL would be some- thing like this:

<script

type="text/javascript"

src="js/example.js?F1142850447932JLI=_"></script>

Altering the String => String function that lives in LiftRules.attachResourceId can easily modify this appended string.

DOCTYPES AND MARKUP VALIDATION

As Lift runs all your template markup through its rendering pipeline, it will automati- cally strip off any Document Type Definition (DTD) information you apply directly in the template, and it will render your pages with XHTML Transitional document type by default. In order to set the DocType to your preferred type, you need to apply the following in your Boot class:

import net.liftweb.common.Full

import net.liftweb.http.{LiftRules,Req,DocType}

LiftRules.docType.default.set((r: Req) => Full(DocType.xhtmlStrict))

Because the LiftRules.docType configuration parameter is a FactoryMaker, you can set a different document type based upon information in the request. For example, if the request was from an iPhone, you might want to render the content with a mobile

DocType rather than the standard default. FactoryMakers are covered in detail in chapter 14 (section 14.2.2).

Table 6.1 details the available DocType declarations that Lift provides and shows examples of their usage.

Because your application could feasibly end up with a large number of templates, vali- dating that they’re all correct can often be difficult. With this in mind, Lift supports automatic validation of application markup in the normal course of development. If any validation errors exist, a notice is pushed out to the browser explaining the error.

To enable a validator, configure the following option in your Boot class:

import net.liftweb.common.Full

import net.liftweb.http.{LiftRules,StrictXHTML1_0Validator}

LiftRules.xhtmlValidator = Full(StrictXHTML1_0Validator)

This example employs the XHTML Strict validation during development. Lift supplies a couple of validators by default; these are listed in table 6.2.

If you want to implement your own validator, just extend net.liftweb.http.Generic- Validator and provide the location of the relevant XSD.

HTML5 SUPPORT

When Lift was first conceived, XHTML was lined up to be the successor for the HTML standard, and as such XHTM required all templates to be valid XML documents. At the

Table 6.1 DocType declarations in Lift

DocType Usage

XHTML Transitional DocType.xhtmlTransitional

XHTML Strict DocType.xhtmlStrict

XHTML Frameset DocType.xhtmlFrameset

XHTML 1.1 DocType.xhtml11

XHTML Mobile DocType.xhtmlMobile

HTML5 DocType.html5

Table 6.2 Default template validators in Lift

Validator Usage

XHTML Strict LiftRules.xhtmlValidator =

Full(StrictXHTML1_0Validator)

XHTML Transitional LiftRules.xhtmlValidator =

Full(TransitionalXHTML1_0Validator)

113 Templates, snippets, and views

time of writing, HTML5 was starting to make a large impact on the web development world, and HTML5 appeared to be slowly but surely overtaking the XHTML standard, which was not adopted as broadly as the W3C might have hoped. All versions of Lift still fully support XHTML, and it’s the default mode for Lift applications, but as of ver- sion 2.2 onward, Lift fully supports HTML5. There is a parser for HTML5 templates and support within Lift’s own rendering pipeline for emitting HTML5 to the browser.

In order to enable HTML5 support in Lift, just add the following line to your Boot class:

import net.liftweb.http.{LiftRules,Req,Html5Properties}

LiftRules.htmlProperties.default.set((r: Req) =>

new Html5Properties(r.userAgent))

This configuration comes with a couple of oddities that relate to the strictness of HTML5. Specifically, HTML5 has some incompatibilities with XHTML templates and doesn’t like self-closed tags, like <lift:menu.builder />. It also doesn’t function with mixed-case tags, which are frequently used for snippets in Lift.

But there’s a solution; if you want HTML5 output but would prefer XHTML tem- plates, you can implement the following configuration in your Boot class:

import net.liftweb.http.{LiftRules,Req,XHtmlInHtml5OutProperties}

LiftRules.htmlProperties.default.set((r: Req) =>

new XHtmlInHtml5OutProperties(r.userAgent))

This configuration can be exceedingly helpful if you’re migrating an existing applica- tion to HTML5 from XHTML, or if you’d simply prefer to continue to build your tem- plates in the way you’ve become accustomed to. More information about Lift’s HTMLProperties system can be found on the Lift wiki: http://www.assembla.com/

spaces/liftweb/wiki/HtmlProperties_XHTML_and_HTML5.

DISPLAYING MESSAGES

If you’ve been reading this book from the beginning, you’ll be familiar with the S.notice, S.warning, and S.error methods. You can implement these methods in your snippet and pass them a String that represents a message you wish to pass back to the user.

In order to allow the designer to position and style these messages, Lift provides the <lift:msgs/> and <lift:msg/> helpers. These have a variety of options to allow you style the message and interface precisely.

The following listing shows an example of applying custom styles to the built-in notice system through <lift:msgs/>.

<lift:msgs>

<lift:error_msg>Error! The details are:</lift:error_msg>

<lift:error_class>errorBox</lift:error_class>

<lift:warning_msg>Whoops, I had a problem:</lift:warning_msg>

Listing 6.3 Applying custom styling to the Msgs helper

<lift:warning_class>warningBox</lift:warning_class>

<lift:notice_msg>Note:</lift:notice_msg>

<lift:notice_class>noticeBox</lift:notice_class>

</lift:msgs>

The sequence of nodes enclosed by <lift:msgs> allows you to control various aspects of the messages. Each type of notification has a pair of styling elements. The nodes ending in _msg denote the text to be prefixed to each type of notice display, whereas nodes ending with _class allow you to customize the CSS class that’s applied to the relevant display type.

One other interesting point about the notification system is that irrespective of whether it’s displaying notifications after a hard page reload or notifications in response to some AJAX function, they operate in exactly the same way and can be styled through a single mechanism. You can even get the message to dynamically fade out after a defined period of time. In your application Boot, simply define the following:

import net.liftweb.http.{LiftRules,NoticeType}

import net.liftweb.common.Full import net.liftweb.util.Helpers._

LiftRules.noticesAutoFadeOut.default.set(

(notices: NoticeType.Value) =>Full(2 seconds, 2 seconds))

For any messages that are defined in your application, this configuration will automat- ically fade them out after displaying for a period of 2 seconds, and it will take 2 sec- onds to conduct the fade out.

Despite the significant differences between all these methods, they’re all imple- mented using the same mechanism: snippets. Snippets are essentially sections of ren- dering logic, and the only difference between them and other rendering logic is that they’re shipped with Lift and are automatically made available to the template mecha- nism, so they appear to be built in. Otherwise, they aren’t leveraging anything that you couldn’t implement yourself. This should give you a good indication of how pow- erful the snippet idiom can actually be.

6.1.2 Snippets

Way back in chapter 1 (section 1.2.2), we first discussed Lift’s model of snippet opera- tion, and in the previous section you saw how Lift itself, builds on the concept of snip- pets to provide a rich templating system. In this section, we spend some time comparing and contrasting the different types of snippet you can create and cover some useful things to know when making your own snippets.

CSS TRANSFORMERS

Snippets are essentially a way of generating dynamically rendered content, and hav- ing a clean and effective way to actually work with the template markup is key. This is where Lift’s CSS transformers come in. Generally speaking, when you’re building interactive content in Lift, you create server-side controls via the SHtml object and then bind that to a particular sequence of XML nodes. In order to choose which

115 Templates, snippets, and views

nodes have which server controls, Lift supplies a CSS-style selectors API; these are also collectively known as CSS transformers because they take template markup as input and transform that to rendered output markup. Let’s take a look at a few exam- ples of using SHtml and Lift’s CSS transformers to understand the interplay between these two pieces.

The SHtml object has methods for creating text fields, check boxes, AJAX content, and much, much more. Most of the user interaction components you might want in your application can be found in SHtml. With this in mind, let’s take a look at a basic example of attaching a text box and submit button, along with the template code used to call it, as shown in the following listing.

import net.liftweb.util.Helpers._

import net.liftweb.http.SHtml class BasicExample {

def sample = {

var message = "Change this text"

"type=text" #>SHtml.text(message, message = _) &

"type=submit" #>SHtml.onSubmitUnit(

() =>println(message)) }

}

This listing shows a very simple class that has a lone method called sample. This method has a single variable inside called message that will temporarily hold the value that’s entered by the user before it’s printed to the console by the submit function.

The really important parts here are defined at B. At first look, this may look like rather strange syntax, but bear with me while we step through it.

CSS selector statements are defined in the following fashion:

"selector" #> thing-to-bind

The left-hand selector could be a variety of things, examples of which are detailed later in table 6.3. On the right side of the #> symbol is the content that you want to bind to that selector; this content could be a NodeSeq or some type that’s implicitly pro- motable via implicit conversion to CssSel. All the default implicit conversions are imported into scope via the importHelpers._ statement, so be sure to include that or the selectors won’t work as anticipated.

In this example, the elements being bound are SHtml controls for the required form. As this form only has two elements, we can just use the CSS-style selectors to grab the two elements from the template markup that have the attributes type="text" and type="submit". The template code in this instance would look like this:

<p class="l:basic_example.sample?form=post">

<input type="text" /><br />

<input type="submit" /></p>

Listing 6.4 Basic binding snippet

Make SHtml controls

B

Một phần của tài liệu Manning lift in action the simply functional web framework for scala (Trang 131 - 151)

Tải bản đầy đủ (PDF)

(426 trang)