Last Modified 2019.11.8

Comment with RESTful URL

The following is the result of modifying the comment request URL to match the RESTful URL.

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

I want to modify the comment to Ajax program.
Create CommentsController with the same package as BbsController as below.

@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 interpret the 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.10.0</version>
</dependency>

List

Add an editable attribute to the Comment class.

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;
}

If you send a GET /comments/23 request to the server from the detailed view page, A JSON object such as [{"commentNo":100,,,,},{"commentNo":99,,,,},{"commentNo":98,,,,}] is passed to the detailed view page.

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);
    });
  });
}

When the page loads,
when you create a new comment,
when the comment is modified,
or when the comment is deleted,
The displayComments() function should be executed.

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, the detailed view page needs the followings:
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>.

The list of comments is updated in the content of div id="all-comments".

See below to have the list of comments updated when the page loads and when a comment is created.

$(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". If the dynamically generated comment HTML code has edit and delete links, register each handler with JavaScript DOM programming. --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 when deleting a comment, it is a POST request, not a DELETE request. --This will be explained later-- You can now see comments on the detailed view page.

New

Add the following method 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.

Modify

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.

Delete

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 will result in a "Request method 'DELETE' not supported" error. --I do not know the cause. 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 just for deleting 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 value of the _method parameter and passes the request to the handler.

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

<sf:form id="deleteCommentForm" action="/comments/${articleNo }/" method="delete">
</sf:form>
Related Articles