Play framework - flexible layouts Subscribe

I'm lazy!

Yup - you'd think copy/paste is my programming paradigm... well, sometimes anyways.

One issue I had for a while was how to use layouts in my play framework templates.

The simple&stupid way is to have each page include its header/footer and you keep copy/paste-ing it all the time, let's look at it in the context of a page that would paint a list of wiki entries:

@(noteList:List[razie.wiki.model.WikiEntry], curTag: String, tags: model.Tags.Tags, msg: Seq[(String, String)], isSearch:Boolean)(implicit au:Option[model.User], request:Request[_])

@htmlHead("My TITLE", ...)

... // paint the content

@htmlBottom(false, Some(request))

Tons of problems with this approach, the least of which is reusing the content of this page elsewhere. One of the big ones is having to carry some arguments needed to paint the header/footer, like username and site URL (obtained from the request, for multi-site hosting): as you look at it, only the first argument, the noteList is really required here, the rest are there just to pass them to the htmlHead and htmlBottom.

The recommended way is to create and call a layout instead (see www.playframework.com/documentation/2.2.x/ScalaTemplateUseCases ) :

@(noteList:List[razie.wiki.model.WikiEntry], curTag: String, tags: model.Tags.Tags, msg: Seq[(String, String)], isSearch:Boolean)(implicit stok:mod.notes.controllers.NotesOk)

@myLayout("My TITLE", ...) {

... // paint the content

}

It is a little better and more structured - you can now change the headers a little easier and less stuff to refactor when... you know, monthly!

Still have to carry all kinds of stuff around though, like user/request for no reason and clog all signatures. Also, it is just as non-reusable.

Why worry about re-usability? Not only re-using simple widgets across projects, but what if you decide to make some parts of your app, SPA? You're now looking at extra work extracting the small pieces, while you really should have been creating composable bits to begin with!

After fiddling a bit with this, I reached the following pattern:

object NotesLocker extends RazController with Logging {

/** captures the current COMMON state of what to display - common to and passed to all views */
case class MyLayout(curTag: String, tags: model.Tags.Tags, msg: Seq[(String, String)], isSearch:Boolean, au: model.User, request: Request[_]) {
  // this is set by the body/content, as it builds itself and used by the header, heh
  var _title : String = "Default title" 

  /** set the title of this page */
  def title(s:String) = {this._title = s; ""}

  /** the views are now built with this state and use from it what they need */
  def apply(content: MyLayout => Html) = {
    Ok (views.html.notes.notesLayout(content(this), curTag, tags, msg)(this))
  }
}

  // helper so NOK is colored like Ok or others
  val NOK = new {
    def apply (curTag: String, tags: model.Tags.Tags, msg: Seq[(String, String)], isSearch:Boolean)(implicit au: model.User, request: Request[_]) =
      new MyLayout(curTag, tags, msg, isSearch, au, request)
   }

}

The idea is that I capture a class MyLayout which contains all the stuff possibly required to paint the layout like title, salutation, current search etc.

The actual page can now become simple and, more importantly, reusable:

@(noteList:List[razie.wiki.model.WikiEntry])(implicit layout : MyLayout)

@layout.title("My TITLE")   @** set my page's title - used by the frame **@

... // paint the content

This will be invoked to paint the content of the page, which now, instead of:

      Ok(views.html.notes.noteslist(allNotes, "", autags, "msg" -> s"[??]") //with au/request implicit-ed in

will be used like this:

      NOK ("", autags, "msg" -> s"[??]") apply {implicit layout=>
        views.html.notes.noteslist(allNotes)
      }

You can see that I can reuse it anywhere else I may need to represent a list of pages:

      AnotherOK (some...other...args...for...other...layout) apply {implicit layout=>
        views.html.notes.noteslist(allNotes)
      }

      // or, if moving to some kind of SPA

      NoLayout () apply {implicit layout=>
        views.html.notes.noteslist(allNotes)
      }

To reach this state, there will be a little more fiddling with the type signatures, to allow the MyLayout to be more dynamic, but that should be easy.

Enjoy!

P.S. If your MyLayout info is fairly static you can simplify the NOK away, with a simple implicit MyLayout per controller or something. Some of the stuff it could include are breadcrumbs (that's what really the tags are in my example).

P.S.2 I am guessing an even better and more re-usable approach would be to enrich the request with the layout info and then just pass it as such. This would make it easy to refactor code and add more layout infos. See "Different request types" here www.playframework.com/documentation/2.3.x/ScalaActionsComposition.

By: Razie | 2015-05-15 .. 2016-05-16 | Tags: post , scala , play , playframework , template , reuse , programming |    


See more in: Cool Scala Subscribe

Viewed 2701 times ( | Print ) this page.

You need to log in to post a comment!