Examples
Examples
Java |
You should look at |
---|---|
C# |
You should look at |
Python |
You should look at |
Fill-a-Table Example
The manner in which a template engine handles filling an HTML table with data often provides good insight into its programming and design strategy. It illustrates the interaction of the model and view via the controller. Using StringTemplate
, the view may not access the model directly; rather the view is the passive recipient of data from the model.
First, imagine we have objects of type User
that we will pull from a simulated database:
Java |
public static class User { String name; int age; public User(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } |
---|---|
C# |
public class User { string name; int age; public User(string name, int age) { this.name = name; this.age = age; } public string Name { get { return name;} } public int Age { get { return age; } } } |
Python |
class User(object): def __init__(self, name, age): self.name = name self.age = age def getName(self): return self.name def getAge(self): return self.age |
Our database is just a static list:
Java |
static User[] users = new User[] { new User("Boris", 39), new User("Natasha", 31), new User("Jorge", 25), new User("Vladimir", 28) }; |
---|---|
C# |
static User[] users = new User[] { new User("Boris", 39), new User("Natasha", 31), new User("Jorge", 25), new User("Vladimir", 28) }; |
Python |
users = [ User("Boris", 39), \ User("Natasha", 31), User("Jorge", 25), User("Vladimir", 28) ] |
Here is my simple overall page design template, page.st
:
<html> <head> <title>$title$</title> </head> <body> <h1>$title$</h1> $body$ </body> </html>
The body attribute of page.st
will be set to the following template users.inline.st
by my web server infrastructure (part of the controller):
<table border=1> $users:{ <tr> <td>$it.name$</td><td>$it.age$</td> </tr> }$ </table>
Again, it
is the default attribute passed to a template when you apply that template to an attribute or attributes. it.name
gets the name
property, if it exists, from the it
object. That is, StringTemplate
uses reflection to call the Name
property, then the get_Name()
method then others including the getName()
method on the incoming object. By using reflection, I avoid a type dependence between model and view.
Now, imagine the server and templates are set up to format data. My page definition is part of the controller that pulls data from the model (the database) and pushes into the view (the template). That is all the page definition should do--interpret the data and set some attributes in the view. The view only formats data and does no interpretation.
Java |
public static class UserListPage extends SamplePage { /** This "controller" pulls from "model" and pushes to "view" */ public void generateBody(StringTemplate bodyST) throws Exception { User[] list = users; // normally pull from database // filter list if you want here (not in template) bodyST.setAttribute("users", list); } public String getTitle() { return "User List"; } } |
---|---|
C# |
public class UserListPage : SamplePage { /** This "controller" pulls from "model" and pushes to "view" */ public void generateBody(StringTemplate bodyST) { User[] list = users; // normally pull from database // filter list if you want here (not in template) bodyST.SetAttribute("users", list); } public string getTitle() { return "User List"; } } |
Python |
class UserListPage(SamplePage): ## This "controller" pulls from "model" and pushes to "view" # def generateBody(self, bodyST): self.list = users # normally pull from database # filter list if you want here (not in template) bodyST["users"] = self.list def getTitle(self): return "User List" |
Notice that the controller and model have no HTML in them at all and that the template has no code with side-effects or logic that can break the model-view separation. If you wanted to only see users with age < 30, you would filter the list in {{generateBody()} rather than alter your template. The template only displays information once the controller pulls the right data from the model.
Pushing factorization further, you could make a row.st
component in order to reuse the table row HTML:
<tr> <td>$it.name$</td><td>$it.age$</td> </tr>
Then the user list template reduces to the more readable:
<table border=1> $users:row()$ </table>
Naturally, you could go one step further and make another component for the entire table (putting it in file table.st
):
<table border=1> $elements:row()$ </table>
then the body template would simply be:
$table(elements=users)$
Internationalization and localization
StringTemplate provides a simple and effective method for localizing web pages. The goal is to alter a page based upon the locale; that is, page strings or other content must change depending on a locale. This article not only illustrates how to make a pages change text depending on locale, it shows how the same site may easily have two different skins (site "looks").
This technique works well in practice for real sites. Schoolloop.com is a case in point. Click on the link that says "en espanol" to flip the site into Spanish mode. The exact same templates are used; all strings are pulled from a serious of resource bundles. There is no duplication of pages to change the strings.
Multiple skins
First let's look at multiple skins in order to show how templates are loaded for this example.
Multiple site looks are organized into their own directories. A StringTemplateGroup
object rooted at that directory will load templates directly from there. In my example, I made two skins, blue and red. Here is how the group is loaded:
Java |
String skin="blue"; ClassLoader cl = Thread.currentThread().getContextClassLoader(); // get a template group rooted at appropriate skin String absoluteSkinRootDirectoryName = cl.getResource(skin).getFile(); StringTemplateGroup templates = new StringTemplateGroup("test", absoluteSkinRootDirectoryName); |
---|---|
C# |
string skin="blue"; // get a template group rooted at appropriate skin // we are using a path relative to our application assembly's location string absoluteSkinRootDirectoryName = Path.Combine(new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory).Parent.FullName, skin); StringTemplateGroup templates = new StringTemplateGroup("test", absoluteSkinRootDirectoryName); |
Then, when you ask for an instance of a page, it pulls from whichever directory skin
is set to:
Java |
StringTemplate page1ST = templates.getInstanceOf("page1"); |
---|---|
C# |
StringTemplate page1ST = templates.GetInstanceOf("page1"); |
Here is page 1 in the blue skin:
<html> <title>$strings.page1_title$</title> <body> <font color=blue> <p>$strings.intro$ <p>$strings.page1_content$ </font> </body> </html>
and here is page 2:
<html> <title>$strings.page2_title$</title> <body> <font color=blue> <p>$strings.page2_content$ </font> </body> </html>
For the red here is page 1:
<html> <title>$strings.page1_title$</title> <body> <font color=red> <h1>$strings.page1_title$</h1> <p>$strings.intro$ <hr> <p>$strings.page1_content$ </font> </body> </html>
and page 2:
<html> <title>$strings.page2_title$</title> <body> <font color=red> <h1>$strings.page2_title$</h1> <hr> <p>$strings.page2_content$ </font> </body> </html>
The thing to note is that there is no text, just formatting in these page templates. Those strings from the strings
attribute are used for all text that could change per locale.
Localizing template strings
For a more, please see http://www.cs.usfca.edu/~parrt/papers/i18n.pdf.
Once the code knows how to load templates, the locale must dictate which strings are displayed. The templates clearly have attribute references pulling from an object instance labelled strings
(it could be a hash table).
Java |
Assuming that there are a few different strings and that they are stored in a Java properties file called [language-code].strings where [language-code] is the two-letter language code. Here is the intro=Welcome to my test page for internationalization with StringTemplate page1_title=Page 1 testing I18N page2_title=Page 2 testing I18N page1_content=This is page 1's simple page content page2_content=This is page 2's simple page content and here is the intro=Bienvenue sur la page de test d'internationalisation avec StringTemplate page1_title=Page 1 test de I18N page2_title=Page 2 test de I18N page1_content=Le contenu de la page 1 page2_content=Le contenu de la page 2 To load these per the current locale is pretty easy: // use Locale to get 2 letter country code for this computer Locale locale = Locale.getDefault(); String defaultLanguage = locale.getLanguage(); // allow them to override language from argument on command line String language = defaultLanguage; if ( args.length>0 ) { language = args[0]; } // load strings from a properties files like en.strings URL stringsFile = cl.getResource(language+".strings"); if ( stringsFile==null ) { System.err.println("can't find strings for language: "+language); return; } Properties strings = new Properties(); InputStream is = stringsFile.openStream(); strings.load(is); The StringTemplate page1ST = templates.getInstanceOf("page1"); page1ST.setAttribute("strings", strings); To generate the page, just say: System.out.println(page1ST); |
---|---|
C# |
For this example, I'm assuming that I have a few different strings and that I'm storing them in resource files called Content.Strings.resx. The langauge specific alternatives are named Content.Strings[language-code].resx where language-code is the two-letter language code. Here are the name/value entries stored in intro=Welcome to my test page for internationalization with StringTemplate page1_title=Page 1 testing I18N page2_title=Page 2 testing I18N page1_content=This is page 1's simple page content page2_content=This is page 2's simple page content and here are the entries in the intro=Bienvenue sur la page de test d'internationalisation avec StringTemplate page1_title=Page 1 test de I18N page2_title=Page 2 test de I18N page1_content=Le contenu de la page 1 page2_content=Le contenu de la page 2 To load these per the current locale is pretty easy: // allow them to override language from argument on command line String language = defaultLanguage; if ( args.length>0 ) { language = args[0]; } Thread.CurrentThread.CurrentUICulture = new CultureInfo(language); ResourceManager resMgr = new ResourceManager("ST.Examples.i18n.Content.Strings", typeof(ResourceWrapper).Assembly); ResourceWrapper strings = new ResourceWrapper(resMgr); The Here's are the relevant bits of the wrapper class: class ResourceWrapper { ResourceManager mgr; public ResourceWrapper(ResourceManager mgr) { this.mgr = mgr; } public string intro { get { return mgr.GetString("intro"); } } ....... ....... public string page2_content { get { return mgr.GetString("page2_content"); } } } And here's how this is passed directly to the StringTemplate page1ST = templates.GetInstanceOf("page1"); page1ST.SetAttribute("strings", strings); To generate the page, just say: Console.Out.WriteLine(page1ST); |
The output will be (for the en
locale):
<html> <title>Page 1 testing I18N</title> <body> <font color=blue> <p>Welcome to my test page for internationalization with StringTemplate <p>This is page 1's simple page content </font> </body> </html>
If I change the locale to fr
then without changes templates, the following is generated:
<html> <title>Page 1 test de I18N</title> <body> <font color=blue> <p> <p>Le contenu de la page 1 </font> </body> </html>
Source and compilation
Java |
Here is the code including the strings and skins: |
---|---|
C# |
Here is the code including the strings and skins:
You naturally need ANTLR too; get it here. You can compile this example like this: nant -t:net-1.1 |
Python |
Here is the code including the strings and skins: |
Summary
In summary, the key element to demonstrate here is that you do not have to duplicate all of your templates to change what they say. You can leave the formatting alone and, with a simple hashtable pulled from a data file, push in the proper strings per the locale. I also took the opportunity to show off just how easy it is to make multiple site skins.