본문 바로가기
웹개발/DIOS

[웹개발 - DIOS] 운동 목표 개수 설정 페이지

by 오엥?은 2023. 2. 22.
반응형

 

⌦  목표 개수 설정 페이지

 


☑︎  운동의 종류를 선택하는 기능

- HTML

<input class="select squat" rel="squat" type="button" value="스쿼트
squat">
<input class="select lunge" rel="lunge" type="button" value="런지
lunge">
<input class="select plank" rel="pushUp" type="button" value="팔굽혀펴기
push up">

- CSS

body > .main > .form > .select-container > .select:hover,
body > .main > .form > .select-container > .select:focus,
body > .main > .form > .select-container > .select.selected {
    background-color: rgb(30, 28, 28);
    color: rgb(255, 255, 255);
    font-weight: 500;

    border: 0.2rem solid rgb(30, 28, 28);
}

- javaScript

const form = document.getElementById('form');

form.querySelectorAll('input.select').forEach(select => {
    select.addEventListener('click', () => {
        form.querySelectorAll('input.select').forEach(x => x.classList.remove('selected'));
        select.classList.add('selected');
    });
});

운동의 종류를 선택하면 input 태그 중 class = "select" 인 요소에 selected 라는 class 를 추가한다.

CSS 에 .select.selected 를 검정배경으로 설정하면, 운동의 종류를 선택했을 때 검정 배경으로 선택되었음을 표시할 수 있다.

중복선택이 불가능하게 하기 위해 , 클릭 시 forEach(x => x.classList.remove('selected')); 로 selected 클래스를 지우고 시작한다.

 

 

• 운동의 종류를 선택하지 않고 start 했을 때 "선택하세요!" 라는 알림이 뜬다.

- JavaScript

if (!squat.classList.contains("selected") && !lunge.classList.contains("selected") && !pushUp.classList.contains("selected")) {
    form.querySelector('[rel="selectWarning"]').classList.add('visible');
    return false;
}
body > .main > .form > .select-container > .warning {
    display: none;
}

body > .main > .form > .select-container > .warning.visible {
    display: block;
}

뭐라도 선택하면 class에 selected 가 추가되기 때문에 어디에도 selected class 가 없으면 아무 것도 선택되지 않은 것이다.

그래서 아무 것도 선택되지 않았을 때를 조건으로 걸고,

알림 말풍선의 class 에 visible 을 추가하여  display : none; 이었던 걸 display: block; 으로 바꾼다.

 

 


☑︎  운동의 개수를 설정하는 기능

- Controller

@RequestMapping(value = "setting",
        method = RequestMethod.GET)
public ModelAndView getSetting(@SessionAttribute(value = "user", required = false) UserEntity user) {

    ModelAndView modelAndView;

    if (user == null) {
        modelAndView = new ModelAndView("redirect:/dios/login");
    } else {
        modelAndView = new ModelAndView("records/setting");
    }
    return modelAndView;
}


@RequestMapping(value = "setting",
        method = RequestMethod.PATCH,
        produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String patchSetting(@SessionAttribute(value = "user", required = false) UserEntity user,
                           @RequestParam(value = "goalCount", required = false) Integer count) {
                           
    Enum<?> result = this.recordService.updateGoalCount(user, count);

    JSONObject responseObject = new JSONObject();
    responseObject.put("result", result.name().toLowerCase());

    return responseObject.toString();
}

목표 개수 설정 페이지는 회원만 들어갈 수 있다. 그래서 @SessionAttribute로 user 를 받아와서 회원인지 확인해야 한다.

만약 user == null 이라면 페이지에 들어올 수 없고 login 페이지로 이동하도록 했다.

 

유저의 로그인 정보와 목표 개수를 매개변수로 받아온다.

 

✔️ @RequestParam 

: @RequestParam 은  GET 요청 파라미터 전송 방식, HTML Form 전송 방식을 사용할 때에 조회할 수 있는 방법 중 하나이다.

(GET 방식은 요청 시 데이터를 message body에 담지 않고, query string(쿼리 스트링)에 담아서 전달하는 방법이다.)

 

여기서 목표 개수는 HTML Form 전송방식을 사용했기 때문에 RequestParam을 사용하여 value = "goalCount", required = false 로전달해 주었다. required = false 를 해주면 파라미터의 값이 전달되지 않는다면 null 값이 들어간다. 자료형 int 는 null 값을 담을 수 없기 때문에, Integer을 사용했다. 자바스크립트로 목표 개수를 입력하지 않았을 때 경고창이 뜨며 다음 화면으로 넘어갈 수 없게 하긴 했지만 required = false 도 사용했다.

 

recordService 의 updateGoalCount return 결과를 result에 담아 JSONObject에 lower case로 담는다.

 

✔️ JSON

: JSON 은 "키-값" 쌍으로 이루어진 데이터 오브젝트를 전달하기 위해 인간이 읽을 수 있는 텍스트를 사용하는 개방형 표준 포맷이다. 비동기 브라우저/서버 통신을 위해, 넓게는 xml을 대체하는 주요 대이터 포맷이다.

 

service의 updateGoalCount 의 return 값을 값으로, "result" 를 키로 넣기 위해 JSONObject 타입의 responseObject 변수를 만든다. responseObject.put("result", result.name().toLowerCase()); 의 형태로 키와 값 쌍을 변수에 넣어준다.

 

toString()은 JSONObject가 갖고 있는 데이터를 JSON 형식으로 출력한다.

 

 

- Service

public Enum<? extends IResult> updateGoalCount (UserEntity user, int count) {

    UserEntity existingUser = this.memberMapper.selectUserByEmail(user.getEmail());

    existingUser.setGoalCount(count);

    return this.recordMapper.updateCount(existingUser) > 0
            ? CommonResult.SUCCESS
            : CommonResult.FAILURE;
}

selectUserByEmail 은 user table 의 email 을 기준으로 select 하는 Mapper 이다. 이를 existingUser 변수에 담아 준 다음, existingUser 가 가진 goal_count 를 controller 의 매개변수로 받아온 count 로 set 해준다. 이 과정이 완료되었다면 SUCCESS 를 반환하고, 실패하면 FAILURE 를 반환하도록 했다.

 

 

- Mapper, xml

UserEntity selectUserByEmail(@Param(value = "email") String email);

int updateCount(UserEntity user);
<select id="selectUserByEmail"
        resultType="com.blackgreen.dios.entities.member.UserEntity">
    SELECT `email`             AS `email`,
           `goal_count`        AS `goalCount`
    FROM `dios_member`.`users`
    WHERE BINARY `email` = #{email}
    LIMIT 1
</select>

<update id="updateCount"
        parameterType="com.blackgreen.dios.entities.member.UserEntity">
    UPDATE `dios_member`.`users`
    SET `goal_count` = #{goalCount}
    WHERE BINARY `email` = #{email}
    LIMIT 1;
</update>

user table 에는 goal_count column 이 default 0 으로 만들어져 있다. 회원가입할 때는 insert 되는 값이 없지만, 목표 개수 설정을 위해 만들어진 열이다.

 

목표 개수 설정 페이지에 들어있는 session인 사용자 email 을 기준으로 한 row를 select 해 주는 쿼리를 만들었다.

그리고 그 row의 goal_count 값을 목표 개수 설정 페이지 controller에서 받아 온 goalCount 로 update 해 주는 쿼리를 만들었다. email column 은 pk 이기 때문에 WHERE 조건으로 선택했다. 

 

새로운 운동을 선택했을 때도 목표 개수를 변경할 수 있고, 운동을 하다가도 목표 개수를 변경할 수 있도록 만들기 위해 목표 개수를 insert 로 디비에 넣지 않고, update 로 넣도록 쿼리를 짰다.

 

 

- javaScript

form['submit'].addEventListener('click', () => {

    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('goalCount', form['setting'].value);

    xhr.open('PATCH', './setting');
    xhr.onreadystatechange = () => {
        if (xhr.readyState === XMLHttpRequest.DONE) {
            if (xhr.status >= 200 && xhr.status < 300) {
                const responseObject = JSON.parse(xhr.responseText);
                switch (responseObject['result']) {
                    case 'success':
                        if (squat.classList.contains("selected")) {
                            window.location.href = 'squat';
                        } else if (lunge.classList.contains("selected")) {
                            window.location.href = 'lunge';
                        } else if (pushUp.classList.contains("selected")) {
                            window.location.href = 'pushUp';
                        }
                        break;
                    default:
                        alert('실패');
                }
            } else {
                alert('알 수 없는 이유로 서버와 통신하지 못했습니다. 다시 시도해 주세요.');
            }
        }
    }
    xhr.send(formData);
});

start 버튼을 눌렀을 때, FormData 를 이용해서 'goalCount' (프론트에서 입력받은 값)를 받아온다.

 

✔️ formData.append(name, value) 함수를 이용해 데이터를 넣을시에 value는 문자열로만 입력 된다. 만일 문자열 이외의 데이터 타이을 넣으면 무시되고 문자열로 자동 변환 된다.

이렇게 보내진 걸 볼 수 있다.

 

✔️ XMLHttpRequest 

: XMLHttpRequest 객체는 서버로부터 XML 데이터를 전송받아 처리하는 데 사용된다. 이 객체를 사용하면 웹 페이지가 전부 로딩된 후에도 서버에 데이터를 요청하거나 서버로부터 데이터를 전송받을 수 있다. 즉, 웹 페이지 전체를 다시 로딩하지 않고 일부분만을 갱신할 수 있게 된다.

 

✔️ readyState

: readyState 프로퍼티는 XMLHttpRequest 객체의 현재 상태를 나타낸다. 이 프로퍼티의 값은 객체의 현재 상태에 따라 다음과 같은 주기로 변화한다.

 1. UNSENT (숫자 0) : XMLHttpRequest 객체 생성

 2. OPENED (숫자 1) : open() 메소드 성공적 실행

 3. HEADERS_RECEIVED (숫자 2) : 모든 요청에 대한 응답 도착

 4. LOADING (숫자 3) : 요청한 데이터를 처리 중

 5. DONE (숫자 4) : 요청한 데이터의 처리가 완료되어 응답할 준비 완료

 

✔️ status

: status 프로퍼티는 서버로부터 응답 받은 상태 및 리턴 메세지를 확인한다.

1. 200 ~ 201 : 요청이 성공적 상태

2. 그 외 상태 : 요청 실패

 

✔️ xhr.open

: 서버로 보낼 Ajax 요청의 형식을 설정

 

✔️ xhr.send

: Ajax 요청을 서버로 전달

 

 

controller 의  patchSetting 의 반환값이 "success" 면 디비 값이 입력한 목표 개수로 잘 update 되었다는 뜻이므로 selected 가 포함된 운동 화면으로 넘어가게 했다.

db 에 입력한 목표 개수가 잘 들어갔기 때문에, start 를 눌렀을 때 넘어가는 운동 화면에서 목표 개수가 잘 출력된다.

 

 

• 마찬가지로 운동의 개수를 설정하지 않고 start 했을 때는 "입력하세요!" 라는 알림이 뜬다.

반응형