Last Modified 2022.3.8

Comment with RESTful URL

The following is converting comment request URLs to RESTful URLs.

List:		GET /comments/23	--23 is article unique number--
New:		POST /comments/23
Modify:		PUT /comments/23/38 	--38 is a comment unique number--
Delete:		DELETE /comments/23/38

Let's modify the comment part of the board program to the Ajax program. Create CommentsController with the same package as BbsController.

@RestController
@RequestMapping("comments")
public class CommentsController {

  @Autowired
  private BoardService boardService;
  
  //TODO

}	

@RestController is an annotation that combines the @Controller and @ResponseBody annotations. If you declare the @RestController annotation in the class declaration, you do not have to add @ResponseBody for each method.

The return value of a method with the @ResponseBody annotation is not used to resolve a view but is converted via the message converter and written directly to the response body. To have the message converter the method return value to a JSON object, add the following dependency to pom.xml:

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.12.0</version>
</dependency>

Add a field named editable to the Comment class.

The editable field will decide whether to show the edit and delete links to the user.

package net.java_school.board;

import java.util.Date;

public class Comment {

  private int commentNo;
  private int articleNo;
  private String email;
  private String name;
  private String memo;
  private Date regdate;
  private boolean editable;

  public int getCommentNo() {
    return commentNo;
  }

  public void setCommentNo(int commentNo) {
    this.commentNo = commentNo;
  }

  public int getArticleNo() {
    return articleNo;
  }

  public void setArticleNo(int articleNo) {
    this.articleNo = articleNo;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getMemo() {
    return memo;
  }

  public void setMemo(String memo) {
    this.memo = memo;
  }

  public Date getRegdate() {
    return regdate;
  }

  public void setRegdate(Date regdate) {
    this.regdate = regdate;
  }
  
  public boolean isEditable() {
    return editable;
  }

  public void setEditable(boolean editable) {
    this.editable = editable;
  }
  
}

Add the following method to the CommentsController.

@GetMapping("{articleNo}")
public List<Comment> getAllComments(@PathVariable Integer articleNo, 
  Principal principal, Authentication authentication) {

  List<Comment> comments = boardService.getCommentList(articleNo);

  Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

  boolean isAdmin = false;

  for (GrantedAuthority authority : authorities) {
    String role = authority.getAuthority();
    if (role.equals("ROLE_ADMIN")) {
      isAdmin = true;
      break;
    }
  }

  if (isAdmin) {
    for (Comment comment : comments) {
      comment.setEditable(true);
      comment.setEmail(null);
    }
  } else {
    String username = principal.getName();
    for (Comment comment : comments) {
      if (comment.getEmail().equals(username)) {
        comment.setEditable(true);
      }
    }
    for (Comment comment : comments) {
      comment.setEmail(null);
    }
  }

  return comments;
}

Let's implement so that when the detail view page sends a GET /comments/23 request to the server, the same detail view page will receive a JSON object such as [{"commentNo":100,,,,},{"commentNo":99,,,,},{ "commentNo":98,,,,}] from the server.

Add the following JavaScript function to the detailed view page.

function displayComments() {
  var url = '/comments/' + ${articleNo};
  $.getJSON(url, function (data) {
    $('#all-comments').empty();
    $.each(data, function (i, item) {
      var creation = new Date(item.regdate);
      var comments = '<div class="comments">'
        + '<span class="writer">' + item.name + '</span>'
        + '<span class="date">' + creation.toLocaleString() + '</span>';
      if (item.editable === true) {
        comments = comments
          + '<span class="modify-del">'
          + '<a href="#" class="comment-modify-link">' 
          + 'Modify' + '</a> | '
          + '<a href="#" class="comment-delete-link" title="' + item.commentNo + '">' 
          + 'Del' + '</a>'
          + '</span>';
        }
        comments = comments
          + '<div class="comment-memo">' + item.memo + '</div>'
          + '<form class="comment-form" action="/comments/' + ${articleNo } + '/' 
          + item.commentNo + '" method="put" style="display: none;">'
          + '<div style="text-align: right;">'
          + '<a href="#" class="comment-modify-submit-link">' 
          + 'Sumbit' 
          + '</a> | <a href="#" class="comment-modify-cancel-link">' 
          + 'Cancel' + '</a>'
          + '</div>'
          + '<div>'
          + '<textarea class="comment-textarea" name="memo" rows="7" cols="50">' 
          + item.memo + '</textarea>'
          + '</div>'
          + '</form>'
          + '</div>';
        $('#all-comments').append(comments);
        console.log(item);
    });
  });
}

The displayComments() function should run in the following situations:

  • When the page loads
  • When you create a new comment
  • When you modify the comment
  • When you delete the comment

Modify HTML code for comments like below.

HTML code for comments
<sf:form id="addCommentForm" action="/comments/${articleNo }" method="post" style="margin: 10px 0;">
  <div id="addComment">
    <textarea id="addComment-ta" name="memo" rows="7" cols="50"></textarea>
  </div>
  <div style="text-align: right;">
    <input type="submit" value="Write Comments" />
  </div>
</sf:form>
<!--  comments begin -->
<div id="all-comments">
</div>
<!--  comments end -->

To write as above, you need to add the following to the detailed view page:

<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>.

All comments displays in <div id="all-comments">. See below to update the list of comments when the page loads or users create comments.

$(document).ready(function () {
  displayComments();
  
  //..omit..    
  
  $("#addCommentForm").submit(function (event) {
    event.preventDefault();
    var $form = $(this);
    var memo = $('#addComment-ta').val();
    memo = $.trim(memo);
    if (memo.length === 0) {
      $('#addComment-ta').val('');
      return false;
    }
    var dataToBeSent = $form.serialize();
    var url = $form.attr("action");
    var posting = $.post(url, dataToBeSent);
    posting.done(function () {
      displayComments();
      $('#addComment-ta').val('');
    });
  });    
});

Each time the displayComments() function is executed, comment HTML code is dynamically generated for the content of div id="all-comments". A comment HTML code may have links for editing and deleting the comment. Register each handler using JavaScript DOM programming for these links. If you need to do DOM programming in dynamically generated HTML code after the page has loaded, use the $(document).on() function.

$(document).on('click', '#all-comments', function (e) {
  if ($(e.target).is('.comment-modify-link')) {
    e.preventDefault();
    var $form = $(e.target).parent().parent().find('.comment-form');
    var $div = $(e.target).parent().parent().find('.comment-memo');

    if ($form.is(':hidden') === true) {
      $form.show();
      $div.hide();
    } else {
      $form.hide();
      $div.show();
    }
  } else if ($(e.target).is('.comment-modify-cancel-link')) {
    e.preventDefault();
    var $form = $(e.target).parent().parent().parent().find('.comment-form');
    var $div = $(e.target).parent().parent().parent().find('.comment-memo');

    if ($form.is(':hidden') === true) {
      $form.show();
      $div.hide();
    } else {
      $form.hide();
      $div.show();
    }
  } else if ($(e.target).is('.comment-modify-submit-link')) {
    e.preventDefault();
    var $form = $(e.target).parent().parent().parent().find('.comment-form');
    var $textarea = $(e.target).parent().parent().find('.comment-textarea');
    var memo = $textarea.val();
    $('#modifyCommentForm input[name*=memo]').val(memo);
    var dataToBeSent = $('#modifyCommentForm').serialize();
    var url = $form.attr("action");
    $.ajax({
      url: url,
      type: 'PUT',
      data: dataToBeSent,
      success: function () {
        displayComments();
      },
      error: function () {
        alert('error!');
      }
    });
  } else if ($(e.target).is('.comment-delete-link')) {
    e.preventDefault();
    var chk = confirm('Are you sure you want to delete this item?');
    if (chk === false) {
      return;
    }
    var $commentNo = $(e.target).attr('title');
    var url = $('#deleteCommentForm').attr('action');
    url += $commentNo;
    var dataToBeSent = $('#deleteCommentForm').serialize();
    $.ajax({
      url: url,
      type: 'POST',
      data: dataToBeSent,
      success: function () {
        displayComments();
      },
      error:function(request,status,error){
        console.log("code:"
          + request.status
          + "\n" + "message:" 
          + request.responseText 
          + "\n" + "error:"
          + error);
      }
    });
  }
});

Note that it is a POST request when deleting comments, not a DELETE request. The reason is covered later.

Add the following method for adding comments to the CommentsController:

@PostMapping("{articleNo}")
public void addComment(@PathVariable Integer articleNo, 
      String memo, Principal principal) {
    
  Comment comment = new Comment();
  comment.setMemo(memo);
  comment.setArticleNo(articleNo);
  comment.setEmail(principal.getName());

  boardService.addComment(comment);
}

Now you can add a comment on the detailed view page.

Add the following method to the CommentsController:

@PutMapping("{articleNo}/{commentNo}")
public void updateComment(@PathVariable Integer articleNo, 
      @PathVariable Integer commentNo, String memo, Principal principal) {
      
  Comment comment = boardService.getComment(commentNo);
  comment.setMemo(memo);
  boardService.modifyComment(comment);
}

Now you can modify a comment on the detailed view page.

Add the following method to the CommentsController:

@DeleteMapping("{articleNo}/{commentNo}")
public void deleteComment(@PathVariable Integer articleNo, @PathVariable Integer commentNo) {
  Comment comment = boardService.getComment(commentNo);
  boardService.removeComment(comment);
}

In an environment using Spring MVC and Spring Security, Ajax DELETE request occurs a "Request method 'DELETE' not supported" error. Ajax DELETE requests work fine in an environment using Spring MVC only.

Spring can map POST requests to DELETE requests. This feature is for when the browser doesn't support PUT or DELETE requests, but let's use it for deleting comments. --At this time, you should also modify the code for editing comments--

Add the following filter to web.xml:

<filter>
  <filter-name>httpMethodFilter</filter-name>
  <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
  <filter-name>httpMethodFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

The added filter determines the HTTP method by the value of the _method parameter and passes that to the handler.

Modify the modify comment form and delete comment form in the detailed view page as follows.

<sf:form id="modifyCommentForm" method="put">
  <input type="hidden" name="memo" />
</sf:form>
<sf:form id="deleteCommentForm" action="/comments/${articleNo }/" method="delete">
</sf:form>

Change the type from PUT to POST in the ajax transmission code related to comment editing.

  } else if ($(e.target).is('.comment-modify-submit-link')) {
    e.preventDefault();
    var $form = $(e.target).parent().parent().parent().find('.comment-form');
    var $textarea = $(e.target).parent().parent().find('.comment-textarea');
    var memo = $textarea.val();
    $('#modifyCommentForm input[name*=memo]').val(memo);
    var dataToBeSent = $('#modifyCommentForm').serialize();
    var url = $form.attr("action");
    $.ajax({
      url: url,
      type: 'POST',
      data: dataToBeSent,
      success: function () {
        displayComments();
      },
      error: function () {
        alert('error!');
      }
    });
  } else if ($(e.target).is('.comment-delete-link')) {
Related Articles