Displaying comments

Now we can make edits to our Thymeleaf template and create input fields for people to write comments:

    <td> 
        <ul> 
            <li th:each="comment : ${image.comments}" 
                th:text="${comment.comment}"></li> 
        </ul> 
    </td> 
    <td> 
        <form th:method="post" th:action="@{'/comments'}"> 
            <input name="comment" value="" type="text" /> 
            <input name="imageId" th:value="${image.id}"
type="hidden" /> <input type="submit" /> </form> </td>

The section of our preceding template where each row is rendered can be explained as follows:

  • There is a new column containing an HTML unordered list to display each comment
  • The unordered list consists of an HTML line item for each comment via Thymeleaf's th:each construct
  • There is also a new column containing an HTML form to post a new comment
  • The form contains an HTML text input for the comment itself
  • The form also contains a hidden HTML element specifying the ID of the image that the comment will be associated with

To support this, we need to update HomeController as follows:

    private final ImageService imageService; 
    private final CommentReaderRepository repository; 
 
    public HomeController(ImageService imageService, 
     CommentReaderRepository repository) { 
       this.imageService = imageService; 
       this.repository = repository; 
    } 

We have updated the class definition as follows:

  • A new repository field is created for CommentReaderRepository (which we'll define further ahead in the chapter)
  • This field is initialized by constructor injection

We need to look up the comments. To do that, we need a Spring Data repository that can read comments. And reading comments is ALL this repository needs to do at this stage of our social media app.

Let's take this new repository and use it inside the Spring WebFlux handler for GET /, like this:

    @GetMapping("/") 
    public Mono<String> index(Model model) { 
      model.addAttribute("images", 
       imageService 
       .findAllImages() 
       .flatMap(image -> 
         Mono.just(image) 
           .zipWith(repository.findByImageId( 
             image.getId()).collectList())) 
       .map(imageAndComments -> new HashMap<String, Object>(){{ 
            put("id", imageAndComments.getT1().getId()); 
            put("name", imageAndComments.getT1().getName()); 
            put("comments", 
              imageAndComments.getT2()); 
        }})
); model.addAttribute("extra", "DevTools can also detect code changes too"); return Mono.just("index"); }

This last code contains a slight adjustment to the model's images attribute:

  • The code takes the Flux returned from our ImageService.findAll() method and flatMaps each entry from an Image into a call to find related comments.
  • repository.findByImageId(image.getId()).collectList() actually fetches all Comment objects related to a given Image, but turns it into Mono<List<Comment>>. This waits for all of the entries to arrive and bundles them into a single object.
  • The collection of comments and it's related image are bundled together via Mono.zipWith(Mono), creating a tuple-2 or a pair. (This is the way to gather multiple bits of data and pass them on to the next step of any Reactor flow. Reactor has additional tuple types all the way up to Tuple8.)
  • After flatMapping Flux<Image> into Flux<Tuple2<Image,List<Comment>>>, we then map each entry into a classic Java Map to service our Thymeleaf template.
  • Reactor's Tuple2 has a strongly typed getT1() and getT2(), with T1 being the Image and T2 being the list of comments, which is suitable for our needs since it's just a temporary construct used to assemble details for the web template.
  • The image's id and name attributes are copied into the target map from T1.
  • The comments attribute of our map is populated with the complete List<Comment> extracted from T2.
Since Thymeleaf templates operate on key-value semantics, there is no need to define a new domain object to capture this construct. A Java Map will work just fine.

As we continue working with Reactor types, these sorts of flows are, hopefully, becoming familiar. Having an IDE that offers code completion is a key asset when putting flows like this. And the more we work with these types of transformations the easier they become.

If you'll notice, ImageService is fully reactive given that we use MongoDB's reactive drivers. The operation to retrieve comments is also reactive. Chaining reactive calls together, using Reactor's operators and hitching them to Thymeleaf's reactive solution, ensures that everything is being fetched as efficiently as possible and only when necessary. Writing reactive apps hinges on having a fully reactive stack.

To round out our feature of reading comments, we need to define CommentReaderRepository as follows:

    public interface CommentReaderRepository 
     extends Repository<Comment, String> { 
 
       Flux<Comment> findByImageId(String imageId); 
    } 

The preceding code can be described as follows:

  • It's a declarative interface, similar to how we created ImageRepository earlier in this book.
  • It extends Spring Data Commons' Repository interface, which contains no operations. We are left to define them all. This lets us create a read-only repository.
  • It has a findByImageId(String imageId) method that returns a Flux of Comment objects.

This repository gives us a read-only readout on comments. This is handy because it lets us fetch comments and does not accidentally let people write through it. Instead, we intend to implement something different further in this chapter.

Our CommentReaderRepository needs one thing: a Comment domain object:

    package com.greglturnquist.learningspringboot.images; 
 
    import lombok.Data; 
 
    import org.springframework.data.annotation.Id; 
 
    @Data 
    public class Comment { 
 
      @Id private String id; 
      private String imageId; 
      private String comment; 
 
    } 

This preceding domain object contains the following:

  • The @Data annotation tells Lombok to generate getters, setters, toString(), equals(), and hashCode() methods
  • The id field is marked with Spring Data Commons' @Id annotation so we know it's the key for mapping objects
  • The imageId field is meant to hold an Image.id field, linking comments to images
  • The comment field is the place to store an actual comment
For both CommentReaderRepository and Comment, the entire class is shown including the package. That's to show that it's located in the images subpackage we defined earlier in this chapter. This domain object provides the comment information pertinent to images. And this information is read-only, which means that this is not where updates regarding comments are made.
..................Content has been hidden....................

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