-
TypeSafe URLs in Lift
Par divarvel dans Web Development le 3 Août 2011 à 16:07I've been working for a few months with Lift, a really nice web framework. Lift takes ideas and concepts from a few other frameworks. The main difference between Lift and other frameworks is that Lift does not enforce MVC (even though you can do MVC if you want to). Lift's approach is view-first, which means you first create the view, then you tell which part of the code will take care of dynamism.
For more propaganda: Seven Things and Web Framework Manifesto
I'll explain how to build REST-y URLs in a type-safe manner, with the SiteMap. The goal is to access a blog post with this kind of URL: /posts/{id}
I'll illustrate this article with something tremendously original: a Blog, with posts. I'll use mongoDB as a backend, but it's not really important.
The model
It's a simple blog post. Let's just assume the author is stored in plain text.
class BlogPost private() extends MongoRecord with ObjectIdPk[BlogPost] {
def meta = BlogPost
object author extends StringField(this, 32)
object date extends DateField(this)
object content extends TextareaField(this, 500)
val url = … // We'll see that later
}
object BlogPost extends BlogPost with MongoMetaRecord[BlogPost] {
val entry = … // We'll see that later
def calcHref(bp: BlogPost) = … // We'll see that later
}The template
Nothing special there. Just save it in
webapp/posts.html. We have ah1for the title, apfor some info, and apfor the actual post.<div class="lift:surround?with=default;at=content">
<div class="lift:BlogPostSnippet">
<h1 class="title"></h1>
<p class="info"></p>
<p class="blogpost"></p>
</div>
</div>The snippet
This class will be instantiated with the blog post we want to display. We just inject data in the markup.
class BlogPostSnippet(val blogPost: BlogPost) {
def render = ".title *" #> blogPost.title.is &
".info *" #> ("By " + blogPost.author.is) &
".blogpost *" #> blogPost.content.is
}The SiteMap
So, back to
BlogPost.entry, where the magic happens. We define an entry of the sitemap which is associated with theBlogPostclass. The two first arguments are the id and the displayed name of the link. The third argument is a function which takes aStringand returns aBox[BlogPost]. This tells Lift how to instantiateBlogPostSnippetwith the right blog post. The fourth argument is used to create an url from a given blog post. Finally, we specify the address and tell Lift we don't want this entry to show up in the menus.The calcHref method is just a shortcut.
class BlogPost … {
/* Snip */
val url = BlogPost.calcHref(this)
}
object BlogPost … {
val entry = Menu.param[BlogPost](
"blogpost",
S ? "blogpost.view",
(id: String) => BlogPost.find(id),
(bp: BlogPost) => bp.id.is
) / "posts" / * >> Hidden
def calcHref(bp: BlogPost) = entry.toLoc.calcHref(bp)
}Don't forget to add
BlogPost.entryto theSiteMapval entries = /* other entries */ :: BlogPost.entry :: Nil
LiftRules.setSiteMap(SiteMap(entries: _*))
Next time I'll show you how to display comments after the blog post, while keeping the HTML structure of every comment in the template :)
Tags : liftweb, scala, sitemap, lift, typesafe, url






