Any web application would require a style sheet or some other resources such as images, scripts, and so in. In a non-Play application, we would refer to these by figuring out the relative location of the file. For example, suppose that our application has a webapp
folder with index.html
, where we need to add a homePage.css
stylesheet, which is located at webapp/styles
. Now, the reference in index.html
would be something similar to the following:
<link rel="stylesheet" href="styles/homePage.css" />
Such relative paths can get very confusing and, at times, difficult to manage. In a Play application, the resources are placed in the public directory and can be accessed using a request. It is suggested that you split the public directory into three subdirectories for images, CSS style sheets, and JavaScript files for consistency, as shown in the following figure:
In addition to this, Play provides an asset controller by default to support requests, which can access resources (assets). In most Play applications, a route for assets is also available in the routes file, as shown here:
GET /assets/*file controllers.Assets.at(path="/public", file)
This route gives access to resources, such as style sheets, scripts, and so on. A file is expected to be the remainder of the path after /public
, which is required to access it. For example, to get the homePage.css
style sheet, we would send a GET request to /assets/stylesheets/homePage.css
. The path preceded by /assets/
is considered to be the path for the file.
In views, we would need to use a routes
helper. So, if we wish to add a style sheet in one of our views, we would refer to it as follows:
<link rel="stylesheet" href="@routes.Assets.at("stylesheets/homePage.css")" />
Similarly, we will refer to a JavaScript script as follows:
<script src="@routes.Assets.at("javascripts/slider.js")" type="text/javascript"></script>
It is also possible to specify a separate path for images, style sheets, or scripts so that the request paths are shorter, as shown here:
GET /styles/*file controllers.Assets.at(path="/public/styles", file) GET /images/*file controllers.Assets.at(path="/public/images", file)
The Action at
is defined as follows:
def at(path: String, file: String, aggressiveCaching: Boolean = false): Action[AnyContent] = Action.async { implicit request => import Implicits.trampoline val pendingResult: Future[Result] = for { Some(name) <- Future.successful(resourceNameAt(path, file)) (assetInfo, gzipRequested) <- assetInfoForRequest(request, name) } yield { val stream = assetInfo.url(gzipRequested).openStream() Try(stream.available -> Enumerator.fromStream(stream)(Implicits.defaultExecutionContext)).map { case (length, resourceData) => maybeNotModified(request, assetInfo, aggressiveCaching).getOrElse { cacheableResult( assetInfo, aggressiveCaching, result(file, length, assetInfo.mimeType, resourceData, gzipRequested, assetInfo.gzipUrl.isDefined) ) } }.getOrElse(NotFound) } pendingResult.recover { case e: InvalidUriEncodingException => Logger.debug(s"Invalid URI encoding for $file at $path", e) BadRequest case e: Throwable => Logger.debug(s"Unforseen error for $file at $path", e) NotFound } }
As well as the resource, AssetController
adds the etag
header.
The etag
acronym is used for an entity tag. This is a unique identifier for the resource being requested, and is generally a hash of the resource or of its last modified timestamp.
Views in most applications rely on third-party libraries. In Play, we could define dependencies located in such libraries using webJars and npm.
Play extracts the assets from the WebJar dependencies as well as from npm
packages into the lib
directory within the public assets. We can refer to these when defining an asset with a dependency on the files present there. For example, if our view depends on d3.js
, then we use the following:
<script src="@routes.Assets.at("lib/d3/d3.v3.min.js")" charset="utf-8"></script>
To use a WebJar, we would need to define our project's dependency on it just as in any other module, as shown here:
libraryDependencies+="org.webjars" % "d3js" % "3.4.6-1"
To include npm packages, we would need to place the package.json
file in a project root. The package.json
file would be similar to this:
{ "name": "myApp", "version": "1.0.0", "dependencies": { }, "devDependencies": { "grunt": "~0.4.1", "grunt-contrib-concat": "~0.1.3", "grunt-contrib-cssmin": "~0.5.0", "grunt-contrib-clean": "~0.4.0", "grunt-contrib-less": "~0.7.0" }, "engines": { "node": ">=0.8.0" } }
52.15.42.128