11. Web views

Showing off

After having put some data in the graph database, we also wanted to show it to the user. Adding the controller method to show a single movie with its attributes and cast in a JSP was straightforward. It basically just involved using the repository to look the movie up and add it to the model, and then forwarding to the /movies/show view and voilá.

Example 11.1. Controller for showing movies

@RequestMapping(value = "/movies/{movieId}",
method = RequestMethod.GET, headers = "Accept=text/html")
public String singleMovieView(final Model model, @PathVariable String movieId) {
    Movie movie = repository.findById(movieId);
    model.addAttribute("id", movieId);
    if (movie != null) {
        model.addAttribute("movie", movie);
        model.addAttribute("stars", movie.getStars());
    }
    return "/movies/show";
}


Example 11.2. Populating the database - JSP /movies/show

<%@ page session="false" %>
		<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
		<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

		<c:choose>
		  <c:when test="${not empty movie}">
		    <h2>${movie.title} (${stars} Stars)</h2>
		    <c:if test="${not empty movie.roles}">
		    <ul>
		    <c:forEach items="${movie.roles}" var="role">
		      <li>
		        <a href="/actors/${role.actor.id}"><c:out value="${role.actor.name}" /> as
		        <c:out value="${role.name}" /></a><br/>
		      </li>
		    </c:forEach>
		    </ul>
		    </c:if>
		  </c:when>
		  <c:otherwise>
		      No Movie with id ${id} found!
		  </c:otherwise>
		</c:choose>
		


The UI had now evolved to this:

11.1 Searching

The next thing was to allow users to search for movies, so we needed some fulltext search capabilities. As the default index provider implementation of Neo4j is based on Apache Lucene, we were delighted to see that fulltext indexes were supported out of the box.

We happily annotated the title field of the Movie class with @Indexed(type = FULLTEXT). Next thing we got an exception telling us that we had to specify a separate index name. So we simply changed it to @Indexed(type = FULLTEXT, indexName = "search").

With derived finder methods, finding things became easy. By simply declaring a finder-method name that expressed the required properties, it worked without annotations. Cool stuff and you could even tell it that it should return pages of movies, its size and offset specified by a Pageable which also contains sort information. Using the like operator indicates that fulltext search should be used, instead of an exact search.

Example 11.3. Searching for movies

public interface MovieRepository ... {
    Movie findById(String id);
    Page<Movie> findByTitleLike(String title, Pageable page);
    Slice<Movie> findAll(Pageable page);
}


11.2 Listing results

We then used this result in the controller to render a page of movies, driven by a search box. The movie properties and the cast were accessible through the getters in the domain classes.

Example 11.4. Search controller

@RequestMapping(value = "/movies",
method = RequestMethod.GET, headers = "Accept=text/html")
public String findMovies(Model model, @RequestParam("q") String query) {
    Page<Movie> movies = repository.findByTitleLike(query, new PageRequest(0,20));
    model.addAttribute("movies", movies);
    model.addAttribute("query", query);
    return "/movies/list";
}


Example 11.5. Search Results JSP

<h2>Movies</h2>

<c:choose>
    <c:when test="${not empty movies}">
        <dl class="listings">
        <c:forEach items="${movies}" var="movie">
            <dt>
                <a href="/movies/${movie.id}"><c:out value="${movie.title}" /></a><br/>
            </dt>
            <dd>
                <c:out value="${movie.description}" escapeXml="true" />
            </dd>
        </c:forEach>
        </dl>
    </c:when>
    <c:otherwise>
        No movies found for query &quot;${query}&quot;.
    </c:otherwise>
</c:choose>
            


The UI now looked like this: