Jan Christiansen
Jan Christiansen

Reputation: 3173

Add Prefix to Reverse Routes

I am looking for a way to add a prefix to all reverse routes of an html template without wrapping them all in function applications. I planned to use an html base tag but the reverse routes all start with a slash and therefore are relative to the host of the base tag rather than the full URL. Is there any feature I might be missing for a more robust solution?

Upvotes: 1

Views: 245

Answers (2)

acjay
acjay

Reputation: 36491

To accomplish something like this, to facilitate communication to my app of my app's own URLs in a DRY way, to propagate query parameters debug settings used in development through internal links, and also because I find Play's reverse router to be super ugly, I created a URL abstraction I like a lot better. It works like this:

1) I created a type to represent URLs symbolically:

/** Represents a URL that can possibly be a webpage */
sealed trait PageUrl

case class SectionUrl(slug: String) extends PageUrl
case class StaticPageUrl(slug: String) extends PageUrl
case class ExternalUrl(url: String) extends PageUrl

2) I created a class for resolving these objects into full URLs:

/** Wrapper to propagate request override flags to internal links */
case class UrlWrapper(params: Seq[(String, String)]) {
  def apply(url: PageUrl, additionalParams: Seq[(String, String)] = Seq.empty): String = {
    url match {
      case SectionUrl(slug) => urlAndParams(routes.PageRendererController.showSectionPage(slug).url)
      case StaticPageUrl(slug) => urlAndParams(routes.PageRendererController.showStaticPage(slug).url)
      case ExternalUrl(u) => u
    }
  }

  def urlAndParams(url: String, additionalParams: Seq[(String, String)] = Seq.empty): String = {
    def urlEncode = (u: String) => java.net.URLEncoder.encode(u, "UTF-8")
    val formattedParams = (queryParams ++ additionalParams).map{ case (key, value) => s"$key=${urlEncode(value)}" }.mkString("&")
    val paramOption = if (formattedParams.nonEmpty) Some(formattedParams) else None
    (Seq(url) ++ paramOption).mkString(if (url.indexOf("?") > 0) "&" else "?")
  }
}

You could easily modify this to provide a prefix always by default, or upon request via some other method.

3) In a trait/class that all my views extend, I declare an implicit field of type UrlWrapper, that will be available to my templates, so I can do:

@(option1: String, urlParam: PageUrl)(implicit url: UrlWrapper)

...

<a href="@url(urlParam)">My link</a>

...

As a bonus, since my pages all correspond to models in my app, I added to UrlWrapper additional methods for converting model objects to resolved URLs:

case class UrlWrapper(...) {
  ...

  def forSection(section: Section, additionalParams: Seq[(String, String)] = Seq.empty): String = {
    apply(SectionUrl(section.slug), additionalParams)
  }

  def forStaticPage(staticPage: StaticPage, additionalParams: Seq[(String, String)] = Seq.empty): String = {
    apply(StaticPageUrl(staticPage.slug), additionalParams)
  }
}

Upvotes: 1

Andrzej Jozwik
Andrzej Jozwik

Reputation: 14649

  • rename routes to context.routes
  • create empty routes file
  • add to routes the line:

    -> /context context.Routes

Upvotes: 3

Related Questions