Less Verbose way of generating Play 2's javascript router
Asked Answered
I

5

10

Currently I define my app's javascript router in a fairly verbose way

def javascriptRoutes = Action { implicit request =>
  import routes.javascript._
  Ok(Routes.javascriptRouter("jsRoutes")(
    Login.method1,Login.Method2,
    OtherController.method1,OtherController.method2,
    //[...]
  )).as("text/javascript")
}

What I really would like to do is to create a javascriptRouter with all of the routes in the routes file, so I don't have to manually update the javascriptRoutes definition each time I add a new controller method.

Is there a way to accomplish this task, or is there even a slightly less verbose way of defining the javascriptRouter?

Intolerance answered 17/8, 2012 at 20:23 Comment(2)
Is reflection out of the question?Culpa
@Culpa reflection is acceptableIntolerance
C
17

You can do it via reflection like so:

val routeCache = {
    import routes._
    val jsRoutesClass = classOf[routes.javascript]
    val controllers = jsRoutesClass.getFields().map(_.get(null))
    controllers.flatMap { controller =>
        controller.getClass().getDeclaredMethods().map { action =>
            action.invoke(controller).asInstanceOf[play.core.Router.JavascriptReverseRoute]
        }
    }
}

def javascriptRoutes = Action { implicit request =>
    Ok(Routes.javascriptRouter("jsRoutes")(routeCache:_*)).as("text/javascript")
}

This was derived from the generated source files found in target/scala-2.x.x/src_managed. You could actually add your own source generator and parse the routes file yourself, but I find doing it via reflection easier.

An additional thing you might want to do is filter out the methods you don't want as this will give you ALL the routes (including the javascriptRouter itself).

Culpa answered 19/8, 2012 at 7:41 Comment(4)
Thanks! That does exactly what I wanted.Intolerance
Is there any drawback of using reflections e.g. regarding runtime performance compared to listing the methods manually?Oldenburg
There's a small one time cost whenever the server starts up / reloads, but beyond that, nothing else comes to mind. In practice, I find that it doesn't feel any slower.Culpa
Perfect solution have used it long time . But it does not work for 2.8 anymoreCorposant
M
3

Also, if you are using Play 2.4, some Classes/packages have been changed:

def javascriptRoutes = Action { implicit request =>
  Ok(play.api.routing.JavaScriptReverseRouter("jsRoutes")(routeCache:_*)).as("text/javascript")
}

val routeCache: Array[JavaScriptReverseRoute] = {
  import routes._
  val jsRoutesClass: Class[javascript] = classOf[routes.javascript]
  val controllers = jsRoutesClass.getFields.map(_.get(null))
  val met = for (
    controller <- controllers;
    method <- controller.getClass.getDeclaredMethods if method.getReturnType == classOf[play.api.routing.JavaScriptReverseRoute]
  ) yield method.invoke(controller).asInstanceOf[play.api.routing.JavaScriptReverseRoute]
  met
}
Mina answered 17/2, 2016 at 9:8 Comment(0)
C
2

I needed that in java. Copying it here in case it is of use for someone.

public static Result javascriptRoutes() throws IllegalAccessException, IllegalArgumentException,
        InvocationTargetException {

    // use reflection to get the fields of controllers.routes.javascript
    Set<Object> reverseRoutes = new HashSet<Object>();
    for (Field f : controllers.routes.javascript.class.getFields()) {
        // get its methods
        for (Method m : getAllMethods(f.getType(), withReturnType(JavascriptReverseRoute.class))) {
            // for each method, add its result to the reverseRoutes
            reverseRoutes.add(m.invoke(f.get(null)));
        }
    }

    // return the reverse routes
    response().setContentType("text/javascript");
    return ok(Routes.javascriptRouter("jsRoutes",
            reverseRoutes.toArray(new JavascriptReverseRoute[reverseRoutes.size()])));
}
Channelize answered 28/5, 2015 at 13:40 Comment(1)
it is generated by playChannelize
M
1

Very nice solution by thatsmydoing. If you have the JavaScript-routes under some other subpackage, you need to declare routeCache like this

val routeCache = {
  val jsRoutesClass = classOf[controllers.api.routes.javascript]
  val controllerArray = jsRoutesClass.getFields().map(_.get(null))
  controllerArray.flatMap { controller =>
    controller.getClass().getDeclaredMethods().map { action =>
    action.invoke(controller).asInstanceOf[play.core.Router.JavascriptReverseRoute]
  }
 }
}
Mina answered 4/5, 2015 at 7:59 Comment(0)
P
1

Expanding @rochb's answer for Play 2.4 Java, where the package names are slightly different, with support for multiple controller packages.

public Result javascriptRoutes() throws IllegalAccessException, IllegalArgumentException,
        InvocationTargetException {

    // use reflection to get the fields of controllers.routes.javascript and other controller packages
    Set<Object> reverseRoutes = new HashSet<Object>();
    Class[] routeClasses = {controllers.routes.javascript.class, com.example.package1.routes.javascript.class, com.example.package2.routes.javascript.class};
    for (int i = 0; i < routeClasses.length; i++) {
        for (Field f : routeClasses[i].getFields()) {
            // get its methods
            for (Method m : getAllMethods(f.getType(), withReturnType(play.api.routing.JavaScriptReverseRoute.class))) {
                // for each method, add its result to the reverseRoutes
                reverseRoutes.add(m.invoke(f.get(null)));
            }
        }
    }
    // return the reverse routes
    response().setContentType("text/javascript");
    return ok(Routes.javascriptRouter("jsRoutes",
            reverseRoutes.toArray(new play.api.routing.JavaScriptReverseRoute[reverseRoutes.size()])));
}
Pluto answered 20/7, 2015 at 5:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.