The purpose of this assignment is to introduce you to styling web pages in Grails and includes introductions to:
- HTML and CSS
- Groovy
- Twitter Bootstrap CSS framework
- g-tags
You are to complete this programming assignment individually. In this assignment you will make “responsive” public views for the book store’s website using Twitter Bootstrap. In particular you will make responsive home page, and list views for books and authors.
Step 1: Learn HTML and CSS
If you do not already know HTML and CSS, read the brief tutorial at W3Schools:
http://www.w3schools.com/html/default.asphttp://www.w3schools.com/css/default.asp
You do not have to be an expert in HTML and CSS, but you should know the basics. Read through the tutorial in the above links and get an idea of the language and the material that is in these tutorials. This will take you about 60 minutes. When you need to use the language, you’ll know where to look.
Step 2: Study Twitter Bootstrap Framework
Read the Twitter Bootstrap documentation. In particular scan the the layout, content and the components documentations:
- https://getbootstrap.com/docs/4.5/layout/overview/
- https://getbootstrap.com/docs/4.5/content/reboot/
- https://getbootstrap.com/docs/4.5/components/alerts/
Reading this material should take you about 60 minutes. Pay particular attention to the section about the bootstrap grid system:
https://getbootstrap.com/docs/4.5/layout/grid/
This is the heart of the bootstrap’s responsiveness. When you read, realize that the grid system is mobile first. This implies that on small devices the columns in a row will be stacked. When you set the “div class=”, you specify how it will look on larger devices. You will have to experiment with the grid system to fully understand it.
Also study the Navbar component:
https://getbootstrap.com/docs/4.5/components/navbar/
Step 3: Examine Bootstrap Files
Currently, basic Twitter Bootstrap styling and component files are already incorporated into Grails 3. Examine the files “grails-app/assets/stylesheets/bootstrap/” and “grails-app/assets/javastripts/bootstrap/”. These files are minified and sufficient for the programming assignment.
Step 4: Make App Home Page
I like to save the Grails generated views for the administrative backend of the web site because they do not have to look as pretty as the public views and the generated pages look well enough for administrative use. For the public pages, I like to code separately the views and style them using bootstrap.
1. Read about Grails Templates and Layout:
2. Make Site Layout GSP
You should notice that there is already a “main.gsp” layout in the “grail-app/views/layout/” directory. Your public pages will use a different layout, so create a gsp file in the views/layout/. Call it site.gsp.
Copy and paste the code below to your site.gsp
<!DOCTYPE html> <html> <head> <title><g:layoutTitle default="Book Store"/></title> <asset:stylesheet src="application.css"/> <g:layoutHead/> </head> <body> <g:render template="/navbar" /> <g:layoutBody/> <asset:javascript src="application.js"/> </body> </html>
Grails uses Sitemesh to implement layouts:
http://wiki.sitemesh.org/wiki/display/sitemesh3/SiteMesh+3+Overview
You will not have to understand all of Sitemesh complexities to create a simple layout. The layout GSP specifies the general layout of the view. In essences, any view rendered with a layout will be a combination of two GSP files, the layout GSP and the specific view GSP files, called the “target page” in the documentation. Both the layout GSP and specific view GSP use special “g:layout…” tags. In the above layout GSP, “site.gsp”, the special “g:layout” tags are
- <g:layoutTitle …/> – specifies the title for the page (the text that appears in the browser tab). In this case a default title, “Book Store”, is specified. The specific view GSP can override the default title
- <g:layoutHead /> – specifies where to put the specific view GSP “head” content
- <g:layoutBody /> – specifies where to put the specific view GSP “body” content
Soon we will make the specific GSP view for the home page and this will make more sense.
There are three other tags in the layout GSP
- <assest:stylesheet src=…/> – uses asset-pipeline plugin to source the style sheets
- <g:render template=…> – locates where to render a template, in this case the navigation bar
- <asset:javascript src=…/> – uses asset-pipeline plugin to source the JavaScript files
The navigation bar is complex enough to have it own GSP file. Also having the navigation bar in a separate file will let you swap out and in different navigation bars.
3. Make the Navigation Bar Template
Read about navbar in the bootstrap documentation:
https://getbootstrap.com/docs/4.5/components/navbar/
Add the “_navbar.gsp” file to the root of the views directory. Note the leading underscore. You need the underscore in the name of the file so that Grails can identify it as a template view.
Copy and paste the code into your _navbar.gsp template view.
<!DOCTYPE html> <!-- navbar wrapped with navbar-expand-? for controlling responsiveness --> <nav class="navbar navbar-expand-md navbar-static-top navbar-dark"> <!-- The navbar branding. Will appear on the right --> <a class="navbar-brand" href="/${grails.util.Metadata.current.getApplicationName()}"> Book Store </a> <!-- Hamburger button for toggling. In this location will stay in the above and to the right --> <!-- data-target should have the id of the collapsing menu id. --> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav"> <!-- aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> Don't have to have! --> <span class="navbar-toggler-icon"></span> </button> <!-- The collapsing menu --> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav mr-auto"> <li class="nav-item"><a class="nav-link" href="#">Books</a></li> <li class="nav-item"><a class="nav-link" href="#">Authors</a></li> </ul> </div> </nav>
The navbar is described by three major div classes, the “navbar”, “navbar-toggler” and the “navbar-collapse”. The nav-tag, has the class “navbar-expand-md” that describes the break point in which the collapsing menu will disappear and the navbar-toggler-icon will appear. The other classes apply styling to the navbar. The button-tag with the class navbar-toggler contains the navbar-toggler-icon. It is the button for expanding the menu when the screen width is smaller then the break point. It must have “data-target” id that must match the id of “navbar-collapse” The “navbar-collapse” describes a list with the links in the collapsed menu. Notice that the navbar-collapse is linked to the navbar-header through its id, “navbarNav”.
4. Make the Home Specific GSP
Study the Examples in the bootstrap examples.
https://getbootstrap.com/docs/4.5/examples/
Decide how you want the Book Store home page to look. For example you may want to use a Jumbotron for the home page. Look at the page source for how the Jumbotron is coded. If you lack initiative you can use code below for your home page. Replace the content of grails-app/views/index.gsp with the code below.
<!DOCTYPE html> <html> <head> <meta name="layout" content="site"/> <title>Book Store Home</title> </head> <body> <div class="jumbotron jumb-margin"> <div class="container"> <h1>Book Store</h1> </div> </div> <p>Under construction</p> <p> <g:link controller="controllerList">Go to ControllerList</g:link> </p> </body> </html>
Now stop and run the app again. You should see the public home page with the navigation bar. Adjust the browser window width. Notice that when the browser window width is narrow the hamburger menu appears and when clicked the menu expands. Also notice that when the browser window is wide then the menu is expanded into the navbar area.
5. Clean Up the Control List Link
If you visited the controller list view and clicked on the Grails icon at the top left, you will notice that it points the browser to the root of the server context instead of the app home page.
Open the main.gsp file. In the navbar section of main.gsp layout, look at the anchor tag with the class designation “navbar-brand.” The href attributes points to the root of the server.
<nav class="navbar navbar-expand-lg navbar-dark navbar-static-top" role="navigation"> <a class="navbar-brand" href="/#"><asset:image src="grails.svg" alt="Grails Logo"/></a> .... </nav>
We should change the URL for the navbar-brand. Also, we probably do not want the Grials icon if the link is to point to our app home page. Replace the entire anchor tag with
<a class="navbar-brand" href="/${grails.util.Metadata.current.getApplicationName()}/"> <div class="navbar-brand title-name">Book Store</div> </a>
Now someone using the admin pages has an easy link back to the public pages.
Step 5: Styling the Home Page
The public home page probably does not look as good as you might like. You should add some styling.
1. Add Style Sheet
Add the file “myStyles.css” to the directory grails-app/assets/stylesheet/, and then cut and paste the content below into the myStyles.css:
/* Change nav bar background color */ .navbar { background-color: blue; }
Now you must source your style sheet to the site.gsp file. There are two ways to source the style sheet into the view. If you want the syles to appear only on the public pages then in the head of the site.gsp file just below the asset:stylesheet tag for application.css add the link to your style sheet so that your styles will over write bootstrap styles:
<asset:stylesheet src="application.css"/> <asset:stylesheet src="myStyles.css"/>
If you should reverse the order then bootstrap styles will overwrite your styles and your styles will have no effect.
If you’d like your styles to effect all views including the admin pages then you will need to edit the “asset/stylesheets/application.css” file. The asset-pipeline documentation is at:
http://bertramdev.github.io/grails-asset-pipeline/
In particular, you need to read the usage section:
http://bertramdev.github.io/grails-asset-pipeline/guide/usage.html#directives
In short, we need to add to the list of “require” style files to application.css
/* ... *= require bootstrap *= require grails *= require main *= require mobile *= require myStyles *= require_self */
I added the “*= require myStyles” at the bottom but above “*= require_self”. In this location myStyles will over write any styles in sheets listed above, but any styles defined in application.css will over write the styles in myStyles.
You need to re-run the application for the styles to appear so that asset-pipeline can source them into the app’s views.
2. Styling the Jumbotron
In the views/index.gsp add the center class to the h1 tag:
<h1 class="text-center">Book Store</h1>
You do not need to re-run Grails to view the changes. You only need to refresh the page in your browser.
3. Experiment with More Styling
Use the Chrome browser to inspect element styles. Open the Chrome browser, and right click on the page, and select “inspect element.” This will open the Developer’s Tools. The tool allows you examine styling inheritance and temporally add new styles to elements.
Note that you do not have to stop and run the app to view your style changes, but the effects may not show for a while. Generally, the app only needs restarting when you change the domain class or controller.
Step 6: Make the Public Books Page
Currently, I like to make separate controllers for the public pages. This keeps the admin pages completely separate from the public pages.
1. Make the Books Controller
Naming the public controllers can be tricky because the controller name will appear in the URL. Consequently, we want the name to be short and memorable. In the case of a public page showing a list of books, we might like to call it “Book”, but that name is already being used by the admin controllers. We could call it “Books”, especially since its index view will show a list of books. This is not always the best choice for the name because it differs from the admin controller by a single letter, “s”. Nevertheless, we will name our public controller “Books”, and we’ll have to be careful to discriminate it from the admin controller.
Use the grail command to make a new controller. Call it “Books”. The editor should show the BooksController.grooy file with an empty index method or action.
2. Write the Index Action for Books
We will want the cs4760progassign/books/ page to show the list of books sorted by the title of the book and showing the author of the book. We will develop the code using Test Driven Development (TTD). The TTD process is to first write the unit tests for the component and then write the code to pass the tests.
i. Write the Unit Test
It is hard to write a test without the code, but I suggest thinking about the data and writing an example on how the data should behave. Assume that the Author domain has objects:
- Author Object A = [id: authorA_ID, name: “Author A”, …]
- Author Object B = [id: authorB_ID, name: “Author B”, …]
- Author Object C = [id: authorC_ID, name: “Author C”, …]
Where I am modeling the Author object as a map, and authorA_ID is a long integer. Also assume that the Book domain has objects:
- Book Object A = [title: “Title A”, author: authorA_ID, …]
- Book Object B = [title: “Title B”, author: authorB_ID, …]
- Book Object C = [title: “Title C”, author: authorC_ID, …]
Again I have modeled the domain objects as maps. The index action of the BooksController should pass to the view a model which has a list of maps that contain the title of the book and the author for the book. We want the list to be ordered by the book title. In other words, the model passed to the view should be:
[bkList: [ [title: “Title A”, author: “Author A”], [title: “Title B”, author: “Author B”], [title: “Title C”, author: “Author C”] ], …]
Where bkList is the key to retrieve the list of ordered title/author maps:
[ [title: “Title A”, author: “Author A”], [title: “Title B”, author: “Author B”], [title: “Title C”, author: “Author C”] ]
Now we can write the unit test. It will use MockDomains so BooksControllerSpec will need to also implement DataTest.
package cs4760progassign import grails.testing.gorm.DataTest import grails.testing.web.controllers.ControllerUnitTest import spock.lang.Specification class BooksControllerSpec extends Specification implements ControllerUnitTest <BooksController>, DataTest { void setupSpec(){ mockDomains Author, Book } def 'Test the index method returns the correct model'(){ given: new Author(name:"Author A") .addToBooks(new Book(title:"Title A", publishYear:1978)) .save(flush: true, failOnError: true) new Author(name:"Author B") .addToBooks(new Book(title:"Title B", publishYear:1876)) .save(flush: true, failOnError: true) new Author(name:"Author C") .addToBooks(new Book(title:"Title C", publishYear:1876)) .save(flush: true, failOnError: true) when: 'The index action is executed' controller.index() then: 'The model is correct' model.bkList model.bkList.size == 3 model.bkList == [ [title: "Title A", author: "Author A"], [title: "Title B", author: "Author B"], [title: "Title C", author: "Author C"] ] } // End 'Test the index method returns the correct model' }
The setupSpec method declares the mock domains for Author and Book. The test uses blocks to setup the test, execute the action, and then test the results. The “given” block setups the tests by adding the data to the mockDomains. The “when” block executes the action by calling the controller index action. Finally the “then” block test the results. Note that “model” in the “then” block is a property of the response object sent the view. The model is a map that the view can use to populate dynamics properties in the view. For the index BooksController view the model should contain a list of books and authors. We need to use the render method of the Controller in order to set the model. If we use the render method then we also have to specify the view rather than just use the default behavior.
Note that the test is not hard to write if we used the approach of considering the data. Run the test and it will fail. There is a short cut for running a single test in IntelliJ by clicking the double green arrow adjacent to the class definition. The output form the test will appear cs4760progassign/build/reports/tests/test/index.html. You can right click the index.html and select “run” to view the report.
Now we can write the code for the index action.
ii. Write the code for index action
Copy and paste the code below for the Books controller methods.
package cs4760progassign class BooksController { static final boolean debugIndex = true def index() { // Book.list() gets all Book instances from the database and puts them in a list. def bks = Book.listOrderByTitle() // println outputs to console if(debugIndex){ println "" bks.each{ println it.title+" by "+Author.get(it.authorId).name} } // Make a list of all books title and authors def bkList = [] for(int i=0; i < bks.size; i++){ def bkAuthor = [:] bkAuthor.put('title', bks[i].title) bkAuthor.put('author', Author.get(bks[i].authorId).name) bkList << bkAuthor } if(debugIndex){ println " " println bkList } // So that the unit test can access the model, we need // to explicitly use the render method and specify the model. // We also have to explicitly specify the view, or // text will be rendered and not the view. render view: "index", model: [bkList: bkList] // If we did not have to access the model in the view // then we could use the default behavior and return // [bkList: bkList] } }
Now run the test. It passes.
iii. Study the Groovy Code
The programming language in the Books controller is groovy. Groovy is a scripting language built on top of Java. You can almost always write Java instead of groovy in a groovy file.
Because the BooksController is in the same package as the domain classes, it has access to the domain classes. The call “Book.listOrderByTitle() gets a list of books in the Book table and sort them by the title. You can read the about GORM Querying at
http://gorm.grails.org/6.0.x/hibernate/manual/#querying
The “list” call can work very much like dynamic finders
http://gorm.grails.org/6.0.x/hibernate/manual/#finders
The particular documentation for “listOrderBy” can be found in the “Quick Reference” listed on the right of the documentation. Look in “Domain Classes”:
http://docs.grails.org/latest/ref/Domain%20Classes/listOrderBy.html
After getting the list of all the books, the code constructs a sub Map, “bkAuthor” which will contain the title of the book under the key, “title”, and the author of the book under the key, “author”. Finally this sub map is added to the “bkList” List. The action returns a Map with key “bkList” to the List “bkList.”
You can read about groovy at
http://groovy-lang.org/documentation.html
In particular you will want to study the syntax of the language
http://groovy-lang.org/syntax.html
This code uses List and Map syntax built into groovy
http://groovy-lang.org/syntax.html#_lists
http://groovy-lang.org/syntax.html#_maps
3. Make the Books Index View
Add an “index.gsp” file to the /views/books/ directory.
Cut and paste the code below into the views/books/index.gsp.
<!DOCTYPE html> <html> <head> <meta name="layout" content="site"/> <title>Book Store Books</title> </head> <body> <h1> BOOKS </h1> <ul> <g:each in="${bkList}"> <li> ${it.title} by ${it.author} </li> </g:each> </ul> </body> </html>
4. Study the HTML Code
The index view makes use of Grails tags, g-tags. Documentation for g-tags are found in the “Quick Reference” at the bottom under “Tags” in the Groovy Server Pages documentation. In particular the “each” tag is at:
https://gsp.grails.org/latest/ref/Tags/each.html
5. Edit the Navbar
We need a link to the Books index view. This can be in the navbar menu. Open the _navbar.gsp file in the editor. Replace the anchor tag for the Books link:
<li class="nav-item"><a class="nav-link" href="#">Books</a></li>
with a g:link tag in the navbar-collapse section of the navbar:
<li class="nav-item"><g:link class="nav-link" controller="Books">Books</g:link></li>
6. Run the app and inspect the view
You should be able to view the new public books controller by clicking on the books link in the navbar. Note that the outputs from println appear in the run console at the bottom of IntelliJ IDEA. Select the “Grails: cs4760progassign” tap. You can modify the either code and refresh page in the browser without “stopping and running” the app. As long as no new classes are made the browser will pick up the changes.
Step 7: Make the Authors Public Page.
You are on your own making the new Authors controller, coding the index method/action and making the new view. Use what you have learned from the steps above.
We want the page to look a little different than the Books view. The Authors public view should be hierarchical list. The top most list should be an alphabetical list of authors and below each author should be an indented alphabetical list of book titles for the author. In other words, we want the view to appear similar to:
Authors Mark Twain Huckleberry Finn Tom Sawyer Stephen King The Shining The Stand
Note that the code and model will be more complex than for the BooksController index action. You probably will want to use nested maps in the model. I suggest that you think about the data first and what the model should look like. Make an example of the model and use it to write the unit test.
Also do not forget to change the link for the Authors menu item in the navbar.
Step 9: Make Screen Shots and Submit
After creating the public Authors index view, make a screen shoot of the public Authors page. The URL is
http://localhost:8080/cs4760progassign/authors/index
Also make a screen shot of AuthorsControllerSpec.groovy that you used to develop AuthorsController.index().
Submit the screen shoots in canvas for the “Programming Assignment 2 – Styling your App”.