Customizing LiftWeb’s error/warning/notice messages
After some CRUD experiments with LiftWeb, I'm really in love with the "view first" paradigm. Specially because the XHTML bindings are very webdesigner-friendly.
One big limitation is the <lift:msgs> snippet - it produces a mandatory "Error" title. This obligatory text is unacceptable when developing enterprise applications. Hopefully, there's an workaround: you can create a snippet named "Msgs" with a "render" method. Your snippet will "override" the builtin (without overriding the class), like this:
package mypkg.snippets class Msgs { def msgs(cls : String, ms : List[NodeSeq]) = ms match { case Nil => Nil case x => <div class={ cls }>{ ms.flatMap(m => <p>{ m }</p>) }</ul> } def render(xml : NodeSeq) : NodeSeq = <div id={ LiftRules.noticesContainerId }> { msgs("msgError", noIdMessages(errors)) } { msgs("msgWarning", noIdMessages(warnings)) } { msgs("msgNotice", noIdMessages(notices)) } </div> }
This little snippet will emit messages using <div> and <p> instead of <ul> and <li> (with that hardcoded title block). And, since you have the control over the snippet, you can put stub code inside <lift:msgs/> and allow your webdesigner use their favorite WYSIWYG editor:
<lift:msgs> <div class="msgError"> <p>name must have 3 charactes</p> </div> </lift:msgs>
Copying html attributes on Lift bindings
Since Java and Scala are complementary, I will post Scala-related stuff here, too (without creating a new blog).
Today's tip refers to Lift (Scala's JavaEE/Rails/Grails/Django). Suppose your webdesigner gives you this XHTML:
<input type="text" class="x" style="width: 100px;"/>When you bind this using Lift, one of possible solutions is surround it with a tag:
<mybind:myfield><input type="text" class="x" style="width: 100px;"/></mybind:myfield>Please notice I put it without spaces or newlines between "mybind:myfield" and "input" - this will be important later.
I prefer this way because this XHTML can be opened in other applications, like Firefox and Dreamweaver, making life easier for the webdesigner.
Then, if I bind it using old mama's recipe:
bind("mybind", form, "myfield" -> myfield.toForm)
Lift will remove my "input" tag and replace it - destroying "class" and "style". To solve this, I merge "toForm" tag with original attributes:
def merge(form : => Box[NodeSeq])(input : NodeSeq) : NodeSeq = { var in = input.first var attrs = form.open_!.first.attributes new Elem(in.prefix, in.label, in.attributes append attrs, in.scope, Group(in.child)) } bind("mybind", form, FuncBindParam("myfield", merge(myfield.toForm)))
Function "merge" takes the form and returns a function that receives the original XHTML and translates it into XHTML with Lift's attributes ("id", "name", "lift:gc", etc). I guess I can improve it somehow, but works great. And, since I'm keeping only the first child of source XHTML, you need to keep "bind" and "input" together (as I said before).
I know I can use a map function to ignore whitespaces, but I'll leave it as an later exercise (for you and for me, too).