Moving to a fully asynchronous web client

Now we are geared up to receive asynchronous messages from the server as comments are created, and display them dynamically on the site. However, there is something else that warrants attention.

Remember how, in the previous chapter, we had an HTML form for the user to fill out comments? The previous chapter's controller responded to such POSTs like this:

    @PostMapping("/comments") 
    public Mono<String> addComment(Mono<Comment> newComment) { 
 
      /* stream comments to COMMENTS service */ 
 
      return Mono.just("redirect:/"); 
    } 

redirect:/ is a Spring Web signal to re-render the page at / via an HTTP redirect. Since we are shifting into dynamically updating the page based on asynchronous WebSocket messages, this is no longer the best way.

What are the issues? A few can be listed as follows:

  • If the comment hasn't been saved (yet), the redirect would re-render the page with no change at all.
  • The redirect may cause an update in the midst of handling the new comment's WebSocket message. Based on the race conditions, the comment may not yet be saved, causing it to not appear, and the refresh may miss the asynchronous message, causing the entire comment to not be displayed unless the page is manually refreshed.
  • Setting up a WebSocket handler with every new comment isn't efficient.

Either way, this isn't a good use of resources, and could introduce timing issues. Instead, it's best if we convert this into an AJAX call.

To do so, we need to alter the HTML like this:

    <td> 
      <input th:id="'comment-' + ${image.id}" type="text" value="" /> 
      <button th:id="${image.id}" class="comment">Submit</button> 
    </td> 

Instead of a form with a text input and a Submit input, we remove the HTML form and replace it with a button:

  • The <input> contains an id attribute unique to its corresponding image
  • The <button> has a similar id attribute

The <button> also has class="comment", which we'll use to find, and decorate it with an event handler to process clicks as follows:

    // Register a handler for each button to make an AJAX call 
    document.querySelectorAll('button.comment') 
     .forEach(function(button) { 
       button.addEventListener('click', function() { 
         var comment = document.getElementById( 
           'comment-' + button.id); 
 
         var xhr = new XMLHttpRequest(); 
         xhr.open('POST', /*[[@{'/comments'}]]*/'', true); 
 
         var formData = new FormData(); 
         formData.append('comment', comment.value); 
         formData.append('imageId', button.id); 
 
         xhr.send(formData); 
 
         comment.value = ''; 
       }); 
    }); 

This last block of JavaScript, contained inside our tidy little (function(){})(), has the following:

  • document.querySelectorAll('button.comment') uses a native JavaScript query selector to find all the HTML buttons that have the class comment.
  • Iterating over each button, an event listener is added, responding to the click events.
  • When a click is received, it fetches the corresponding comment input.
  • Then it fashions an XMLHttpRequest object, opening a POST operation set for asynchronous communications.
  • With Thymeleaf's JavaScript support, it will plug in the URL for @{'/comments'} upon rendering.
  • Then it constructs a FormData, and loads the same fields as the previous chapter as if we had filled out an HTML form on the page.
  • It transmits the form data over the wire. Since we don't depend on the results, they are ignored.
  • Finally, it clears out the comment input's entry box.
In this example, we're using JavaScript's native APIs. But if you're using Rest.js, jQuery, Restangular, lodash, or any other toolkit, feel free to assemble your AJAX call using that instead. The point is to asynchronously transmit the data instead of navigating to another page.
..................Content has been hidden....................

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