감 잃지말고 개발하기

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #5. FAQ 페이지 구현 본문

JSP/MVC

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #5. FAQ 페이지 구현

persii 2023. 4. 20. 04:07

교보문고 고객센터 페이지를 모델로 하여 MVC패턴을 지키면서 ajax를 이용한 비동기 통신으로 DB에 저장된 데이터를 브라우저에 출력하는 로직을 기록하고자 한다.

 

지난 포스팅에서는 FAQ 페이지가 요청되는 두 가지 방식 중 첫 번째 방식의 로직을 정리해 보았다.

이번 포스팅에서는 두 번째 방식의 로직을 기록해 보겠다. 두 번째 방식의 로직은 AJAX를 이용한 데이터 통신 방식이다. 

 

목표

 JSP에서 MVC 패턴을 지키면서 AJAX를 통해 데이터를 출력할 수 있다.

 교보문고 고객센터 페이지를 구현할 수 있다.

 

 

로직 흐름

  1. 이미 한번 요청된 FAQ 페이지에서 다시 대분류 카테고리를 클릭하면 ajax로 해당 대분류 코드(PK)가 서버에 넘어간다(faq.jsp).
  2. 해당 컨트롤러에서 URL을 받고, 해당 요청을 처리하는 클래스를 호출한다(AjaxFrontController.java).
  3. Action 클래스에서 비즈니스 로직을 처리하는 클래스를 호출하여 DB로부터 데이터를 가져오고, 가져온 데이터를 JSON 형태의 문자열로 만들어 response 객체에 담아 보낸다(CsCenterFaqAction.java).
  4. Service 클래스에서 DAO클래스를 호출한다(BookCscenterFaqService.java).
  5. DAO 클래스에서 MySQL8.0 DB에 접근해 데이터를 가져온다(FaqDAO. FaqKeyDAO).
  6. 다시 FAQ 페이지로 돌아와 적절한 응답을 한다.

 

 

로직 흐름 코드 및 실행화면

1. FAQ 페이지(faq.jsp)

1-1.  FAQ 페이지 대분류 영역

FAQ 페이지에서 대분류를 하나 클릭하면 스크립트 함수가 실행된다.

FAQ 페이지

 

1-2.  FAQ 페이지 대분류 영역 HTML

<a href="#" onclick="return false" class="fcode" id="f0"><h5>Best 10</h5></a>

  • href="#" onclick="return false" : 대분류를 클릭했을 때 AJAX로 통신할 것이므로 <a> 태그의 링크 기능(href로 이동)을 무시해 준다. 
  • class="fcode" : 대분류 링크를 클릭했을 때 스크립트 함수를 편리하게 호출하기 위한 설정이다.
  • id="f0" : 대분류 링크를 클릭했을 때 대분류 코드(PK)를 서버로 넘기기 위해 PK값을 id="f" 뒤에 붙인다.
<section class="features-area section_gap">
  <div class="container">
    <div class="container-header">
      <h3><b>자주 묻는 질문</b></h3>
    </div>
    <div class="features-inner text-center"> 
      <div class="row row-cols-4 mx-2 faqRow">
        <div class="col-3 p-2 border">	
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f0"><h5>Best 10</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f1"><h5>회원</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f2"><h5>도서/상품정보/교과서</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f3"><h5>주문/결제</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f4"><h5>배송/수령일 안내</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f5"><h5>반품/교환/환불</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f6"><h5>세금계산서/증빙</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f7"><h5>서비스</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f8"><h5>eBook</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f9"><h5>중고장터</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f10"><h5>POD 주문형출판</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f11"><h5>PubPle(퍼플)</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f12"><h5>sam</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false" class="fcode" id="f13"><h5>북모닝</h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false"><h5></h5></a>
          </li>
        </div>
        <div class="col-3 p-2 border">
          <li class="mt-2">
            <a href="#" onclick="return false"><h5></h5></a>
          </li>
        </div>
      </div>
    </div>
  </div>
</section>

 

1-3.  FAQ 페이지 대분류 링크 클릭 JS

대분류 링크를 클릭했을 때 호출되는 스크립트 함수이다.클릭하면 해당 태그의 id값에서 f를 뺀 숫자가 fcode 변수에 저장되어 서버에 넘어간다.

 

  • ajax로 호출할 URL : "/cscenter/faq.ax"
  • ajax로 보낼 데이터 통신타입 : POST
  • ajax로 서버에 요청 시 보낼 파라미터: 해당 대분류의 코드(PK)
  • 응답받을 데이터 타입 : TEXT
    • 서버에서 DB 조회에 대한 결과물을 문자열로 만들어 보낼 것이므로 dataType을 TEXT로 설정한다.
  • 정상적으로 요청/응답된 경우(success) : 서버 딴 코드를 거친 후 설명하겠다.
<script type="text/javascript">
  $(document).ready(function(){

    $(".fcode").click(function() {

      var fcode = $(this).attr('id');
      fcode = fcode.substring(1);	

      // ajax로 보내서 출력
      $.ajax({
        url: '/cscenter/faq.ax',
        type: "POST",
        data: {"fcode": fcode},
        dataType: "TEXT",
        success: function() {
		  // 추후에 코드 추가
        }, 
        error: function(request, status, error) {
          console.log("code: "+request.status+"\n"+"message : "+request.responseText+"\n"+"error: "+error);
        } 
      });
    });
  });
</script>

 

2. 컨트롤러(AjaxFrontController.java)

웹 브라우저에서 요청한 URL에 해당하는 if문으로 들어오고 CsCenterFaqAction클래스의 execute() 메서드가 호출된다. 

package controller;

import java.io.IOException;
import java.rmi.ServerException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import action.CsCenterFaqAction;
import action.CsCenterFkcodeFaqAction;
import svc.AjaxAction;

/** AJAX로 데이터 통신
 * ActionForward 클래스 필요 X */
@WebServlet("*.ax")
public class AjaxFrontController extends HttpServlet {

	protected void doProcess(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {

		req.setCharacterEncoding("UTF-8");
		
        	/* 1. 요청 주소 파악 */
		String requestURI = req.getRequestURI();
		String contextPath = req.getContextPath();
		String command = requestURI.substring(contextPath.length());

		/* 2. 각 요청 주소의 매핑 처리 */
		AjaxAction action = null;
		
		// 전체 Faq 처리 요청
		if (command.equals("/cscenter/faq.ax")) {
			action = new CsCenterFaqAction();
			try {
				action.execute(req, resp);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}	
	}

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		doProcess(req, resp);
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
			throws ServerException, IOException, ServletException {
		doProcess(req, resp);
	}

}

 

3. Action 클래스(CsCenterFaqAction.java)

Action 클래스에서 해야 할 중요한 부분은 DB로부터 데이터를 들고 와 JSON 형태로 만드는 것이다. 

 

  • ajax로 건너온 대분류 코드(fcode)를 저장한다.
    • 이 fcode에는 0이 저장될 수 있으므로 모든 과정에서 "Best10"의 경우도 생각해야 한다.
  • DB에서 fcode에 따른 중분류 리스트와 FAQ 리스트를 가져온다.
    • "Best10"의 경우 별다른 중분류가 필요 없으므로 중분류 리스트는 fcode가 0이 아닌 경우에만 가져오도록 한다. 
  • 가져온 데이터를 JSON 형태의 문자열로 저장한다.
    • 만들 JSON 형태는 #2. 구현 틀 잡기 포스팅을 참고하길 바란다.
    • 데이터 유무에 따라 저장되는 정보가 다르니 아래 코드의 주석을 참고하길 바란다.
  • 보낼 문자열을 response에 담아 보낸다.

2023.04.18 - [JSP/MVC] - [JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #2. 구현 틀 잡기

 

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #2. 구현 틀 잡기

교보문고 고객센터 페이지를 모델로 하여 MVC패턴을 지키면서 ajax를 이용한 비동기 통신으로 DB에 저장된 데이터를 브라우저에 출력하는 로직을 기록하고자 한다. 지난 포스팅에서는 교보문고

persimmon-ary-stepbystep.tistory.com

 


package action;

import java.io.PrintWriter;
import java.util.ArrayList;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

import svc.AjaxAction;
import svc.BookCscenterFaqService;
import vo.Faq;
import vo.FaqKeyword;

public class CsCenterFaqAction implements AjaxAction {

	@SuppressWarnings("unchecked")
	@Override
	public void execute(HttpServletRequest req, HttpServletResponse resp) throws Exception {

		/* 파라미터 저장 */
		int fcode = Integer.parseInt(req.getParameter("fcode"));
		
		/* DB에서 fcode에 따른 데이터 가져오기 */
		BookCscenterFaqService service = new BookCscenterFaqService();
		ArrayList<FaqKeyword> fkList = new ArrayList<>();
		if(fcode != 0) {
			 fkList = service.selectFks(fcode);
		}
		ArrayList<Faq> faqList = service.selectFaq(fcode);
		
		String faqStr = null;  // 최종적인 JSONObject를 문자열로 만들어 저장할 변수
		JSONArray faqsarr = new JSONArray(); // FAQ 리스트를 저장할 JSONArray
		JSONArray fkssarr = new JSONArray(); // 중분류 리스트를 저장할 JSONArray
		JSONObject faqs = new JSONObject();	 // 최종적으로 저장할 JSONObject
		
		if(fkList.size() > 0) {
			// Best10 링크 외의 링크를 클릭한 경우
            
			for(FaqKeyword fk : fkList) {
            			// 하나씩 접근해 JSONObject에 저장
				JSONObject fkObj = new JSONObject();
				fkObj.put("fk_code", fk.getFk_code());
				fkObj.put("fk_value", fk.getFk_value());
					
                   		 // JSONArray에 저장
				fkssarr.add(fkObj);
			}
			
			// 최종적인 faqs 오브젝트에 중분류 리스트, 대분류 정보 저장
			faqs.put("fks", fkssarr);
			faqs.put("fc_code", fkList.get(0).getFk_fc_code()); 
		    faqs.put("fc_value", fkList.get(0).getFc_value());
			
		} else {
			// Best10 링크를 클릭한 경우
			faqs.put("fks", "no-fks");
			faqs.put("fc_code", fcode); 
		    faqs.put("fc_value", "Best10");
		}
		
		if(faqList != null) {
        	// FAQ 리스트 
            
			for(Faq faq : faqList) {
				JSONObject faqObj = new JSONObject();
				faqObj.put("fk_code", faq.getF_fk_code());
				faqObj.put("fk_value", faq.getFk_value());
				faqObj.put("f_id", faq.getF_id());
				faqObj.put("f_title", faq.getF_title());
				faqObj.put("f_text", faq.getF_text());

				// 오브젝트 데이터를 JSONArray에 순서대로 저장
				faqsarr.add(faqObj);
			}
			
			// 최종적인 faqs 오브젝트에 저장
			faqs.put("faqs", faqsarr);
			
		} else {
        	// FAQ 데이터가 없는 경우
			faqs.put("faqs", "no-faqs");
		}
		
		// 파싱할 데이터 저장
		faqStr = faqs.toJSONString();
		
		/* 파싱할 최종 데이터 보내기 */
		resp.setCharacterEncoding("utf-8");
		PrintWriter out = resp.getWriter();
		
		out.print(faqStr);
		out.close();
	}
}

 

4. Service 클래스(BookCscenterFaqService.java)

5. DAO 클래스(FaqKeyDAO.java & FaqDAO.java)

Service 클래스와 DAO 클래스의 코드는 바로 이전 포스팅(#4. FAQ 페이지 구현)에서 다루었으므로 코드 설명은 생략하겠다.


2023.04.19 - [JSP/MVC] - [JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #4. FAQ 페이지 구현

 

[JSP] [MVC] [AJAX] [JS] [JSON] AJAX를 이용해 고객센터 페이지 구현하기 #4. FAQ 페이지 구현

교보문고 고객센터 페이지를 모델로 하여 MVC패턴을 지키면서 ajax를 이용한 비동기 통신으로 DB에 저장된 데이터를 브라우저에 출력하는 로직을 기록하고자 한다. 지난 포스팅에서는 고객센터

persimmon-ary-stepbystep.tistory.com


 

6. FAQ 페이지(faq.jsp)

ajax로 보낸 요청이 정상적으로 처리되고, 서버로부터 정상적인 응답이 이루어진 경우, 화면에 그에 따른 응답이 출력되도록 할 것이다. 

이제 ajax가 success 된 상태의 JS 코드를 마저 작성해 보자.

6-1.  FAQ 페이지 대분류 링크 클릭 JS

이 부분에서의 중요한 점은 서버로부터 넘어온 데이터 구조를 정확하게 파악하여 원하는 화면 영역에 출력되도록 작성하는 것에 있다. 

 

살펴보면,

중분류 데이터가 있다면, 그 데이터는 <div class="faqkeywordUI"> 태그 안에 루프를 돌면서 출력되어야 한다.

반대로 중분류 데이터가 없다면, 즉 Best 10의 경우, <div class="faqkeywordUl"> 태그는 없다.

중분류 데이터가 있던 없던, 중분류 데이터와  FAQ 데이터를 구분하는 실선 <hr class="my-3"> 태그는 출력되어야 한다.

 

FAQ 데이터는 <div class="faqCardList"> 태그 안에 출력되어야 한다.

만약 FAQ 데이터가 있다면, <div class="card"> 태그 안에 루프를 돌면서 출력되어야 한다.

반대로 FAQ 데이터가 없다면, <h3> 등록된 문의가 없습니다. </h3>가 출력되어야 한다.

 

모든 if-else문을 경유하여 완성된 HTML 코드는 <section class="faqSection"> 태그 안에 출력되어야 한다.

 

아래의 이미지를 보면서 코드를 이해하면 출력 부분을 이해할 수 있을 것이다.

HTML 태그 클래스명

<script type="text/javascript">
  $(document).ready(function(){

    $(".fcode").click(function() {

      var fcode = $(this).attr('id');
      fcode = fcode.substring(1);	

      // ajax로 보내서 출력
      $.ajax({
        url: '/cscenter/faq.ax',
        type: "POST",
        data: {"fcode": fcode},
        dataType: "TEXT",
        success: function(text) {

          // String타입의 text를 자바스크립트 객체로 변환
          var toJsObj = JSON.parse(text);

          var html = "";

          html = '<div class="container-header faqTabArea">';
            html += '<h3><b>'+toJsObj["fc_value"]+'</b></h3>';
            html += '<input type="hidden" id="fc_code" value="'+toJsObj["fc_code"]+'">';

          // 중분류 출력부분
          if(toJsObj["fks"] != "no-fks") {
            html += '<div>';
              html += '<ul class="nav faqkeywordUl">';
                html += '<li class="nav-item">';
                  html += '<button type="button" class="fkbtn" id="tab00">'; 
                    html += '<span class="text-center">전체</span>';
                  html += '</button>';
                html += '</li>';

              $.each(toJsObj["fks"], function(index, value) {
                html += '<li class="nav-item">';
                  html += '<button type="button" class="fkbtn" id="tab0'+value.fk_code+'">'; 
                    html += '<span class="text-center">'+value.fk_value+'</span>';
                  html += '</button>';
                html += '</li>';
              });
            
              html += '</ul>';
            html += '</div>';
          } 	

            html += '<hr class="my-3">';
          html += '</div>';

          html += '<div class="accordion faqCardList" id="accordionExample">';	

          // FAQ 출력부분
          if(toJsObj["faqs"] === "no-faqs"){
            html += '<div>';
              html += '<h3><b>등록된 문의가 없습니다.</b></h3>';
            html += '</div>';
          } else {
            // FAQ(질문/답변이 있는 경우)
            $.each(toJsObj["faqs"], function(index, value) {
              html += '<div class="card">';
                html += '<div class="card-header" id="heading"'+index+'">'; 
                  html += '<h2>';
                    html += '<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse"';
                    html += ' data-target="#collapse' + index + '" aria-expanded="true"';
                    html += ' aria-controls="collapse' + index + '">';
                      html += '['+value.fk_value+']'+ value.f_title;
                    html += '</button>';
                  html += '</h2>';
                html += '</div>';

                html += '<div id="collapse'+index+'" class="collapse" aria-labelledby="heading'+index+'" data-parent="#accordionExample">';
                  html += '<div class="card-body" style="background-color:rgba(0,0,0,.03);">';
                    html += value.f_text;
                  html += '</div>';
                html += '</div>';
              html += '</div>';
            });

          html += '</div>';

          }

          // class="faqSection" 아래에 붙이기
          $(".faqSection").html(html);

        }, 
        error: function(request, status, error) {
          console.log("code: "+request.status+"\n"+"message : "+request.responseText+"\n"+"error: "+error);
        } 
      });
    });
  });
</script>

 

6-2. 경우에 따른 FAQ 페이지 화면

경우 1.  중분류 데이터와 FAQ 데이터가 모두 있는 경우

경우1

경우 2. 중분류 데이터는 있지만 FAQ 데이터는 없는 경우

경우2

경우 3.  Best 10 링크를 클릭한 경우

경우3

 

 

중분류 버튼을 클릭하면 해당 중분류에 따른 FAQ 데이터만 출력되는 로직은 다음 포스팅에서 이어 정리해 보겠다. 

 

 

 추가 설명 

1. <a> 태그에서 링크 이동 무시하기 

<a href="#" onclick="return false">

<a> 태그의 기본 속성은 링크 기능이다. 즉, href 속성이 활성화되어 해당 웹페이지의 주소로 이동하는 것이다.

하지만 만약 <a> 태그에서 onclick 이벤트 핸들러를 걸어주면, <a> 태그가 클릭되었을 때 onclick 이벤트가 먼저 액션을 취한 후, href로 이동하게 된다.  

여기다 onclick="return false"를 붙여 준다면 원래 가지고 있는 기본 속성은 무시되어 <a> 태그를 클릭해도 링크가 활성화되지 않는다.

 

 

끝.