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 Google Maps APIs.

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 administer 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 file
  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

http://grails.github.io/grails-doc/latest/guide/theWebLayer.html#uploadingFiles

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:

http://docs.grails.org/latest/guide/single.html#validation

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:

http://docs.grails.org/latest/ref/Constraints/nullable.html

http://docs.grails.org/latest/ref/Constraints/blank.html

http://docs.grails.org/latest/ref/Constraints/maxSize.html

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

http://www.w3schools.com/html/html_forms.asp

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

https://gsp.grails.org/latest/guide/index.html#formsAndFields

or in the Quick Reference at

https://gsp.grails.org/latest/ref/Tags/form.html

The g-form tag made by the scaffolding uses the action attribute.  The default controller is the controller for the view, in this case BookController. The “action” 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”

http://www.w3schools.com/tags/att_form_enctype.asp

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

enctype="multipart/form-data"

so the form tag looks like

<g:form action="save" enctype="multipart/form-data">

The other way is to use g:uploadForm tag.

https://gsp.grails.org/latest/ref/Tags/uploadForm.html

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

<g:uploadForm action="save">
...
</g:uploadForm>

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 action="save">
    <fieldset class="form">
        <f:all bean="book"/>
    </fieldset>
    ...
</g:uploadForm>

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

http://grails-fields-plugin.github.io/grails-fields/latest/guide/index.html

in particular the f:all tag reference:

http://grails-fields-plugin.github.io/grails-fields/latest/ref/Tags/all.html

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

http://grails-fields-plugin.github.io/grails-fields/latest/ref/Tags/with.html

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

<g:uploadForm action="save">
    <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"/>
        </f:with>
    </fieldset>
     ...
</g:uploadForm>

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:

http://grails-fields-plugin.github.io/grails-fields/latest/guide/index.html#customizingFieldRendering

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" />
    </label>
    <input style="display:inline" type="file" name="cover" id="cover" accept="image/*" capture />
</div>

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 messages.properties. 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,

id="cover"

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:

http://www.w3schools.com/html/html_form_input_types.asp

http://www.w3schools.com/html/html_form_attributes.asp

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

accept="image/*"

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

http://www.w3schools.com/tags/att_input_accept.asp

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

https://www.w3.org/TR/html-media-capture/#the-capture-attribute

You can test the attribute on your phone at this site

http://mobilehtml5.org/ts/?id=23

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"
    if(debugCover){
        println " "
        println "In showCover"
        println "params.id: "+params.id
    }
    def book = Book.get(params.id)
    response.outputStream << book.cover
    response.outputStream.flush()
}

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

http://docs.grails.org/latest/ref/Domain%20Classes/get.html

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:

http://localhost:8080/cs4760progassign/book/show/5

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 params.id with this URL it will be given 5. The params variable can give much more information. The Quick Reference

http://docs.grails.org/latest/ref/Controllers/params.html

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

http://docs.grails.org/latest/guide/theWebLayer.html

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:

http://www.grails.org/doc/latest/ref/Servlet%20API/response.html

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"/>
     </f:with>
 </ol>

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: " />
        </span>
        <img class="property-value" alt="Missing Cover" src="${createLink(controller:'book', action:'showCover', id:"${book.id}")}">
    </li>
</g:if>

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

https://gsp.grails.org/latest/ref/Tags/if.html

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:

http://www.w3schools.com/tags/tag_img.asp

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

https://gsp.grails.org/latest/ref/Tags/createLink.html

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="book.author.label" default="Author" />
        </span>
        <span class="property-value">
            <g:link controller="author" action="show" id="${book?.author?.id}">
                ${book?.author?.encodeAsHTML()}
            </g:link>
        </span>
    </li>
</g:if>

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

http://docs.grails.org/latest/guide/security.html#codecs

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>
        <span class="property-value" aria-labelledby="title-label">
            <g:fieldValue bean="${book}" field="publishYear"/>
        </span>
    </li>
</g:if>

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>
        <span class="property-value" aria-labelledby="title-label">
            <g:fieldValue bean="${book}" field="title"/>
        </span>
    </li>
</g:if>

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:

http://blog.anorakgirl.co.uk/2016/01/what-the-f-is-ftable/

Step 5: Add Map to the Home Page

This step is a diversion and 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

https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/Using_geolocation

You can learn more about the Gelocation API at

http://www.w3.org/TR/geolocation-API/

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 home/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="map-canvas" style="width:500px; height:400px"></div>

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 home/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. Between <head> and </head>, copy the line below

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

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");
    var outputMap = document.getElementById("map-canvas");

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

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

         outputLatLong.innerHTML = '<p>Latitude is ' + latitude + '° <br>Longitude is ' + longitude + '°</p>';
 
        // map image 
        var img = new Image();
        img.src = "https://maps.googleapis.com/maps/api/staticmap?center=" + latitude + "," + longitude + "&zoom=13&size=300x300&sensor=false";
        outputMap.appendChild(img);
        // end of map image
 
     };

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

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

The geoloc.js defines only the geoFindMe function. The function first locates the html elements for writing the latitude and longitude and displaying the map. They are given names in the script, outputLatLong and outputMap. Then the script checks if it can access the navigator.geolocation. After that, it defines the callback for success and error. At the bottom of the file the script first writes “Locating …” in the div to show the latitude and longitude.

Review the success callback. It first assigns latitude and longitude from the position.coords. Then it adds it to the html. Finally, it makes an Image and assign the source to the a map at maps.googleapis.com. Note that this is a static map. Also note this example of a get and the latitude and longitude is added to the URL.

Save your all the files 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 the geolocation will not be accessed. Both the latitude and longitude coordinates and the map should appear.

ii. Use Google Map API

The map is good but it is static and it is hard to add markers to it. We need to use Google Maps API to manipulate the map and add markers.

Read about adding a Google Map at

https://developers.google.com/maps/tutorials/fundamentals/adding-a-google-map

You can learn more about Google Map API at

https://developers.google.com/maps/documentation/javascript/tutorial

First we need to include the the Google Map API javascript on the home page. In views/index.gps and the line below to the bottom of the view just above the terminating body-tag, </body>

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js"></script>

Note that Google no longer requires using a key.

Now we need to modify the script in geoloc.js to use the google maps api. In the geoloc.js file, comment out the lines between “// map image” and “// end of map image” in the success function. Then add the code below to the success function

 var coordinates = new google.maps.LatLng(latitude, longitude);
 var mapOptions = {
     center: coordinates,
     zoom: 8
 };
 var map = new google.maps.Map(outputMap, mapOptions);

 // Set up and display the Marker
 var markerOptions = {
     map: map,
     position: coordinates,
     draggable: false,
     animation: google.maps.Animation.DROP
 };
 var maker = new google.maps.Marker(markerOptions);

Notice how the google.maps.Map object is made. The options for the map are set in the mapOptions object. Then the Map is created with a reference to the html element to display the map, outputMap, and the map options, mapOptions. The marker on the map is set up similar to the map. The marker is configured by the markerOptions object. Notice that the marker is associated with a map in the marker options by the map property. The position of the marker is set by the position property. We use the coordinates from getCurrentLocation.  You can guess what the draggable and and animation properties mean.

Save the files and refresh the index page. Notice that you can drag the map but not the marker. Try changing the draggalble markerOptions property and confirm your guess what draggable means.

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 book.id to the bkAuthor map in the BooksController.index action.
  • Try styling the img-tag using twitter bootstrap.  Try the img-thumbnail class.

http://www.w3schools.com/bootstrap/bootstrap_ref_css_images.asp

When you are done, email me a screen shoot of the book index page. Use the subject line

cs4760 Programming Assignment Proof 4