Implementing the home view

As expected, at this point it will be required of us to do a bit of coding. In keeping with practices we have adhered to in previous chapters, it will be wise of us to firstly make a barebones graphical mockup of the view we want to create before commencing with coding. This will save a significant amount of time in the long run by providing a clear direction regarding what we want to build.

We want the home page we are creating to do the following:

  1. Show the latest place reviews posted on the platform
  2. Provide direct access to a web page for review creation
  3. Provide a means by which a user can sign out of their account
  4. Enable a user to view the exact location of a reviewed place with the help of a map

Keeping all these requirements in mind, we can draw a rough sketch of our final template that looks something like this:

We are aiming for functionality over flash in our layout design. You can see that layout requirements one through four are immediately satisfied by this sketch. Clicking on View location will present the user of the application with a modal within which a map showing the exact place reviewed will be displayed.

Having clearly stated the template we will be creating, let us code. As always, first and foremost we need to include external stylesheets and scripts to our template. Open up home.html and add the following code to it:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Home</title>
<!-- Addition of external stylesheets -->
<link rel="stylesheet" th:href="@{/css/app.css}"/>
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js
/latest/toastr.min.css"
>
<link rel="stylesheet"
href="/webjars/bootstrap/4.0.0-beta.3/css/bootstrap.min.css"/>
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/font-awesome
/4.7.0/css/font-awesome.min.css"
/>
<link href="https://fastcdn.org/Buttons/2.0.0/css/buttons.css"
rel="stylesheet">

<!-- Inclusion of external Javascript -->
<script src="/webjars/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs
/toastr.js/latest/toastr.min.js"
>
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs
/popper.js/1.12.6/umd/popper.min.js"
>
</script>
<script src="/webjars/bootstrap/4.0.0-beta.3/
js/bootstrap.min.js"
></script>
<script src="https://fastcdn.org/Buttons/2.0.0/js/buttons.js"></script>
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?key={{API_KEY}}
&libraries=places"
>
</head>
</html>

Nice work! In addition to external stylesheets, we are going to make use of internal styles in this template. To define internal stylesheets in HTML files, simply add a <style> tag within the head of the HTML and input your desired CSS rules. Add the following style to home.html:

</script>

<!-- Definition of internal styles -->
<style>
#map {
height: 400px;
}

.container-review {
background-color: white;
border-radius: 2px;
font-family:sans-serif;
box-shadow: 0 0 1px black;
border-color: black;
padding: 0;
min-width: 250px;
height: 230px;
}

.review-author {
font-size: 15px
}

.review-location {
font-size: 12px
}

.review-title {
font-size: 13px;
text-decoration-style: dotted;
height: calc(20 / 100 * 230px);
}

.review-content {
font-size: 12px;
height: calc(40 / 100 * 230px);
}

.review-header {
height: calc(20 / 100 * 230px)
}

hr {
margin: 0;
}

.review-footer {
height: calc(20 / 100 * 230px);
}
</style>

Now, lets work on the body of the page. As you already know, all elements that make up the body of an HTML template must exist in a <body>  tag. With that in mind, we can work on home.html. Start by adding the following HTML to the template file:

  <!-- Invokes the showNoReviewNotification() function defined in -->
<!-- internal Javascript of this file upon document load. -->
<body
th:onload="'javascript:showNoReviewNotification(
' + ${reviews.size() == 0} + '
)'"
>
<div th:insert="fragments/navbar :: navbar"></div>
<div class="container">
<div class="row mt-5">
<!-- Creates view containers for each review retrieved -->
<!-- Distinct <div> containers are created for the -->
<!-- review author, location, title and body. -->
<div th:each="review: ${reviews}"
class="col-sm-2 container-review mt-4 mr-2">
<div class="review-header pt-1">
<div class="col-sm-12 review-author text-success">
<b th:text="${review.reviewer.username}"></b>
</div>
<div th:text="${review.placeName}"
class="col-sm-12 review-location">
</div>
</div>
<hr>
<b>
<div
th:text="${review.title}"
class="col-sm-12 review-title pt-1">
</div>
</b>
<hr>
<div th:text="${review.body}"
class="col-sm-12 review-content pt-2">
</div>
<div class="review-footer">
<!-- Creation of distinct DOM
<button> elements for the display of reviewed locations. -->
<!-- Upon button click, the application renders a modal
showing the reviewed location on a map -->
<button class="col-sm-12 button button-small button-primary"
type="button" data-toggle="modal" data-target="#mapModal"
style="height: inherit; border-radius: 2px;"
th:onclick="'javascript:showLocation(
' + ${review.latitude} + ','
+ ${review.longitude} + ',''
+ ${review.placeId} + ''
)'"
>
<i class="fa fa-map-o" aria-hidden="true"></i>
View location
</button>
</div>
</div>
</div>
</div>

Great job! Do not worry too much about what the preceding code block does as of now. We shall explain everything in due time. Moving on continue the body of home.html by adding the following lines of code to it:

<!-- Modal creation -->
<div class="modal fade" id="mapModal">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Reviewed location</h5>
<button type="button" class="close"
data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<div id="map"> </div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary"
data-dismiss="modal">
Done
</button>
</div>
</div>
</div>
</div>

The preceding code declares a modal that will hold a map displaying the exact location a review was created—upon the request of the user. We are not done with the home template yet. Continue work on the <body>  further by adding the following lines of code to it:


<span style="bottom: 20px; right: 20px; position: fixed">
<form method="get" th:action="@{/create-review}">
<button class="button button-primary button-circle
button-giant navbar-bottom"
type="submit">
<i
class="fa fa-plus"></i>
</button>
</form>
</span>

Finally, add internal JavaScript for the HTML page:

     <script>
//Shows a toast notification to the user when no review is present
function showNoReviewNotification(show) {
if (show) {
toastr.info('No reviews to see');
}
}

The following function initializes and displays a map showing the location where a review was written:


function showLocation(latitude, longitude, placeId) {
var center = new google.maps.LatLng(latitude, longitude);

var map = new google.maps.Map(document.getElementById('map'), {
center: center,
zoom: 15,
scrollwheel: false
});
var service = new google.maps.places.PlacesService(map);

loadPlaceMarker(service, map, placeId);
}

Load place marker creates a map marker on the reviewed location:


function loadPlaceMarker(service, map, placeId) {
var request = {
placeId: placeId
};

service.getDetails(request, function (place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
new google.maps.Marker({
map: map,
title: place.name,
place: {
placeId: place.place_id,
location: place.geometry.location
}

})
}
});
}
</script>
</body>

Quite a lot has been done on home.html, so let's talk a bit about what exactly is going on in the view, starting with the <head> tag. We included stylesheets and scripts required by the home page from lines 4 through 16 of the <head> tag. The CSS included is as follows:

<link rel="stylesheet" th:href="@{/css/app.css}"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css">
<link rel="stylesheet" href="/webjars/bootstrap/4.0.0-beta.3/css/bootstrap.min.css"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"/>
<link href="https://fastcdn.org/Buttons/2.0.0/css/buttons.css" rel="stylesheet">

These are external stylesheet inclusions for our application's CSS; Toastr, a library for the creation of JavaScript toast notifications; Bootstrap, a powerful library for designing websites and web applications; Font Awesome, an icon toolkit for websites and web applications; and buttons, a powerful and highly customizable web and CSS buttons library.

Right after the CSS inclusions, we have a number of external JavaScript inclusions as well:

<script src="/webjars/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.6/umd/popper.min.js"></script>
<script src="/webjars/bootstrap/4.0.0-beta.3/js/bootstrap.min.js"></script>
<script src="https://fastcdn.org/Buttons/2.0.0/js/buttons.js"></script>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key={{API_KEY}}&libraries=places"></script>

The script inclusions in their respective order are for: JQuery, a JavaScript library designed specifically to simplify the client-side HTML scripting process; Toastr; Popper, a library used to manage poppers in web applications; Bootstrap; buttons; and the Google Places API web service. Once again, ensure you replace the {{API_KEY}} with your API key for the Google Places API web service—this is important.

Immediately after the JavaScript inclusions, we defined an internal stylesheet for the web page. Unfortunately, an explanation of stylesheets and their creation is beyond the scope of this book. However, it will be a good idea to brush up on CSS in your spare time. Further down home.html, we added a <body> tag as follows:

<body th:onload="'javascript:showNoReviewNotification(' + ${reviews.size() == 0} + ')'">

th:onload in this is used to specify JavaScript that must be run after the page has been completely loaded. In short, it specifies code to be executed after an onload event occurs. In this case, the script to be run is a JavaScript function we defined further down the template, showNoReviewNotification(boolean).  The function shows a toast message indicating that no reviews are available to be viewed when the reviews list provided by the model is empty. showNoReviewNotification(boolean) is declared in our template as follows:

function showNoReviewNotification(show) {
if (show) {
toastr.info('No reviews to see');
}
}

showNoReviewNotification(boolean) takes a single Boolean argument, show. When show is true, a toast notification with the message No reviews to see is rendered to the user. The display of toast notifications to a user is made possible by the Toastr library we are utilizing.

When there are reviews available to be shown to the user, then a container is created for each review item as follows:

<!-- Creates view containers for each review retrieved -->
<!-- Distinct <div> containers are created for the -->
<!-- review author, location, title and body. -->
<div th:each="review: ${reviews}" class="col-sm-2 container-review mt-4 mr-2">
<div class="review-header pt-1">
<div class="col-sm-12 review-author text-success">
<b th:text="${review.reviewer.username}"></b>
</div>
<div th:text="${review.placeName}" class="col-sm-12 review-location">
</div>
</div>
<hr>
<b>
<div
th:text="${review.title}" class="col-sm-12 review-title pt-1">
</div>
</b>
<hr>
<div th:text="${review.body}" class="col-sm-12 review-content pt-2">
</div>
<div class="review-footer">
<!-- Creation of distinct DOM <button> elements for the
display of reviewed locations. -->
<!-- Upon button click, the application renders a modal
showing the reviewed location on a map -->
<button class="col-sm-12 button button-small button-primary"
type="button" data-toggle="modal" data-target="#mapModal"
style="height: inherit; border-radius: 2px;"
th:onclick="'javascript:showLocation('
+ ${review.latitude} + ','
+ ${review.longitude} + ',''
+ ${review.placeId} + '
')'"
>
<i class="fa fa-map-o" aria-hidden="true"></i>
View location
</button>
</div>
</div>

Each review container displays the username of the reviewer, the name of the place reviewed, the review title, the review body and a button enabling the user to view the reviewed location. Thymeleaf's th:each attribute was used to iterate over each review in the reviews list, as shown here:

<div th:each="review: ${reviews}" class="col-sm-2 container-review mt-4 mr-2">

A good way to understand the iteration process is by reading th:each="review: ${reviews}" as For each review in reviews. The review currently being iterated upon is held by the review variable. Hence, the data held by the review being iterated upon can be accessed like any other object. This is the case here:

<div th:text="${review.placeName}" class="col-sm-12 review-location"></div>

th:text sets the text held by the <div> to the value assigned to review.placeName. It is also necessary to explain the process by which location maps are shown to the user. Take a close look at the following lines of code:

<button class="col-sm-12 button button-small button-primary" type="button"
data-toggle="modal" data-target="#mapModal" style="height: inherit;
border-radius: 2px;"
th:onclick="'javascript:showLocation('
+ ${review.latitude} + ','
+ ${review.longitude} + ',''
+ ${review.placeId} + '
')'"
>
<i class="fa fa-map-o" aria-hidden="true"></i>
View location
</button>

This code block defines a button that does two things when a click event occurs on it. Firstly, it displays a modal identified by the ID mapModal to the user. Secondly, it initializes and renders a map displaying the exact location that was reviewed. The rendering of the map is made possible by the showLocation() JavaScript function we defined in our template file.

showLocation() takes three parameters as its arguments. The first is the longitudinal coordinate, the second a latitudinal coordinate, and the third the unique identifier of the location reviewed—a place ID. The place ID for the location is provided by the Google Places API. Firstly, showLocation() retrieves a central point for the locational coordinates provided. This is done by utilizing the Google Places API's google.maps.LatLng class. Simply defined, a LatLng is a point in geographical coordinates (longitude and latitude). Upon retrieving the central point, a new map is created with the use of the Map class (again, provided by the Google Places API) as shown in this code snippet:

var map = new google.maps.Map(document.getElementById('map'), {
center: center,
zoom: 15,
scrollwheel: false
});

The created map is placed within a DOM container element with an ID map. After creating the necessary map, we create a location marker at the exact location with the help of the loadPlaceMarker() function. loadPlaceMarker() takes instances of google.maps.places.PlacesService, Map, and a place id as its three arguments. PlacesService is a class that possesses methods for the retrieval of place information and searching places.

The instance of google.maps.places.PlacesService is firstly used to retrieve the details of the place with the specified place ID (the reviewed location). If the details of the place are successfully retrieved, status === google.maps.places.PlacesServiceStatus.OK evaluates to true and a marker for the location is placed on the map. The marker is created with the google.maps.Marker class. Marker() takes an optional options object as a its sole argument. When the options object is present, the place marker is created with the options specified. In this case, we specified a map in the options object. As such, the marker is added to the map upon its creation.

Lastly, we added a form to our template that sends a GET request to the /reviews/new path upon its submission, and added a button that submits the form upon clicking. This was done in the lines shown here:

<form method="get" th:action="@{/reviews/new}">
<button class="button button-primary button-circle button-giant
navbar-bottom"
type="submit"><i class="fa fa-plus"></i></button>
</form>

That is all we need to do regarding the home page, so go ahead and check it out! Rebuild and run the application, register an account, and view the home page you just created!

As you can see, no reviews have been created on the platform for viewing. We must now work on a web page that allows the creation of reviews.

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

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