스프링

반드시 알아야 할 기본 기능들 02

H-V 2021. 10. 18. 11:35

 

 

01 검색창

  • 검색창은 페이징이 같이 되야하기때문에 Pageable이 같이 들어가야 오류없이 작동이 된다. 
  1. 일단 JSP를 둘러볼 필요가 있다
    <div style="margin: 1rem" class="float-right">
    <form class="form-inline" action="" id="saerchForm">
    <span class="m-3"><a href="/blog/blogForm" class="btn btn-success">블로그 쓰기</a></span>
    <br>
    <div class="form-group">
    <label for="sel1"></label> <select class="form-control" id="sel1">
    <option>검색</option>
    <option>작성일</option>
    <option>아이디</option>
    <option>제목+내용</option>
    </select>
    </div>
    <input class="form-control mr-sm-2" type="text" placeholder="Search"
    id="search" name="">
    <button class="btn btn-success" type="submit"
    onclick="onSearch(event)">Search</button>
    </form>
    </div>​
    검색창을 만드는 기본 디자인이 들어가고 거기에 검색구분을 나누는 부분과 검색 버튼에 각각 아이디를 준다
  2. AJAX
    <script>
    	function onSearch(event) {
    		event.preventDefault()
    
    		let gubun = $("#sel1 option:selected").val() //sel 값을 받아옴
    		console.log(gubun)
    		if (gubun == "검색") {
    			alert("검색 구분자를 선택하세요!")
    			return false;
    		}
    
    		let text = $("#search").val()
    
    		if (text == "") {
    			alert("검색어를 입력 하세요");
    			$("#search").focus();
    			return false;
    		}
    
    		window.location = "/blog/findbytext" + "?page=0&gubun=" + gubun
    				+ "&text=" + text
    	}
    </script>​
    검색구분자 선택 + 단어 입력 없이는 검색이 안되도록 1차로 막고 'window.location' 부분에서 gubun(검색구분자선택용) + text(입력된검색단어)를 받아서 주소가 만들어지도록 한다 (gubun+text는 dto에서 받는다)
  3. DTO를 만들어서 검색 구분자 + 입력된 검색어를 위한 변수 설정
    @Data
    public class SearchDto {
    	private String gubun;
    	private String text;
    }​
  4.  컨트롤러
    	@GetMapping("/admin/findbytext")
    	public String findByText(Model model, @ModelAttribute SearchDto dto,
    	@PageableDefault(size = 3, sort = "id",
    	direction = Sort.Direction.DESC) Pageable pageable) {
                Page<User> lists = userService.searchByText(dto, pageable);
                model.addAttribute("lists", lists);
                return "admin/userlist";
    	}​
    검색기능을 담당하는 컨트롤러를 따로 만들자 
  5. 서비스
    	// 검색 - 모두
    	@Transactional(readOnly = true)
    	public Page<Board> searchByText(SearchDto dto, Pageable pageable) {
    
    		Page<Board> boardlist = null;
    
    		System.out.println("pageable.getOffset()=" + pageable.getOffset());
    		System.out.println("pageable.getPageSize()=" + pageable.getPageSize());
    		System.out.println("pageable.getPageNumber()=" + pageable.getPageNumber());
    
    		switch (dto.getGubun()) {
    		/*
    		 * case "전체": System.out.println("======================="); userlist =
    		 * boardRepository.findByText(dto.getText(), pageable); break;
    		 */
    			case "작성일":
    				System.out.println("1111==========호출============");
    				boardlist = boardRepository.searchByDate(dto.getText(), pageable);
    				break;
    			case "제목 내용":
    				System.out.println("2222==========호출============");
    				boardlist = boardRepository.findByText(dto.getText(), pageable);
    				break;
    		}
    		return boardlist;
    	}​
    dto에서 넘어온 값을가지고 구분자 선택이름에 맞게 switch문을 걸고 레파지토리 호출
  6. 레파지토리
    public interface BoardRepository extends JpaRepository<Board, Integer> {
    
    public void deleteById(int id);
    
    //날짜 검색
    @Query(value = "SELECT * from Board WHERE date_format(board.createDate, '%Y-%m-%d') LIKE %?1%",
    countQuery = "SELECT count(*) FROM Board WHERE date_format(board.createDate, '%Y-%m-%d') LIKE %?1%",
    nativeQuery = true)
    Page<Board> searchByDate(String date, Pageable pageable);
    
    //제목+타이틀
    @Query(value = "SELECT * FROM Board WHERE " + "title LIKE %?1% or " + "content LIKE %?1% ",
    countQuery = "SELECT count(*) FROM Board WHERE title LIKE %?1% or content LIKE %?1%",
    nativeQuery = true)
    Page<Board> findByText(String searchText, Pageable pageable);
    }​
    날짜 혹은 레파지토리에서 기본적으로 지원하는 검색전용 네이명을 제외하고는 직접 만들어야하고 쿼리도 짜야한다 (예는 아래와 같다)
더보기

*부트+JPA에서 지원하는 기능들은 네이밍을 반드시 맞춰줘야한다 (쿼리는 안짜도 됨!)

 

Page<User> findByUsernameContaining(String username, Pageable pageable);
Page<User> findByEmailContaining(String email, Pageable pageable);

 

 

 

 

02 썸머노트 이미지 불러오기

  • 게시판 글쓰기를 할때 하이라이트,정렬,이미지삽입등을 구현하려면 맨땅에서부터 모든 코드를 구현해야한다. 이를 쉽게 해주는게 섬머노트. 하지만 섬머노트를 이용해서 이미지 삽입을 하면 아래와 같이 저장 된다. 
  • 이 이미지 태그를 꺼내오는작업이 쉽지 않다.
  1. JSP에 이 이미지를 뿌릴 곳을 정한다.
    <c:forEach var="item" items="${lists.content }">
    <div class="card m-2"
    style="text-align: center;"
    display="inline-block; ">
    <div class="card-body" >
    <span>${item.id}</span> /
    <javatime:format value="${item.createDate}" pattern="yyyy.MM.dd" />
    / <span style="font-size: 20px;">${item.user.username}</span> / <span>⬆조회수(${item.count })</span>
    / <span>⬆댓글수(${item.replyCount })</span> / <span>좋아요(${item.likeCount})</span>
    <hr>
    <h4 class="card-title" id="h1" style="color: #1E90FF;">${item.title}</h4>
    <hr>
    <p class="card-text" name="myname" id="c1"></p> <---- 이부분에 이미지를 뿌릴 예정
    <hr>
    <a href="/blog/blogDetail/${item.id}" class="btn btn-primary float-right">상세 보기</a>
    </div>
    </div>
    </c:forEach>
  2. JS를 이용해서 <img> 태그 부분을 추출 한다
    <script>
    	function imageExtract(text) {
    		
    		let myArray = myRe.exec(text);
    		
    		//console.log(myArray.length);
    
    		if (myArray != null && myArray.length > 0) {
    			//console.log(myArray[0]); 
    			let mytext = myArray[0];
    			let myRe2 = /width: \d+(px|%)/;
    			let abc = mytext.replace(myRe2, 'width: 200px; height: 150px')
    			//console.log(abc)
    			return abc;
    		} else {
    			return "";
    		}
    	}
    	/* imageExtract(); */
    
    	$(document).ready(function() {
    		let array = new Array();
    		//var temp = "";
    		<c:forEach var="item" items="${lists.content }">
    		//temp = '${item.content}';
    		//temp = imageExtract(temp)
    		//console.log(temp)
    		array.push(imageExtract('${item.content}'))
    		</c:forEach>
    		//console.log(array)
    
    		array.forEach(function(item, index, arr2) {
    			//console.log(item)
    			$("p[name='myname']:eq(" + index + ")").html(item);
    		})
    	});
    </script>​
    게시판 특성상 <forEach>문으로 뿌리고 그 안에 콘텐트 영역이 있기때문에 그걸 그대로 들고와서 extract를 해준다. JS의 큰장점으로는 자바코드를 그대로 가져와서 사용가능하다는 것!
    또한 '정규표현식'이라는것(.exec)을 써서 여러태그 + 여러문자종류들이 섞여있는부분을 나눠서 뽑아올수있다!
    중간의 if부분은 이렇게 추출해서 뿌려보면 잘 나오지만 유저가 이미지를 안올리고 글을쓰게되면 이미지를 가지고있는 나머지 글도 안보이는 현상이 있다. 그것을 해결하는 코드. 직접 이미지 사이즈 조절이 가능하다. 

 

 

03 좋아요

  • 좋아요를 구현하려면 따로 모델링을 해줘야 한다! 그리고 나머지는 똑같은 개념으로 구성
  1. 모델
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @Entity
    @Table(uniqueConstraints = { @UniqueConstraint(name = "likes_uk", columnNames = { "blogId", "userId" }) })
    public class Likes {
    
    	@Id
    
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	private int id;
    
    	@JoinColumn(name = "blogId")
    
    	@ManyToOne
    	private Blog blog; // 1개의 블로그는는 여러번 좋아요를 받는다
    
    	@JoinColumn(name = "userId")
    
    	@ManyToOne
    	private User user; // 1명의 유저는 여러번 좋아요를 한다
    
    	private LocalDateTime createDate;
    
    	@PrePersist
    	public void createDate() {
    		this.createDate = LocalDateTime.now();
    	}
    }​
  2. 여기서 중요한 개념은 이 좋아요를 쓰는 모델에 무한참조 + 캐스캐이드(글 삭제시 좋아요도 함께 삭제되는데 캐스캐이드 없으면 오류가 걸린다.)를 걸어줘야 오류가 잡힌다.
    public class Blog {
    	...
        	@JsonIgnoreProperties({ "blog" })
    	@OneToMany(mappedBy = "blog", cascade = CascadeType.REMOVE)
    	private List<Likes> likes;
    	...
        }
    }​
  3. 좋아요 컨트롤러
    public class BlogApiController {
    	...
        	// 좋아요
    	@PostMapping("/api/blog/{blogId}/likes")
    	public ResponseEntity<?> likes(@PathVariable int blogId,
    			@AuthenticationPrincipal PrincipalDetails principalDetails) {
    		likesService.like(blogId, principalDetails.getUser().getId());
    		return new ResponseEntity<>(new CMRespDto<>(1, "success", null), HttpStatus.CREATED);
    	}
    
    	// 좋아요 취소
    	@DeleteMapping("/api/blog/{blogId}/likes")
    	public ResponseEntity<?> unlikes(@PathVariable int blogId,
    			@AuthenticationPrincipal PrincipalDetails principalDetails) {
    		likesService.unlike(blogId, principalDetails.getUser().getId());
    		return new ResponseEntity<>(new CMRespDto<>(1, "success", null), HttpStatus.CREATED);
    	}
    	
    	// 좋아요 조회
    	@GetMapping("/api/blog/{blogId}/likes")
    	public ResponseEntity<?> checklikes(@PathVariable int blogId,
    			@AuthenticationPrincipal PrincipalDetails principalDetails) {
    		List<Likes> likesList = likesService.likescheck(blogId, principalDetails.getUser().getId());
    		if (likesList.size() == 0) {
    			return new ResponseEntity<>(new CMRespDto<>(1, "no", null), HttpStatus.CREATED);
    		} else {
    			return new ResponseEntity<>(new CMRespDto<>(1, "yes", null), HttpStatus.CREATED);
    		}
    	}​
    좋아요 컨트롤러는 따로 없다. 좋아요는 게시판에 구현되기때문에 거기에 넣으면 된다. 좋아요/좋아요 취소는 쉬운 로직이다. 조회가 필요한 이유는 좋아요가 찍혀있는지 안찍혀있는지를 판단해서 no/yes로 받아서 AJAX를 돌리는 것. 
  4. 좋아요 서비스
    @Service
    @RequiredArgsConstructor
    public class LikesService {
    	private final LikesRepository likesRepository;
    	private final BlogRepository blogRepository;
    
    	
    	//좋아요
    	@Transactional
    	public void like(int blogId, int principalId) {
    		Optional<Blog> blog = blogRepository.findById(blogId);
    		if(blog.isPresent()) {
    			System.out.println("좋아요 증가 로직탐?==================");
    			Blog blog1 = blog.get();
    			blog1.setLikeCount(blog1.getLikeCount()+1);
    			blogRepository.save(blog1);
    		}
    		likesRepository.like(blogId, principalId);
    	}
    
    	//좋아요 취소
    	@Transactional
    	public void unlike(int blogId, int principalId) {
    		Optional<Blog> blog = blogRepository.findById(blogId);
    		if(blog.isPresent()) {
    			Blog blog1 = blog.get();
    			blog1.setLikeCount(blog1.getLikeCount()-1);
    			blogRepository.save(blog1);
    		}
    		likesRepository.unlike(blogId, principalId);
    	}
    	
    	//좋아요 서치
    	@Transactional
    	public List<Likes> likescheck(int blogId, int principalId) {
    		return likesRepository.checklikes(blogId, principalId);
    	}
    
    }​
    증가와 삭제는 어려운 부분이 없다. 중요한 부분은 좋아요가 체크가 되는 부분을 따로 구현해야한다. 
  5. 레파지토리
    public interface LikesRepository extends JpaRepository<Likes, Integer> {
    
    	@Modifying
    	@Query(value = "INSERT INTO likes(blogId, userId, createDate) VALUES (:blogId, :principalId, now())", nativeQuery = true)
    	int like(int blogId, int principalId);
    
    	@Modifying
    	@Query(value = "Delete FROM likes WHERE blogId = :blogId AND userId = :principalId", nativeQuery = true)
    	int unlike(int blogId, int principalId);
    	
    	@Modifying
    	@Query(value = "SELECT * FROM likes WHERE blogId = :blogId AND userId = :principalId", nativeQuery = true)
    	List<Likes> checklikes(int blogId, int principalId);
    	
    }​
    좋아요/좋아요 취소는 쿼리문을 직접 짜서 넣어주고, 좋아요가 되었는지 확인을 하기위해서 하나더 추가를 해준다
  6. JSP
    -> 일반 게시판
    <span>좋아요(${item.likeCount})</span>
    
    
    -> 상세보기 들어가서 좋아요 가능하도록 화면 구현
    <span id="likebtn">🧡</span>​
  7. 여기에 ajax를 통해 화면이동없이 좋아요 기능을 추가
    $(document).ready(function(){
    	checkheart();
    });
    function checkheart(){
    	$.ajax({
    		type :"get",
    		url : "/api/blog/${blog.id}/likes",
    		success:function(resp){
    			console.log(resp);
    			if(resp.message=="no"){
    				//alert("좋아요를 할 수 있어요!");
    				$("#likebtn").html("🤍")
    				isliked = false;
    			} else {
    				$("#likebtn").html("🧡")
    				isliked = true;
    			}
    		}, //success
    		error:function(error){
    			console.log(error)
    			alert("error")
    		}		
    	})//ajax
    }
    
    function addlike(){
    	$.ajax({
    		type :"post",
    		url : "/api/blog/${blog.id}/likes",
    		success:function(resp){
    			console.log(resp);
    			if(resp.message=="success"){
    				//alert("좋아요 성공");
    				checkheart();
    			} else {
    				alert("좋아요 실패");
    			}
    		}, //success
    		error:function(error){
    			console.log(error)
    			alert("error")
    		}		
    	})//ajax
    }
    
    function deletelike(){
    	$.ajax({
    		type :"delete",
    		url : "/api/blog/${blog.id}/likes",
    		success:function(resp){
    			console.log(resp);
    			if(resp.message=="success"){
    				//alert("좋아요 성공");
    				checkheart();
    			} else {
    				alert("좋아요 실패");
    			}
    		}, //success
    		error:function(error){
    			console.log(error)
    			alert("error")
    		}		
    	})//ajax
    }
    
    </script>​

 

04 댓글

  • 댓글같은 경우에는 레파지토리를 제외하고 모두 쓰이는곳의 컨트롤러/서비스에서 구현 된다
  1. 모델
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @Entity
    public class Reply {
    	
    	@Id
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	private int id;
    	
    	@Column(nullable = false, length = 200)
    	private String content;
    	
    	@ManyToOne
    	@JoinColumn(name="blogId")
    	private Blog blog;
    	
    	@ManyToOne
    	@JoinColumn(name="userId")
    	private User user;
    	
    	private LocalDateTime createDate;
    	
    	@PrePersist
    	public void createDate() {
    		this.createDate = LocalDateTime.now();
    	}
    	
    	/*
    	 * public void update(User user, Blog blog, String content) { setUser(user);
    	 * setBlog(blog); setContent(content); }
    	 */
    }​
  2. 컨트롤러
    public class BlogApiController {
    	...
        	// 댓글 쓰기
    	@PostMapping("api/blogDetail/{blogId}/reply")
    	public ResponseEntity<?> replySave(@RequestBody ReplyDto replyDto) {
    		blogService.replyInsert(replyDto);
    		return new ResponseEntity<>(new CMRespDto<>(1, "Success", null), HttpStatus.OK);
    	}
    
    	// 댓글 삭제
    	@DeleteMapping("/api/blogDetail/{blogId}/reply/{replyId}")
    	public ResponseEntity<?> replyDelete(@RequestBody ReplyDto replyDto) {
    		
    		System.out.println(replyDto.toString()+"==============================");
    		
    		blogService.deleteReply(replyDto);
    		return new ResponseEntity<>(new CMRespDto<>(1, "Success", null), HttpStatus.OK);
    	}​
     
  3. 서비스
    public class BlogService {
    	..
        // 댓글 쓰기
    	@Transactional
    	public void replyInsert(ReplyDto replyDto/* , Reply reply */) {
    
    		/*
    		 * 영속화 첫번째 개념 User user =
    		 * userRepository.findById(replyDto.getUserId()).orElseThrow(() -> { return new
    		 * IllegalArgumentException("댓글 실패 : 유저 아이디 못찾음"); }); // 영속화
    		 * 
    		 * Blog blog = blogRepository.findById(replyDto.getBlogId()).orElseThrow(() -> {
    		 * return new IllegalArgumentException("댓글 실패 : 블로그 아이디 못찾음"); });
    		 * 
    		 * 
    		 * Reply reply = new Reply(); reply.update(user, blog, replyDto.getContent());
    		 * 
    		 * 
    		 * Reply reply =
    		 * Reply.builder().user(user).blog(blog).content(replyDto.getContent()).build();
    		 */
    
    //		Optional<Blog> b = blogRepository.findById(reply.getBlog().getId());
    //		b.get().setReplyCount(b.get().getReplyCount()+1);
    
    		replyRepository.mSave(replyDto.getUserId(), replyDto.getBlogId(), replyDto.getContent());
    
    		int blogId = replyDto.getBlogId();
    		Optional<Blog> blog = blogRepository.findById(blogId);
    		if (blog.isPresent()) {
    			blog.get().setReplyCount(blog.get().getReplyCount() + 1);
    			blogRepository.save(blog.get());
    		}​
  4. 레파지토리
    public interface ReplyRepository extends JpaRepository<Reply, Integer>{
    	
    	@Modifying
    	@Query(value = "INSERT INTO reply (userId,  blogId, content, createDate) VALUES(?1,?2,?3, now())", nativeQuery = true)
    	int mSave(int userId, int blogId, String content);
    	//modifying은 리턴값이 숫자형이기때문에 int로 받아야 함
    }​
  5. JSP
    <div class="card">
    <form>
    <input type="hidden" id="userId" value="${principal.user.id}" /> <input
    type="hidden" id="blogId" value="${blog.id}" />
    <div class="card-body">
    <textarea id="reply-content" class="form-control" rows="1"></textarea>
    </div>
    <div class="card-footer">
    <button type="button" id="btn-reply-save" class="btn btn-warning">댓글
    쓰기</button>
    </div>
    </form>
    </div>
    <br>
    
    <div class="card">
    <div class="card-header">댓글</div>
    <ul id="reply--box" class="list-group">
    <c:forEach var="reply" items="${blog.replies}">
    <li id="reply--${reply.id}"
    class="list-group-item d-flex justify-content-between">
    <div>
    ${reply.content} <span class="text-primary">(<javatime:format
    value="${reply.createDate}" pattern="yyyy.MM.dd HH:mm" />)
    </span>
    </div>
    
    
    <div class="d-flex">
    <div class="font-weight-bold">작성자: ${reply.user.username }
    &nbsp;</div>
    <c:if test="${reply.user.id == principal.user.id}">
    <button onClick="index.replyDelete(${blog.id}, ${reply.id})"
    class="btn btn-danger btn-sm badge" style="font-size: 13px">삭제</button>
    </c:if>
    </div>
    
    </li>
    </c:forEach>
    </ul>
    </div>​
    
    <script src="/js/blog.js"></script>
  6. blog.js
    let index = {
    	init: function() {
        ...
        $("#btn-reply-save").on("click", () => {
        	this.replySave();
        });
        },​
        ...
        replySave: function() { //this.save()의 save
    		//alert("user.js의 save 함수 호출됨");
    		if ($("#reply-content").val() == "") {
    			alert("댓글을 입력하세요.");
    			$("title").focus();
    			return false;
    		}
    
    		let data = {
    			userId: $("#userId").val(),
    			blogId: $("#blogId").val(),
    			content: $("#reply-content").val()
    		};
    
    		console.log(data);
    		$.ajax({
    			//ajax 기본 호출은 비동이
    			//오브젝트가 들어오는 곳
    			//통신 수행
    			type: "post",
    			url: `/api/blogDetail/${data.blogId}/reply`,
    			data: JSON.stringify(data),//HTTP BODY DATA
    			contentType: "application/json; charset=utf-8", //body데이터의 타입
    			dataType: "json" //서버에서 받을 데이터 형식, 즉 json으로 던지고 서버를위해 자동 파싱 = JSON->JS
    		}).done(function(resp) { //위의 데이터가 js로 바뀌고 파라미터로 사용 가능
    			//통신이 정상이면 done
    			alert("댓글 쓰기 완료");
    			//alert(resp)
    			console.log(resp);
    			location.href = `/blog/blogDetail/${data.blogId}`;
    		}).fail(function(error) {
    			//통신이 비정상이면 fail
    			alert(JSON.stringify(error));
    		});
    	},
    	
    	replyDelete: function(blogId, replyId) { //this.save()의 save
    		if (!confirm("정말 삭제할까요?"))
    		return false;
    		
    		let data = {
    			replyId: replyId,
    			blogId: blogId
    		};
    		
    		console.log(data);
    		
    		$.ajax({
    			type: "DELETE",
    			url: `/api/blogDetail/${blogId}/reply/${replyId}`, 
    			data: JSON.stringify(data),
    			contentType: "application/json; charset=utf-8",
    			dataType: "json" //서버에서 받을 데이터 형식, 즉 json으로 던지고 서버를위해 자동 파싱 = JSON->JS
    		}).done(function(resp) { //위의 데이터가 js로 바뀌고 파라미터로 사용 가능
    			//통신이 정상이면 done
    			alert("댓글 삭제 완료");
    			//alert(resp)
    			console.log(resp);
    			location.href = `/blog/blogDetail/${blogId}`;
    		}).fail(function(error) {
    			//통신이 비정상이면 fail
    			alert(JSON.stringify(error));
    		});
    	},
    }
    index.init();



05 날씨 API

  • 날씨 API는 자료가 별로 없다. 'openweathermap.org'에서 쓰는것이 가장 베스트인거 같다.
  • 이 API는 따로 컨트롤러등을 만들필요없이 뿌리는 화면에서 AJAX로 처리해서 뿌리기만 하면 끝
  1. JSP
    		<h2>부산 날씨</h2>
    		<br>
    		<table class="table table-hover" style="text-align: center">
    			<thead class="thead-light " style="font-wiehgt: bold;">
    				<tr>
    					<th>날짜</th>
    					<th>날씨</th>
    					<th>온도</th>
    					<th>습도</th>
    					<th>바람</th>
    				<tr>
    			</thead>
    			<tbody>
    				<tr>
    					<td><%=month%>월 <%=day%>일 <span id="placeholder"></span></td>
    					<td><span id="Weather3"></span></td>
    					<td><span id="Tmp3"></span></td>
    					<td><span id="Hmd3"></span></td>
    					<td><span id="Wind3"></span></td>
    				</tr>
    				<tr>
    					<td><%=month%>월 <%=day + 1%>일 <span id="placeholder2"></span></td>
    					<td><span id="Weather11"></span></td>
    					<td><span id="Tmp11"></span></td>
    					<td><span id="Hmd11"></span></td>
    					<td><span id="Wind11"></span></td>
    				</tr>
    				<tr>
    					<td><%=month%>월 <%=day + 2%>일 <span id="placeholder3"></span></td>
    					<td><span id="Weather19"></span></td>
    					<td><span id="Tmp19"></span></td>
    					<td><span id="Hmd19"></span></td>
    					<td><span id="Wind19"></span></td>
    				</tr>
    				<tr>
    					<td><%=month%>월 <%=day + 3%>일 <span id="placeholder4"></span></td>
    					<td><span id="Weather27"></span></td>
    					<td><span id="Tmp27"></span></td>
    					<td><span id="Hmd27"></span></td>
    					<td><span id="Wind27"></span></td>
    				</tr>
    				<tr>
    					<td><%=month%>월 <%=day + 4%>일 <span id="placeholder5"></span></td>
    					<td><span id="Weather35"></span></td>
    					<td><span id="Tmp35"></span></td>
    					<td><span id="Hmd35"></span></td>
    					<td><span id="Wind35"></span></td>
    				</tr>
    			</tbody>
    		</table>
    		<br>
    	</div>
    	<br>​
  2. AJAX
    <script>
    var apiURI = "https://api.openweathermap.org/data/2.5/forecast?q=Busan&appid=51a9596817c1a197fb2e23f7a88ec9cd";
    $(function() {
    $.ajax({
    type : "GET",
    url : apiURI,
    success : function(resp) {
    let result = resp.list;
    let a = 1;
    for (i = 1; i < result.length; i++) {
    if (i % 8 == 3) {
    console.log(result[i]);
    $("#Weather" + a).html(
    result[i].weather[0].main + "("
    + result[i].weather[0].description
    + ")")
    $("#Tmp" + a).html(
    Math.ceil(result[i].main.temp - 273.15)
    + "˚")
    $("#Hmd" + a).html(result[i].main.humidity + "%")
    
    $("#Wind" + a).html(result[i].wind.speed + "(m/s)")
    }
    a += 1;
    }
    }
    })
    })
    </script>
    <script>
    var now = new Date();
    var week = new Array('일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일');
    
    function oneDay() {
    var today = new Date();
    var todayLabel = week[today.getDay()];
    return todayLabel;
    }
    function twoDays() {
    var today = new Date();
    var tomorrow1 = new Date(today.valueOf() + (24 * 60 * 60 * 1000));
    var tomorrow1Label = week[tomorrow1.getDay()];
    return tomorrow1Label;
    }
    function threeDays() {
    var today = new Date();
    var tomorrow2 = new Date(today.valueOf()
    + (24 * 60 * 60 * 1000 * 2));
    var tomorrow2Label = week[tomorrow2.getDay()];
    return tomorrow2Label;
    }
    function fourDays() {
    var today = new Date();
    var tomorrow3 = new Date(today.valueOf()
    + (24 * 60 * 60 * 1000 * 3));
    var tomorrow3Label = week[tomorrow3.getDay()];
    return tomorrow3Label;
    }
    function fiveDays() {
    var today = new Date();
    var tomorrow4 = new Date(today.valueOf()
    + (24 * 60 * 60 * 1000 * 4));
    var tomorrow4Label = week[tomorrow4.getDay()];
    return tomorrow4Label;
    }
    
    document.getElementById('placeholder').innerText = oneDay();
    document.getElementById('placeholder2').innerText = twoDays();
    document.getElementById('placeholder3').innerText = threeDays();
    document.getElementById('placeholder4').innerText = fourDays();
    document.getElementById('placeholder5').innerText = fiveDays();
    </script>​
    AJAX를 제외하고 요일별로 뿌리기 위해서는 따로 함수 + 계산식이 필요하다!