Ajax 비동기 요청 발생시 로딩 바 만들기.
지금 만들고 있는 프로젝트를 SPA(Single Page Application ) 으로 만들고 있습니다.
동기 방식의 페이지 이동을 하는 웹 어플리케이션을 만들었을때와는 다르게 세세히 신경써야 하는 부분이 굉장히 많은데요,
그 중에는 수업시간에서 따로 다룬 적 없는 부분도 종종 있었습니다.
몇가지 예를 들자면,
1. 페이지 이동시 url 변경 시키기
-> history.pushState 함수를 이용해 해결 했습니다.
2. 뒤로가기 이벤트 발생시 처리
-> pushState 발생시 data에 기록해둔 데이터를 바탕으로 $(window).bind("popstate", function(event){} 로 뒤로가기에 대한 바인딩을 해 해결 했습니다.
3. 페이지 이동시 url 을 변경 시켜 뒀는데, 그 url 상태에서 새로 고침시 페이지 해당 페이지 그대로 보여주기
package best.gaia.project.controller;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import best.gaia.project.dao.ProjectDao;
import best.gaia.project.service.ProjectService;
import best.gaia.utils.CookieUtil;
import best.gaia.utils.exception.ResourceNotFoundException;
import static best.gaia.utils.SessionUtil.*;
@Controller
@RequestMapping("{manager_id:^(?:(?!admin$|view$|restapi$).)*$}/{project_title:^(?:(?!new$|overview$|help$|setting$|activity$).)*$}")
public class ProjectUrlMapper {
@Inject
private ProjectService service;
@Inject
private ProjectDao dao;
@Inject
private WebApplicationContext container;
private ServletContext application;
@PostConstruct
public void init() {
application = container.getServletContext();
}
private static final Logger logger = LoggerFactory.getLogger(ProjectUrlMapper.class);
@GetMapping({""
,"{pageParam}"
,"issue/{issue_no}"
,"milestone/{milest_no}"
})
public String projectMenuOverview(
@PathVariable String manager_id
,@PathVariable String project_title
,@PathVariable Optional<String> pageParam
,@PathVariable Optional<String> issue_no
,@PathVariable Optional<String> milest_no
,Authentication authentication
,HttpSession session
,Model model
,HttpServletResponse resp
) throws UnsupportedEncodingException {
// manager_id랑 project_title로 proj_no 찾아 내기
Map<String, Object> map = new HashMap<>();
map.put("manager_id", manager_id);
map.put("project_title", project_title);
Integer proj_no = dao.getProjNoFromIdAndTitle(map);
// 존재하는 프로젝트 인지 검사 후 존재하지 않으면 404 에러 응답.
if(proj_no == null)
throw new ResourceNotFoundException();
// 접속중인 유저가 해당 proj_no에 대해 조회할 수 있는 권한이 있는지 체크
int mem_no = getMemberNoFromAuthentication(authentication);
/* 코드 작성 필요*/
// 조회중인 프로젝트의 proj_no 를 세션에 저장하기
session.setAttribute("proj_no", proj_no);
// Cookie 에 접속중인 회원의 proj 내에서의 닉네임을 쿠키에 저장하기
String proj_user_nick = service.getProjectNick(proj_no, mem_no);
CookieUtil.addCookie("proj_user_nick", proj_user_nick, resp);
// pageParam 없는 요소들은 수동으로 pageParam 넣어주기.
// 매핑 패턴을 {pageParam}/{paramNo}하고 paramNo도 Optional로 받으면 하드코딩 하지 않아도 될듯.
if(issue_no.isPresent()) {
if("new".equals(issue_no.get())) {
pageParam = Optional.of("issue/new");
}else {
pageParam = Optional.of("issueview");
}
}else if(milest_no.isPresent()) {
if("new".equals(milest_no.get())) {
pageParam = Optional.of("milestone/new");
}else {
pageParam = Optional.of("milestoneview");
}
}
if(!pageParam.isPresent())
pageParam = Optional.of("code");
model.addAttribute("manager_id", manager_id);
model.addAttribute("project_title", project_title);
model.addAttribute("pageParam", pageParam.get());
model.addAttribute("issue_no", issue_no.isPresent() ? issue_no.get() : null);
model.addAttribute("milest_no", milest_no.isPresent() ? milest_no.get() : null);
return "view/template/project";
}
}
> 위에 작성한 컨트롤러가 모두 잡아낸다음 요청을 분석해 보내도록 해서 해결 했습니다. issue/ 와 milestone/ 쪽은 {pageParam}/{numParam} 이런식으로 해결하면 조금 더 코드가 간결해질텐데 워낙 일이 밀려 아직 변경하지 못했습니다.
이 외에도 여러가지 고려 사항과 고민이 많았는데요, 그 중 하나인 페이지 이동시 로딩 바를 보여주는 기능에 대해 기술 해 보도록 하겠습니다.
위의 영상에서 보이는 것 처럼, 비동기 이동시에는 사용자 입장에서 화면에 변화가 즉각 즉각 일어나지 않으면, 본인의 요청에 대해 응답이 없다고 느껴질 수 있는데요. 영상에서는 그나마 빠릿 빠릿 했는데, 요청에 대한 응답 데이터가 크거나, WAS 서버와 DB 서버 둘중 하나라도 느려지는 상황이 발생하면 UX 적인 측면에서 굉장히 불편함을 느낄 수 있습니다.
1. http://www.ajaxload.info/ 에서 원하는 로딩 이미지를 선택해 생성 하고 다운 받습니다. License는 WTFPL 라이센스를 따르고 있으니 편하게 사용하시면 됩니다.
2. document가 준비 되면 로딩 화면 돔을 생성 하고 body 에 넣도록 지정을 해 줍니다.
jquery를 사용한다면 , $function(){} 안에 넣어야 준비가 되었는지를 알 수 있습니다.
vanilla javascript를 사용한다면
document.addEventListener("DOMContentLoaded", function() {
// code...
});
를 이용 하시면 됩니다.
$(function(){
let loading = $('<div id="loading" class="loading"><img id="loading_img" alt="loading" src="/resources/images/loading/ajax-loader.gif" /></div>')
.appendTo(document.body).hide();
$(window).ajaxStart(function(){
loading.show();
}).ajaxStop(function(){
loading.hide();
});
})
loading 이라는 돔을 생성 한 뒤, document.body 에 append 시키고 hide 합니다.
그 후에 ajaxStart와 ajaxStop 함수에 .show와 .hide 를 걸어줍니다.
이번에는 css 설정도 해줍니다. loading_img의 height 를 조절하면 로딩이미지 크기를 조절 할 수 있습니다. margin-top 와 left에는 해당 크기를 그대로 넣어줘야 중앙에 위치하도록 할 수 있습니다.
@charset "utf-8";
/* 로딩 관관 css */
#loading {
height: 100%;
left: 0px;
position: fixed;
_position:absolute;
top: 0px;
width: 100%;
filter:alpha(opacity=50);
-moz-opacity:0.5;
opacity : 0.5;
}
.loading {
z-index: 100;
}
#loading_img{
position:absolute;
top:50%;
left:50%;
height:100px;
margin-top:-100px;
margin-left:-100px;
z-index: 200;
}
이후 실행을 시켜보았습니다.
비동기 요청으로 ajax 함수가 실행되는 동안에는 중간에서 로딩 바가 돌기 시작했습니다.
.loading에 background-color 를 주면 로딩 시에 화면을 어둡게 하는 효과가 가능한데요, 그러면 저희는 싱글 페이지의 정체성이 줄어들게 되어서 그렇게 하지 않았습니다.
로딩 바 적용 후 영상입니다.
이상입니다.
'Development > Projects-DDIT' 카테고리의 다른 글
최종 프로젝트 GAIA 소개 (0) | 2021.07.18 |
---|---|
Google Analytics 데이터 java 통해 받아오기 (1) | 2021.06.22 |
Github REST API 사용하기 (1) | 2021.06.11 |
GAIA 알람 시스템을 만들기 위해 구축한 여러가지 모듈 소개와 과정 (0) | 2021.06.09 |
Google Analytics 구글 애널리틱스 활용하기 - 웹 어플리케이션에 적용 (3) | 2021.05.23 |