Web Development Shouldn't Be Hard!
Published by Matt Hicks under on Thursday, November 03, 2011I've spent most of my career developing in Java for the web. I've used the full range of frameworks: Servlets, JSPs, Struts, JSF, Wicket, and dozens more. Early in my career I did a lot of Swing development. Now I grant there are a lot of inherent problems there as well, but nowhere near the painfulness of web development. At least in Swing you just have to know Java. In the majority of web frameworks you have to know HTML, CSS, JavaScript, whatever your server-side technology is, and then you have to deal with the client-server model and even stateful / stateless decisions. It's such a mess and although frameworks like Vaadin do a phenomenal job of removing much of that pain, the norm in the Java world is still focused towards a complicated web where you have to deal with cross-browser compliance, HTML 5 compliance, and a slew of crappy languages most developers would rather gouge their eyes out than have to stare at all day long.
I've recently been looking at templating frameworks for larger front-facing web sites as larger organizations like where I work have entire departments to create the HTML and design the pages and then the developers have to hook up the dynamic elements. We've been talking about moving more toward Wicket, but as I've been using Wicket more and more I find myself liking it less and less. I dislike the one-to-one mapping that has to take place between the template and the Java code first of all. Secondly, the more complex features are not at all intuitive (at least to me...I'm sure some think it's the most rational thing ever and will readily tell me so). However, I very much like the concept that designers can work completely independently of the developers and can create the markup and even test it with bogus information that is replaced at runtime with production content. To that end I started thinking about what I could see as a better templating system. I like templating frameworks like Mustache, but unfortunately for many cases it's just not powerful enough. After further thought I came up with a similar concept to Wicket that would parse through XHTML content as XML and then provide components that would manipulate those Elements based on the HTML "id" associated. This means that designers assign ids to their elements (like many would already for CSS and JavaScript to access) and then the developer can opt to interact with these elements when the page is being rendered.
For this prototype I opted to use Scala as native XML support made it so much easier. So I started with the idea of a "Component" with the simple concept of Element to Element conversion:
import xml.Elem trait Component { def render(elem: Elem): Elem }
I then created a couple simple implementations of the Component:
import xml.{Text, Elem} class Label(textFunction: () => String) extends Component { def this(text: String) = this(() => text) def render(elem: Elem) = { val text = textFunction() match { case null => "" case s => s } Elem(elem.prefix, elem.label, elem.attributes, elem.scope, Text(text)) } }
import xml.Elem class Content(content: Elem) extends Component { def render(elem: Elem) = Elem(elem.prefix, elem.label, elem.attributes, elem.scope, content) }
The Label component simply replaces the content of the element with the supplied text (offering function support for more advanced usage) and the Content component even more simply just replaces the content of the element with the passed content.
Now I needed a way to map the Components to ids and render the XML:
import component.Component import xml.{Node, Elem} import xml.transform.{RuleTransformer, RewriteRule} import java.lang.ThreadLocal object Xemplate { private val components = new ThreadLocal[Map[String, Component]] def apply(components: Map[String, Component], elem: Elem): Node = { this.components.set(components) val result = apply(elem) this.components.set(null) result } protected[xemplate] def apply(elem: Elem): Node = { val transformer = new RuleTransformer(new Rewriter(components.get())) transformer(elem) } } class Rewriter(components: Map[String, Component]) extends RewriteRule { override def transform(n: Node) = n match { case elem: Elem => convert(elem) case node => node } def convert(elem: Elem) = { val id = elem.attribute("id").map(ns => ns.text).getOrElse("") components.get(id) match { case Some(component) => component.render(elem) case None => elem } } }
Fortunately Scala has some really powerful features to do exactly this. The RuleTransformer and RewriteRule classes provide the majority of the functionality I need immediately out of the box. I simply take in a Map of id to Component mapping along with the XML to process and when I find an element that has a mapped id I simply replace it with the Component's render result. I called it Xemplate just because I was being lazy and it's XML templating.
At this point I needed a way to simplify bringing this together into an individual page rendering so I created a Page class:
import component.Component import xml.Elem class Page(xhtml: Elem) { private var components = Map.empty[String, Component] def register(id: String, component: Component) = components += id -> component def render() = Xemplate(components, xhtml) }
Now I have ability to instantiate a Page, register Component instances to ids, and then render to get the modified XML. Lets see an example of this in action:
val xhtml =Original content val body = Yes, I can simply inject XML!
val page = new Page(xhtml) page.register("title", new Label("Example Title")) page.register("body", new Content(body)) val modified = page.render() println(modified)
We simply create a Page instance with our XHTML content, register a Label for the title with "Example Title" as the text, and set a Content component for the body id. This is the resulting content we should see:
Example Title Yes, I can simply inject XML!
Lets get a little bit more complicated though. One of the most painful things in a framework like this is often cycling over a list of elements and trying to populate them onto the page. In Wicket this is relatively effectively done, but you often end up with a lot of anonymous subclasses layering down so you have a reference to the current item being rendered in the list. I took a different approach to this by creating what I called an EntryHolder that is responsible for holding the current instance being iterated over:
import java.lang.ThreadLocal class EntryHolder[T] extends Function0[T] { val current = new ThreadLocal[T] def apply() = current.get() }
As you can see this is an extremely simple wrapper over ThreadLocal. For the purposes of testing I could have just had a var, but if we ever want to handle a web environment we would presumably have several threads handling requests at the same time and obviously would want to run the risk of transferring one pages' content to someone elses' page.
Now we need to create our Sequence class that allows us to iterate over items:
import xml.Elem import xemplate.{Xemplate, EntryHolder} class Sequence[T](items: Seq[T], holder: EntryHolder[T]) extends Component { def render(elem: Elem) = { val content = for (item <- items) yield { holder.current.set(item) for (n <- elem.child) yield n match { case elem: Elem => Xemplate(elem) case node => node } } Elem(elem.prefix, elem.label, elem.attributes, elem.scope, content.flatMap(s => s): _*) } }
Though more complex than Label or Content, it's still relatively simple. We iterate over the items, set the current item to the EntryHolder, and then ask Xemplate to process the child elements so they can be templatized as well.
Lets see an example of this:
val xhtml =
object NameHolder extends EntryHolder[String]
I simply instantiate my Page with the XHTML content, create a List of names, register "names" to a new Sequence with the names list and NameHolder, then I register "name" to a new Label with NameHolder as the text function. Notice above that EntryHolder extends Function0[T] allowing us to pass it in as a String function to generate the String. What this does is the function is called per iteration on the names list and NameHolder has the current name associated and returns it. This keeps us from having to deal with layering our components for visibility by creating companion objects that are visible for a specific function.
The resulting XHTML should look like this:
- Alan
- Brad
- Chris
- Don
- Edward
That's about it. Again, where possible I would still recommend using something like Vaadin as it keeps you from dealing with the majority of the evils of the web, but I think this little example demonstrates an extremely simple and fast (performance tests on a 1 meg XML file averaged around 25ms per render) templating system that doesn't have to overcomplicate or obscure development. If anyone is interested in turning this into a full-fledged project feel free to do so and build on it. I would appreciate the credit, but the code above is free to use for anything you see fit.
2 comments:
If you detest doing website programming why do you spend time working on code that shields YOU from "the evils of the web"?
The people that build great template engines know what they are dealing with and will not make the mistake of injecting multiple ids with the same name into a html document.
Expand your knowledge and do it right.
@xwero, indeed you are correct that the Sequence component does have the problem of duplicating ids. Like I state multiple times in this post, this is meant as a starting point, not a framework meant to be used in production.
Yes, it is ironic that I've spent most of my career writing web frameworks when I really do despise web programming, but as a developer I see a problem and desire to fix it.
Post a Comment