Serving static assets

The web layer we wrote in the previous chapter was not really complete; we could add some beautiful CSS styles and some cool JavaScript behavior. CSS files, JavaScript files, as well as images do not change once your application is started, so they are usually referred to as static assets. The most convenient way to serve them is to map a URL path to a directory of your filesystem. Play comes with an Assets controller that does just this. Consider the following route definition:

GET   /assets/*file   controllers.Assets.at(path = "/public",   file)

This route maps the public directory of your application to the assets path of your HTTP layer. This means that, for example, a public/stylesheets/shop.css file is served under the /assets/stylesheets/shop.css URL.

This works because Play automatically adds the public/ directory of your application to the classpath. To use an additional directory as an assets folder, you have to explicitly add it to the application classpath and to the packaging process by adding the following setting to your build.sbt file:

unmanagedResourceDirectories in Assets += baseDirectory.value / "my-directory"

The Assets controller is convenient to serve files whose content does not change during the application lifetime. Let's create a public/stylesheets/shop.css file and request it:

$ curl -I http://localhost:9000/assets/stylesheets/shop.css
HTTP/1.1 200 OK
Last-Modified: Fri, 02 May 2014 09:35:37 GMT
Content-Length: 0
Cache-Control: no-cache
Content-Type: text/css; charset=utf-8
Date: Fri, 02 May 2014 09:37:58 GMT
ETag: "1d2408ce266a8226416fa8901bd7865364452bd6"

There are several things to note about the Assets controller from the preceding response:

  • It automatically detects asset's content type (from the filename extension) and sets the corresponding Content-Type HTTP header accordingly
  • It leverages caching headers and sets the Last-Modified header to the last modification date obtained from the filesystem and the Etag header to a checksum of the file contents
  • The Cache-Control header is set to no-cache in the development mode in order to prevent web browsers from caching the response, but in production, this value is set to 33600 (one hour) and can be overridden by configuration

Obviously, the Assets controller replies with a 304 response (Not Modified) if one makes a request with an If-Modified-Since or If-None-Match header matching the resource, and if this resource has not changed:

$ curl -I -H "If-None-Match: "1d2408ce266a8226416fa8901bd7865364452bd6"" http://localhost:9000/assets/stylesheets/shop.css
HTTP/1.1 304 Not Modified
ETag: "1d2408ce266a8226416fa8901bd7865364452bd6"
Last-Modified: Fri, 02 May 2014 09:35:37 GMT
Cache-Control: no-cache
Content-Length: 0

The Last-Modified and Etag response headers as well as their request counterparts, If-Modified-Since and If-None-Match, save bandwidth in the case of large files, but they still require an HTTP round trip, which checks that there is no newer version of the resource.

On the other hand, the Cache-Control header tells clients that they can keep the response content in their local cache and reuse it for a given duration instead of performing an HTTP request. As previously said, in the development mode, this header is set to no-cache in order to prevent clients from caching the responses because you might often change their content. However, when you run in the production mode, this header is set to 33600, telling clients that they can cache the response content for one hour before requesting it again.

Sprinkling some JavaScript and CSS

For the sake of completeness, here is how your HTML layout template (the app/views/layout.scala.html file) can look so that each web page loads a favicon image, CSS style sheet, and JavaScript program:

@(body: Html)
<!DOCTYPE html>
<html>
<head>
<title>Shop</title>
<link rel=stylesheet src="@routes.Assets.at("stylesheets/shop.css")">
<link rel=favicon src="@routes.Assets.at("images/favicon.png")">
</head>
<body>
    @body
<script src="@routes.Assets.at("javascripts/shop.js")"></script>
</body>
</html>

The preceding template refers to a shop.css file located in the public/stylesheets/ directory, a favicon.png file in the public/images/ directory, and a shop.js file in the public/javascripts/ directory.

Here is the possible content for the JavaScript public/javascripts/shop.js file, which performs an Ajax call to the Items.delete action:

(function () {
  var handleDeleteClick = function (btn) {
    btn.addEventListener('click', function (e) {
      var xhr = new XMLHttpRequest();
      xhr.open('DELETE', '/items/' + btn.dataset.id);
      xhr.addEventListener('readystatechange', function () {
        if (xhr.readyState === XMLHttpRequest.DONE) {
          if (xhr.status === 200) {
            var li = btn.parentNode;
            li.parentNode.removeChild(li);
          } else {
            alert('Unable to delete the item!'),
          }
        }
      });
      xhr.send();
    });
  };

  var deleteBtns = document.querySelectorAll('button.delete-item'),
  for (var i = 0, l = deleteBtns.length ; i < l ; i++) {
    handleDeleteClick(deleteBtns[i]);
  }
})();

This code finds all the HTML buttons with the delete-item class and sets up a click handler that performs an HTTP request on the Items.delete route. If this request succeeds, the item is also removed from the page, otherwise, an error message is shown to the user. The code retrieves the corresponding item ID using the data-id attribute of the button. It assumes that the following HTML markup represents an item:

<li>
  <a href="@routes.Items.details(item.id)">@item.name</a>
  <button class="delete-item" data-id="@item.id">Delete</button>
</li>

Let's define the public/stylesheets/shop.css file so that the delete button is made visible only when the user hovers over an item:

li button.delete-item {
  visibility: hidden;
}
li:hover button.delete-item {
  visibility: visible;
}

Finally, feel free to design a public/images/favicon.png image of your choice!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.217.17