/play-navigator

Better router for Play Framework 2.x

Primary LanguageScalaMIT LicenseMIT

Better router for Play Framework 2.0

The "why" and "how" - http://codetunes.com/2012/scala-dsl-tutorial-writing-web-framework-router

Installation

Add play-navigator to your project/Build.scala file

val appDependencies = Seq(
  "eu.teamon" %% "play-navigator" % "0.5.0"
)

val main = PlayProject(appName, appVersion, appDependencies, mainLang = SCALA).settings(
  resolvers += "teamon.eu repo" at "http://repo.teamon.eu"
)

From 0.5.0 only Play >= 2.2.0 is supported. Use 0.4.0 if you need to support Play < 2.2.0.

Delete conf/routes file (optional since 0.3.0, you can use both)

Create new file PROJECT_ROOT/app/controllers/nav.scala:

package controllers

import play.navigator._

object nav extends PlayNavigator {
    // Your routes definition (see below)
}

Your app/Global.scala should look like this

import play.api._
import play.api.mvc._

object Global extends GlobalSettings {

  override def onRouteRequest(request: RequestHeader) = {
    controllers.nav.onRouteRequest(request) // handle requests with play-navigator

    // optionally you can fallback to standard Play routes with
    // controllers.nav.onRouteRequest(request) orElse super.onRouteRequest(request)
  }

  override def onHandlerNotFound(request: RequestHeader) = {
    // display 404 page with routes documentation
    val result = controllers.nav.onHandlerNotFound(request)
    // Play < 2.2.0
    result 
    // Play >= 2.2.0
    Future.successful(result)
  }

}

Routes definition

// Basic. Remember to add '_' after parameterless functions.
val home  = GET   on root       to Application.index _
val index = GET   on "index"    to Application.index _
val about = GET   on "about"    to Application.about _
val foo   = POST  on "foo"      to Application.about _
val show  = GET   on "show" / * to Application.show
val ws    = GET   on "ws"       to Application.ws _
val bar   = GET   on "bar" / * / * / "blah" / * to Application.bar

// Catches /long/a/b/c/.../z
var long  = GET   on "long" / ** to Application.long

// Require extension: /ext/{param}.{ext}
GET on "ext" / * as "json" to Application.extJson
GET on "ext" / * as "xml"  to Application.extXml

// REST routes
val todos = resources("todos", Todos)

// Namespace ...
namespace("api"){
  namespace("v1"){
    GET on "index" to Application.index _
  }
}

// ... or with reverse routing support
val api = new Namespace("api"){
  val v2 = new Namespace("v2"){
    val about = GET on "about" to Application.about _
  }
}

// and back to top-level namespace
GET   on "showalt" / * to Application.show

// redirect
GET on "redirect-me" to redirect("http://google.com")

// assets
val assets = GET on "assets" / ** to { s: String => Assets.at(path="/public", s) }

Application and Todos controllers used in example

// app/controllers/Application.scala
package controllers

import play.api.mvc._

object Application extends Controller {

  def index(): Action[_] = Action {
    Ok("Applcation.index => " + routes.index())
  }

  def about(): Action[_] = Action {
    Ok("Application.about => " + routes.about() + " or " + routes.api.v2.about())
  }

  def show(id: Int): Action[_] = Action {
    Ok("Application.show(%d) => %s" format (id, routes.show(id)))
  }

  def bar(f: Float, b: Boolean, s: String): Action[_] = Action {
    Ok("Application.bar(%f, %b, %s) => %s" format (f, b, s, routes.bar(f,b,s)))
  }

  def long(path: String) = Action {
    Ok("Application.long(%s)" format path)
  }

  def extJson(id: Int) = Action { Ok("Application.extJson(%d)" format id) }
  def extXml(id: String) = Action { Ok("Application.extXml(%s)" format id) }

  import play.api.libs.iteratee._

  def ws() = WebSocket.using[String] { request =>
    val in = Iteratee.foreach[String](println).mapDone { _ =>
      println("Disconnected")
    }

    val out = Enumerator("Hello!")

    (in, out)
  }
}


// app/controllers/Todos.scala
package controllers

import play.api._
import play.api.mvc._
import navigator._

object Todos extends Controller with PlayResourcesController[Int] {
  def index() = Action { Ok("Todos.index => %s" format nav.todos.index()) }
  def `new`() = Action { Ok("Todos.new => %s" format nav.todos.`new`()) }
  def create() = Action { Ok("Todos.create => %s" format nav.todos.create()) }
  def show(id: Int) = Action { Ok("Todos.show(%d) => %s" format (id, nav.todos.show(id))) }
  def edit(id: Int) = Action { Ok("Todos.edit(%d) => %s" format (id, nav.todos.edit(id))) }
  def update(id: Int) = Action { Ok("Todos.update(%d) => %s" format (id, nav.todos.update(id))) }
  def delete(id: Int) = Action { Ok("Todos.delete(%d) => %s" format (id, nav.todos.delete(id))) }
}

Mountable routers

case class FirstModule(parent: PlayNavigator) extends PlayModule(parent) with Controller {
  val home = GET on root to first.Application.index _
  val foobar = GET on "foo" / "bar" / * to first.Application.foo
}

case class SecondModule(parent: PlayNavigator) extends PlayModule(parent) with Controller {
  val home = GET on root to (() => second.Application.index)
  val foobar = GET on "foo" / "bar" / * to second.Application.foo
}

// Main router
object nav extends PlayNavigator {
    val first = "first" --> FirstModule
    val second = "second" / "module" --> SecondModule
}

Generated routes:

/first
/first/foo/bar/*
/second/module
/second/module/foo/bar/*

and reverse routing:

nav.first.home() // => "/first"
nav.first.foo(3) // => "/first/foo/bar/3"
nav.second.home() // => "/second/module"
nav.second.foo(3) // => "/second/module/foo/bar/3"