Programming Assignment 4 – Uploading and Displaying Images

The purpose of this assignment is to learn how users of your website can upload files and later view them on your website. You will learn to modify domain classes and views. In addition, you will learn the details of form input and image displaying. As a bonus, you will also learn about Geolocation and Leaflet.

You will continue working on the book store website. The book store client asks that images of the book cover should appear in the list of books on the books index page. The bookstore client also wants that his administrator should be able to upload images of the cover. Your client’s request implies that the Book domain class should have a “cover” property that either stores the image file or the URI (file path) to the image file. In this tutorial, we’ll store the image file in the database. Grails makes storing the image file in the database easier then storing the URI of the file in the database.

Considering the client’s demands, your modifications to the website needs to modify:

  1. The Book domain by adding a “cover” property
  2. The views/book/create.gsp and edit.gsp to upload the cover image file
  3. The Book controller by adding an action to stream the image in the database to the views
  4. The views/book/show by adding the cover image to the fil
  5. Finally the Books controller and the views/books/index.gsp to add the cover image to the list

Step 1: Modify Book Domain

Read about file upload in the Grails documentation

Study in particular the section “File Uploads through Data Binding.”

You should add the “cover” property to the Book domain:

byte[] cover

We also need to add to the constraints mapping. The primary role of constraints are for specifying constraints on the database, and how fields are validated. You can read about constraints and validation in the documentation:

The Quick Reference has a list of constraints that you can use. In the Quick Reference click the “Constraints” link and a list of constraints will appear. The constraints we will use are:

Add to the Book domain class the constraints below:

static constraints = {
        title nullable: false, blank: false
        author nullable: false, blank: false
        publishYear nullable: false
        cover nullable: true, maxSize: 1024*1024*2

The cover maxSize must be specified or the database will not be created with an entry large enough to hold an image file. Also note that 2 megabytes might not be larger enough for photos in your citizen science project. Notice that the string fields use the constraints “blank: false”, this prevents the user from enter an empty string.

Rerun Grails. Login as admin and navigate to the book index view. You will notice that “cover” has been added to the list of fields. You might also notice that order is different. The constraints closure can also be used to specify the order of the fields in the view. There is another way to change the order which we’ll discuss in the next section.

If you navigate to the create view by clicking the  “add new book” book, you’ll notice that the fields title and author are required. This is enforced by the “nullable: false” constraint. Actually, “nullable: false” is default setting, so we did not really have to specify them.

Step 2: Modify Book Views

1. Modify the Create View

If you try to create a book and upload a cover image, you’ll get an validation error:

Property cover is type-mismatched

This is because the form tag is wrong.

Open the views/book/create.gsp file in the editor. Near the bottom of the html is the g:form tag. This grails tag for making a form on the page. Read about the form tag at w3schools

You can read the general properties about g:form tags at

or in the Quick Reference at

The g-submitButton tag made by the scaffolding uses the “value” attribute.  The “value” attribute specifies the controller action that will be invoked when the form is submitted.

Our problem is that the form needs to be a “multipart form”

There are two ways to modify the form tag so that it is mulitpart. One way is to specify the encoding of the submitted data by adding the attribute to the form tag


so the form tag looks like

<g:form resource="${}" method="POST" enctype="multipart/form-data">

The other way is to use g:uploadForm tag.

So replace the g:form tag with the g:uploadForm tag

<g:uploadForm resource=${" method="POST">

Use either technique. Refresh the page and you can upload a file. Please note it must be a small file because of the maxSize constraint.

The styling on create page is not prefect. We would like for the choose file button to be in the proper place, meaning on the same line as the “cover” label. To fix this we need to control the styling of the individual inputs to the form. Inside the fieldset tag is a f:all tag

<g:uploadForm resource="${}" method="POST">
    <fieldset class="form">
        <f:all bean="book"/>
    <fieldset class="buttons">
        <g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />

Grails is using the fields plugin. You can read the guide at:

in particular the f:all tag reference:

We want to replace the f:all tag with f:with tag

and then list the field separately. So replace the f:all tag in create.gsp with

<g:uploadForm resource="${}" method="POST">
    <fieldset class="form">
        <!-- <f:all bean="book"/> -->
        <f:with bean="book">
            <f:field property="title"/>
            <f:field property="author"/>
            <f:field property="publishYear"/>
            <f:field property="cover"/>
    <fieldset class="buttons">
        <g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />

By the way, this is another way to order fields in a form. Perhaps a better way then specifying the order in the domain constraint closure because the view should really express the order not the domain class.

If you refresh the page. You’ll notice that nothing has changed, but now we have access to the individual inputs and can style them. Read about customized field rendering in the field guide:

We want to create a customized wrapper for the cover field. We can do this by creating a _wrapper.gsp in views/book/cover/. Make a cover directory in views/book/ directory. Then make a _wrapper.gsp file in the views/book/cover/ directory.

Add the code below to the file

<div class="fieldcontain ${hasErrors(bean: book, field: 'cover', 'error')} ">
    <label for="cover">
        <g:message code="book.cover.label" default="Cover" />
    <input style="display:inline" type="file" name="cover" id="cover" accept="image/*" capture />

The code consist of three parts. The div class for expressing any errors for validation. But more to the point a label tag for the “Cover” text in the input and an input tag.

The label tag uses grails messaging technique. It will look into the grails-app/i18n files for text of the file. If you open the grails-app/i18n/ directory, you will see a list of files named messages-<some country>.properties. Users can set language on their browsers and grails will look into the proper file for the text. The English text in If you open that file in the editor you’ll see that it is just a list of values for keys into a map. In this case, grails with look for the “book.cover.label” entry. It does not exist, so it will use the default value, “Cover.” If you want to internationalize your app you would make entries for all text on your web in this file and the other language files.

The label tag also defines the “for” attribute which specifies the element id of the input tag to associated with the label. In the example above this is “cover”. Notice below the label-tag is the input tag. The input tag specifies an id,


It is the matching id for the “for” attribute in the label-tag.

Educate yourself on all the different form inputs by reading about input types at W3schools:

The input tag also sets the styling to “display:inline”. This is called inline styling, not because of the “inline” in “display:inline”, but because the styling is specified within the element tag using the style attribute. The value, “display:inline” forces the file upload box and button to appear in line with the label. Without this styling, the input tag is styled with display:block which will cause the file upload box and button to appear below the label.

Notice that the input tag also has the accept attribute specified


This will filter the file picker to show only image files. For an explanation see W3schools

Notice that the input-tag also has the capture attribute. This enables media capture devices. For image capture this is the camera. The user can choose between uploading a file or making a photo with the camera. See the API standard

You can test the attribute on your phone at this site

Now you can rerun grails and test the create book page. The styling is good and the image file uploads. Use dbconsole, to check that the file has really been saved to the database. You will need to add a static rule to the conf/application.groovy file.

[pattern: '/dbconsole/**',        access: ['ROLE_ADMIN']],

2. Modify the Show View

We want the cover image to show in the book’s show view. Besides editing the view we will also have to use the book controller to retrieve the image from and render it to the view.

i. Add showCover action to BookController

Open the BookContoller in the editor, and add the method below to the controller:

def debugCover = true

def showCover(){
    println "In showCover"
        println " "
        println "In showCover"
        println " "
    def book = Book.get(
    response.outputStream << book.cover

Besides printing messages to the console, the action retrieves the book instance from the database using the get method:

It uses the parameters in the URL to specify the id. The params variable is frequently used in grails coding. One use is to access the URL. When you select to show a book, the URL is for example:


You already know that the first part, “//localhost:8080”, is specifying the domain and the port number. The second part, “cs4760progassign”, is specifying the app. The third part, “book”, is specifying the controller, and the fourth part, “show”, is specifying the action. The fifth and final part, in this case “5” is specifying the id. So when the controller asks for with this URL it will be given 5. The params variable can give much more information. The Quick Reference

does not give much information. But you can search for params in

to see the many uses of params.

To display the cover image, we need to stream the database entry into the response. Read about the “response” object in the reference manual:

The example demonstrates the use of response.outputStream with the bit shift operator, “<<“.

ii. Modify the Show View

Open the views/book/show.gsp file in the editor. Near the bottom of the code you will find a f:display tag. As in the create view we need to access the individual fields. So we replace the f:display tag with the code below

<!-- <f:display bean="book" /> -->
<ol class="property-list book">
    <f:with bean="book">
        <f:display property="title" widegt-label="Title" />
        <f:display property="author" label="Author"/>
        <f:display property="publishYear" label="Publication Year"/>
        <f:display property="cover"/>

Now we can overwrite the cover like we did for the create and edit view. Unfortunately for display wrappers you have to overwrite all the fields. Make the views/book/show directory. Then in the views/book/show make sub-directories title/, author/, publishYear/, and cover/. You cannot use IntelliJ IDEA to make the sub directories. The easiest way is to use file explorer to make the directories. IntelliJ will automatically notice that you have made the directories and add them to the project panel.

In the views/book/show/cover/ directory add a file named _displayWrapper.gsp. Add the code below to the file

<g:if test="${book?.cover}">
    <li class="fieldcontain">
        <span id="cover-label" class="property-label">
            <g:message code="book.cover.label" default="Cover: " />
        <img class="property-value" alt="Missing Cover" src="${createLink(controller:'book', action:'showCover', id:"${}")}">

The code first checks if that the cover exist in the book instance, useing a g:f tag surrounding the list item. Read about the g:if tag in the GSP reference manual

The g:if tag checks that  the book instance has the cover property.  It then renders the label using the message file or in this case the default value. Finally it uses an img tag to locate the image in the view. You can read about img tags at:

The image is sourced using the createLink tag as a GSP method:

To retrieve the image it will ask it from the BookController.showCover action giving it the book id as a parameter.

In the views/book/show/author/ directory add a _displayWrapper.gsp file, and then add the code below to the file

<g:if test="${book?.author}">
    <li class="fieldcontain">
        <span id="author-label" class="property-label">
            <g:message code="" default="Author" />
        <span class="property-value">
            <g:link controller="author" action="show" id="${book?.author?.id}">

The code should looks very familiar. The only difference is that now the author field is rendered with encodeAsHTML().

Also notice that there is a link around the author name to the author show view.

In the views/book/show/publishYear/ directory add a _displayWrapper.gsp file, and then add the code below to the file

<g:if test="${book?.publishYear}">
    <li class="fieldcontain">
        <span id="publishYear-label" class="property-label">
            <g:message code="book.publishYear.label" default="Publication Year" />
        <span class="property-value" aria-labelledby="title-label">
            <g:fieldValue bean="${book}" field="publishYear"/>

This should look very familiar now. Now we get the field value from the bean so we do not need to encode it.

Finaly in the views/book/show/title/ directory add a _displayWrapper.gsp file. Add the code below to the file

<g:if test="${book?.title}">
    <li class="fieldcontain">
        <span id="title-label" class="property-label">
            <g:message code="book.title.label" default="Title" />
        <span class="property-value" aria-labelledby="title-label">
            <g:fieldValue bean="${book}" field="title"/>

And this is all very familiar .

Now re-run grails and create a new book. You should see the cover image displayed in the show view.

Now you maybe asking, “How did you know to make all this code?” I had two tools to use. The first was my experience with prior versions of Grails that did not use field plugin to scaffold the views. The second was this blog:

Step 5: Add Map to the Home Page

This step is a diversion using the Geolocation API and Google Maps API. Our goal is detect the user’s current location and display it on a map in the home page.

i. Use geolocation

Read about using the geolocation object at

You can learn more about the Gelocation API at

On the home page, views/index.gsp, we need to add a div for the latitude and longitude to display. In addition, we’ll add a div for where to display the map. At the bottom of views/index.gsp add the code below just above the terminating body-tag.

<!-- Simple get location -->
<p style="margin-top:20px"><button onclick="geoFindMe()" >Show my location</button></p>
<div id="latlong-out"></div>
<div id="mapid" style="height: 400px; width: 400px;"></div>

<asset:javascript src="geoloc.js"/>

The button will call the geoFindMe function. We will add this function in a JavaScript file. We’ll display the latitude and longitude in the div with id equal to “latlong-out” and the map will display in the div with id equal to “map-canvas”. Note that the map-canvas must have a width and height, or other wise it will not show.

We write the geoFindMe function in a JavaScript file called geoloc.js. We need to include the JavaScript file, geoloc.js, to the view/index.gsp in order to access the navigator. Because the JavaScript in geoloc.js will only be used on this page of the website, we should load it only on this page. We will use the asset-pipeline to include the file at the end of body.

Now we need to make the geoloc.js file. In grails-app/assets/javascripts/ add a file and name it geoloc.js. When the editor displays the blank page copy the code below into the file

function geoFindMe() {
    var outputLatLong = document.getElementById("latlong-out");

    if (!navigator.geolocation){
        outputLatLong.innerHTML = "<p>Geolocation is not supported by your browser</p>";

    function success(position) {
        var latitude = position.coords.latitude;
        var longitude = position.coords.longitude;

        outputLatLong.innerHTML = '<p>Latitude is ' + latitude + '° <br>Longitude is ' + longitude + '°</p>';

    function error() {
        outputLatLong.innerHTML = "Unable to retrieve your location";

    outputLatLong.innerHTML = "<p>Locating…</p>";
    navigator.geolocation.getCurrentPosition(success, error);

The geoloc.js defines the geoFindMe function. The function first locates the html elements for writing the latitude and longitude. It is given the name outputLatLong in the script. Then the script checks if it can access the navigator.geolocation. If geolocation is not available, the script writes to outputLatLong informing the user that “GeoLocation is not supported by your browser.” This is a standard JavaScript design pattern, called feature testing. Before using an advance feature, i.e. a HTML5 API, first check that you can access the feature. If not then offer an alternative or inform the user that the request cannot be made. As a developer your can check which browser support a feature by using “Can I use?” website.

For example, try

So nearly all browsers have been supporting GeoLocation for sometime. Note that at the bottom of the page are useful information. For example look at the “Notes” which informs that “only works on secure (https) servers”. Most browser permit using GeoLocation on localhost without be secure for development.

The device location is retrieved by

navigator.geolocation.getCurrentPosition(success, error);

The getCurrentPostion takes two callback functions, success and error. The function success is called if the browser can get the location or otherwise error is called. The script needs to define these callbacks.

The success has a single argument, position, which is a Position JavaScript object.

Position has coords property which the script can use to access the latitude and longitude. Finally the latitude and longitude are displayed on the page.

Launch your website and test.  Click the button. A modal should appear telling you that the website is accessing your location and if you want to allow or block. Be sure to allow or GeoLocation will not be accessed and the location of the device will not be available.

ii. Use Leaflet Map API

Leaflet is an opensource JavaScript library for mobile-friendly interactive maps. It is a preferred library for Open Street Maps (OSM) for “slippy maps”.

Read about adding a Leaflet at

You can learn more about Leaflet API at

First we need to include the the Leaflet styles and JavaScript on the page. In views/index.gps and the line below to the bottom of the head of the view just above the terminating head-tag, </head>

<link rel="stylesheet" href=""
<!-- Make sure you put this AFTER Leaflet's CSS -->
<script src=""

Now we need to modify the script in geoloc.js to use the google maps api. In the geoloc.js file,  add the code below to the bottom of the success function.

// Add map
var mymap ='mapid').setView([latitude, longitude], 13);

var tileURL = 'https://{s}{z}/{x}/{y}.png'
var attribution = 'Map data &copy; <a href="">OpenStreetMap</a> contributors'
var maxZoom = 17

L.tileLayer(tileURL, {
    attribution: attribution,
    maxZoom: maxZoom
// end add map

The map is created by generator function. Note that “L” is a global variable for the Leaflet library. This is a standard technique in JavaScript for accessing libraries. Note that you should not make a variable “L” in your script or you’ll not be able to access the Leaflet library. requires the id of the div for the map location in the web page. Chaining is used to set the view of the map which is specified by an array of latitude and longitude, and the zoom level. The higher the number the more the zoom and the smaller the extent.

We need “tile server” to provide the maps for browser to display in the div. The URL for the tile sever is specified.

var tileURL = 'https://{s}{z}/{x}/{y}.png'

The variables in the URL {s}, {z}, {x} and {y} specifies the server and the requested title. Title servers provide map sections which are typically 256×256 pixels. The tiles are specified by their center x,y and the zoom level, z. To display the map Leaflet makes several call to the title sever.

It is consider good practice to include an attribution the map creator. The attribution is HTML is that is displayed at the bottom of the map by Leaflet.

The last property to specify before making the tile layer is the maximum zoom level. Each title server has it own specific maximum zoom level. You may want to specify a lower zoom level.

The tile layer is created with L.tileLayer() generator function. The generation function requires template for title server URL and an optional TileLayer options.

Refresh the index page. Notice that you can drag the map.

iii. Add Marker to the Map

A nice feature of Leaflet is adding objects to the map. For example, an marker icon marking the current location. Add the code below just after adding the title layer to the map.

// Add marker
var marker = L.marker([latitude, longitude], {
        draggable: false,  // set true to enable dragging the marker
        autoPan: false     // set true so the maps pans with the marker
// end add marker

The marker is created with an array of latitude and longitude, and the marker options object. Chaining is used to add the marker to the map. You can learn of the marker options draggable and autoPan at:

Refresh the page and click the “Show my location” button. Notice how you can drag the map but not the marker.Try changing the draggalble markerOptions property and confirm your guess what draggable means.

iv. Add Marker Event Listener and Popup

Another nice feature of Leaflet is that can generate events (using initiated by the user) and respond to the events via a listener. Add the code below to geoFindMe just after adding the marker to the map.

// Add listener for marker moved
function onMarkerMove(e){
    marker.bindPopup("Marker at "+e.latlng.toString()).openPopup();
marker.on('move', onMarkerMove);
// end add listener

The function onMarkerMove is our listener for the move event. It gets Event object. The marker move event is augmented with the latlng property that we can use to retrieve the latitude and longitude of the marker on the map. The onMarkerMove listener binds a popup to the marker with a content specifying the latitude and longitude. Chaining is used to open the popup.  The listener function is attached to the marker move event with the “on” method specifying the event to listener to and the listener.

Refresh the page. Try moving the marker. Be sure that the marker is created with draggable set to true.

iv. Change the Tile Sever

The tutorial uses OpenTopo Maps.

It is one of my favorite maps. It adds topographical lines to the basic map and subtle hill shading. It may not be the most appropriate map for your app’s purposes. A Leaflet plugin, leaflet-providers, simplifies the process for adding a title server.

Leaflet-providers has a very nice preview page at:

In the preview, you can select the title server in the overlay on the right of the displayed map. In the top center overlay, you can see the L.tileLayer to make to use the tile server in Leaflet.

Select a tile server a different title server and edit geoFindMe function to display the map on Book Store home page. Make a screen shot of the home page with the new title server.

Step 6: Modify books/index.gsp

You are on your own for this step. The goal is to display the book cover in the list of books on the books public index page. Show the cover above or below each list item for the book in the list.

Some hints:

  • You’ll probably want to add the to the bkAuthor map in the BooksController.index action.
  • Try styling the img-tag using twitter bootstrap.  Try the img-thumbnail class.

Step 7: Screen Screen Shots and Submit

When you are done, make a screen shoot of the book index page. Also make a screen shot of the Book Store home page with a new title server. Submit the screen shots to “Programming Assignment 4 – Uploading and Displaying Images”.