수업내용/프로젝트

[Spring] fullcalendar 적용하기 (async/await, lodash 사용)

주니어주니 2023. 2. 21. 23:49

 

 

Fullcalendar 

- ajax로 db에 있는 일정을 가져와서 달력에 표시

- 일정 별로 색상 적용

 

 

 

 

0. 라이브러리 추가 

 

https://fullcalendar.io/

 

FullCalendar - JavaScript Event Calendar

Open Source... With over 10 years of open source and over 120 contributors, FullCalendar will always have a free and open source core. Learn more

fullcalendar.io

 

 

<!-- FullCalendar 라이브러리 -->
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.4/index.global.min.js'></script>
<!-- array 관련 개꿀 라이브러리인 lodash 라이브러리 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

 

 

1. 기본 달력 표시

* jsp에 달력이 들어갈 위치 지정

 

 

* 기본달력 표시 

// 달력 API 
let calendarEl = document.getElementById("calendar");
// FullCalendar의 Calender객체를 생성한다.
let calendar = new FullCalendar.Calendar(calendarEl, {
    locale: 'ko',
    initialView: 'dayGridMonth',	
});

// Calendar를 렌더링한다.
calendar.render();

 

이렇게만 적었을 때 표시되는 달력 

(근데 저 밑줄들을 어케 없애는건가요?) 

 

 

 

 

 

2.  VO 

* Program : 프로그램 객체

- 프로그램 시작 날짜, 종료 날짜, 시작 시간, 종료 시간 등

 

* ProgramDay : 프로그램 요일 객체 

 

* ScheduleCheckDto : 달력에 표시될 내용들의 객체

- Fullcalendar는 id, start, end, title 등의 변수 필요! 

 

 

 

 

3. Mapper 파일 작성 

1) 달력에 표시될 모든 일정을 조회하기 위한 sql문

2) 요일별로 반복되는 일정을 표시하기 위해서 요일을 조회하는 sql문

 

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mapper.ClassRegisterMapper" >

	<!-- 전체 프로그램 조회 -->
	<select id="getAllPrograms" resultType="Program">
		select
			program_no				as no,
			program_name			as name,
			program_start_date		as startDate,
			program_end_date		as endDate,
			program_start_hour		as startHour,
			program_end_hour		as endHour,
			program_quota			as quota,
			program_request_count	as requestCount,
			program_price			as price,
			program_status			as status,
			program_created_date	as createdDate,
			program_updated_date	as updatedDate,
			employee_id				as employeeId,
			fitness_program_category_no	as categoryNo
		from
			fitness_programs
	</select>
	
	<!-- 프로그램 요일 조회 -->
	<select id="getAllProgramDays" parameterType="int" resultType="string">
		select
		    program_open_day
		from
		    fitness_program_days
		where
			program_no = #{value}
	</select>
	
	<!-- 수업 등록 -->
	<insert id="insertClassRegistration" parameterType="map">
		insert into class_registration_histories
		    (class_registration_no, user_no, program_no, class_payment_status, total_payment_price)
		values
		    (custom_sequence('수업등록번호','yymmdd'), #{userNo}, #{programNo}, 'Y', #{programPrice})
	</insert>
	
	<!-- 회원권 등록 -->
	<insert id="insertMembership" parameterType="map">
		insert into membership_histories
		    (membership_no, user_no, membership_start_date, membership_end_date, membership_period,
		     membership_payment_status, total_payment_price)
		values
		    (custom_sequence('회원권등록번호','yymmdd'), #{userNo}, #{memStartDate}, #{memEndDate}, #{memPeriod},
		     'Y', #{memPrice})
	</insert>

</mapper>

 

 

 

4. Controller 

- ajax로 요청 보내고 응답 받을 컨트롤러 작성 

 

 

 

5. Service 

 

 

* getAllSchedules() : 달력에 표시되는 내용(start, end, title) 으로 구성된 객체(ScheduleCheckDto)의 프로그램별, 시작날짜부터 종료날짜까지 반복되는 리스트를 생성해서 컨트롤러로 넘기는 메소드

 

1) 일단 모든 프로그램을 조회

2) 각 프로그램 번호에 해당하는 요일 리스트를 조회 ( [요가: 월, 수, 금] 이런식으로 구성되어 있음)

3) ScheduleCheckDto객체의 start, end 변수에 담기 위해 각 프로그램의 시작날짜 종료날짜를 문자열로 바꿔서 변수에 저장

4) 각 프로그램의 시작날짜, 종료날짜, 프로그램 정보, 요일 리스트를 받아서 ScheduleCheckDto객체를 완성하고 시작날짜부터 종료날짜까지 반복되는 ScheduleCheckDto객체의 리스트를 완성하기 위한 메소드 실행 

5) 메소드 결과로 받은 ScheduleCheckDto객체 리스트를 프로그램별로 반복해서 schedules 변수에 담아서 반환

 

 

 

* getEvents() : 한 프로그램의 시작날짜, 종료날짜, 프로그램 정보, 요일정보를 전달받아서 Schedule 객체를 완성하고, 시작날짜부터 종료날짜까지 반복한 뒤 그 리스트를 반환하는 메소드

 

1) 문자열로 전달받은 시작날짜, 종료날짜를 LocalDateTime 객체로 변환

( LocalDateTime : 날짜와 시간 정보를 쉽게 다루는 객체 (날짜 더하기, 빼기 등)

LocalDateTime.parse("날짜 문자열", 패턴) : 날짜 문자열을 패턴에 맞춰 LocalDateTime 타입으로 변환 ) 

2) 프로그램의 시작날짜부터 종료날짜까지 반복할 사항

      (1) 시작날짜에 해당하는 요일을 알아서 찾아서 문자열로 변환

      (2) 넘겨받은 한 프로그램의 요일 리스트가 위의 시작날짜에 해당하는 요일을 포함하고 있으면 

           Schedule 객체의 id, start, end, title에 값을 넣어서 객체 완성

           (이 때, setEnd를 endDate를 넣어버리면 시작날짜부터 종료날짜까지 쭉 이어져버림 -> setEnd도 startDate로 설정해서 하루치 일정이 반복되도록 해야함)

      (3) 시작날짜를 하루씩 늘려가면서 종료날짜까지 반복

3) 시작날짜부터 종료날짜까지 반복해서 완성한 Schedule 객체 리스트 반환

 

 

package com.example.service.user;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.dto.ScheduleCheckDto;
import com.example.exception.ApplicationException;
import com.example.mapper.ClassRegisterMapper;
import com.example.mapper.ProgramMapper;
import com.example.mapper.UserMapper;
import com.example.vo.Program;
import com.example.vo.User;

@Service
@Transactional
public class ClassRegisterService {
	
	@Autowired
	ClassRegisterMapper classRegisterMapper;
	@Autowired
	ProgramMapper programMapper;
	@Autowired
	UserMapper userMapper;

	// 모든 프로그램 조회 
	public List<Program> getAllPrograms(){
		return classRegisterMapper.getAllPrograms();
	}
	
	// 프로그램 번호로 프로그램 조회
	public Program getProgramByProgNo(int programNo) {
		return programMapper.getProgramByProgramNo(programNo);
	}
	
	// 달력에 표시될 프로그램 일정 조회
	public List<ScheduleCheckDto> getAllSchedules(){
		// 달력에 표시될 내용(시간,이름)으로 구성된 객체를 담을 리스트
		List<ScheduleCheckDto> schedules = new ArrayList<>();
		// 모든 프로그램 조회
		List<Program> programs = classRegisterMapper.getAllPrograms();
		for(Program program : programs) {
			// 각 프로그램별 요일 리스트 조회
			List<String> dayNames = classRegisterMapper.getAllProgramDays(program.getNo());
			// 프로그램의 시작날짜, 종료날짜를 문자열로 바꿔서 전달하기 
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			String progstartDate = sdf.format(program.getStartDate());
			String progEndDate = sdf.format(program.getEndDate());
			
			// getEvents 메소드 실행해서 시작날짜, 종료날짜, 프로그램, 요일을 이용해 schedulecheckdto 리스트 생성
			List<ScheduleCheckDto> scheduleEvents = getEvents(progstartDate, progEndDate, program, dayNames);
			// 모든 수업에 대해 리스트를 담아서 전체 리스트 반환
			schedules.addAll(scheduleEvents);
		}
		return schedules;
	}
	// 지정된 기간, 요일에 해당하는 schedulecheckdto 리스트를 반환하는 메소드  
	public List<ScheduleCheckDto> getEvents(String startDateStr, String endDateStr, Program program, List<String> dayNames){
		// 문자열로 받은 startDate, endDate를 LocalDate 객체로 변환(날짜 정보를 쉽게 다루는 클래스) - 날짜 + 시간 정보 
		// startDsateStr : 2023-02-01 00:00:00
		// endDate에 1을 더해야 마지막 날짜까지 포함
		LocalDateTime startDate = LocalDateTime.parse(startDateStr + " " + program.getStartHour() + ":00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
		LocalDateTime endDate = LocalDateTime.parse(endDateStr + " " + program.getStartHour() + ":00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).plusDays(1);	
		// 반환할 schedulecheckdto 리스트 만들기 
		List<ScheduleCheckDto> scheduleEvents = new ArrayList<>();
		// 시작일부터 종료일 전까지 반복 
		while(startDate.isBefore(endDate)) {
			// startDate에 해당하는 요일을 문자열로 반환
			String dayname = startDate.format(DateTimeFormatter.ofPattern("EEE"));
			
			// 해당 요일이 해당 프로그램의 요일 리스트에 포함되면 Schedule 객체 생성 -> 리스트에 추가
			if(dayNames.contains(dayname)) {
				ScheduleCheckDto schedule = new ScheduleCheckDto();
				// schedule 객체의 식별자, 시작일, 종료일, 제목 설정
				schedule.setId(String.valueOf(program.getNo()));
				schedule.setStart(toDate(startDate));
				schedule.setEnd(toDate(startDate.plusHours(1)));
				schedule.setTitle(program.getName());
				
				scheduleEvents.add(schedule);
			}
			// 시작일을 하루씩 늘려가면서 반복 
			startDate = startDate.plusDays(1);
		}
		return scheduleEvents;
	}
	// LocalDateTime 객체를 Date 객체로 변환
	private Date toDate(LocalDateTime startDate) {
		String text = startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		try {
			return format.parse(text);
		} catch (ParseException e) {
			return null;
		}
	}

}

 

 

 

6. view 

 

- jsp 코드 

<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css">
<link rel="stylesheet" href="/resources/css/common.css">
<title>애플리케이션</title>
<style type="text/css">
</style>
</head>
<body class="pt-5">
<c:set var="menu" value="info" />
<%@ include file="../common/header.jsp" %>
<div class="container mt-5">
	<div class="row mb-3" id="row">
		<!-- 매장 정보 (좌) -->
		<div class="col-4 border p-4 bg-light">
			<input type="hidden" id="latitude" name="latitude" value="${club.latitude }" />
			<input type="hidden" name="longitude" value="${club.longitude }" />
			<div class="row mb-5">
				<h4>
					<img src="/resources/images/logo.svg" alt="Logo" width="40" height="27" class="d-inline-block text-black">
					<strong>중앙피트니스</strong>
				</h4>
			</div>
			<div class="row mb-3">
				<span><i class="bi bi-megaphone-fill me-2"></i>${club.description }</span>
			</div>
			<div class="row mb-3">
				<span><i class="bi bi-telephone-fill me-2"></i>${club.tel }</span>
			</div>
			<div class="row mb-5">
				<span><i class="bi bi-geo-alt-fill me-2"></i>${club.basicAddress } ${club.detailAddress }</span>
			</div>
			<div class="row mb-5">
				<div id="map" style="width:530px; height:400px;"></div>
			</div>
			<div class="row mb-3">
				<span><i class="bi bi-pin-angle-fill me-2"></i>평일 영업시간 : ${club.weekdaysOpenHours }</span>
			</div>
			<div class="row mb-3">
				<span><i class="bi bi-pin-angle-fill me-2"></i>주말 영업시간 : ${club.weekendsOpenHours }</span>
			</div>
			<div class="row mb-3">
				<span><i class="bi bi-pin-angle-fill me-2"></i>휴무일 : ${club.closedDays }</span>
			</div>
		</div>
		<!-- 프로그램 신청 (우) -->
		<div class="col">
			<form class="form border bg-light p-4" id="form-classReg" method="post" action="/user/classReg">
				<div class="row mb-3">
					<h4><strong>프로그램 신청</strong></h4>
				</div>
				<div class="row mb-3">
					<div class="mb-3">
						<div id="calendar"></div>
					</div>
				</div>
				<div class="row mb-3">
					<div class="col-2">
						<label class="form-label"><strong>프로그램</strong></label>
					</div>
					<div class="col-5">
						<select name="programNo" class="form-select d-inline">
							<option value="">프로그램명 선택</option>
							<c:forEach var="program" items="${programList }">
								<option value="${program.no }" ${param.programNo eq program.no ? 'selected' : '' }>${program.name }</option>
							</c:forEach>
						</select>
					</div>
				</div>
				<div class="row mb-3">
					<div class="col-2">
						<label class="form-label"><strong>시간</strong></label>
					</div>
					<div class="col-5">
						<input type="text" class="form-control" id="class-time" value="시간" disabled="disabled">
					</div>
				</div>
				<div class="row mb-5">
					<div class="col-2">
						<label class="form-label"><strong>가격</strong></label>
					</div>
					<div class="col-5">
						<input type="text" class="form-control" id="class-price" value="가격" disabled="disabled">
					</div>
				</div>
				<div class="row mb-4">
					<h4><strong>회원권 신청 (선택사항)</strong></h4>
				</div>
				<div class="row mb-3">
					<div class="col-2">
						<label class="form-label"><strong>신청여부</strong>
					</div>
					<div class="col-10">
						<div class="form-check form-check-inline">
							<input class="form-check-input" type="checkbox" id="memRegister" checked>
							<label class="form-check-label">신청</label>
						</div>										
					</div>
				</div>
				<div class="row mb-3 member-box">
					<div class="col-2">
						<label class="form-label"><strong>시작날짜</strong>
					</div>
					<div class="col-4">
						<input type="date" name="memStartDate" class="form-control" >
					</div>
					<div class="col-2">
						<label class="form-label"><strong>기간</strong>
					</div>
					<div class="col-4">
						<select name="memPeriod" class="form-select d-inline" >
							<option value="">회원권 기간 선택</option>
							<option value="1">1개월</option>
							<option value="3">3개월</option>
							<option value="6">6개월</option>
							<option value="12">12개월</option>
						</select>
					</div>
				</div>
				<div class="row mb-5 member-box">
					<div class="col-2">
						<label class="form-label"><strong>종료날짜</strong>
					</div>
					<div class="col-4">
						<input type="date" name="memEndDate" class="form-control" readonly="readonly">
					</div>
					<div class="col-2">
						<label class="form-label"><strong>가격</strong>
					</div>
					<div class="col-4">
						<input type="text" name="memPrice" class="form-control" value="" readonly="readonly">
					</div>
				</div>
				<div class="row mb-3">
					<button type="submit" class="btn btn-lg btn-dark">신청하기</button>
				</div>
			</form>
		</div>
	</div>
</div>

 

- 자바스크립트

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=지도앱키"></script>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.4/index.global.min.js'></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script type="text/javascript">
$(function(){
	
 	// 달력 API 
	let calendarEl = document.getElementById("calendar");
	// FullCalendar의 Calender객체를 생성한다.
	let calendar = new FullCalendar.Calendar(calendarEl, {
		locale: 'ko',
		initialView: 'dayGridMonth',	
		// 일정정보를 조회하고, successCallback(이벤트배열)함수의 매개변수로 일정정보를 제공하고 실행하면 화면에 반영된다.
		events: function(info, successCallback, failureCallback) {	// events 프로퍼티에는 달력이 변경될 때마다 실행되는 함수를 등록한다.
			refreshEvents(info, successCallback);			// info는 화면에 표시되는 달력의 시작일, 종료일을 제공한다.
		}
	});
	// Calendar를 렌더링한다.
	calendar.render();

	// 16진수(hex) 색상 코드를 랜덤하게 생성하는 함수
	function generateColor() {
		// hexArray 배열에 16진수 코드 저장
		const hexArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F'];
		let code = "";
		for (let i = 0; i < 6; i++) {
			// 랜덤한 인덱스를 구하고 해당 인덱스에 해당하는 코드를 code 문자열에 추가, 이 과정을 6번 반복
			code += hexArray[Math.floor(Math.random() * 16)];
		}
		// 16진수 색상 코드 생성
		return '#' + code;
	}

	// 반복되는 수업들을 db에서 가져오기
	// 지금 많이 쓰이는 async/await 비동기 호출 방식 (.then(), .done(), catch()를 사용하지 않고 결과값을 바로 변수에 넣는 방식)
	async function refreshEvents(info, successCallback) {
		try {
			// 비동기 방식으로 응답받은 결과값을 events 변수에 저장
			const events = await $.get("/user/events");
			// lodash 라이브러리
			const data =
				_.chain(events)							// 메소드 체이닝
				 .orderBy(["id"], ["asc"])					// 응답객체의 id 속성 기준으로 정렬
				 .groupBy("id")							// id 속성 기준으로 그룹화
				 .map((groupById) => {						// 그룹화된 배열 내 각각의 요소에 대해 함수를 적용하고, 반복하는 메소드
					const color = generateColor();				// 랜덤 색상 생성 메소드 결과를 color변수에 저장
					groupById.forEach((data) => data.color = color);	// 그룹화된 데이터를 돌면서 각 데이터의 color를 color로 설정
					return groupById;					// 색상이 지정된 응답객체를 반환 
				 })
				 .value()							// 메소드 체인 종료, 최종결과값 반환
				 .reduce((o1, o2) => o1.concat(o2), []);			// 중첩된 배열을 평평하게 반환
			successCallback(data);	// list<ScheduleCheckDto>
		} catch(e) {
			// Ajax 요청 실패 시 처리 로직
			log.error(e);
			successCallback([]);
		}
	}
	
	// 프로그램 선택 유효성 체크 
	$("#form-classReg").submit(function(){
		const progNo = $(":input[name=programNo]").val();
		const memStartDate = $(":input[name=memStartDate]").val();
		const memPeriod = $(":input[name=memPeriod]").val();
		
		if(progNo === ""){
			alert("프로그램을 선택하세요.");
			return false;
		}
		if($("#memRegister").prop("checked")){
			if(memStartDate === ""){
				alert("회원권 시작날짜를 선택하세요.");
				return false;
			}
			if(memPeriod === ""){
				alert("회원권 기간을 선택하세요.");
				return false;
			}
		}
	})
	
	// 회원권 신청여부를 선택할 때만 서버로 전송
	$("#memRegister").change(function() {
		if ($(this).prop("checked")) {
			$(".member-box :input").prop("disabled", false);
		} else {
			$(".member-box :input").prop("disabled", true);
		}
	});
	
	// 선택한 프로그램에 해당하는 시간, 가격 표시 ajax
	$(":input[name=programNo]").change(function(){
		const programNo = $(this).val();
		$.getJSON("/user/classInfo", {no:programNo}, function(program){
			$("#class-time").val(program.startHour);
			$("#class-price").val(program.price);
		})
	})
	
	// 회원권 기간 설정에 따른 회원권 종료 날짜, 가격 표시 ajax 
    $(":input[name=memPeriod]").on('change', function(){
    	// 종료날짜 계산
	    const startDate= $("input[name=memStartDate]").val();
	    const period = $(":input[name=memPeriod]").val();
	    
	    const endDate = moment(startDate).add(period, 'months').format("YYYY-MM-DD");
	    $("input[name=memEndDate]").val(endDate);
	    
	    // 가격 표시
	    const totalPrice = period*150000;
	    $("input[name=memPrice]").val(totalPrice);
    })
	
	// 카카오 지도 API 
	var latitude = $("input[name=latitude]").val();
	var longitude = $("input[name=longitude]").val();
	var container = document.getElementById('map'); // 지도를 담을 영역의 DOM 레퍼런스
	
	var options = { // 지도를 생성할 때 필요한 기본 옵션
		center: new kakao.maps.LatLng(latitude, longitude), // 지도의 중심좌표
		level: 3 // 지도의 레벨(확대, 축소 정도)
	};
	// 지도 생성 및 객체 리턴
	var map = new kakao.maps.Map(container, options); 
	// 마커가 표시될 위치
	var markerPosition  = new kakao.maps.LatLng(latitude, longitude); 
	// 마커를 생성
	var marker = new kakao.maps.Marker({
	    position: markerPosition
	});
	// 마커가 지도 위에 표시되도록 설정
	marker.setMap(map);
	
})
</script>
</body>
</html>

 

 

 

* async / await 

// 요일별로 반복되는 수업들을 db에서 가져오기
// async/await 비동기 호출 방식 (.then(), .done(), catch()를 사용하지 않고 결과값을 바로 변수에 넣는 방식)
async function refreshEvents(info, successCallback) {
    try {
        // 비동기 방식으로 응답받은 결과값을 events 변수에 저장
        const events = await $.get("/user/events");
        // lodash 라이브러리
        const data =
            _.chain(events)											// 메소드 체이닝
             .orderBy(["id"], ["asc"])								// 응답객체의 id 속성 기준으로 정렬
             .groupBy("id")											// id 속성 기준으로 그룹화
             .map((groupById) => {									// 그룹화된 배열 내 각각의 요소에 대해 함수를 적용하고, 반복하는 메소드
                const color = generateColor();						// 랜덤 색상 생성 메소드 결과를 color변수에 저장
                groupById.forEach((data) => data.color = color);	// 그룹화된 데이터를 돌면서 각 데이터의 color를 color로 설정
                return groupById;									// 색상이 지정된 응답객체를 반환 
             })
             .value()												// 메소드 체인 종료, 최종결과값 반환
             .reduce((o1, o2) => o1.concat(o2), []);				// 중첩된 배열을 평평하게 반환
        successCallback(data);	// list<ScheduleCheckDto>
    } catch(e) {
        // Ajax 요청 실패 시 처리 로직
        log.error(e);
        successCallback([]);
    }
}

 

* ajax 

- 자바스크립트에서 비동기적으로 클라이언트-서버 간에 json이나 xml 데이터 주고받는 기술 ( xml : 사용자 지정 태그를 사용한 마크업 언어 )

- "async : false" 로 설정하면 동기식 가능 

- $.get("/user/events") : ajax 요청을 보내서 데이터를 비동기식으로 받아옴

- promise 기반이 아니라서 then, catch 메소드 사용 불가, promise를 반환하는 형태로는 구현할 수 있음

(ajax 함수 내에서 return new Promise(function(~, ~)) )

- $.get은 ajax 함수 중 하나로, promise를 반환하지 않고, .done(), .then(), .catch()  등의 콜백 함수로 비동기 작업 처리

-> async / await 사용하지 않고 $.get 만으로 구현하려면? -> .done(), .then() 으로 체이닝해서 처리 

 

ajax 요청의 비동기 작업을 처리하기 위해 사용되는 콜백 함수

.done() : ajax 요청이 성공적으로 완료되었을 때 호출되는 콜백 함수 -> 성공적인 응답을 받았을 때 실행되는 로직 정의 
.then() : Promise 객체에서 사용되는 메소드. 비동기 작업이 완료되었을 때 호출되는 콜백 함수 (성공시, 실패시)
.catch() : Promise 객체에서 사용되는 메소드. 비동기 작업 중 발생한 오류를 처리하기 위한 콜백 함수 

ex) 일반적으로 
$.get() 을 사용하는 경우 -> .done(function(data) { 성공적인 응답 처리 }) .catch(function(error) { 오류 처리 })
Promise 기반의 비동기 작업 -> .then() , .catch()

 

 

 

* Ajax 요청의 비동기 작업을 처리하는 방법들 

(1) 콜백 함수 

전통적인 방법. 

쉬운 방법이지만 코드가 복잡, 가독성 떨어짐.

try catch 로 에러 잡아낼 수 없음.

다른 함수의 파라미터로써 전달되는 함수. 

비동기 처리 방식의 문제점을 해결하기 위해 어떤 함수의 파라미터로 함수를 전달하고,

특정 로직이 끝났을 때 원하는 동작 실행 -> 함수 실행 순서 보장

 

(2) promise 

비동기 작업의 결과를 나타내는 것, 비동기 작업을 처리하기 위한 객체 

체이닝 지원되어 콜백지옥 해결. 

promise를 리턴하면 promise가 제공하는 .then(), .catch(), .finally() 등의 콜백 함수를 통해서 이어서 작업. 

성공, 에러 처리 가능.

 

 

(3) async-await 

 

async function 함수명() {
   await HTTP 통신을 하는 비동기 처리 메소드명();
}

내 코드 예시

: refreshEvents 함수를 실행하면

  -> "/user/events/" 경로로 비동기처리를 요청해서 응답받은 객체가 events 변수에 바로 담김

  -> 처리된 data는 successCallback 함수로 전달

  -> 예외가 발생하면 catch 블록 내부의 코드를 실행. 실패한 경우, successCallback([])를 호출하여 빈 배열을 전달

 

 

- 비동기 작업을 동기적으로 처리.

- promise가 있는 함수에 async를 붙이고, 해당 promise 함수에 await를 붙이면 동기로 사용.

- promise를 더 쉽게 사용, 절차적(동기적)인 프로그래밍 방식.

- .then(), .catch() 같은 콜백 함수를 사용하지 않고도, 비동기 작업의 결과값을 변수에 직접 할당

- 자동으로 promise 반환 -> 체이닝 가능. 

- refreshEvents 함수는 async 함수로 선언 -> 비동기 동작

- await $.get("/user/events") -> 비동기 작업의 완료를 기다리고, 작업이 완료되면 해당 결과 반환 

- promise 기반으로 동작 -> 반환된 promise는 완료될 때까지 기다리며 await 뒤에 오는 promise가 완료되면 해당 결과 반환 -> 이를 통해 코드 가독성 향상, 비동기 작업을 동기적인 코드처럼 작성 

(동기적으로 처리되지 않으면 event 변수에 데이터가 안담긴 채로 다른 작업을 수행할 수도 있어서)

- 성능 개선보다는 코드의 가독성, 유지보수성 

 

$.get("/user/events") 의 반환값 : 달력에 표시되는 일정 리스트

(id : 프로그램 번호 / start : 프로그램 시작날짜,시간 / end : 프로그램 종료날짜,시간 / title : 프로그램명)

 

 

* lodash (*많이 사용하기)

- 자바스크립트의 라이브러리

- array, collection, date 등 데이터의 필수적인 구조를 쉽게 다룰 수 있음

- 자바스크립트에서 배열 안의 객체들의 값을 핸들링(배열, 객체 및 문자열 반복/복합적인 함수 생성)할 때 유용

- _. (변수) 이런식으로 작성할 경우 lodash wrapper로 변수를 감싸게 되면서 해당 변수에 대한 chaining 시작

- 달력에 표시되는 프로그램 일정 별로 각자 다른 색상을 적용하기 위해 프로그램 번호로 그룹화, 

 

내 코드 예시 

 

$.get("/user/events") 의 반환값 events : 달력에 표시되는 일정 리스트 -> JSON 데이터

(id : 프로그램 번호 / start : 프로그램 시작날짜,시간 / end : 프로그램 종료날짜,시간 / title : 프로그램명)

 

_.chain(events) : lodash 라이브러리의 chain 함수를 사용하여 events 배열을 래핑

.orderBy(["id"], ["asc"]) : 래핑된 배열을 id 속성으로 오름차순 정렬 (응답받은 schedule 객체의 id 값 = program번호)

.groupBy("id") : id 속성 기준으로 그룹화

.map((groupById) => { ... }) : 그룹화된 각 배열에 대해 함수를 적용하여 새로운 배열을 반환 ( id별로 그룹화된 데이터를 순회하며 각각의 데이터에 대해 색상을 생성하고 borderColor 속성에 색상 값을 할당) 

   const color = generateColor(); : 랜덤 색상을 생성하는 함수를 호출해서 color 변수에 할당

   groupById.forEach((data) => data.color = color); : 그룹화된 배열의 각 요소에 대해 forEach()를 사용하여 color 속성 추가

   return groupById; : 색상이 지정된 그룹화된 배열 반환 

.value() : lodash 체인을 종료하고 결과를 반환

.reduce((o1, o2) => o1.concat(o2), []) : reduce() - 중첩된 배열을 평평하게 만듦. o1은 누적값, o2는 배열의 각 요소. 두 요소를 합쳐서 하나의 배열로 반환. []는 초기값으로 빈 배열을 사용한다는 의미