[Spring] Week03
[수업 목표]
- 페이지를 만들기 위한 HTML,CSS, Javascript를 익힌다.
- 스프링을 이용해 API를 만들고 기능 확인하는 법을 손에 익힌다.
- 타임라인 서비스를 완성한다.
[목차]
모든 토글을 열고 닫는 단축키 Windows : Ctrl
+ alt
+ t
Mac : ⌘
+ ⌥
+ t
01. [3주차] 이번 주 배울 것
만들 프로젝트 미리보기
End to End 로 프로젝트를 완성해보는 3주차입니다! 🔥
[코드스니펫] 타임라인 서비스 주소
http://spring.spartacodingclub.kr/timeline
- 서버 완성하기 - Memo API
- 2주차 때 배운 내용을 바탕으로 CRUD 기능을 가진 Memo API를 만듭니다.
- 키워드: RestController, Service, Repository, RequestDto
- HTML, CSS
- 웹 화면을 구성하는 HTML, CSS에 대해 배워봅니다.
- Javascript, jQuery
- 웹 화면을 움직이게 만들어주는 Javascript의 기초를 배웁니다.
- jQuery 를 이용하여 화면을 숨기고, 나타내는 방법을 익혀봅니다.
- 클라이언트 완성하기
- HTML, CSS, Javascript 를 바탕으로 클라이언트를 완성하고, 서버와 연결합니다.
- 완성된 페이지를 로컬에서 확인합니다.
02. [3주차] 프로젝트 만들고 API 설계하기
잠깐, 전체 그림을 다시 볼까요?
- Controller - Service - Repository 3계층이 존재한다는 것, 기억하시죠?
- 우리는 안에서 바깥으로, 즉 Repository 쪽부터 Service, Controller 방향으로 만들어나갈거에요.
- 이번 시간에는 Repository 부터 만들어보겠습니다.
프로젝트 새로 만들기
팁) 이미지를 더블클릭하시면 크게 보실 수 있어요 😎
- 인텔리제이를 실행합니다.
New Project를 클릭합니다.
왼쪽 메뉴에서 “Spring Initializr”를 클릭하고 “Next”를 클릭합니다.
🚒[중요] 꼭 다음 사항을 확인해주세요.
- Group: com.sparta
- Artifact: week03
- Type: Gradle
- Language: Java
- Java Version: 8
특히 Type, Language, Java version 중 하나라도 다르면 정상적으로 실행이 되지 않습니다. 꼭 정상입력 확인해주세요!
검색창을 클릭하고, 다음을 차례대로 검색한 뒤 엔터를 눌러주세요. 검색이 끝난 뒤 아래 캡처의 우측과 같이 5개의 요소가 포함되어있으면 완료된 것입니다.
- Lombok
- Spring Web
- Spring Data JPA
- H2 Database
- MySQL Driver
Finish를 클릭합니다.
잠시 기다리면, 아래 화면이 완성될 것입니다.
API 설계하기 (CRUD)
03. [3주차] Repository 만들기
Memo 클래스 만들기
- 메모는 1) 익명의 작성자 이름(username), 2) 메모 내용(contents) 으로 이루어져 있습니다.
- domain 패키지를 만듭니다.
[코드스니펫] Memo.java
@NoArgsConstructor // 기본생성자를 만듭니다. @Getter @Entity // 테이블과 연계됨을 스프링에게 알려줍니다. public class Memo extends Timestamped { // 생성,수정 시간을 자동으로 만들어줍니다. @GeneratedValue(strategy = GenerationType.AUTO) @Id private Long id; @Column(nullable = false) private String username; @Column(nullable = false) private String contents; public Memo(String username, String contents) { this.username = username; this.contents = contents; } public Memo(MemoRequestDto requestDto) { this.username = requestDto.getUsername(); this.contents = requestDto.getContents(); } }
[코드스니펫] Timestamped.java
@MappedSuperclass // Entity가 자동으로 컬럼으로 인식합니다. @EntityListeners(AuditingEntityListener.class) // 생성/변경 시간을 자동으로 업데이트합니다. public class Timestamped { @CreatedDate private LocalDateTime createdAt; @LastModifiedDate private LocalDateTime modifiedAt; }
MemoRepository 인터페이스 만들기
- ID가 Long 타입입니다. Repository는 어떻게 생겼을까요?
[코드스니펫] MemoRepository.java
public interface MemoRepository extends JpaRepository<Memo, Long> { List<Memo> findAllByOrderByModifiedAtDesc(); }
[코드스니펫] JPA 공식 홈페이지 살펴보기
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
연습퀴즈 - MemoRequestDto 클래스 만들기
Memo.java에 뜨는 에러를 해결하려면 MemoRequestDto.java를 만들어야 합니다. Memo 클래스의 코드를 바탕으로 MemoRequestDto.java를 만들어보세요.
정답
@Getter public class MemoRequestDto { private String username; private String contents; }
04. [3주차] Service 만들기
전체 그림 다시 보기
- Controller - Service - Repository 3계층이 존재한다는 것, 기억하시죠?
- 이번 시간에는 Service 계층을 만들어보겠습니다.
MemoService 클래스 만들기
- src > main > java > com.sparta.week03 에 service 패키지를 만듭니다.
- 해당 패키지 아래에 MemoService.java 파일을 만듭니다.
[코드스니펫] MemoService.java
@RequiredArgsConstructor @Service public class MemoService { private final MemoRepository memoRepository; @Transactional public Long update(Long id, MemoRequestDto requestDto) { Memo memo = memoRepository.findById(id).orElseThrow( () -> new IllegalArgumentException("아이디가 존재하지 않습니다.") ); memo.update(requestDto); return memo.getId(); } }
update 기능 만들기
[코드스니펫] Memo.java에 update 메소드 추가하기
public void update(MemoRequestDto requestDto) { this.username = requestDto.getUsername(); this.contents = requestDto.getContents(); }
05. [3주차] Controller 만들기
전체 그림 다시보기
- Controller - Service - Repository 3계층이 존재한다는 것, 기억하시죠?
- 이번 시간에는 Controller 계층을 만들어보겠습니다.
- API 완성하기
- ARC로 기능 확인하기
연습퀴즈 - Update API 만들어보기
메모의 정보를 받아 DB의 데이터를 변경하는 메소드를 만들어보세요. 힌트) Create 와 유사합니다.
정답
@PutMapping("/api/memos/{id}") public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) { memoService.update(id, requestDto); return id; }
06. [3주차] HTML, CSS 기초
- HTML, CSS, Javascript 의 구분
- HTML은 뼈대고, CSS는 꾸며주는 녀석입니다. 바로 살펴볼까요?
- https://naver.com 에서 <head></head> 녀석을 날려보겠습니다.
HTML의 기초
HTML의 특징
- 여는 태그, 닫는 태그가 쌍으로 존재합니다.
교차가 불가능합니다.
(O) <h1><span>타이틀</span>입니다.</h1> (X) <span><h1>타이틀</span>입니다.</h1>
- head, body 태그로 구분됩니다.
- head 태그는 CSS, Javascript 코드를 포함하고,
- body 태그는 뼈대 전체를 포함합니다.
대표적인 HTML 태그들
h1~h6
→ headline의 약자, 신문 제목 같은 녀석이에요.
div
→ divison, 나누는 녀석이에요. 투명 비닐봉투!
p
→ paragraph, 신문 기사 단락과 같은 텍스트 내용을 담습니다.
ul, ol, li
→ (un)ordered list, list에 해당합니다. bullet point.
span
→ 글을 중간중간 잘라내서 색을 입혀준다든지 할 때 사용합니다.
table, th, tr, td
→ 표 입니다! 엑셀 같은 표를 그릴 때 사용합니다.
img
→ 이미지를 나타낼 때 사용합니다.
그 외에도 무수히 많습니다!
더 공부하고 싶다면?
[코드스니펫] 코드카데미 HTML
https://www.codecademy.com/learn/learn-html
[코드스니펫] 생활코딩 HTML
https://opentutorials.org/course/2039
CSS의 기초
문법
- head > style 태그 안에 작성합니다.
- 세미콜론(;)으로 마무리합니다.
.wrap { width: 538px; margin: 10px auto; } #contents { width: 538px; } .area-write { position: relative; width: 538px; }
선택자(id, class)
- 꾸미려면 가리켜야 하겠죠? 가리키는 방법은 두 가지가 있습니다.
- id: HTML 파일을 통틀어 단 하나만 존재합니다. #으로 표시합니다. (#contents)
- class: 중복 적용이 가능합니다. .으로 표시합니다. (.area-write)
타임라인 페이지를 미리 한 번 살펴볼까요?
- src > main > resources > static 에 index.html 파일을 만듭니다.
아래 코드스니펫 내용을 복사/붙여넣기 합니다.
[코드스니펫] index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Timeline Service</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&display=swap" rel="stylesheet" /> <script> $(document).ready(function () { getMessages(); }); function getMessages() { $("#cards-box").empty(); $.ajax({ type: "GET", url: "/api/memos", data: {}, success: function (response) { for (let i = 0; i < response.length; i++) { let message = response[i]; let id = message["id"]; let username = message["username"]; let contents = message["contents"]; let modifiedAt = message["modifiedAt"]; addHTML(id, username, contents, modifiedAt); } }, }); } function addHTML(id, username, contents, modifiedAt) { let tempHtml = makeMessage(id, username, contents, modifiedAt); $("#cards-box").append(tempHtml); } function makeMessage(id, username, contents, modifiedAt, i) { return `<div class="card"> <!-- date/username 영역 --> <div class="metadata"> <div class="date"> ${modifiedAt} </div> <div id="${id}-username" class="username"> ${username} </div> </div> <!-- contents 조회/수정 영역--> <div class="contents"> <div id="${id}-contents" class="text"> ${contents} </div> <div id="${id}-editarea" class="edit"> <textarea id="${id}-textarea" class="te-edit" name="" id="" cols="30" rows="5"></textarea> </div> </div> <!-- 버튼 영역--> <div class="footer"> <img id="${id}-edit" class="icon-start-edit" src="images/edit.png" alt="" onclick="editPost('${id}')"> <img id="${id}-delete" class="icon-delete" src="images/delete.png" alt="" onclick="deleteOne('${id}')"> <img id="${id}-submit" class="icon-end-edit" src="images/done.png" alt="" onclick="submitEdit('${id}')"> </div> </div>`; } function isValidContents(contents) { if (contents == "") { alert("내용을 입력해주세요"); return false; } if (contents.trim().length > 140) { alert("공백 포함 140자 이하로 입력해주세요"); return false; } return true; } function genRandomName(length) { let result = ""; let characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let charactersLength = characters.length; for (let i = 0; i < length; i++) { let number = Math.random() * charactersLength; let index = Math.floor(number); result += characters.charAt(index); } return result; } function writePost() { let contents = $("#contents").val(); if (isValidContents(contents) == false) { return; } let username = genRandomName(10); let data = { username: username, contents: contents }; $.ajax({ type: "POST", url: "/api/memos", contentType: "application/json", data: JSON.stringify(data), success: function (response) { alert("메시지가 성공적으로 작성되었습니다."); window.location.reload(); }, }); } function editPost(id) { showEdits(id); let contents = $(`#${id}-contents`).text().trim(); $(`#${id}-textarea`).val(contents); } function showEdits(id) { $(`#${id}-editarea`).show(); $(`#${id}-submit`).show(); $(`#${id}-delete`).show(); $(`#${id}-contents`).hide(); $(`#${id}-edit`).hide(); } function hideEdits(id) { $(`#${id}-editarea`).hide(); $(`#${id}-submit`).hide(); $(`#${id}-delete`).hide(); $(`#${id}-contents`).show(); $(`#${id}-edit`).show(); } function submitEdit(id) { let username = $(`#${id}-username`).text().trim(); let contents = $(`#${id}-textarea`).val().trim(); if (isValidContents(contents) == false) { return; } let data = { username: username, contents: contents }; $.ajax({ type: "PUT", url: `/api/memos/${id}`, contentType: "application/json", data: JSON.stringify(data), success: function (response) { alert("메시지 변경에 성공하였습니다."); window.location.reload(); }, }); } function deleteOne(id) { $.ajax({ type: "DELETE", url: `/api/memos/${id}`, success: function (response) { alert("메시지 삭제에 성공하였습니다."); window.location.reload(); }, }); } </script> <style> @import url(//spoqa.github.io/spoqa-han-sans/css/SpoqaHanSans-kr.css); body { margin: 0px; } .area-edit { display: none; } .wrap { width: 538px; margin: 10px auto; } #contents { width: 538px; } .area-write { position: relative; width: 538px; } .area-write img { cursor: pointer; position: absolute; width: 22.2px; height: 18.7px; bottom: 15px; right: 17px; } .background-header { position: fixed; z-index: -1; top: 0px; width: 100%; height: 428px; background-color: #339af0; } .background-body { position: fixed; z-index: -1; top: 428px; height: 100%; width: 100%; background-color: #dee2e6; } .header { margin-top: 50px; } .header h2 { /*font-family: 'Noto Sans KR', sans-serif;*/ height: 33px; font-size: 42px; font-weight: 500; font-stretch: normal; font-style: normal; line-height: 0.79; letter-spacing: -0.5px; text-align: center; color: #ffffff; } .header p { margin: 40px auto; width: 217px; height: 48px; font-family: "Noto Sans KR", sans-serif; font-size: 16px; font-weight: 500; font-stretch: normal; font-style: normal; line-height: 1.5; letter-spacing: -1.12px; text-align: center; color: #ffffff; } textarea.field { width: 502px !important; height: 146px; border-radius: 5px; background-color: #ffffff; border: none; padding: 18px; resize: none; } textarea.field::placeholder { width: 216px; height: 16px; font-family: "Noto Sans KR", sans-serif; font-size: 16px; font-weight: normal; font-stretch: normal; font-style: normal; line-height: 1; letter-spacing: -0.96px; text-align: left; color: #868e96; } .card { width: 538px; border-radius: 5px; background-color: #ffffff; margin-bottom: 12px; } .card .metadata { position: relative; display: flex; font-family: "Spoqa Han Sans"; font-size: 11px; font-weight: normal; font-stretch: normal; font-style: normal; line-height: 1; letter-spacing: -0.77px; text-align: left; color: #adb5bd; height: 14px; padding: 10px 23px; } .card .metadata .date { } .card .metadata .username { margin-left: 20px; } .contents { padding: 0px 23px; word-wrap: break-word; word-break: break-all; } .contents div.edit { display: none; } .contents textarea.te-edit { border-right: none; border-top: none; border-left: none; resize: none; border-bottom: 1px solid #212529; width: 100%; font-family: "Spoqa Han Sans"; } .footer { position: relative; height: 40px; } .footer img.icon-start-edit { cursor: pointer; position: absolute; bottom: 14px; right: 55px; width: 18px; height: 18px; } .footer img.icon-end-edit { cursor: pointer; position: absolute; display: none; bottom: 14px; right: 55px; width: 20px; height: 15px; } .footer img.icon-delete { cursor: pointer; position: absolute; bottom: 12px; right: 19px; width: 14px; height: 18px; } #cards-box { margin-top: 12px; } </style> </head> <body> <div class="background-header"></div> <div class="background-body"></div> <div class="wrap"> <div class="header"> <h2>Timeline Service</h2> <p>공유하고 싶은 소식을 입력해주세요. 24시간이 지난 뒤에는 사라집니다.</p> </div> <div class="area-write"> <textarea class="field" placeholder="공유하고 싶은 소식을 입력해주세요" name="contents" id="contents" cols="30" rows="10" ></textarea> <!-- <button class="btn btn-danger" onclick="writePost()">작성하기</button>--> <img src="images/send.png" alt="" onclick="writePost()" /> </div> <div id="cards-box" class="area-read"> <div class="card"> <!-- date/username 영역 --> <div class="metadata"> <div class="date">October 10, 2020</div> <div class="username">anonymous</div> </div> <!-- contents 조회/수정 영역--> <div class="contents"> <div id="1-contents" class="text"> dsafnkalfklewakflekelafkleajfkleafkldsankflenwaklfnekwlafneklwanfkelawnfkelanfkleanfklew </div> <div id="1-editarea" class="edit"> <textarea id="1-textarea" class="te-edit" name="" id="" cols="30" rows="5" ></textarea> </div> </div> <!-- 버튼 영역--> <div class="footer"> <img id="1-edit" class="icon-start-edit" src="images/edit.png" alt="" onclick="editPost('1')" /> <img id="1-delete" class="icon-delete" src="images/delete.png" alt="" onclick="deleteOne('1')" /> <img id="1-submit" class="icon-end-edit" src="images/done.png" alt="" onclick="submitEdit('1')" /> </div> </div> </div> </div> </body> </html>
- static 폴더에 images 폴더를 만듭니다.
이미지를 다운로드 받고, images 폴더에 넣습니다.
[코드스니펫] delete 이미지
https://s3.ap-northeast-2.amazonaws.com/materials.spartacodingclub.kr/spring/week03/delete.png
[코드스니펫] done 이미지
https://s3.ap-northeast-2.amazonaws.com/materials.spartacodingclub.kr/spring/week03/done.png
[코드스니펫] edit 이미지
https://s3.ap-northeast-2.amazonaws.com/materials.spartacodingclub.kr/spring/week03/edit.png
[코드스니펫] send 이미지
https://s3.ap-northeast-2.amazonaws.com/materials.spartacodingclub.kr/spring/week03/send.png
07. [3주차] Javascript 기초 - 1
- Javascript 란?
- 브라우저를 살아 숨쉬게 만드는 친구입니다.
- 클릭, 마우스 오버 시 색 변화, 숨기기, 나타내기, 등등 수많은 일을 할 수 있답니다 😎
- 크롬 개발자 도구
- Javascript 공부, 아니 웹서비스 개발을 하는데 필수적인 녀석입니다.
- F12 키를 누르면 개발자 도구가 뜹니다! console 탭을 눌러볼게요.
Javascript 기초 문법
변수
[코드스니펫] 자바스크립트 변수
let a = 3; // 변수를 처음 선언할 때 let을 써줍니다. 자료형은 써주지 않아도 되어요. let b = 2; console.log(a + b); // System.out.println()과 같은 녀석입니다. b = 7; console.log(a + b);
자료형 (문자, 숫자, boolean, 리스트, 딕셔너리)
[코드스니펫] 자바스크립트 문자, 숫자
let name = "bknam"; let course = "웹개발의 봄 Spring"; // 자바와 다르게 홑/쌍따옴표 상관없습니다. let num = 10; console.log(num + name); // 문자 + 숫자 하면 둘 모두를 문자로 묶습니다.
[코드스니펫] 자바스크립트 boolean
let age1 = 18; let age2 = 20; let isAdult = age1 > 19; console.log(isAdult); // false isAdult = age2 > 19; console.log(isAdult); // true
[코드스니펫] 자바스크립트 리스트
let fruits = ["사과", "딸기", "수박"]; // List 보다 편하게 사용할 수 있습니다. console.log(fruits[0]); console.log(fruits[1]); console.log(fruits[2]);
[코드스니펫] 자바스크립트 딕셔너리
let course = { title: "웹개발의 봄, Spring", tutor: "남병관", }; console.log(course);
반복문
[코드스니펫] 자바스크립트 반복문
let fruits = ["사과", "딸기", "수박"]; // List 보다 편하게 사용할 수 있습니다. for (let i = 0; i < fruits.length; i++) { let fruit = fruits[i]; console.log(fruit); }
08. [3주차] Javascript 기초 - 2
Javascript 기초 문법
조건문
[코드스니펫] 자바스크립트 조건문
let fruits = ["사과", "딸기", "수박"]; // List 보다 편하게 사용할 수 있습니다. for (let i = 0; i < fruits.length; i++) { let fruit = fruits[i]; console.log(fruit == "수박"); }
함수
[코드스니펫] 자바스크립트 함수
function sample() { alert("얼럿!"); }
백틱
[코드스니펫] 자바스크립트 백틱 연습
let name = "내 이름"; let text = `${name}님의 스프링 5주 완주를 축하합니다!`; console.log(text);
연습 퀴즈 - 과일 개수 세기
Javascript 배열 안에서 과일이 몇 개인지 세는 함수를 만들어보겠습니다. 다음 실행 결과를 만들 수 있으면 됩니다.
let count = countFruit(‘감’); console.log(count); // 배열에 들어있는 감의 개수 인쇄
정답
let fruits = ["사과", "딸기", "수박", "감", "배", "딸기", "감"]; function countFruit(name) { let result = 0; for (let i = 0; i < fruits.length; i++) { let fruit = fruits[i]; if (fruit == name) { result += 1; } } return result; } let count = countFruit("감"); console.log(count); // 배열에 들어있는 감의 개수 인쇄
09. [3주차] jQuery 기초 - 1
- jQuery 란?
- jQuery는, 미리 작성된 자바스크립트 함수 모음집입니다.
- 되게 많이들 쓰는 HTML, CSS 조작 함수를 미리 만들어서 제공해주는 것이죠!
- 우리는 다 스스로 만들 필요 없이 사용법만 알면 됩니다. 😎
- https://www.w3schools.com/jquery/jquery_get_started.asp
[코드스니펫] jQuery 임포트 (head 태그 사이에!)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
jQuery 사용하는 법
$ 로 시작하고, 괄호 안에 선택자로 대상을 적으면 됩니다.
$("#contents").hide();
연습 장소로 들어가보기
- 개발자 도구 > 콘솔 에서 jQuery 를 사용하려면 해당 웹사이트에 이미 임포트 되어 있어야 합니다.
[코드스니펫] jQuery 놀이터 들어가기
[http://spartacodingclub.shop](http://spartacodingclub.shop)
숨기기 / 나타내기
[코드스니펫] HTML 나타내기
$("#post-box").show();
[코드스니펫] HTML 숨기기
$("#post-box").hide();
10. [3주차] jQuery 기초 - 2
input 값 가져오기, 넣기
[코드스니펫] input 값 가져오기
$("#post-url").val();
[코드스니펫] input 값 넣기
$("#post-url").val("new text");
HTML 없애기, 추가하기
[코드스니펫] HTML 없애기
$("#cards-box").empty();
[코드스니펫] HTML 추가하기
$("#cards-box").append(`<div class="card"> <img class="card-img-top" src="https://www.eurail.com/content/dam/images/eurail/Italy%20OCP%20Promo%20Block.adaptive.767.1535627244182.jpg" alt="Card image cap"> <div class="card-body"> <a href="#" class="card-title">여기 기사 제목이 들어가죠</a> <p class="card-text">기사의 요약 내용이 들어갑니다. 동해물과 백두산이 마르고 닳도록 하느님이 보우하사 우리나라만세 무궁화 삼천리 화려강산...</p> <p class="card-text comment">여기에 코멘트가 들어갑니다.</p> </div> </div>`);
11. [3주차] 클라이언트 설계하기
- 필요한 기능 살펴보기
- 시작코드
12. [3주차] 메모 생성하기 - writePost 함수
- 개발 스펙 확인
- [코드스니펫] 사용자가 입력한 메모 내용 확인하기
- [**코드스니펫] 작성 내용 확인하기**
- [코드스니펫] 랜덤한 username 만들기
- [코드스니펫] 전달할 data 를 JSON으로 만들기
- [코드스니펫] POST API 사용해서 메모 신규 생성하기
- [코드스니펫] writePost 함수 완성
13. [3주차] 메모 조회하기 - getMessages 함수 - 1
- 개발 스펙 확인
- 메모 조회하기
- [코드스니펫] 기존 메모 제거하기
- [코드스니펫] GET API 사용해서 메모 목록 불러오기
14. [3주차] 메모 조회하기 - getMessages 함수 - 2
- [코드스니펫] 메모 마다 HTML 만들고 붙이는 함수 만들기
- Timestamped, Week03Application 수정하기
- [코드스니펫] 반복문 안에서 addHTML 호출
- [코드스니펫] getMessages, addHTML 완성
15. [3주차] 메모 변경하기 - submitEdit 함수
- 개발 스펙 확인
- [코드스니펫] addHtml 리뷰
- [코드스니펫] 작성 대상 메모의 username과 contents 를 확인
- [코드스니펫] 작성한 메모가 올바른지 확인
- [코드스니펫] 전달할 data 를 JSON으로 바꾸기
[코드스니펫] PUT /api/memos/{id} 에 data를 전달
$.ajax({ type: "PUT", url: `/api/memos/${id}`, contentType: "application/json", data: JSON.stringify(data), success: function (response) { alert("메시지 변경에 성공하였습니다."); window.location.reload(); }, });
[코드스니펫] submitEdit 함수 완성
// 메모를 수정합니다. function submitEdit(id) { // 1. 작성 대상 메모의 username과 contents 를 확인합니다. let username = $(`#${id}-username`).text().trim(); let contents = $(`#${id}-textarea`).val().trim(); // 2. 작성한 메모가 올바른지 isValidContents 함수를 통해 확인합니다. if (isValidContents(contents) == false) { return; } // 3. 전달할 data JSON으로 만듭니다. let data = { username: username, contents: contents }; // 4. PUT /api/memos/{id} 에 data를 전달합니다. $.ajax({ type: "PUT", url: `/api/memos/${id}`, contentType: "application/json", data: JSON.stringify(data), success: function (response) { alert("메시지 변경에 성공하였습니다."); window.location.reload(); }, }); }
16. [3주차] 메모 삭제하기 - deleteOne 함수
- 개발 스펙 확인
- DELETE API 사용해서 메모 삭제하기
[코드스니펫] DELETE API 사용해서 메모 삭제하기
function deleteOne(id) { $.ajax({ type: "DELETE", url: `/api/memos/${id}`, success: function (response) { alert("메시지 삭제에 성공하였습니다."); window.location.reload(); }, }); }
[**코드스니펫] 클라이언트 완성 코드**
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Timeline Service</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&display=swap" rel="stylesheet"> <style> @import url(//spoqa.github.io/spoqa-han-sans/css/SpoqaHanSans-kr.css); body { margin: 0px; } .area-edit { display: none; } .wrap { width: 538px; margin: 10px auto; } #contents { width: 538px; } .area-write { position: relative; width: 538px; } .area-write img { cursor: pointer; position: absolute; width: 22.2px; height: 18.7px; bottom: 15px; right: 17px; } .background-header { position: fixed; z-index: -1; top: 0px; width: 100%; height: 428px; background-color: #339af0; } .background-body { position: fixed; z-index: -1; top: 428px; height: 100%; width: 100%; background-color: #dee2e6; } .header { margin-top: 50px; } .header h2 { /*font-family: 'Noto Sans KR', sans-serif;*/ height: 33px; font-size: 42px; font-weight: 500; font-stretch: normal; font-style: normal; line-height: 0.79; letter-spacing: -0.5px; text-align: center; color: #ffffff; } .header p { margin: 40px auto; width: 217px; height: 48px; font-family: 'Noto Sans KR', sans-serif; font-size: 16px; font-weight: 500; font-stretch: normal; font-style: normal; line-height: 1.5; letter-spacing: -1.12px; text-align: center; color: #ffffff; } textarea.field { width: 502px !important; height: 146px; border-radius: 5px; background-color: #ffffff; border: none; padding: 18px; resize: none; } textarea.field::placeholder { width: 216px; height: 16px; font-family: 'Noto Sans KR', sans-serif; font-size: 16px; font-weight: normal; font-stretch: normal; font-style: normal; line-height: 1; letter-spacing: -0.96px; text-align: left; color: #868e96; } .card { width: 538px; border-radius: 5px; background-color: #ffffff; margin-bottom: 12px; } .card .metadata { position: relative; display: flex; font-family: 'Spoqa Han Sans'; font-size: 11px; font-weight: normal; font-stretch: normal; font-style: normal; line-height: 1; letter-spacing: -0.77px; text-align: left; color: #adb5bd; height: 14px; padding: 10px 23px; } .card .metadata .date { } .card .metadata .username { margin-left: 20px; } .contents { padding: 0px 23px; word-wrap: break-word; word-break: break-all; } .contents div.edit { display: none; } .contents textarea.te-edit { border-right: none; border-top: none; border-left: none; resize: none; border-bottom: 1px solid #212529; width: 100%; font-family: 'Spoqa Han Sans'; } .footer { position: relative; height: 40px; } .footer img.icon-start-edit { cursor: pointer; position: absolute; bottom: 14px; right: 55px; width: 18px; height: 18px; } .footer img.icon-end-edit { cursor: pointer; position: absolute; display: none; bottom: 14px; right: 55px; width: 20px; height: 15px; } .footer img.icon-delete { cursor: pointer; position: absolute; bottom: 12px; right: 19px; width: 14px; height: 18px; } #cards-box { margin-top: 12px; } </style> <script> // 미리 작성된 영역 - 수정하지 않으셔도 됩니다. // 사용자가 내용을 올바르게 입력하였는지 확인합니다. function isValidContents(contents) { if (contents == '') { alert('내용을 입력해주세요'); return false; } if (contents.trim().length > 140) { alert('공백 포함 140자 이하로 입력해주세요'); return false; } return true; } // 익명의 username을 만듭니다. function genRandomName(length) { let result = ''; let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let charactersLength = characters.length; for (let i = 0; i < length; i++) { let number = Math.random() * charactersLength; let index = Math.floor(number); result += characters.charAt(index); } return result; } // 수정 버튼을 눌렀을 때, 기존 작성 내용을 textarea 에 전달합니다. // 숨길 버튼을 숨기고, 나타낼 버튼을 나타냅니다. function editPost(id) { showEdits(id); let contents = $(`#${id}-contents`).text().trim(); $(`#${id}-textarea`).val(contents); } function showEdits(id) { $(`#${id}-editarea`).show(); $(`#${id}-submit`).show(); $(`#${id}-delete`).show(); $(`#${id}-contents`).hide(); $(`#${id}-edit`).hide(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 여기서부터 코드를 작성해주시면 됩니다. $(document).ready(function () { // HTML 문서를 로드할 때마다 실행합니다. getMessages(); }) // 메모를 불러와서 보여줍니다. function getMessages() { // 1. 기존 메모 내용을 지웁니다. $('#cards-box').empty(); // 2. 메모 목록을 불러와서 HTML로 붙입니다. $.ajax({ type: 'GET', url: '/api/memos', success: function (response) { for (let i = 0; i < response.length; i++) { let message = response[i]; let id = message['id']; let username = message['username']; let contents = message['contents']; let modifiedAt = message['modifiedAt']; addHTML(id, username, contents, modifiedAt); } } }) } // 메모 하나를 HTML로 만들어서 body 태그 내 원하는 곳에 붙입니다. function addHTML(id, username, contents, modifiedAt) { // 1. HTML 태그를 만듭니다. let tempHtml = `<div class="card"> <!-- date/username 영역 --> <div class="metadata"> <div class="date"> ${modifiedAt} </div> <div id="${id}-username" class="username"> ${username} </div> </div> <!-- contents 조회/수정 영역--> <div class="contents"> <div id="${id}-contents" class="text"> ${contents} </div> <div id="${id}-editarea" class="edit"> <textarea id="${id}-textarea" class="te-edit" name="" id="" cols="30" rows="5"></textarea> </div> </div> <!-- 버튼 영역--> <div class="footer"> <img id="${id}-edit" class="icon-start-edit" src="images/edit.png" alt="" onclick="editPost('${id}')"> <img id="${id}-delete" class="icon-delete" src="images/delete.png" alt="" onclick="deleteOne('${id}')"> <img id="${id}-submit" class="icon-end-edit" src="images/done.png" alt="" onclick="submitEdit('${id}')"> </div> </div>`; // 2. #cards-box 에 HTML을 붙인다. $('#cards-box').append(tempHtml); } // 메모를 생성합니다. function writePost() { // 1. 작성한 메모를 불러옵니다. let contents = $('#contents').val(); // 2. 작성한 메모가 올바른지 isValidContents 함수를 통해 확인합니다. if (isValidContents(contents) == false) { return; } // 3. genRandomName 함수를 통해 익명의 username을 만듭니다. let username = genRandomName(10); // 4. 전달할 data JSON으로 만듭니다. let data = {'username': username, 'contents': contents}; // 5. POST /api/memos 에 data를 전달합니다. $.ajax({ type: "POST", url: "/api/memos", contentType: "application/json", data: JSON.stringify(data), success: function (response) { alert('메시지가 성공적으로 작성되었습니다.'); window.location.reload(); } }); } // 메모를 수정합니다. function submitEdit(id) { // 1. 작성 대상 메모의 username과 contents 를 확인합니다. let username = $(`#${id}-username`).text().trim(); let contents = $(`#${id}-textarea`).val().trim(); // 2. 작성한 메모가 올바른지 isValidContents 함수를 통해 확인합니다. if (isValidContents(contents) == false) { return; } // 3. 전달할 data JSON으로 만듭니다. let data = {'username': username, 'contents': contents}; // 4. PUT /api/memos/{id} 에 data를 전달합니다. $.ajax({ type: "PUT", url: `/api/memos/${id}`, contentType: "application/json", data: JSON.stringify(data), success: function (response) { alert('메시지 변경에 성공하였습니다.'); window.location.reload(); } }); } // 메모를 삭제합니다. function deleteOne(id) { // 1. DELETE /api/memos/{id} 에 요청해서 메모를 삭제합니다. $.ajax({ type: "DELETE", url: `/api/memos/${id}`, success: function (response) { alert('메시지 삭제에 성공하였습니다.'); window.location.reload(); } }) } </script> </head> <body> <div class="background-header"> </div> <div class="background-body"> </div> <div class="wrap"> <div class="header"> <h2>Timeline Service</h2> <p> 공유하고 싶은 소식을 입력해주세요. 24시간이 지난 뒤에는 사라집니다. </p> </div> <div class="area-write"> <textarea class="field" placeholder="공유하고 싶은 소식을 입력해주세요" name="contents" id="contents" cols="30" rows="10"></textarea> <!-- <button class="btn btn-danger" onclick="writePost()">작성하기</button>--> <img src="send.png" alt="" onclick="writePost()"> </div> <div id="cards-box" class="area-read"> <div class="card"> <!-- date/username 영역 --> <div class="metadata"> <div class="date"> October 10, 2020 </div> <div class="username"> anonymous </div> </div> <!-- contents 조회/수정 영역--> <div class="contents"> <div id="1-contents" class="text"> dsafnkalfklewakflekelafkleajfkleafkldsankflenwaklfnekwlafneklwanfkelawnfkelanfkleanfklew </div> <div id="1-editarea" class="edit"> <textarea id="1-textarea" class="te-edit" name="" id="" cols="30" rows="5"></textarea> </div> </div> <!-- 버튼 영역--> <div class="footer"> <img id="1-edit" class="icon-start-edit" src="edit.png" alt="" onclick="editPost('1')"> <img id="1-delete" class="icon-delete" src="delete.png" alt="" onclick="deleteOne('1')"> <img id="1-submit" class="icon-end-edit" src="done.png" alt="" onclick="submitEdit('1')"> </div> </div> <div class="card"> <!-- date/username 영역 --> <div class="metadata"> <div class="date"> October 10, 2020 </div> <div class="username"> anonymous </div> </div> <!-- contents 조회/수정 영역--> <div class="contents"> <div id="1-contents" class="text"> dsafnkalfklewakflekelafkleajfkleafkldsankflenwaklfnekwlafneklwanfkelawnfkelanfkleanfklew </div> <div id="1-editarea" class="edit"> <textarea id="1-textarea" class="te-edit" name="" id="" cols="30" rows="5"></textarea> </div> </div> <!-- 버튼 영역--> <div class="footer"> <img id="1-edit" onclick="editPost('1')" class="icon-start-edit" src="edit.png" alt=""> <img id="1-delete" onclick="deleteOne('1')" class="icon-delete" src="delete.png" alt=""> <img id="1-submit" onclick="submitEdit('1')" class="icon-end-edit" src="done.png" alt=""> </div> </div> </div> </div> </body> </html>
17. [3주차] 끝 & 숙제설명
잠깐 복습
드디어! 우리가 End to End 로, 처음부터 끝까지,
를 만들어 보았습니다.🎉 다소 빠르고 힘겹게 느껴지신다구요? 제대로 공부하고 계시다는 증거입니다. 😎 약간의 속도감이 우리의 실력을 한 차원 끌어올리고 있어요! 4주차 수업 <나만의 셀렉샵="">을 위해, 이번 주 잘 소화해주실거죠?나만의>
- Rest API의 CRUD 전체 프로세스를, 다시 한 번 복습했습니다.
- HTML, CSS, Javascript, jQuery를 통해 보이는 화면을 만들고 조작하는 방법에 대해 배웠습니다.
- API를 먼저 설계하고, 그에 따라 클라이언트 코드 작성하는 프로세스를 익혔습니다.
숙제 설명
타임라인 서비스가 불러오는 메모 목록의 시간을, 조회 시간으로부터 24시간 이내로 바꿔보세요. 힌트1) spring jpa localtime between 라고 구글링해보세요. 힌트2) 지금은 LocalDateTime.now(), 하루 전은 LocalDateTime.now().minusDays(1) 입니다.
- 제출 파일
- MemoRepository.java
- MemoController.java
- 제출 파일