(61)

for, for in, for of, forEach

Python에는 for와 while이라는 반복문이 있고, 이 둘은 범위냐 조건이냐의 차이만 빼면 반복문이라는 기본 골자는 같은데, 이런 게 자바스크립트에도 당연히 있다. 라디오버튼 뺑뺑이 돌면서 얘가 뭘 체크했나 보는 것도 반복문의 일이기 때문. 그런데 이 반복문... Python처럼 심플하지 않아요... for, for in, for of See the Pen for, for in, for of by koreanraichu (@koreanraichu) on CodePen. 얘네들은 Pyhlogenic tree로 치자면 그 트리가 막 갈라지는 와중에도 진화적으로 매우 유사한 homolog이기 때문에 또이또이 쌤쌤임을 인정받은 애들... 맞는 비유인지는 모르겠지만, 자매나 형제같은 느낌이다. 그래서 형식 ..

매우 간단한 스크롤 이벤트를 해보자

addEventListener에 대해 설명하면서 스크롤 이벤트도 '있다'고 했는데, 이건 말 그대로 마우스를 스크롤했을 때 일어나는 이벤트이다. 그게 다다. Reference https://velog.io/@dunde/Javascript-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8 Javascript 스크롤 이벤트 web에서 발생하는 스크롤 이벤트를 이용해 다루는 방법을 써보았습니다. velog.io https://velog.io/@dunde/Javascript-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8 Javascript 스크롤 이벤트 web에서 발생하는 스크롤 이벤트를 이용해 다루는 방법을..

DOM 선택하는 방법 (부제: 너로 정했다?)

DOM은 Document Object Model의 약어로, XML이나 HTML에 접근하기 위한 일종의 인터페이스이다. 자바스크립트는 프론트엔드에서 많이 사용하는 언어의 특성인지는 모르겠지만 아무튼 이 DOM들을 지지고 볶는 게 가능하다. 이벤트 추가는 기본이고 생성 삭제 CSS 클래스 부여까지 다 된다. 남자친구 못 만드는 것만 빼면 만능... 어차피 여욱이도 못 구함 질량이 0이라 document.querySelector() 얘는 같이 다니는(?) 친구가 하나 있는데 바로 querySelectorAll()이다. 그럼 같은 역할인가요? 기본적으로는 그렇다. 기본적으로는 그런데 친구인 쿼리셀렉터올은 해당하는 요소를 죄다 가져온다. 예를 들어서 어떤 문서에 p태그가 6개 있다 그러면 쿼리셀렉터는 맨 위에꺼 ..

addEventListener()로 고급진 이벤트 추가하기

근데 생각해보니까 이거 전에 픽하는게 먼저 와야 하지 않나... 뭐 아무튼. 뭔가 기깔나는 것을 구현하기 위해 이 블로그 혹은 다른 블로그를 보다 보면 그런 게 있다. 어? HTML에서 온클릭도 안 줬는데 작동하네? 어? 이거 더블클릭 하니까 작동하네? 어? 스크롤 내리니까 작동하네? 를 알아보자. 굳이 버튼에 온클릭을 안 줘도 작동하는 비결이 바로 addEventListener()이다. getElementByID나 querySelector같은 걸로 DOM(쉽게 말하자면 문서의 어떤 요소)을 가져와서 addEventListener()로 이벤트를 추가할 수 있다. 이 추가라는 게 단순히 알림이나 콘솔 이런걸 떠나서 뭐 모달 소환 곱하기 이런 함수까지 된다. See the Pen Eventlistner by..

다크모드 간단하게 토글로 구현해보기

근데 말이 간단이지 헐 나 이것도 못해 개발자 하면 안되나봐 ㅠㅠ 이럴 정도는 아닙니다… Reference https://blogpack.tistory.com/1117 다크모드 토글 기능 구현과 다크모드 토글 디자인 구현 웹페이지, 또는 사이트에 적용하는 다크 모드는 구현하는 방법이 여러 가지 있습니다. CSS 속성을 기준으로 구분하면 사용된 배경색과 전경(글자) 색들을 바꾸기만 하는 간단한 구현이기 때문에 blogpack.tistory.com https://www.w3schools.com/howto/howto_css_switch.asp How To Create a Toggle Switch W3Schools offers free online tutorials, references and exercises..

나홀로메모장 파생형 만들기-Call of AJAX

원래 페이지네이션이랑 수정/삭제도 구현하려고 했는데 못함... 페이지네이션은 전에 했던 게 오브젝트를 동적으로 생성하는건데 여기서는 DB에 있는 걸 받아와서 진행해야 하는거라 버튼 렌더링/로직에서 막혔고 수정/삭제는 이따 보시면 아시겠지만 저기서 뭐가 뭔지를 구분할 수단이 없습니다. 아, 사람은 구별할 수 있지. 컴퓨터가 못해요. 인간적으로 달력 위에 시계 마렵잖아요 Reference https://stickode.tistory.com/124 [JavaScript] 디지털 시계 만들기 자바스크립트을 활용하여 디지털 시계를 만들어보겠습니다. 먼저, 스틱코드에 올라온 '디지털 시계 만들기' 코드를 즐겨찾기 추가해주세요. 먼저 html 코드를 작성해봅시다. html 파일에서 스틱코 stickode.tistor..

삼항연산자

내 최근에 달력 코드를 올렸는데 거기서 보면 dates.forEach((date, i) => { const condition = i >= firstDate && i < lastDate + 1 ? 'this' : 'other'; dates[i] = `${date}`; }) 이놈이 있다. 어디서 나오냐면 이전달/다음달 날짜 구별할 때 나온다. 근데 이게 뭔 람다식인가 했더니 그건 아니고 삼항연산자래요... 그럼 이게 뭔지 한번 알아보자. 얘는 연산자는 연산자인데 항이 세 개이다. 엥? 그게 왜요? 항이 세개일수도 있잖음! 자 생각해봅시다. 우리 더하기 하나에 숫자 몇개 들어감? 두개 들어가죠? 곱하기는? 곱하기도 두개지? 제곱은? 제곱도 두개다. 나누기는? 피제수 제수 두개다. 루트는? 두개다. 예? 루트가..

나홀로 메모장 파생형 만들기-구성 및 사이드바 달력

Reference https://bigtop.tistory.com/63 [JavaScript] 일반적인 달력 만들기 - HTML 뼈대 잡기 만학도 첫 번째 프로젝트였던 간단한 달력 만들기에 이어서 두 번째는, 하루만 나타내는 달력이 아니라 우리가 일반적으로 생각하는 Full Calendar를 만들어 볼까 합니다. 이번에도 지금까지 정리 bigtop.tistory.com https://opentutorials.org/course/1375/6761 addEventListener() - 생활코딩 addEventListener은 이벤트를 등록하는 가장 권장되는 방식이다. 이 방식을 이용하면 여러개의 이벤트 핸들러를 등록할 수 있다. var t = document.getElementById('target'); t..

마우스를 따라다니는 무언가 만들기

참고로 이 블로그에도 적용해뒀다. 여러분의 마우스 커서를 따라다니는 Thinking Face가 그것. 워드프레스는 HTML이나 JS는 건드릴 수 없어서 적용 못했고 네이버나 미디움은 HTML쪽 편집이 안돼서 적용 못했음. Reference https://stickode.tistory.com/318 [JavaScript] 마우스를 따라다니는 원 만들기 이번시간에는 [JavaScript] 를 사용해 마우스를 따라다니는 원을 만들어보겠습니다. 스틱코드를 즐겨찾기해주세요. https://stickode.com/detail.html?no=2671 스틱코드 stickode.com Html안에 javascript와 css.. stickode.tistory.com https://codepen.io/falldowngob..

페이지네이션

매우 고급져보이는 이 단어의 뜻은 정말 생각지도 못하게 심플하고, 여러분은 이미 이걸 몇 번 봤다. 이런거 다들 한번씩 봤잖음. 블로그건 검색이건... 이게 페이지네이션이다. 저게 코딩관련 글을 11페이지로 옮겼는데(다 옮겼음) 분량이 어마무시하다 그죠...? 아무튼, 페이지네이션은 이런 식으로 한 페이지에 정해진 양의 콘텐츠를 보여준다. 반대되는 개념인 무한 스크롤은 끝이 보일때까지 아래로 아래로 내리는 것. 무한 스크롤은 보통 모바일 앱에서 많이 사용하는 방식인데, 인별이나 미디움도 무한 스크롤이다. 아래로 스크롤하면서 콘텐츠를 계속 볼 수 있어서 굳이 페이지 이동하고 로딩하는 걸 기다리지 않아도 된다. 하지만 콘텐츠가 많아지면 로딩하는 데 시간이 걸리기도 하고(...), 읽다 보면 스크롤바는 줄어드..

원페이지 쇼핑몰 보강하기-익스트림 CRUD

진행 상황: 회원가입(완료)/로그인(로직만 완료) 전에도 얘기했듯, 쇼핑몰에서 ‘모든 고객의 주문 내역을 볼 수 있는’건 물건을 파는 사람이다. 다른 고객들은 자기 주문만 볼 수 있다. 내가 옆집 김씨가 뭘 시켰는지 모르고, 뒷집 박씨는 내가 뭘 시켰는지 모르는것처럼. 그래서 나중에 페이지 하단에 있는 얘는 관리자 계정으로 로그인해야만 볼 수 있게끔 할 예정이다. 콩둘기 메일의 상태가? 회원가입 로그인과 회원가입을 모달창에서 받는다는 얘기는 전에 했는데, 그럼 어떤 정보를 받느냐… 아이디, 비밀번호, 비밀번호 확인, 이름, 전화번호, 이메일, 주소 받는다. 타임스탬프도 있긴 한데, 그건 입력받는 건 아니고 가입년월일이다. function account_make() { let id = $('#userID'..

To-do list 바닐라JS로 만들기

쇼핑몰이요? 그거 로그인은 둘째치고 SSL 설정 개꼬여서 걍 인스턴스 엎고 만들라고… 아니 nginx 깔았는데 uWSGI에서 막혀서 하루종일 그거 찾았다니까. 아무도 안 알려줘 그걸… 깝깝해서 오라클 클라우드도 알아봐야 하나 생각중임 지금.. Reference https://woojong92.tistory.com/entry/JS-%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A1%9C-ToDo-List-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%EA%B8%B0%EB%8A%A5%EC%A0%95%EC%9D%98-%EB%B0%8F-HTMLCSS [JS] 바닐라 자바스크립트로 ToDo-List..

원페이지 쇼핑몰 보강하기-익스트림 어드레스+익스트림 CRUD(준비)

생각해보니 주소는 유효성 검사를 어떻게 해야 하지? 싶어서 물어봤더니 다음API가 잘 되어 있다고 함. 그래서 주소를 그걸로 검색해서 우편번호랑 함께 받고, 상세주소만 별도로 입력받는다. (상세주소까지 입력해야 하는 데도 있고 필수는 아닌데도 봤음) 아무튼 그래서 ㄱㄱ함. 익스트림 어드레스 일단 입력을 받으려면 준비를 해야 한다. 그 준비가 뭐냐면 입력란을 만들고 입력란 아이디를 수정하고 API 가져와서 연결하기 이다. MongoDB는 콜렉션이 없으면 알아서 만들기때문에 콜렉션 청소만 한번 해 주면 되므로 DB는 작업이 다 끝나면 그냥 비우는걸로… 아무튼 이게 그 결과다. 우편변호와 주소는 직접 입력하는 게 아니라 검색하면 가져오는거고, 그 다음에 사용자가 입력하는 부분은 상세주소이다. 그니까 자바스크립..

만들 수 있다! 이미지 슬라이더!

단점: 아직 자동재생이 안됨 기본편 참고 사이트: https://penguingoon.tistory.com/255 자바스크립트 이미지 슬라이드 구현하기 안녕하세요! 오늘은 라이브러리 없이 적은 양의 자바스크립트 코드만으로 간단한 이미지 슬라이더를 구현해보았습니다. 이미지 슬라이더 구현하는 방법이야 한트럭이지만, 그 중에 한 가지 방 penguingoon.tistory.com 하단에 있는 이전/다음버튼을 누르면 그림이 움직인다. HTML Dotted wallpaper 둘러보기 이전 다음 본 이미지 슬라이더에 사용된 이미지는 전부 수제입니다. 도트찍느라 고생했습니다... 사실 얘는 뼈대라 특별히 설명할 건 없다. 응용편의 경우 밑에 뭐가 하나 더 들어간다는 것 빼곤 진짜 특별할 게 없다. CSS @impor..

원페이지 쇼핑몰 보강하기-유효성 검사와 공백처리

내 드디어!!! 서버에 올렸어!!! 여러분 이거 해써여!!! 하고 올렸는데… 피드백 온 것 중에 어? 이거 다 입력 안 해도 주문됐다고 뜨는데요? 유효성 검사 추가해주세요. 그게 뭔데요 감마펑션때 온갖가지 경우의 수 다 따져놓고 뭐한겨… (사실 몰랐음) 그래서 보강함. 아 그 HTTP HTTPS로 바꾸는거는… 내가 해봤어요 해봤는데… 그거 따라했는데 접속 안돼서 인스턴스 버렸어… 스크립트파일 분리 이게 과제 할 때는 스크립트고 CSS고 다 HTML파일에 박혀있었는데… 이게 코드 규모가 작으면 그래도 되요 그래도 되는데… 코드 규모가 커지면 인제 골치아퍼… 수정하다 욕해요… 그래서 분리할거다. 파이참에서 Flask project를 만들면 이렇게 된다. 이게 Flask 프로젝트의 국룰인데 HTML파일은tem..

for, for in, for of, forEach

Coding/JavaScript 2022. 10. 11. 02:46

Python에는 for와 while이라는 반복문이 있고, 이 둘은 범위냐 조건이냐의 차이만 빼면 반복문이라는 기본 골자는 같은데, 이런 게 자바스크립트에도 당연히 있다. 라디오버튼 뺑뺑이 돌면서 얘가 뭘 체크했나 보는 것도 반복문의 일이기 때문.

그런데 이 반복문... Python처럼 심플하지 않아요...


for, for in, for of

See the Pen for, for in, for of by koreanraichu (@koreanraichu) on CodePen.

얘네들은 Pyhlogenic tree로 치자면 그 트리가 막 갈라지는 와중에도 진화적으로 매우 유사한 homolog이기 때문에 또이또이 쌤쌤임을 인정받은 애들... 맞는 비유인지는 모르겠지만, 자매나 형제같은 느낌이다. 그래서 형식 말고 크게 다를 건 없다.

 

let pokemon = ['김부추씨','김상추씨','김배추씨','김양상추씨','김양배추씨','김후추씨']

for (let i = 0;i < pokemon.length;i++) {
  let p_tag = document.createElement('p')
  p_tag.innerText = pokemon[i]
  pokemon_div.appendChild(p_tag)
}
//for

for

 

let num_list = [806, 26, 607, 601, 752, 504, 485, 162, 885, 361, 309, 480, 401, 313, 771, 141, 9, 526, 390, 237]

for (let i in num_list) {
  let p_tag = document.createElement('p')
  if (num_list[i] % 4 == 0) {
    p_tag.innerText = num_list[i]
    for_in_div.appendChild(p_tag)
  }
}
// for in

for in

 

for (let i of num_list) {
  let p_tag = document.createElement('p')
  if (i % 3 == 0) {
    p_tag.innerText = i
    for_of_div.appendChild(p_tag)
  }
}
//for of

for of

 

각 소스에서 이 반복문이 하는 일은

1) 포켓몬 리스트의 요소를 내용으로 하는 p태그를 생성하라
2) 위에 있는 숫자 리스트에서 4의 배수를 p태그로 생성하라
3) 위에 있는 숫자 리스트에서 3의 배수를 p태그로 생성하라

이렇게 세 개다. 그리고 세 반복문은 주어진 일을 하기 위해 각각 주어진 배열에 접근해 해당 요소를 가져와 p태그로 만들어서 내보내게 된다. for of는 파이썬으로 치자면 for i in (리스트)로 직접 접근하는 것으로, 저 경우 i에 들어가는 게 배열이 담고 있는 요소가 된다. DOM이면 DOM, 숫자면 숫자, 문자면 문자 이런 식. 반면 위의 두 반복문은 i 자체는 숫자라 인덱싱 절차가 따로 필요하다. 즉 for in이 for i in (리스트)면 나머지 둘은 for i in range(n)으로 인덱스 번호 만들어서 인덱싱 하는 것.

 

forEach

얘는 위에 셋과는 성격이 조금 다르다. Phylogenic tree로 치자면 쭉 내려가다가 마지막에 가지가 한번 갈라지고 그 다음에 for, for in, for of가 갈라지는 정도. 왜냐하면 얘는 배열에서 뭘 가져온다는 개념이 아니라 배열을 돌면서 주어진 함수를 실행하는 반복문이기 때문이다. 물론 for로 구현할 수 있는 건 forEach로도 구현할 수 있다.

 

See the Pen For and Foreach by koreanraichu (@koreanraichu) on CodePen.

여기서 for와 forEach의 차이점을 볼 수 있다.

 

let p_for = document.querySelectorAll('.for > p')
let p_forEach = document.querySelectorAll('.foreach > p')
// querySlectorAll은 배열로 가져온다

let number_list = ['일','이','삼']

for (let i = 0;i < p_for.length;i++) {
  p_for[i].innerText = number_list[i]
}
//for

p_forEach.forEach(function(element, index){
  element.innerText = number_list[index]
})
//forEach

아니 저거 저렇게 선택되는 줄 몰랐음... 아무튼... 위 코드가 하는 일은 div 안에 있는 p태그의 내용을 number_list에 있는 일, 이, 삼으로 바꾸는 것이다. (원문은 외국어) 둘 다 querySelectorAll()로 가져오기 때문에 가져왔을 때 상태는 배열이고, 배열 안에 p태그 세 개가 담겨 있기 때문에 for나 forEach를 사용해서 안에 있는 p태그의 내용을 바꿔줘야 한다.

 

for문은 말 그대로 위의 배열에서 i번째 요소를 num_list의 i번째 요소로 바꾸는 대단히 심플한 코드인데... forEach에 저거 뭡니까... 아... 저거는 요소와 인덱스로 함수 돌린다는 얘기인데, querySelectAll()로 가져온 배열을 순회하면서 배열이 담고 있는 요소(p태그)를 num_list[인덱스]의 요소로 채운다는 얘기다. 저 인덱스는 querySelectorAll()로 가져온 배열의 인덱스를 말한다. 아니 실화임.

 

See the Pen For and Foreach 2 by koreanraichu (@koreanraichu) on CodePen.

여기서는 for of와 forEach를 비교해보자.

 

let sqrt_n = document.querySelector(".sqrt")
let square_n = document.querySelector(".square")
let num_list = [1,2,3,4,5,6,7,8,9,10]
//sqrt: 제곱근(루트)
//square: 2제곱(3제곱은 cube)

for (let i of num_list) {
  let p_tag = document.createElement('p')
  p_tag.innerText = `sqrt ${i}: ${(i ** 0.5).toFixed(3)}`
  sqrt_n.appendChild(p_tag)
}
// 리스트에 있는 수의 2제곱근을 출력(소수점 이하 3자리에서 반올림)

num_list.forEach(function(element){
  let p_tag = document.createElement('p')
  p_tag.innerText = element ** 2
  square_n.appendChild(p_tag)
})
//리스트에 있는 수의 제곱을 출력

위 코드는 배열 안에 있는 수의 제곱근을 소수점 세자리까지 출력하는 코드이고, 아래 코드는 배열 안에 있는 수를 제곱해서 출력하는 코드이다. 아, 왜 **냐면 자바스크립트와 파이썬에서 ^는 XOR을 의미한다. 그래서 파이썬이나 자바스크립트로 2 ^ 2 하면 4가 아니라 0이 나온다. (XOR은 입력 두 개가 달라야 참임)

 

두 코드가 크게 다를 건 없다. 위 코드는 num_list에 있는 요소(숫자)를 가져와 0.5제곱(2루트)을 하고 innerText로 만든 다음 그걸 추가하는거고, 아래는 num_list를 순회하면서 안에 있는 요소(숫자)의 2승을 구하고 그걸 innerText로 만들어서 추가한다.

 

See the Pen For and Foreach 3 by koreanraichu (@koreanraichu) on CodePen.

for in과 forEach를 비교해보자.

 

let num_list = [1,2,3,4,5,6,7,8,9,0,11,12,13,14,15,16,17,18,19,20]
let num_list2 = [21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40]
//아니 자바스크립트는 list(range()) 없나?
let odd_number = document.querySelector('.odd')
let even_number = document.querySelector('.even')

for (let i in num_list) {
  let p_tag = document.createElement('p')
  if (num_list[i] % 2 == 0) {
    p_tag.innerText = `${num_list[i]} is even number`
    even_number.appendChild(p_tag)
  }
  else {
    p_tag.innerText = `${num_list[i]} is odd number`
    odd_number.appendChild(p_tag)
  }
}

num_list2.forEach(function(element){
  let p_tag = document.createElement('p')
  if (element % 2 == 0) {
    p_tag.innerText = `${element} is even number`
    even_number.appendChild(p_tag)
  }
  else {
    p_tag.innerText = `${element} is odd number`
    odd_number.appendChild(p_tag)
  }
})

위 코드는 1~20까지의 배열에 있는 요소(숫자)가 짝수냐 홀수냐에 따라 다른 div에 추가한다. 그리고 아래 코드도 비슷한 일을 하는데, forEach는 21~40까지 있는 배열을 순회하면서 요소(숫자)에 조건문을 적용하고 각각의 div에 추가하게 된다.

 

See the Pen Untitled by koreanraichu (@koreanraichu) on CodePen.

const button = document.getElementById('button')
const print = document.querySelector('.print')
const choose = document.querySelectorAll('.pokemon')

button.addEventListener('click',(e)=>{
  let question = ''
  choose.forEach(function(element){
    if (element.checked) {
      if (element.value == 'grass') {
        question = '풀타입 포켓몬인 이상해씨로 하겠느냐?'
      }
      else if (element.value == 'water') {
        question = '물타입 포켓몬인 꼬부기로 하겠느냐?'
      }
      else if (element.value == 'fire') {
        question = '불타입 포켓몬인 파이리로 하겠느냐?'
      }
      else {
        question = '전기타입 포켓몬인 피카츄로 하겠느냐?'
      }
      print.innerText = question
    }
  })
})

for나 forEach를 사용하면 이런 식으로 동일한 클래스의 라디오버튼을 배열로 받아오고 순회하면서, 선택이 되었는지를 확인하고 선택된 라디오버튼의 값에 따라 다른 이벤트를 줄 수 있다.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

매우 간단한 스크롤 이벤트를 해보자

Coding/JavaScript 2022. 9. 21. 16:27

addEventListener에 대해 설명하면서 스크롤 이벤트도 '있다'고 했는데, 이건 말 그대로 마우스를 스크롤했을 때 일어나는 이벤트이다. 그게 다다.

 

Reference

https://velog.io/@dunde/Javascript-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8

 

Javascript 스크롤 이벤트

web에서 발생하는 스크롤 이벤트를 이용해 다루는 방법을 써보았습니다.

velog.io

https://velog.io/@dunde/Javascript-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8

 

Javascript 스크롤 이벤트

web에서 발생하는 스크롤 이벤트를 이용해 다루는 방법을 써보았습니다.

velog.io

https://stackoverflow.com/questions/40475155/does-javascript-have-a-method-that-returns-an-array-of-numbers-based-on-start-s

 

Does JavaScript have a method that returns an array of numbers based on start, stop and desired number of return values?

I'm looking for something similar to numpy.linspace to generate an array of numbers based on a starting value, an ending value and the desired number of values in the array. Example: start=2.0, end...

stackoverflow.com


Basic-마우스 스크롤 위치를 화면에 표시하기

See the Pen scroll events-Basics by koreanraichu (@koreanraichu) on CodePen.

 

 

여기서는 

let contents = document.querySelector('#content1');

window.addEventListener('scroll', () => { 
  console.log(window.scrollX, window.scrollY);
  let new_pTag = document.createElement('p');
  new_pTag.innerHTML = 'scroll are here: ' + window.scrollY;
  contents.appendChild(new_pTag)
});

를 썼는데 이거는 말 그대로 스크롤을 내렸을 때 X, Y축 위치를 나타낸다. 근데 얘는 스크롤바가 없는 상태에서는 안되더라...

 

스크롤을 올리고 내릴때마다 색깔 바꾸기

이건 addEventListener가 아니라 windows.onwheel을 사용해서 구현했다. addEventListener('wheel',())와 비슷하다. 일단 블로그를 참고해서 만든 코드부터 보고 가자.

 

See the Pen Scroll Events-Animation by koreanraichu (@koreanraichu) on CodePen.

HTML이나 CSS는 사실 뭐 특별한 건 없고, 자바스크립트 코드를 보자.

 

//Reference: https://velog.io/@minkyeong-ko/Javascript-%EC%8A%A4%ED%81%AC%EB%A1%A4%EC%97%90-%EB%94%B0%EB%A5%B8-%EB%B0%B0%EA%B2%BD%EC%83%89-%EB%B3%80%EA%B2%BD

var colors = ['#000000', '#333333', '#666666', '#999999', '#CCCCCC', '#FFFFFF'];
var wrapper = document.querySelector('.box');
var title = document.getElementsByTagName('h1')[0];
var text = document.querySelector('#text');

wrapper.onwheel = changeColor;

var colorIndex = 0;
var scrollValue = 0;

function changeColor(e) {
    scrollValue += e.deltaY * 0.01;
    if (scrollValue > 10) {
        colorIndex += 1;
        if (colorIndex > colors.length - 1) {
            colorIndex = 0;
        }
        text.innerText = colors[colorIndex];
        wrapper.style.backgroundColor = colors[colorIndex];
        if (colorIndex >= 3) {
            title.style.color = '#000000'
            text.style.color = '#000000';
        }
        scrollValue = 0;
    }

    if (scrollValue < -10) {
        colorIndex -= 1;
        if (colorIndex < 0) {
            colorIndex = colors.length - 1;
        }
        text.innerText = colors[colorIndex];
        wrapper.style.backgroundColor = colors[colorIndex];
        if (colorIndex >= 3) {
            title.style.color = '#000000';
            text.style.color = '#000000';
        }
        scrollValue = 0;
    }
}

델타는 말 그대로 Δ이다. 그... 자 그래서 저 세모 어디서 봤어요? 슈뢰딩거 방정식? 그건 ∇(del operator)임... 저거 미분에 나오잖아여... 변화량. 근데 변화량이랑 스크롤이랑 뭔 상관이냐.... 휠 이벤트는 위에 스크롤과 달리 휠을 올렸는가/내렸는가를 확인해 거기에 맞는 이벤트를 준다. 크게 if문이 두 개인데 위쪽은 스크롤을 올렸을 때, 아래쪽은 스크롤을 내렸을 때. scrollValue라는 변수를 통해 delta, 즉 스크롤의 변화를 누적시킨 다음 어느정도 변화가 생겼다 그러면 다음 색으로 넘긴다.

 

그리고 원래 코드에는 없던 기능이 하나 있는데, 검정->하양으로 가다 보면 글자 색이 하양일 때 안 보이는 배경색이 당연히 존재한다. 이건 뭐 착한 사람도 안 보이는 배경이여... 그래서 colorIndex 배열에서 #999999부터는 글자색도 흰색으로 바꿨다.

 

See the Pen Scroll Events-Animation(Gradation) by koreanraichu (@koreanraichu) on CodePen.

얘는 위의 코드를 살짝 응용했다.

 

function makeArr(startValue, stopValue, cardinality) {
    var arr = [];
    var step = (stopValue - startValue) / (cardinality - 1);
    for (var i = 0; i < cardinality; i++) {
        arr.push(startValue + (step * i));
    }
    return arr;
}

var colors = [];
var red = makeArr(65, 53, 8);
var green = makeArr(184, 73, 8);
var blue = makeArr(131, 94, 8);

for (let i = 0; i < red.length; i++) {
    var red_grad = (Math.floor(red[i]).toString(16).padStart(2, '0'))
    var green_grad = (Math.floor(green[i]).toString(16).padStart(2, '0'))
    var blue_grad = (Math.floor(blue[i]).toString(16).padStart(2, '0'))
    var color_code = '#' + red_grad + green_grad + blue_grad
    colors.push(color_code)
}

배열 관련해서 추가 및 수정된 부분이 이거. 기본 코드는 배열 안에 풀로 색깔 코드가 들어갔는데, 여기서는 좀 자연스러운 변화를 줘야 하는데 인제 그걸 시작과 끝을 이용해서 알아서 만드는? 뭐 그런거다. 예를 들어서 빨강이 255이고 0까지 5개로 만들어달라고 하면 알아서 딱딱 나누는 대충 넘파이의 linspace같은 기능...인데 이제 그게 자바스크립트에 없어요... 그럼 어쩌겠음. 함수 때려박아야지...

 

그래서 RGB에 대해 일정한 간격을 가지는 배열을 만든 다음 그걸 합해서 색깔 코드를 만든 게 저거다.

 

참고로 배경 고정값이 Vue.js 로고 컬러다. 그거 이쁘잖음.

 

See the Pen Scroll Events-Animation(Gradation 2) by koreanraichu (@koreanraichu) on CodePen.

또 응용편이 나왔는데 이거는 처음 색과 끝 색을 지정해서 매우 자연스러운 색 변화를 유도할 수 있다. 좋아 자연스러웠어 그래서 함수가 또 복잡해졌다. 왜냐하면 입력한 색에서 RGB를 또 가져와야 하거든... 

 

const button = document.getElementById('confirm');
button.addEventListener('click', () => {
    makeColor()
})

function makeColor() {
    var startcolor = document.getElementById('startcolor').value;
    var endcolor = document.getElementById('endcolor').value;

    var start_red = startcolor.substr(0, 2);
    start_red = parseInt(start_red, 16);
    var end_red = endcolor.substr(0, 2);
    end_red = parseInt(end_red, 16);

    var start_green = startcolor.substr(2, 2);
    start_green = parseInt(start_green, 16);
    var end_green = endcolor.substr(2, 2);
    end_green = parseInt(end_green, 16);

    var start_blue = startcolor.substr(4, 2);
    start_blue = parseInt(start_blue, 16);
    var end_blue = endcolor.substr(4, 2);
    end_blue = parseInt(end_blue, 16);

    var red = makeArr(start_red, end_red, 10);
    var green = makeArr(start_green, end_green, 10);
    var blue = makeArr(start_blue, end_blue, 10);

    for (let i = 0; i < red.length; i++) {
        var red_grad = (Math.floor(red[i]).toString(16).padStart(2, '0'))
        var green_grad = (Math.floor(green[i]).toString(16).padStart(2, '0'))
        var blue_grad = (Math.floor(blue[i]).toString(16).padStart(2, '0'))
        var color_code = '#' + red_grad + green_grad + blue_grad
        colors.push(color_code)
    }

    text.innerText = colors[colorIndex];
    wrapper.style.backgroundColor = colors[colorIndex];

    return colors
}

버튼은 뭐... 다들 아실테니 패스하고, 색깔 부분을 살펴보자. 시작색과 끝색의 RGB로 자연스러운 그라데이션을 만들기 위해서는 RGB별로 10진수가 필요하고, 그걸로 등간격인 10개의 요소를 만들게 된다. 그리고 적용하는 건 똑같다. 근데 이제 여기까지는 글자색이 안바꼈었는데...

 

function makeColor() {
    var startcolor = document.getElementById('startcolor').value;
    var endcolor = document.getElementById('endcolor').value;

    var start_red = startcolor.substr(0, 2);
    start_red = parseInt(start_red, 16);
    var invert_start_red = 255 - start_red;
    var end_red = endcolor.substr(0, 2);
    end_red = parseInt(end_red, 16);
    var invert_end_red = 255 - end_red;

    var start_green = startcolor.substr(2, 2);
    start_green = parseInt(start_green, 16);
    var invert_start_green = 255 - start_green;
    var end_green = endcolor.substr(2, 2);
    end_green = parseInt(end_green, 16);
    var invert_end_green = 255 - end_green;

    var start_blue = startcolor.substr(4, 2);
    start_blue = parseInt(start_blue, 16);
    var invert_start_blue = 255 - start_blue;
    var end_blue = endcolor.substr(4, 2);
    end_blue = parseInt(end_blue, 16);
    var invert_end_blue = 255 - end_blue;

    var red = makeArr(start_red, end_red, 10);
    var invert_red = makeArr(invert_start_red, invert_end_red, 10);
    var green = makeArr(start_green, end_green, 10);
    var invert_green = makeArr(invert_start_green, invert_end_green, 10);
    var blue = makeArr(start_blue, end_blue, 10);
    var invert_blue = makeArr(invert_start_blue, invert_end_blue, 10);

    for (let i = 0; i < red.length; i++) {
        var red_grad = (Math.floor(red[i]).toString(16).padStart(2, '0'));
        var invert_red_grad = (Math.floor(invert_red[i]).toString(16).padStart(2, '0'));
        var green_grad = (Math.floor(green[i]).toString(16).padStart(2, '0'));
        var invert_green_grad = (Math.floor(invert_green[i]).toString(16).padStart(2, '0'));
        var blue_grad = (Math.floor(blue[i]).toString(16).padStart(2, '0'));
        var invert_blue_grad = (Math.floor(invert_blue[i]).toString(16).padStart(2, '0'));
        var color_code = '#' + red_grad + green_grad + blue_grad;
        var invert_color_code = '#' + invert_red_grad + invert_green_grad + invert_blue_grad;
        colors.push(color_code)
        invert_colors.push(invert_color_code)
    }

    text.innerText = colors[colorIndex];
    text.style.color = invert_colors[colorIndex];
    title.style.color = invert_colors[colorIndex];
    button.style.color = colors[colorIndex];
    button.style.backgroundColor = invert_colors[colorIndex];
    wrapper.style.backgroundColor = colors[colorIndex];
    for (let i = 0; i < label.length; i++) {
        label[i].style.color = invert_colors[colorIndex]
    }
    for (let i = 0; i < input.length; i++) {
        input[i].style.border = '2px solid' + invert_colors[colorIndex];
    }

    return colors, invert_colors
}

놀랍게도 글자색도 바뀌게 할 수 있었다. 이것은 매우 놀랍게도 진짜다. 입력값을 RGB로 나눠서 배열로 만들 때 invert_colors라는 배열을 추가로 만들게 되는데, 여기에 들어가는 요소는 시작색의 RGB, 끝색의 RGB의 255의 보수로 만든 배열이 들어간다. 이게 뭐야 우리가 2의 보수 1의 보수가 유명해서 글치 이진법에서만 보수 쓰라는 법은 없음. 아무튼 그래서.. 

이렇게 됐다. 근데 글자색 알고싶은데 어떻게 하냐고? 

text.innerText = 'background-color: ' + colors[colorIndex] + '/' + 'font-color: ' + invert_colors[colorIndex];

이걸로 다 바꿨지... 

 

참 쉽죠? 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

DOM 선택하는 방법 (부제: 너로 정했다?)

Coding/JavaScript 2022. 9. 19. 16:05

DOM은 Document Object Model의 약어로, XML이나 HTML에 접근하기 위한 일종의 인터페이스이다. 자바스크립트는 프론트엔드에서 많이 사용하는 언어의 특성인지는 모르겠지만 아무튼 이 DOM들을 지지고 볶는 게 가능하다. 이벤트 추가는 기본이고 생성 삭제 CSS 클래스 부여까지 다 된다. 남자친구 못 만드는 것만 빼면 만능... 어차피 여욱이도 못 구함 질량이 0이라 


document.querySelector()

얘는 같이 다니는(?) 친구가 하나 있는데 바로 querySelectorAll()이다. 그럼 같은 역할인가요? 기본적으로는 그렇다. 기본적으로는 그런데 친구인 쿼리셀렉터올은 해당하는 요소를 죄다 가져온다. 예를 들어서 어떤 문서에 p태그가 6개 있다 그러면 쿼리셀렉터는 맨 위에꺼 하나만 가져오는데 쿼리셀렉터올은 p태그를 다 가져온다.

 

1) HTML 요소를 가져올 때 -> document.querySelector('p')
2) 클래스 이름으로 가져올 때 -> document.querySelector('.classname')
3) 아이디로 가져올 때 -> document.querySelector('#id')

왜 이렇게 쓰냐면 쟤는 종류불문 저거랑 맞는 걸 가져오기 때문. 클래스건 아이디건 종류 안 가리고 지명이 가능하다. 

 

See the Pen document.querySelector by koreanraichu (@koreanraichu) on CodePen.

(document.querySelector)

 

See the Pen document.querySelectorAll by koreanraichu (@koreanraichu) on CodePen.

(document.querySelectorAll)

 

아, 한가지 중요한 게 있다. querySelectorAll()로 요소를 가져올 때는 배열로 가져온다. 위 예시의 경우 div 세 개를 배열로 가져왔는데, 이 경우 내용이나 CSS를 바꾸기 위해 위의 querySelector에서 했던 것처럼 하게 되면 자바스크립트가 미쳤습니까 휴먼? 하게 된다. 그래서 querySelectorAll로 가져온 것은 for 혹은 foreach 등의 반복문을 돌려서 적용해야 한다.

document.getElement~ 

위에 있는 쿼리 시리즈는 조건이 일치하면 아이디건 클래스건 지명이 되지만, 얘네들은 종류에 따라 쓰는 게 다르다.


1) 클래스명으로 부를때는 document.getElementsByClassName('class')
2) 아이디로 부를때는 document.getElementById('id')
3) 태그 네임으로 부를때는 document.getElementsByTagName('p)

 

쿼리시리즈랑 달리 이쪽은 아이디로 가져올래, 클래스명으로 가져올래, 태그명으로 가져올래가 정해져 있어서 뒤에 가져올 게 뭔지만 쓰면 된다. 즉, 쿼리셀렉터와 달리 클래스나 아이디명으로 넣지 않는 이상 .이나 #이 들어갈 필요가 없다.

 

See the Pen document.getElement~ by koreanraichu (@koreanraichu) on CodePen.

한가지 중요한 게 있다. 아이디는 HTML 내에서 동일할 수가 없는 대신 요소나 클래스명은 동일할 수가 있다. HTML에 p태그 여러개 넣잖아요? 그래서 클래스 이름이나 태그명으로 가져오는 게 Elements인것. 그리고 얘네들은 '배열로' 가져오기 때문에 쿼리셀렉터올마냥 반복문을 써야 한다.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

addEventListener()로 고급진 이벤트 추가하기

Coding/JavaScript 2022. 9. 19. 14:39

근데 생각해보니까 이거 전에 픽하는게 먼저 와야 하지 않나... 뭐 아무튼.


뭔가 기깔나는 것을 구현하기 위해 이 블로그 혹은 다른 블로그를 보다 보면 그런 게 있다. 어? HTML에서 온클릭도 안 줬는데 작동하네? 어? 이거 더블클릭 하니까 작동하네? 어? 스크롤 내리니까 작동하네?

를 알아보자.


굳이 버튼에 온클릭을 안 줘도 작동하는 비결이 바로 addEventListener()이다. getElementByID나 querySelector같은 걸로 DOM(쉽게 말하자면 문서의 어떤 요소)을 가져와서 addEventListener()로 이벤트를 추가할 수 있다. 이 추가라는 게 단순히 알림이나 콘솔 이런걸 떠나서 뭐 모달 소환 곱하기 이런 함수까지 된다. 

 

See the Pen Eventlistner by koreanraichu (@koreanraichu) on CodePen.

참 쉽죠? 저 코드는 간단하다. h1을 가져온다->h1을 클릭하면 알림이 뜨게 만든다. 끝. 오케이? 그럼 클릭만 되나요? ㄴㄴ 그건 아니다.

 

See the Pen Untitled by koreanraichu (@koreanraichu) on CodePen.

더블클릭도 된다. 그럼 클릭이나 더블클릭 말고 키를 눌러서 입력하는 건 어떻게 할까? addEventListener()에 keydown을 주면 된다. 예전에는 키프레스를 썼었는데 요즘은 키다운/키업이라고... (키다운: 눌렀을 때/키업: 뗐을 때)

 

See the Pen Untitled by koreanraichu (@koreanraichu) on CodePen.

clear버튼에도 addEventListener를 통해 입력된 것을 전부 지우는 기능을 추가했다. 아무 키나 눌렀을 때 표시되는 것은 e.key와 e.code, 그리고 e.keyCode인데

 

e.key a & e.code KeyA & e.Keycode 65

 

이렇게 뜬다. 솔직히 무슨 차이인지는 모르겠다.

 

그럼... 스크롤도 되나요...? 네, 됩니다. 그런데 그건 설명할 게 많아서 이 글에서는 다루지 않을 예정이다. 나중에 스크롤 이벤트 하면서 다뤄보겠듬...

 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

다크모드 간단하게 토글로 구현해보기

Coding/JavaScript 2022. 9. 13. 14:21

근데 말이 간단이지 헐 나 이것도 못해 개발자 하면 안되나봐 ㅠㅠ 이럴 정도는 아닙니다…

 

Reference

https://blogpack.tistory.com/1117

 

다크모드 토글 기능 구현과 다크모드 토글 디자인 구현

웹페이지, 또는 사이트에 적용하는 다크 모드는 구현하는 방법이 여러 가지 있습니다. CSS 속성을 기준으로 구분하면 사용된 배경색과 전경(글자) 색들을 바꾸기만 하는 간단한 구현이기 때문에

blogpack.tistory.com

https://www.w3schools.com/howto/howto_css_switch.asp

 

How To Create a Toggle Switch

W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.

www.w3schools.com

https://codechacha.com/ko/javascript-foreach/

 

JavaScript - forEach(), 다양한 예제로 이해하기

forEach()는 배열을 순회하면서 인자로 전달한 함수를 호출하는 반복문입니다. 배열 뿐만 아니라, Set이나 Map에서도 사용 가능합니다. forEach()의 문법은 아래와 같으며, 함수로 value, index, array를 전

codechacha.com


들어가기 전에: 딥-다크-모드

VScode
Wikipedia의 다크모드

요즘은 많이 쓰이고 있는 다크모드는 배경이 어두운 색인 모드로, 다크 테마라고도 한다. OS나 브라우저, 각종 앱(게임 말고)에서도 지원하는 기능으로, 개발자들 IDE는 다크테마가 국룰. 밝으면 벌레가 꼬인다나... 본인은 PC에서는 디스코드만 다크모드고 폰/패드는 일출/일몰 시간에 따라 다르다. 그래서 내 폰은 해 떨어지면 다크모드가 된다.

 

그런데 왜 이놈을 쓰는거죠? 일단 나도 가끔 개발하시는 분들을 보는데 다 IDE가 어두운 색이라 한번 물어본 적 있었다.

https://okky.kr/articles/1088381

 

OKKY - IDE 배경 다들 검정색으로 쓰세요?

요즘 스터디카페에서 공부를 하는데, 여기가 노트북 존이 아예 분리되어서 그런지 개발자분들이 가끔 오십니다. 모니터 부럽... 와... 난 그거까지 들고갔다간 진짜 팔 떨어지는데... (노트북 말

okky.kr

다크모드가 익숙해서, 기본 설정이라서 쓰시는 분들도 계셨지만 눈이 덜 피곤해서 쓰신다고...

 

낮에는 햇빛이 있으니까 상관 없지만 밤에 캄캄한데 계속 밝은 화면을 보면

이렇게 된다. 그리고 밝은 화면을 계속 보여주게 되면 배터리도 훅훅 떨어진다. 자고로 빛의 혼합은 가산 혼합이라서 흰색 배경을 띄울 때 RGB를 풀로 쏴제껴야 흰색이 되거든... 

 

물론 CSS에서 색깔 잘못 잡으면 라이트모드에서는 잘 보이던 UI가 다크모드로 바꿨더니 안 보이는 경우도 있다.


초간단 다크모드 토글하기

그래서 이번에는 뭘 만들거냐... 

이거요. 이미지를 잘 보면 글이랑 슬라이드 버튼이 있고, 글상자 두개 밑에 보이는 버튼도 다크모드를 토글할 때 색깔이 바뀐다.

 

<html>

<head>
    <title>딥-다크-모드</title>
    <meta charset="utf-8">
    <link href="style.css" rel="stylesheet">
    <script src="https://kit.fontawesome.com/dc58858c96.js" crossorigin="anonymous"></script>
</head>

<body>
    <div class="wrap">

        <div class="title">
            <h1>Deep-Dark-mode</h1>
            <p>아래 슬라이드 버튼을 눌러 라이트모드와 다크모드를 테스트해보세요! </p>
            <label class="switch" id="toggle">
                <input type="checkbox">
                <span class="slider round"></span>
            </label>
        </div>

        <div class="article">
            <h1>Title: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
                dolore magna aliqua.</h1>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
                dolore magna aliqua. Lacinia at quis risus sed vulputate. Et tortor consequat id porta nibh venenatis.
                Tellus integer feugiat scelerisque varius morbi enim nunc faucibus a. At lectus urna duis convallis.
                Lorem ipsum dolor sit amet consectetur adipiscing elit. Nec feugiat nisl pretium fusce id velit ut
                tortor. Nulla facilisi cras fermentum odio eu feugiat pretium. Consequat semper viverra nam libero justo
                laoreet sit amet cursus. Orci sagittis eu volutpat odio facilisis mauris sit amet. Pellentesque sit amet
                porttitor eget dolor morbi non arcu. Et netus et malesuada fames ac turpis egestas. Pellentesque
                habitant morbi tristique senectus et netus et malesuada fames. Augue interdum velit euismod in. Urna
                cursus eget nunc scelerisque viverra mauris in aliquam sem. Ac tortor dignissim convallis aenean et
                tortor at risus. Pulvinar elementum integer enim neque volutpat ac tincidunt.</p>
            <h1>Title: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
                dolore magna aliqua.</h1>
            <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
                dolore magna aliqua. Commodo nulla facilisi nullam vehicula ipsum. Nullam ac tortor vitae purus faucibus
                ornare suspendisse. Tortor at auctor urna nunc id cursus metus. Diam vulputate ut pharetra sit amet
                aliquam id. Tincidunt lobortis feugiat vivamus at augue eget arcu dictum varius. In est ante in nibh
                mauris cursus mattis molestie a. Scelerisque purus semper eget duis at tellus. Pharetra massa massa
                ultricies mi quis hendrerit dolor. Neque ornare aenean euismod elementum nisi.</p>
        </div>
        <button class="button" type="button">Button 1</button>
        <button class="button" type="button">Button 2</button>
        <button class="button" type="button">Button 3</button>
    </div>
    <script src="script.js"></script>
</body>

</html>

HTML(슬라이드 버튼은 W3schools에 있는 소스 갖다 배경색만 바꿨음)

 

@font-face {
    font-family: 'KyoboHand';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@1.0/KyoboHand.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

:root {
    --dark-color: #32475e;
    --green-color: #41b883;
    --white-color: #f9f9f9;
    --yellow-color: #ffd41d;
}

* {
    font-family: 'KyoboHand';
    font-size: 16pt;
    margin: 0;
    padding: 0;
    line-height: 1.5em;
}

body {
    background-color: var(--white-color);
    color: var(--dark-color);
}

.dark-mode {
    background-color: var(--dark-color);
    color: var(--white-color);
}

h1,
p {
    margin-bottom: 5px;
}

h1 {
    font-size: 1.5em;
}

.wrap {
    width: 1280px;
    margin: 0 auto;
}

.title,
.article {
    border: 5px solid var(--green-color);
    border-radius: 7.5px;
    margin: 20px auto;
    padding: 10px;
}

/* The switch - the box around the slider */
.switch {
    position: relative;
    display: inline-block;
    width: 60px;
    height: 34px;
    margin-top: 10px;
}

/* Hide default HTML checkbox */
.switch input {
    opacity: 0;
    width: 0;
    height: 0;
}

/* The slider */
.slider {
    position: absolute;
    cursor: pointer;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: var(--dark-color);
    -webkit-transition: .4s;
    transition: .4s;
}

.slider:before {
    position: absolute;
    content: "";
    height: 26px;
    width: 26px;
    left: 4px;
    bottom: 4px;
    background-color: var(--white-color);
    -webkit-transition: .4s;
    transition: .4s;
}

input:checked+.slider {
    background-color: var(--green-color);
}

input:focus+.slider {
    box-shadow: 0 0 1px var(--green-color);
}

input:checked+.slider:before {
    -webkit-transform: translateX(26px);
    -ms-transform: translateX(26px);
    transform: translateX(26px);
}

/* Rounded sliders */
.slider.round {
    border-radius: 34px;
}

.slider.round:before {
    border-radius: 50%;
}

button {
    border: 0;
    width: 150px;
    height: 50px;
    border-radius: 7.5px;
    background-color: var(--yellow-color);
    margin-right: 10px;
}

button:hover {
    background-color: var(--yellow-color);
}

CSS

 

let button = document.getElementById('toggle');

button.addEventListener("click", function() {
    if(document.querySelector('body').classList.contains('dark-mode')){
        document.body.classList.remove("dark-mode");
    }else{
        document.body.classList.add("dark-mode");
    }
},false);

JS

 

슬라이드 버튼은 버튼이 쇽쇽 밀려나가는 것 말고 사실상 체크박스나 라디오버튼이랑 다를 게 없다. 자바스크립트 코드는 버튼을 눌렀을 때 클래스에 dark-mode가 있으면 지우고(라이트모드로) 아니면 붙여라(다크모드). 근데 위 코드대로 하면 어떻게 되느냐... 글이랑 배경은 바뀌는데 버튼 색이 그대로예요. 그러면 버튼에도 이걸 줘야 하는데...

 

button.dark-mode {
    color: var(--white-color);
    background-color: var(--green-color);
}

버튼에도 dark-mode를 추가하고... 위 코드대로 주면 에러난다. 버튼이 세 개니까 다 바꾸려면 querySelector가 아니라 querySelectorAll로 가져와야 하는데 문제가 뭐냐면 저걸로 갖고오면 배열이 된다. 그래서 자바스크립트 입장에서는 아니 배열인데 클래스가 어딨음? 착한 사람만 보임? 이렇게 되기 때문에 반복문으로 배열을 순회해서 볼거다.

 

let togglebutton = document.getElementById('toggle');
let button = document.querySelectorAll('button')

togglebutton.addEventListener("click", function () {
    console.log('버튼!')
    if (document.querySelector('body').classList.contains('dark-mode')) {
        document.body.classList.remove("dark-mode");
    } else {
        document.body.classList.add("dark-mode");
    }
    button.forEach(function(item){
        if (item.classList.contains('dark-mode')) {
            item.classList.remove('dark-mode');
        }
        else {
            item.classList.add('dark-mode');
        }
    })
}, false);

버튼을 querySelectorAll로 가져오게 되면 배열이라고 했는데 그러면 못 주느냐? 놉. 반복문 주면 가능하다. 배열 안의 요소들에 dark-mode가 있으면 지우고(라이트모르도) 반대로 없으면 붙이게(다크모드로) 하면 된다. 하나가 아니라 뭉텅이라 배열로 들고 온 것 뿐이지 내용물이 변하는 건 아니거든.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

나홀로메모장 파생형 만들기-Call of AJAX

Coding/JavaScript 2022. 9. 1. 00:00

원래 페이지네이션이랑 수정/삭제도 구현하려고 했는데 못함...

페이지네이션은 전에 했던 게 오브젝트를 동적으로 생성하는건데 여기서는 DB에 있는 걸 받아와서 진행해야 하는거라 버튼 렌더링/로직에서 막혔고 수정/삭제는 이따 보시면 아시겠지만 저기서 뭐가 뭔지를 구분할 수단이 없습니다. 아, 사람은 구별할 수 있지. 컴퓨터가 못해요.


인간적으로 달력 위에 시계 마렵잖아요

Reference

https://stickode.tistory.com/124

 

[JavaScript] 디지털 시계 만들기

자바스크립트을 활용하여 디지털 시계를 만들어보겠습니다. 먼저, 스틱코드에 올라온 '디지털 시계 만들기' 코드를 즐겨찾기 추가해주세요. 먼저 html 코드를 작성해봅시다. html 파일에서 스틱코

stickode.tistory.com

네? 아날로그요? 아 그거 힘들어... 내가 봤어... 네? 찰떡같이 넘어가서 간지나는 Flipclock이요? 그거 제이쿼리 라이브러리 있으니까 갖다 쓰세요. 이건 돼지털 시계임.

 

function getClock() {
    const date = new Date();
    const hours = String(date.getHours()).padStart(2, "0");
    const min = String(date.getMinutes()).padStart(2, "0");
    const sec = String(date.getSeconds()).padStart(2, "0");
    clock.innerText = `${hours}:${min}:${sec}`;
}

getClock();
setInterval(getClock, 1000);

date 밖으로 빼면 시계가 안가더라... 정확히는 새로고침 해야 감.

 

Create/Read

참고로 이거 원래도 CR만 있었습니다. 예제로 한번 써보라고 올린 거 있었는데 페이지네이션도 없는데다가 카드 높낮이도 제각각+추가되는 순서가 좌우가 아님+많은 사람들이 올려놔서 대혼란시대임... 솔직히 이정도면 페이지네이션 말고 프로그레스바 넣지 좋은데? 

 

일단 완성작 보고 갑시다. 

아니 ㄹㅇ 이걸 하셨어요? 예 했습니다. 그림자는 덤임. 이모지는... 어... 아니 리눅스는 안되는 줄 알았는데 파이참에서 컨트롤 알트 ;하니까 이모지 입력창 떠서 깜놀했음. 아니 안될 줄 알았는데 이게 됐다니까??? 그럼에도 리눅스에서 안되는 카톡은 대체 노션도 웹노션이 있거늘! 

 

Create

//쓰기
function write_article() {
    let article_title = document.getElementById('write-title').value;
    let article_contents = document.getElementById('write-contents').value;
    let date = new Date();
    let year = date.getFullYear();
    let month = String(date.getMonth() + 1).padStart(2, '0');
    let day = String(date.getDate()).padStart(2, '0');
    let hour = String(date.getHours()).padStart(2, "0");
    let minute = String(date.getMinutes()).padStart(2, '0');
    let second = String(date.getSeconds()).padStart(2, '0');
    let timestamp = year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;

    $.ajax({
        type: "POST",
        url: "/write",
        data: {
            'timestamp_give': timestamp,
            'title_give': article_title,
            'contents_give': article_contents
        },
        success: function (response) {
            alert(response['msg'])
            window.location.reload();
            document.getElementById('write-title').value = '';
            document.getElementById('write-contents').value = '';
        }
    })
}

const write_btn = document.getElementById('write_article');
write_btn.addEventListener('click', (e) => {
    write_article();
})

JS

 

@app.route('/write',methods=['POST'])
def write():
    timestamp_receive = request.form['timestamp_give']
    title_receive = request.form['title_give']
    contents_receive = request.form['contents_give']
    print(timestamp_receive)

    doc = {
        'timestamp':timestamp_receive,
        'title':title_receive,
        'contents':contents_receive
    }

    db.diarys.insert_one(doc)

    return jsonify({'msg': '저장되었습니다!'})

Python

 

타임스탬프 찾아보니까 파이썬에서 만들더라... 난 JS에서 만들어서 줬는디... 아무튼, 타임스탬프는 글의 작성일(과 시간)을 기록한 것이다. 예전에 원페이지 쇼핑몰 만들면서도 회원가입할 때 타임스탬프를 받았었는데, 그거다. 그냥 그거다. 제목이랑 내용은 뭐... 다들 아시죠?

 

코드에 짬이 좀 차신 분들이라면 뭔가 위화감을 느낄 수도 있다. 응? idx는 없나요? 이게 MySQL은 그게 강제되는거라 Key로 잡아놓고 뭐 쓸 때 알아서 자기가 추가했는데(그래서 삭제하면 번호가 중간중간 비는 게 그대로 나온다), 몽고DB는 그런게 없다. id가 있긴 있는데 그게 곧 idx가 아니라서 저것도 같이 보내려면 별도로 JS에서 중복 안 되는 번호로 '만들어서' 보내던가, 서버단에서 추가하면서 idx를 붙이던가 해야 한다. 그리고 이게 U/D를 추가 못 하는 원인이 된 게, 컴퓨터 입장에서 타임스탬프 말고 수정/삭제할 글을 식별할 수단이 없다.

 

일단 CRUD까지 다 되는 게시판은 나중에 FLASK/MongoDB/JS 쓰는걸로 찾아서 차근차근 따라해 볼 예정...

 

Read

Read는 DB에 있는 거 불러와서 배치하면 되는데 두가지가 어려웠다. 첫번째는 내가 의도한건 Z자로 정렬되는(위 그림처럼)거였는데 얘는 밑으로 줄줄이 붙고 있다. 그래서 그리드로 줬던 걸 플렉스로 바꿨고, 두번째로 게시판은 가장 최근에 올린 글이 먼저 나온다. SNS도 그렇지만 최근에 올린 글이 먼저다.

 

$.ajax({
        type: "GET",
        url: "/load_diary",
        data: {},
        success: function (response) {
            let articles = response['all_articles']
            for (let i = articles.length; i >= 1; i--) {
                let timestamp = articles[i-1]['timestamp'];
                let title = articles[i-1]['title'];
                let content = articles[i-1]['contents'];
                let content_area = document.querySelector('.contents-area');
                let card_border = document.createElement('div');
                card_border.classList.add('card');
                card_border.id = 'contents';

                let card_body = document.createElement('div');
                card_body.classList.add('card-body');

                content_area.appendChild(card_border);
                card_border.appendChild(card_body);

                card_body.innerHTML = `<h4 class="card-title">${title}</h4><h6 class="timestamp">${timestamp}</h6><p class="card-text">${content}</p>
                    <a href="#" class="card-link">More...</a>`
            }
        }
    })

JS

 

@app.route('/load_diary',methods=['GET'])
def read():
    articles = list(db.diarys.find({}, {'_id': False}))
    return jsonify({'all_articles': articles})

Python

 

배치는 CSS에서 display를 그리드 말고 플렉스고 바꾼 다음, 플렉스 랩 주고 카드 크기를 1/3으로 맞춰서 해결봤다. 동적으로 생성할때는 안그러더니 왜그러나 몰라... ㅡㅡ 근데 두번째는 어떻게 했냐... 서버에서 요청받고 데이터를 불러올 때 딕셔너리 형식으로 저장된 것들이 들어있는 '배열'로 가져온다. 그럼 그 배열을 뒤집으면 되나요? 일단 그렇게 하긴 했는데 파이썬에서 시도했다가 에러뜸.

 

그래서 어차피 반복문으로 추가하는거면 거꾸로 불러서 뒤에서부터 인덱싱하자 해서 저렇게 된거다. 그것도 자바스크립트에서도 배열이 있고 인덱싱이 있으면 -1 이런거 되겠네? -> 귀찮은데 걍 거꾸로 인덱싱하지 뭐 해서 이렇게 된 것. 게시물의 개수가 10개이면 인덱스는 0~9니까 반복문 안의 i에서는 1을 빼야 하고(안그러면 에러뜬다), for문의 i는 10에서 시작해서 1이 될때까지 하나씩 뺀다. 저 하나씩 빼는거 ++ 반대라 --인가 했더니 진짜였고...

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

삼항연산자

Coding/JavaScript 2022. 8. 31. 13:22

내 최근에 달력 코드를 올렸는데 거기서 보면

dates.forEach((date, i) => {
        const condition = i >= firstDate && i < lastDate + 1
            ? 'this'
            : 'other';
        dates[i] = `<div class="date"><span class="${condition}">${date}</span></div>`;
    })

이놈이 있다. 어디서 나오냐면 이전달/다음달 날짜 구별할 때 나온다. 근데 이게 뭔 람다식인가 했더니 그건 아니고 삼항연산자래요... 그럼 이게 뭔지 한번 알아보자.

 

얘는 연산자는 연산자인데 항이 세 개이다. 엥? 그게 왜요? 항이 세개일수도 있잖음!

 

자 생각해봅시다. 우리 더하기 하나에 숫자 몇개 들어감? 두개 들어가죠? 곱하기는? 곱하기도 두개지? 제곱은? 제곱도 두개다. 나누기는? 피제수 제수 두개다. 루트는? 두개다. 예? 루트가 두개라고요? 하나 아니었음...? 아, 일반적인 제곱근(2제곱근)은 루트 옆에 2가 생략된거다. 3루트 4루트 이런거는 제곱근 삐쳐나오는 데 작게 3, 4 이런 식으로 붙는다. 로그는? 밑이랑 숫자랑 두개다. 더하기도 세개 가능하지 않아요? 그러려면 더하기가 2개 이상 필요합니다. 근데 쟤는 한 연산자에 세개, 일타삼피다. 

 

그럼 이 삼항연산자를 어떻게 읽나요?

condition ? exprIfTrue : exprIfFalse

이렇게 읽는다. 삼항연산자의 맨 앞은 조건이고, 그 다음이 참일 때 할 것, 맨 뒤에 있는 게 거짓일 때 할 것이다. 그래서 이 코드를 보면

let a = 2 ** 10
let b = 10 ** 2
let size = a > b ? a : b
console.log(size)

a는 2의 10승, b는 10의 2승이다. 그리고 밑에 있는 삼항연산자를 해석하면 a보다 b가 크면 a, 아니면 b를 출력하시오가 된다. 그러니까

let a = 2 ** 10
let b = 10 ** 2
if (a > b) {
  console.log(a)
}
else {
  console.log(b)
}

얘랑 같은거다. 

 

맨 위에 있던 달력 관련 코드는 이번달과 이전달/다음달을 투명도를 줘서 구별하기 위해 나온 코드인데, i >= firstDate && i < lastDate + 1이면 this, 아니면 other 클래스를 주라는 얘기가 된다. 저 조건문을 8월로 예시를 들면 1 <= i < 32가 된다. (8월은 31일까지)

const firstDate = dates.indexOf(1);
const lastDate = dates.lastIndexOf(thisDate);

dates라는 배열은 [31, 1, 2, 3, ... , 31, 1, 2, 3] 이렇게 된다. 그리고 첫 날짜는 1(1이 있는 곳 인덱스가 1이다), 마지막 날짜는 이번달의 말일(31)일이 들어있는 인덱스가 된다. 즉, 첫 날짜의 인덱스보다 크거나 같고 말일의 인덱스보다 작으면 '이번달'이니까 this, 아니면 이전달/다음달이니까 other가 되는 것.

 

아, 참고로 카테고리는 자바스크립트지만 이 연산자는 C언어에도 있다.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

나홀로 메모장 파생형 만들기-구성 및 사이드바 달력

Coding/JavaScript 2022. 8. 29. 16:04

Reference

https://bigtop.tistory.com/63

 

[JavaScript] 일반적인 달력 만들기 - HTML 뼈대 잡기

만학도 첫 번째 프로젝트였던 간단한 달력 만들기에 이어서 두 번째는, 하루만 나타내는 달력이 아니라 우리가 일반적으로 생각하는 Full Calendar를 만들어 볼까 합니다. 이번에도 지금까지 정리

bigtop.tistory.com

https://opentutorials.org/course/1375/6761

 

addEventListener() - 생활코딩

addEventListener은 이벤트를 등록하는 가장 권장되는 방식이다. 이 방식을 이용하면 여러개의 이벤트 핸들러를 등록할 수 있다. var t = document.getElementById('target'); t.addEventListener('click', function(event){ ale

opentutorials.org


이거는 내가 직접적으로 올린적은 없는데, 내일배움단 하면서 만들었던거다. 근데 나는 영화도 안보고 사이트 스크랩도 잘 안하는데 이건 사이트 URL이 있어야 하는 거기도 하고... 영화 좋아하거나 독서록 이런거 쓸 때는 좋음. 근데 나는 안써요. 애초에 마지막으로 본 영화가 명탐정 피카츄인 시점에서 말 다했지. 그래서 새로 만들었음.

 

일단 기본적인 구성은 이렇다. 

컨텐츠는 그리드뷰에 페이지네이션이 사용될 예정이고, 한 페이지에 보여줄 컨텐츠는 3의 배수(3n)개이다. 9개 12개 이런 식. 글에는 제목과 내용이 있고, 링크는 나중에 더보기 버튼이 되거나 빠질 예정이다. 또한 달력이 있는 오른쪽은 사이드바인데... 그렇다. 오늘 만들 게 저 달력이다.

 

우효 www 멋진 달력이제 www 그런데 이걸 할 수 있어요? ㅇㅇ 생각보다 간단함. 아니 농담 아니고 진짜입니다. 일단 달력을 만드는 순서는 다음과 같다.

1. HTML 뼈대 잡기
2. 날짜 가져오기
3. 달력 렌더링하고 모양 잡기
4. 버튼 기능 추가 및 이전달/다음달 날짜 구별하기

어때요? 참 쉽죠? (대충 밥아저씨 짤)

<div class="card" id="calendar">
	<div class="card-header">
		 Calendar
	</div>
	<h5 class="card-title" id="month"></h5>
	<div class="btn-group" role="group" aria-label="Basic outlined example">
		<button type="button" id="prevmonth" class="btn btn-outline-primary">이전달</button>
		<button type="button" id="gotoday" class="btn btn-outline-primary">오늘</button>
		<button type="button" id="nextmonth" class="btn btn-outline-primary">다음달</button>
	</div>
	<div class="calendar-area">
		<div class="days">
			<div class="day">
				일
			</div>
			<div class="day">
				월
			</div>
			<div class="day">
				화
			</div>
			<div class="day">
				수
			</div>
			<div class="day">
				목
			</div>
			<div class="day">
				금
			</div>
			<div class="day">
				토
			</div>
		</div>
		<div class="dates">
		</div>
	</div>
</div>

뼈대는 사실 그렇게 어려울 게 없다. 왜냐하면 부트스트랩에서 다 갖고왔거든... 위 이미지를 보면 달력을 구성하고 있는 것은

1. 달력(Calendar)
2. 현재 몇월인가
3. 이전달/오늘/다음달 버튼
4. 요일
5. 날짜

이렇게 다섯가지이다. 하지만 뼈대를 보면... 응? 저기 뭐가 빠졌는데요??? 아, 지금 뼈대에서 빠져있는 부분은 자바스크립트가 알아서 할 거니까 패스하셔도 됩니다. 잘~ 따라오면 날짜도 동적으로 뿅 나올거임.

#calendar {
    grid-column: 4/span 1;
    grid-row: 2/span 1;
}

#month {
    margin: 20px 0px;
    font-size: 1.4em;
    text-align: center;
    border-bottom: 2px dotted var(--main-color);
}

.calendar-area {
    margin: 15px auto;
    font-size: 1.3em;
}

.days {
    display: flex;
    border-bottom: 1px solid var(--sub-color);
    justify-content: space-evenly;
    text-align: center;
    margin-bottom: 15px;
}

.dates {
    display: flex;
    flex-flow: row wrap;
    justify-content: space-evenly;
    text-align: center;
}

.day, .date {
    width: calc(100% / 7);
    padding: 5px;
}

.date span {
    width: 50px;
}

.day:nth-child(7n + 1),
.date:nth-child(7n + 1) {
    color: var(--red);
}

.day:nth-child(7n),
.date:nth-child(7n) {
    color: var(--blue);
}

.other {
    opacity: .5;
}

.today {
    color: var(--sub-color);
    border-bottom: 2px double var(--main-color);
}

button.btn.btn-outline-primary, button.btn.btn-primary {
    border: 0;
    color: var(--main-color);
}

button.btn.btn-outline-primary:hover, button.btn.btn-primary:hover {
    color: var(--sub-color);
    background-color: transparent;
    border-radius: 0;
}

스타일시트는 이런 식으로 줬다. 

 

let date = new Date();

const renderCalendar = () => {
    const viewYear = date.getFullYear();
    const viewMonth = date.getMonth();

    const prevMonth = new Date(viewYear, viewMonth, 0);
    const thisMonth = new Date(viewYear, viewMonth + 1, 0);
    const prevDate = prevMonth.getDate();
    const prevDay = prevMonth.getDay();
    const thisDate = thisMonth.getDate();
    const thisDay = thisMonth.getDay();

    const prevDates = [];
    const thisDates = [...Array(thisDate + 1).keys()].slice(1);
    const nextDates = [];

    document.querySelector('#month').textContent = `${viewYear}년 ${viewMonth + 1}월`;

    if (prevDay != 6) {
        for (let i = 0; i < prevDay + 1; i++) {
            prevDates.unshift(prevDate - i);
        }
    }

    for (let i = 1; i < 7 - thisDay; i++) {
        nextDates.push(i);
    }

    const dates = prevDates.concat(thisDates, nextDates);

    const firstDate = dates.indexOf(1);
    const lastDate = dates.lastIndexOf(thisDate);
    dates.forEach((date, i) => {
        const condition = i >= firstDate && i < lastDate + 1
            ? 'this'
            : 'other';
        dates[i] = `<div class="date"><span class="${condition}">${date}</span></div>`;
    })

    document.querySelector('.dates').innerHTML = dates.join('');

    const today = new Date();
    if (viewMonth === today.getMonth() && viewYear === today.getFullYear()) {
        for (let date of document.querySelectorAll('.this')) {
            if ((+date.innerText) === today.getDate()) {
                date.classList.add('today');
                break;
            }
        }
    }

}

const prevMonth = () => {
    date.setMonth(date.getMonth() - 1);
    renderCalendar();
}

const nextMonth = () => {
    date.setMonth(date.getMonth() + 1);
    renderCalendar();
}

const goToday = () => {
    date = new Date();
    renderCalendar();
}

prevMonth_button = document.querySelector('#prevmonth');
nextMonth_button = document.querySelector('#nextmonth');
goToday_button = document.querySelector('#gotoday');

prevMonth_button.addEventListener('click', (e) => {
    prevMonth();
})

nextMonth_button.addEventListener('click', (e) => {
    nextMonth();
})

goToday_button.addEventListener('click', (e) => {
    goToday();
})

renderCalendar();

이건 JS 코드. 이걸로 달력이 그려져요? 예 그려집니다. 이따 움짤 보여드림. 


let date = new Date();

const renderCalendar = () => {
    const viewYear = date.getFullYear();
    const viewMonth = date.getMonth();

    const prevMonth = new Date(viewYear, viewMonth, 0);
    const thisMonth = new Date(viewYear, viewMonth + 1, 0);
    const prevDate = prevMonth.getDate();
    const prevDay = prevMonth.getDay();
    const thisDate = thisMonth.getDate();
    const thisDay = thisMonth.getDay();

    const prevDates = [];
    const thisDates = [...Array(thisDate + 1).keys()].slice(1);
    const nextDates = [];

    document.querySelector('#month').textContent = `${viewYear}년 ${viewMonth + 1}월`;

    if (prevDay != 6) {
        for (let i = 0; i < prevDay + 1; i++) {
            prevDates.unshift(prevDate - i);
        }
    }

    for (let i = 1; i < 7 - thisDay; i++) {
        nextDates.push(i);
    }

    const dates = prevDates.concat(thisDates, nextDates);

    const firstDate = dates.indexOf(1);
    const lastDate = dates.lastIndexOf(thisDate);
    dates.forEach((date, i) => {
        const condition = i >= firstDate && i < lastDate + 1
            ? 'this'
            : 'other';
        dates[i] = `<div class="date"><span class="${condition}">${date}</span></div>`;
    })

    document.querySelector('.dates').innerHTML = dates.join('');

    const today = new Date();
    if (viewMonth === today.getMonth() && viewYear === today.getFullYear()) {
        for (let date of document.querySelectorAll('.this')) {
            if ((+date.innerText) === today.getDate()) {
                date.classList.add('today');
                break;
            }
        }
    }

}

이 부분에 달력 렌더링의 정수가 담겨져 있다.

 

let date = new Date();는 이게 있어야 날짜 정보를 가져올 수 있기 때문에 필요하다. 이거 있으면 시간도 가져옵니다. 그게 무슨 말이냐... 시계를 만들 수 있음. 

const viewYear = date.getFullYear();
const viewMonth = date.getMonth();

const prevMonth = new Date(viewYear, viewMonth, 0);
const thisMonth = new Date(viewYear, viewMonth + 1, 0);
const prevDate = prevMonth.getDate();
const prevDay = prevMonth.getDay();
const thisDate = thisMonth.getDate();
const thisDay = thisMonth.getDay();

const prevDates = [];
const thisDates = [...Array(thisDate + 1).keys()].slice(1);
const nextDates = [];

위쪽은 걍 보는거다. 그리고 이전달, 이번달, 다음달에 해당하는 변수와 배열이 있는데 중요한 건

이전달: 이번달의 1일 이전 날짜를 남는 칸 수만큼 거꾸로 채운다.
이번달: 다 채운다.
다음달: 이번달 말일부터 날짜를 남는 칸 수만큼 앞으로 채운다.

이거다. 당연하게도 그래서 이전 달에 대한 정보가 필요한데, 이전 달이 몇월이고 말일이 무슨 요일인가? 를 알아야 채울 수 있다.

 

document.querySelector('#month').textContent = `${viewYear}년 ${viewMonth + 1}월`;

이 코드는 지금 몇월인지를 가져와서 텍스트로 만든 다음 채워주는 코드다.

 

if (prevDay != 6) {
    for (let i = 0; i < prevDay + 1; i++) {
        prevDates.unshift(prevDate - i);
    }
}

for (let i = 1; i < 7 - thisDay; i++) {
    nextDates.push(i);
}

const dates = prevDates.concat(thisDates, nextDates);
const firstDate = dates.indexOf(1);
const lastDate = dates.lastIndexOf(thisDate);

dates.forEach((date, i) => {
        const condition = i >= firstDate && i < lastDate + 1
            ? 'this'
            : 'other';
        dates[i] = `<div class="date"><span class="${condition}">${date}</span></div>`;
    })

document.querySelector('.dates').innerHTML = dates.join('');

이전 달 같은 경우, 이번달 1일이 일요일이면 굳이 채울 이유가 없다. 다르게 말하자면, 이전달 말일이 토요일이면 굳이 채우지 않아도 된다. 그래서 마지막달이 토요일이 아닐 경우이면 PrevDates라는 배열에 거꾸로 하나씩 넣어라, 이런 얘기다. 이번달은 1일이 월요일이므로 저번달 31일(일요일)만 끼워넣으면 된다. 다음달은 남는 칸만큼 끼워넣으면 되기때문에 1, 2, 3일이 들어간 것. 이번 달 31일은 수요일이다.

ForEach문에 있는 건 이번달과 다른 달의 날짜를 구별해 클래스를 부여하기 위한 코드인데 ?랑 :는 모르겠고... 이번달 날짜면 this, 아니면 other 클래스를 주게 된다. 그리고 other 클래스에 투명도를 부여하면 이전달, 다음달 날짜의 투명도가 바뀐다.

const today = new Date();
if (viewMonth === today.getMonth() && viewYear === today.getFullYear()) {
    for (let date of document.querySelectorAll('.this')) {
        if ((+ date.innerText) === today.getDate()) {
            date.classList.add('today');
            break;
        }
    }
}

이건 오늘 날짜 찾는 코드이다. 그리고 today라는 클래스를 부여하게 되면 오늘 날짜만 다르게 표시할 수 있다.

 

const prevMonth = () => {
    date.setMonth(date.getMonth() - 1);
    renderCalendar();
}

const nextMonth = () => {
    date.setMonth(date.getMonth() + 1);
    renderCalendar();
}

const goToday = () => {
    date = new Date();
    renderCalendar();
}

달력에 딸려오는 버튼들에 대한 기능이다. 지금 8월 29일인데 이전달이면 7월, 다음달이면 9월을 기준으로 렌더링하게 된다. renderCalendar()는 아까 설명했던 달력 그리는 함수.

 

prevMonth_button = document.querySelector('#prevmonth');
nextMonth_button = document.querySelector('#nextmonth');
goToday_button = document.querySelector('#gotoday');

prevMonth_button.addEventListener('click', (e) => {
    prevMonth();
})

nextMonth_button.addEventListener('click', (e) => {
    nextMonth();
})

goToday_button.addEventListener('click', (e) => {
    goToday();
})

renderCalendar();

함수는 그냥 짜기만 하면 작동하는건지 아닌건지 모른다. 그래서 맨 밑의 줄처럼 renderCalendar() 함수는 하단에 소환했고, 이전달/다음달/오늘 날짜로 돌아가는 버튼에는 이벤트 리스너로 함수를 연결했다.

 

좀 짤리긴 했는데 아무튼 된다. 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

마우스를 따라다니는 무언가 만들기

Coding/JavaScript 2022. 8. 24. 16:27

참고로 이 블로그에도 적용해뒀다. 여러분의 마우스 커서를 따라다니는 Thinking Face가 그것. 워드프레스는 HTML이나 JS는 건드릴 수 없어서 적용 못했고 네이버나 미디움은 HTML쪽 편집이 안돼서 적용 못했음.


Reference


https://stickode.tistory.com/318

 

[JavaScript] 마우스를 따라다니는 원 만들기

이번시간에는 [JavaScript] 를 사용해 마우스를 따라다니는 원을 만들어보겠습니다. 스틱코드를 즐겨찾기해주세요. https://stickode.com/detail.html?no=2671 스틱코드 stickode.com Html안에 javascript와 css..

stickode.tistory.com

https://codepen.io/falldowngoboone/pen/PwzPYv

 

JavaScript mouse trail

...

codepen.io

 

Basic

스크린캐스트에 커서는 안찍히는데 커서 따라가는 거 맞다.

 

<html>

<head>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div class="mousefollow">

    </div>
    <div class="wrapper">
        <p>Move your mouse! </p>
    </div>

    <script src="script.js"></script>

    <body>

</html>

그냥 따라다닐 div 하나 만들어주면 끝이다. 이 블로그에 따라다니는 thinking face도 HTML에 들어가 있다. 

 

@font-face {
    font-family: 'EarlyFontDiary';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_220508@1.0/EarlyFontDiary.woff2') format('woff2');
    font-weight: normal;
    font-style: normal;
}

* {
    margin: 0;
    padding: 0;
    font-family: 'EarlyFontDiary';
}

p {
    font-size:25pt;
    text-align:center;
    height:50%;
}

.mousefollow {
    position:absolute;
    left:0;
    right:0;
    width:50px;
    height:50px;
    border-radius:50px;
    background-color:blueviolet;
}

아, CSS에서 position 지정 안해주면 안 움직인다.

 

const mousefollow = document.querySelector('.mousefollow');

document.addEventListener('mousemove',(e) => {
    const mouseX = e.clientX;
    const mouseY = e.clientY;
    mousefollow.style.left = mouseX + 'px';
    mousefollow.style.top = mouseY + 'px';
})

심지어 이건 자바스크립트 코드도 개 심플하다.

 

화려하게 따라와DALA

일단 결과물부터 보자. 

 

원이 화려함
아이콘이 화려함
아이콘마저 변화무쌍하다

티스토리에 적용할 생각 없냐고? 이거 적용하면 구경왔다가 눈아프다고 다 갈듯. 

 

<html>

<head>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div class="wrapper">
        <p>Move your mouse! </p>
    </div>

    <script src="script.js"></script>

    <body>

</html>

HTML은 아무것도 없는 동양화의 정수. 근데 저게 어떻게 되냐고? 이따 JS 보면 압니다.

 

@font-face {
    font-family: 'EarlyFontDiary';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_220508@1.0/EarlyFontDiary.woff2') format('woff2');
    font-weight: normal;
    font-style: normal;
}

* {
    margin: 0;
    padding: 0;
    font-family: 'EarlyFontDiary';
}

p {
    font-size:25pt;
    text-align:center;
}

.follower {
    font-size:20pt;
    text-align:left;
    position:absolute;
    left:0;
    right:0;
    width:15px;
		background-color:#f7cac9;
    height:15px;
    border-radius:15px;
    transition: all 0.1s ease-out;
}

응? CSS에 백그라운드 컬러가 있잖아요! 아 그것도 이따 JS에서 설명해드림. 원본에서는 단색이었어요. 

 

// dots is an array of Dot objects,
// mouse is an object used to track the X and Y position
// of the mouse, set with a mousemove event listener below
var dots = [],
    mouse = {
        x: 0,
        y: 0
    };

// The Dot object used to scaffold the dots
var Dot = function () {
    this.x = 0;
    this.y = 0;
    this.node = (function () {
        var n = document.createElement("div");
        n.className = "follower";
        document.body.appendChild(n);
        return n;
    }());
};
// The Dot.prototype.draw() method sets the position of 
// the object's <div> node
Dot.prototype.draw = function () {
    this.node.style.left = this.x + "px";
    this.node.style.top = this.y + "px";
};

// Creates the Dot objects, populates the dots array
for (var i = 0; i < 12; i++) {
    var d = new Dot();
    dots.push(d);
}

// This is the screen redraw function
function draw() {
    // Make sure the mouse position is set everytime
    // draw() is called.
    var x = mouse.x,
        y = mouse.y;

    // This loop is where all the 90s magic happens
    dots.forEach(function (dot, index, dots) {
        var nextDot = dots[index + 1] || dots[0];

        dot.x = x;
        dot.y = y;
        dot.draw();
        x += (nextDot.x - dot.x) * .6;
        y += (nextDot.y - dot.y) * .6;

    });
}

addEventListener("mousemove", function (event) {
    //event.preventDefault();
    mouse.x = event.pageX;
    mouse.y = event.pageY;
});

// animate() calls draw() then recursively calls itself
// everytime the screen repaints via requestAnimationFrame().
function animate() {
    draw();
    requestAnimationFrame(animate);
}

// And get it started by calling animate().
animate();

이게 원본 코드인데, 원래 단색이다. 아까 HTML에서 아무것도 없었다고 했는데, JS가 첨가되면서 for문을 통해 요소를 만들고 클래스명을 붙이게 되면 마우스를 따라다니는 점이 생성되게 된다. 그래서... 색깔 랜덤 어떻게 했냐고?

 

function getRandomColor() {
    const r = Math.floor(Math.random() * 256).toString(16).padStart(2,'0');
    const g = Math.floor(Math.random() * 256).toString(16).padStart(2,'0');
    const b = Math.floor(Math.random() * 256).toString(16).padStart(2,'0');
    return "#" + r + g + b;
}
Dot.prototype.draw = function () {
    this.node.style.left = this.x + "px";
    this.node.style.top = this.y + "px";
    this.node.style.backgroundColor = getRandomColor();
};

위 코드는 색상을 랜덤으로 생성하는 코드(256으로 해야 00~FF까지 나온다)이고, 아래 코드는 점(div) 색깔을 위에서 만든 색으로 적용하는 코드이다. 그래서 위에 나왔던것처럼 개 화려한 동그라미가 우다다다 나오는 것.

 

var Dot = function () {
    this.x = 0;
    this.y = 0;
    this.node = (function () {
        var n = document.createElement("div");
        n.innerHTML= '<i class="fa-solid fa-palette"></i>'
        n.className = "follower";
        document.body.appendChild(n);
        return n;
    }());
};
// The Dot.prototype.draw() method sets the position of 
// the object's <div> node
Dot.prototype.draw = function () {
    this.node.style.left = this.x + "px";
    this.node.style.top = this.y + "px";
    this.node.style.color = getRandomColor();
};

아이콘은 div를 생성하고 안을 FontAwesome 아이콘으로 채운 것. 이렇게 하려면 div에 줬던 높이랑 너비를 빼야 한다.

 

var Dot = function () {
    this.x = 0;
    this.y = 0;
    this.node = (function () {
        var n = document.createElement("div");
        var random_content = ['<i class="fa-solid fa-lemon"></i>','<i class="fa-solid fa-star-half-stroke"></i>','<i class="fa-solid fa-star"></i>','<i class="fa-solid fa-thumbs-up"></i>','<i class="fa-solid fa-meteor"></i>','<i class="fa-solid fa-heart"></i>','<i class="fa-solid fa-star-of-life"></i>','<i class="fa-solid fa-moon"></i>']
        n.innerHTML= random_content[Math.floor(Math.random() * random_content.length)];
        n.className = "follower";
        document.body.appendChild(n);
        return n;
    }());
};

그럼 여기까지 보신 분들은 도대체! 아이콘은 어케한거임? 하실텐데, 간단하다. 랜덤으로 뽑고 싶은 아이콘들을 배열에 담고, 그 배열의 인덱스를 랜덤으로 정해서 인덱싱하게 되면 복불복으로 뽑힌다. 진정한 안구테러 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

페이지네이션

Coding/JavaScript 2022. 8. 23. 17:07

매우 고급져보이는 이 단어의 뜻은 정말 생각지도 못하게 심플하고, 여러분은 이미 이걸 몇 번 봤다.

이 블로그 하단부

이런거 다들 한번씩 봤잖음. 블로그건 검색이건... 이게 페이지네이션이다. 저게 코딩관련 글을 11페이지로 옮겼는데(다 옮겼음) 분량이 어마무시하다 그죠...? 아무튼, 페이지네이션은 이런 식으로 한 페이지에 정해진 양의 콘텐츠를 보여준다. 반대되는 개념인 무한 스크롤은 끝이 보일때까지 아래로 아래로 내리는 것.

 

무한 스크롤은 보통 모바일 앱에서 많이 사용하는 방식인데, 인별이나 미디움도 무한 스크롤이다. 아래로 스크롤하면서 콘텐츠를 계속 볼 수 있어서 굳이 페이지 이동하고 로딩하는 걸 기다리지 않아도 된다. 하지만 콘텐츠가 많아지면 로딩하는 데 시간이 걸리기도 하고(...), 읽다 보면 스크롤바는 줄어드는데 이게 대체 어디까지 있는건지 모르기도 하고, 페이지의 밑으로 내려가면 계속 콘텐츠가 로딩이 되는 특성상 사이트의 푸터가 안보이기 때문에 사이드바로 푸터를 빼야 한다.

네이버 푸터

페이지네이션은 페이지를 클릭하고 로딩해야 하는 수고로움+추가 작업의 수고로움이 있지만 한 페이지에 표시할 수 있는 최대 콘텐츠의 수가 정해져있다. 그래서 내가 아까 봤던 글이 몇페이지 어디 있더라, 만 알면 다시 가서 볼 수 있다. 그리고 아 이쪽 콘텐츠는 재미없다 그러면 그냥 다음 페이지로 가면 된다. 이렇게 둘 다 일장일단이 있기 때문에 적절한 사용이 중요하다고.


페이지네이션 구현하기

Reference

https://nohack.tistory.com/125

 

간단한 페이지네이션 구현하기

Pagination 페이지네이션은 다수의 콘텐츠를 여러 페이지로 나누고, 이전 또는 다음 페이지로 넘어가거나 특정 페이지로 이동할 수 있는 버튼을 제공하는 기술입니다. 페이지네이션은 공통된 주제

nohack.tistory.com

https://velog.io/@eunoia/JS%EB%A1%9C-Pagination-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

 

JS로 Pagination 구현하기

출처페이지네이션이란 콘텐츠를 여러 페이지고 나누고, 이전 혹은 다음 페이지로 넘어가거나 특정 페이지로 넘어갈 수 있는 일련의 링크를 페이지 상단이나 하단에 배치하는 방법입니다.게시

velog.io

 

초간단! 페이지네이션

해당 코드는 참고문헌 1번에 있으니 별도로 올리지는 않는다. 

 

게시판이긴 한데, 테이블이 아니라 li태그 안에 display: flex를 줘서 구현했다. 저거... 아 저 내용물은 동적으로 생성한거라 저래요... DB에서 갖고온 게 아님... 아무튼 그렇다.


페이지네이션을 할 때 필요한 건

1) 한 페이지에 콘텐츠 몇 개를 보여줄 것인가?
2) 페이지를 넘기는 버튼은 한 페이지에 몇 개를 보여줄 것인가? (목록 밑에 숫자 써있는거)

이거다. 이 두 개를 정하고 나면 전체 콘텐츠 개수에 따라 필요한 페이지 수를 계산하고 버튼을 만들면 된다.



그리드 페이지네이션

위에 있는 컨텐츠는 게시판 리스트같이 생긴건데, 이번에 구현할 건 컨텐츠가 그리드 형태이다. 그니까 저렇게 길고 쭉 뻗은 리스트가 아니고 네모땡땡한 거다. 인스타 피드같은 거.

 

<html>

<head>
    <meta charset="UTF-8" />
    <title>Pagenation</title>
    <link rel="stylesheet" href="style.css" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <script src="https://kit.fontawesome.com/dc58858c96.js" crossorigin="anonymous"></script>
</head>

<body>
    <div class="wrapper">
        <div class="card" id="article-title">
            <div class="card-body">
                <h4 class="card-title">est velit</h4>
                <p class="card-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
                    incididunt ut labore et dolore magna aliqua. Quisque egestas diam in arcu cursus euismod. Eu feugiat
                    pretium nibh ipsum consequat nisl vel pretium. Tempus quam pellentesque nec nam. In fermentum
                    posuere urna nec tincidunt. Massa enim nec dui nunc mattis enim ut tellus elementum. Fringilla est
                    ullamcorper eget nulla facilisi etiam. At imperdiet dui accumsan sit amet nulla facilisi morbi. Vel
                    pretium lectus quam id leo. Ut faucibus pulvinar elementum integer enim neque. Gravida neque
                    convallis a cras semper auctor neque vitae. Lacus vestibulum sed arcu non odio euismod lacinia at
                    quis. Faucibus pulvinar elementum integer enim neque volutpat ac tincidunt. Porta nibh venenatis
                    cras sed felis.</p>
                <a href="#" class="btn btn-primary">Go somewhere</a>
            </div>
        </div>
        <div class="content-body">
            
        </div>
        <div class="buttons">
        </div>
    </div>
    <script src="script.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>
</body>

</html>

물논 부트스트랩의 도움을 조금 받았지. (안에 있는 텍스트는 Lorem ipsum generator로 만들었음)

 

@font-face {
    font-family: 'DungGeunMo';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_six@1.2/DungGeunMo.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

* {
    margin: 0;
    padding: 0;
    font-family: 'DungGeunMo';
    font-size: 14pt;
    color: #234E70;
}

.wrapper {
    width: 1280px;
    margin: 0 auto;
}

#article-title {
    margin: 15px auto;
    background-color: #FBF8BE;
    border: 1px solid #234E70;
}

#article-title .btn-primary {
    background-color: #234E70;
    color: #FBF8BE;
    border: none;
}

.content-body {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3.1fr);
    grid-gap:5px;
}

.col-sm-6 {
    width: 100% !important;
}

#contents {
    background-color: #234E70;
    color: #FBF8BE;
}

#contents .card-title,
#contents .card-text {
    color: #FBF8BE;
}

#contents .btn-primary {
    background-color: #FBF8BE;
    color: #234E70;
    border: none;
}

.buttons {
    text-align: center;
    width: 100%;
    margin:15px auto;
}

button {
    border: none;
    background-color: #fff;
    color: #234E70;
    width:50px;
}

.active {
    background-color: #234E70;
    color: #FBF8BE;
}

CSS의 경우 버튼 부분(페이지 아래에 있는 넘어가는 버튼)은 나중에 했다. 봐야 뭘 하지.

 

const contents = document.querySelector('.content-body')
const buttons = document.querySelector('.buttons')
const numOfContent = 100
const maxContent = 9
const maxButton = 5
const maxPage = Math.ceil(numOfContent / maxContent)
let page = 1

const makeContent = (id) => {
    const content = document.createElement('div')
    content.classList.add('col-sm-6')
    content.setAttribute('id', 'contents-card')
    content.innerHTML = `
                <div class="card" id="contents">
                    <div class="card-body">
                        <h5 class="card-title">No. ${id}</h5>
                        <p class="card-text">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</a>
                    </div>
                </div>`
    return content
}

const makeButton = (id) => {
    const button = document.createElement('button')
    button.classList.add('button')
    button.dataset.num = id
    button.innerText = id
    button.addEventListener("click", (e) => {
        Array.prototype.forEach.call(buttons.children, (button) => {
            if (button.dataset.num) button.classList.remove("active")
        })
        e.target.classList.add("active")
        renderContent(parseInt(e.target.dataset.num))
    })
    return button
}

const prevPage = () => {
    page -= maxButton
    render(page)
}

const nextPage = () => {
    page += maxButton
    render(page)
}

const prev = document.createElement('button')
prev.classList.add('button','prev')
prev.innerHTML = '<i class="fa-solid fa-angle-left"></i>'
prev.addEventListener('click', prevPage)

const next = document.createElement('button')
next.classList.add('button','next')
next.innerHTML = '<i class="fa-solid fa-angle-right"></i>'
next.addEventListener('click',nextPage)

const renderContent = (page) => {
    while(contents.hasChildNodes()) {
        contents.removeChild(contents.lastChild)
    }
    for (let id = (page - 1) * maxContent + 1;id <= page * maxContent && id <= numOfContent;id++){
        contents.appendChild(makeContent(id))
    }
}

const renderButton = (page) => {
    while(buttons.hasChildNodes()) {
        buttons.removeChild(buttons.lastChild)
    }
    for (let id = page;id < page + maxButton && id <= maxPage;id++) {
        buttons.appendChild(makeButton(id))
    }
    buttons.children[0].classList.add('active')

    buttons.prepend(prev)
    buttons.append(next)

    if (page - maxButton < 1) buttons.removeChild(prev)
    if (page + maxButton > maxPage) buttons.removeChild(next)
}

const render = (page) => {
    renderContent(page)
    renderButton(page)
}

render(page)

 

JS에서 동적으로 생성했다고 했는데, 그리드뷰도 마찬가지다. render~에서 만드는거다.

 


그리드쪽 높이를 고정하지 않으면 저렇게 버튼이 올라간다. 그래도 페이지네이션 구현했지롱! 참고로 CSS에서 display를 그리드로 줬기 때문에 위치를 잘 계산한 다음 네모땡땡한 것이 grid에서 어느정도를 차지하는가를 지정할 수 있다. 그게 무슨 말이냐면, 어떤건 크고 어떤건 작고 이런 게 가능하다. (근데 내용이 짧으면 자동 채우기가 안된다는 단점이...)

 

테이블 페이지네이션

콘텐츠가 예전 게시판스타일대로 table 태그에 담겨져 있다. 그래서 동적으로 추가할 때 table 전체가 아니라 그 안에 있는 tr태그를 추가해야 한다. (table 안에 tr 안에 td)

 

<html>

<head>
    <meta charset="UTF-8" />
    <title>Pagenation</title>
    <link rel="stylesheet" href="style.css" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <script src="https://kit.fontawesome.com/dc58858c96.js" crossorigin="anonymous"></script>
</head>

<body>
    <div class="wrapper">
        <h1>integer quis auctor elit</h1>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
            magna aliqua. Tellus molestie nunc non blandit. Adipiscing enim eu turpis egestas pretium aenean pharetra.
            Sed lectus vestibulum mattis ullamcorper velit sed.</p>
        <table class="table table-primary table-striped table-hover" id="table-body">
            <thead>
                <tr>
                    <th>ID</th>
                    <th>Title1</th>
                    <th>Title2</th>
                </tr>
            </thead>
            <tbody class="table-body">
                
                <tr class="table-row">
                    <td>1</td>
                    <td>integer</td>
                    <td>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
                        labore et dolore magna aliqua.</td>
                </tr>
            </tbody>
        </table>
        <nav aria-label="Page navigation example">
            <ul class="pagination justify-content-center">
                
            </ul>
        </nav>
    </div>

    <script src="script.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>
</body>

</html>

테이블과 페이지 번호 그거는 부트스트랩꺼 가져왔는데, 이거 생각보다 힘든게 CSS가 안먹혀서 죄다 important 때렸다... 아무튼, 페이지 번호는 ul 안에 li태그 안에 a까지 있는 구조이고, table은 원래 thead tbody가 없었는데 th 추가하려고 넣었다. 우리가 동적으로 추가할 tr은 tbody에 들어간다.

 

@font-face {
    font-family: 'CookieRunOTF-Bold';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_twelve@1.0/CookieRunOTF-Bold00.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

* {
    margin:0;
    padding:0;
    font-family:'CookieRunOTF-Bold';
}

.wrapper {
    width:1280px;
    margin:0 auto;
}

table {
    margin: 15px auto;
}

.page-link {
    border:none!important;
}

.page-item.active {
    border-bottom: 3px solid #0d6efd;
}

.page-item.active .page-link {
    background-color:transparent!important;
    color:#0d6efd!important;
}

위에도 말했듯... 걍 CSS 줬더니 안먹혀서 important 때렸음... 그거 말고는 딱히 특이한 건 없다.

 

const contents = document.querySelector('.table-body')
const buttons = document.querySelector('.pagination')
const numOfContent = 120
const maxContent = 10
const maxButton = 5
const maxPage = Math.ceil(numOfContent / maxContent)
let page = 1

const makeContent = (id) => {
    const content = document.createElement('tr')
    content.classList.add('.table-row')
    content.innerHTML = `
        <td>${id}</td>
        <td>Title_Text ${id}</td>
        <td>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
            labore et dolore magna aliqua.</td>`
    return content
}

const makeButton = (id) => {
    const button = document.createElement('li')
    const link = document.createElement('a')
    button.classList.add('page-item')
    link.classList.add('page-link')
    button.dataset.num = id
    link.dataset.num = id
    button.appendChild(link)
    link.innerText = id
    link.href = '#'
    button.addEventListener("click", (e) => {
        Array.prototype.forEach.call(buttons.children, (button) => {
            if (link.dataset.num) button.classList.remove("active")
        })
        button.classList.add("active")
        renderContent(parseInt(e.target.dataset.num))
    })
    return button
}

const prevPage = () => {
    page -= maxButton
    render(page)
}

const nextPage = () => {
    page += maxButton
    render(page)
}

const prev = document.createElement('li')
prev.classList.add('page-item','prev')
prev.innerHTML = '<a class="page-link" href="#">Previous</a>'
prev.addEventListener('click', prevPage)

const next = document.createElement('li')
next.classList.add('page-item','next')
next.innerHTML = '<a class="page-link" href="#">Next</a>'
next.addEventListener('click',nextPage)

const renderContent = (page) => {
    while(contents.hasChildNodes()) {
        contents.removeChild(contents.lastChild)
    }
    for (let id = (page - 1) * maxContent + 1;id <= page * maxContent && id <= numOfContent;id++){
        contents.appendChild(makeContent(id))
    }
}

const renderButton = (page) => {
    while(buttons.hasChildNodes()) {
        buttons.removeChild(buttons.lastChild)
    }
    for (let id = page;id < page + maxButton && id <= maxPage;id++) {
        buttons.appendChild(makeButton(id))
    }
    buttons.children[0].classList.add('active')

    buttons.prepend(prev)
    buttons.append(next)

    if (page - maxButton < 1) buttons.removeChild(prev)
    if (page + maxButton > maxPage) buttons.removeChild(next)
}

const render = (page) => {
    renderContent(page)
    renderButton(page)
}

render(page)

버튼이 원래는 disabled 클래스가 따로 있었는데, 그거 그대로 따라가려고 했더니 그만... 페이지가 음수가 떠버린것이고... (주륵)

 

기본적인 거 말고 CSS 건드린 건 딱히 없다.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

원페이지 쇼핑몰 보강하기-익스트림 CRUD

Coding/JavaScript 2022. 8. 19. 01:55

진행 상황: 회원가입(완료)/로그인(로직만 완료)


전에도 얘기했듯, 쇼핑몰에서 ‘모든 고객의 주문 내역을 볼 수 있는’건 물건을 파는 사람이다. 다른 고객들은 자기 주문만 볼 수 있다. 내가 옆집 김씨가 뭘 시켰는지 모르고, 뒷집 박씨는 내가 뭘 시켰는지 모르는것처럼. 그래서 나중에 페이지 하단에 있는

얘는 관리자 계정으로 로그인해야만 볼 수 있게끔 할 예정이다. 콩둘기 메일의 상태가?

 

회원가입

로그인과 회원가입을 모달창에서 받는다는 얘기는 전에 했는데, 그럼 어떤 정보를 받느냐…

아이디, 비밀번호, 비밀번호 확인, 이름, 전화번호, 이메일, 주소 받는다. 타임스탬프도 있긴 한데, 그건 입력받는 건 아니고 가입년월일이다.

function account_make() {
    let id = $('#userID').val()
    let password = $('#password').val()
    let password_check = $('#password-confirm').val()
    let username = $('#username').val()
    let phone = $('#userphone').val()
    let email = $('#useremail').val()
    let zipcode = $('#userzipcode').val()
    let useraddr = $('#useraddr').val()
    let detailaddr = $('#useraddr-detail').val()
    let phone_valid = RegExp(/^\d{2,3}-\d{4}-\d{4}/)
    let name_space = RegExp(/^[가-힣]{2,8}$/)
    let mail_valid = RegExp(/^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/)
    let password_valid = RegExp(/^[A-Za-z0-9]{8,20}/)

    let date = new Date()
    let Weekday = new Array(7)
    Weekday[0] = 'Sun'
    Weekday[1] = 'Mon'
    Weekday[2] = 'Tue'
    Weekday[3] = 'Wed'
    Weekday[4] = 'Thu'
    Weekday[5] = 'Fri'
    Weekday[6] = 'Sat'
    let year = date.getFullYear()
    let month = date.getMonth() + 1
    let day = date.getDate()
    let yoil = Weekday[date.getDay()]
    let timestamp = year + '-' + month + '-' + day + '-' + yoil


    if (id.length == 0) {
        $('#reg_userid_blank').css('display', 'block')
        $('#userID').css('border', '2px solid #cc0000')
    } else if ($('#fail').css('display') == 'block') {
        $('#reg_userid_needcheck').css('display', 'block')
        $('#userID').css('border', '2px solid #cc0000')
    }

    if (password != password_check) {
        $('#reg_passwd_nomatch').css('display', 'block')
        $('#password').css('border', '2px solid #cc0000')
        $('#password_check').css('border', '2px solid #cc0000')
    } else if (password.length == 0) {
        $('#reg_passwd_blank').css('display', 'block')
        $('#password').css('border', '2px solid #cc0000')
    } else if (password_check.length == 0) {
        $('#reg_passwd_blank').css('display', 'block')
        $('#password-confirm').css('border', '2px solid #cc0000')
    } else if (!password_valid.test(password)) {
        $('#reg_passwd_invalid').css('display', 'block')
        $('#password').css('border', '2px solid #cc0000')
    }

    if (!name_space.test(username)) {
        $('#reg_name-invalid').css('display', 'block')
        $('#username').css('border', '2px solid #cc0000')
    } else if (username.length == 0) {
        $('#reg_name-blank').css('display', 'block')
        $('#username').css('border', '2px solid #cc0000')
    }

    if (!phone_valid.test(phone)) {
        $('#reg_phone-invalid').css('display', 'block')
        $('#userphone').css('border', '2px solid #cc0000')
    } else if (phone.length == 0) {
        $('#reg_phone-required').css('display', 'block')
        $('#userphone').css('border', '2px solid #cc0000')
    }

    if (!mail_valid.test(email)) {
        $('#reg_email-invalid').css('display', 'block')
        $('#useremail').css('border', '2px solid #cc0000')
    } else if (email.length == 0) {
        $('#reg_email-required').css('display', 'block')
        $('#useremail').css('border', '2px solid #cc0000')
    }

    if ($('#pass').css('display') == 'block') {
        $.ajax({
            type: "POST",
            url: "/register",
            data: {
                'timestamp_give': timestamp,
                'userid_give': id,
                'userpasswd_give': password,
                'username_give': username,
                'useremail_give': email,
                'userphone_give': phone,
                'userzip_give': zipcode,
                'useraddr_give': useraddr,
                'userdetailaddr_give': detailaddr
            },
            success: function (response) {
                alert(response['msg'])
                window.location.reload()
            }
        })
    }
    else {
        alert('아이디 중복확인을 하셨나요? ')
    }

}

유효성검사 진짜 개노가다 맞음… 타임스탬프는 가입년월일이라서, ‘이 사람이 가입한 날짜’ 정보를 Day()를 이용해 조합한 다음 자바스크립트쪽에서 같이 보내준다. 근데 우리가 가입할 때 유효성검사 하는 것 중에 그것도 있음. 아이디 중복검사. 그죠? 그니까 OK하는 조건이 크게

  1. 아이디 중복이 없고
  2. 모든 폼을 다 채웠을 때

이다. AND임. 그럼 ID 중복을 어떻게 체크하는가? 그것은 간단하다.

  1. 자바스크립트에서 파이썬으로 아이디를 넘겨준다. (이거 확인좀)
  2. 파이썬은 몽고DB를 찾는다. (이거 찾아봐)
  3. 몽고DB에서 찾은 결과를 확인한다. (없네?)
  4. 파이썬에서 거기에 대한 처리를 하고 그 값을 반환한다. (없어 쓰라그래)
  5. 자바스크립트는 그 값을 토대로 처리한다. (써도 된대)

대충 이런 식이다. 

@app.route('/check_id',methods=['POST'])
def idcheck():
    id_check_result = ''
    id_receive = request.form['idcheck_give']
    id_find = list(db.register.find({'login id':id_receive}, {'_id': False,'login id':True}))
    if len(id_find) != 0:
        id_check_result = 'Fail'
    else:
        id_check_result = 'Pass'
    return jsonify({'idcheck_result': id_check_result})

JS에서 아이디를 넘겨주면 그걸 찾은 결과값을 처리해서 Pass of Fail을 보내준다. 몽고DB에서 조회한 결과가 리스트로 오게 되는데, 중복되는 ID가 있다면 리스트의 길이가 0이 아니게 된다. (없으면 리스트 안에 암것도 없음) 그래서 리스트 길이가 0이면 Pass, 아니면 Fail.

function idcheck() {
    let id = $('#userID').val()
    let id_test = RegExp(/^[A-Za-z0-9_-]{6,20}$/)
    if (!id_test.test(id)) {
        $('#userid_invalid').css('display', 'block')
        $('#userID').css('border', '2px solid #cc0000')
    } else {
        $.ajax({
            type: "POST",
            url: "/check_id",
            data: {'idcheck_give': id},
            success: function (response) {
                let check_result = response['idcheck_result']
                if (check_result == 'Fail') {
                    alert('이미 존재하는 아이디입니다. ')
                    $('#fail').css('display', 'block')
                    $('#pass').css('display', 'none')
                } else if (check_result == 'Pass') {
                    alert('아이디를 사용하셔도 됩니다!')
                    $('#fail').css('display', 'none')
                    $('#pass').css('display', 'block')
                }
            }
        })
    }

}

JS에서는 ID를 보냈을 때 돌아온 결과를 토대로 아이콘을 보여준다. 위 사진을 보면 아이디 입력란에 X가 있는데, 중복되지 않는 아이디를 입력하면 초록색 체크로 바뀐다.

 

아 그러면 끝인가요? ㄴㄴ 한가지가 더 있다. 비밀번호는 중요하기때문에 이게 털리면 X된다고 보면 된다. 그니까 제발 12345 이런거 하지 말자. 아무튼… 비밀번호를 DB에 쌩으로 저장하는 게 아니라 암호화를 해서 저장할건데, 여기서는 hashlib을 써볼거다.

import hashlib

부르자. 

m = hashlib.sha256()
m.update(userpasswd_receive.encode('utf-8'))
userpasswd_receive_hash = m.hexdigest()

그리고 입력받은 비밀번호를 해싱하고 

'password':userpasswd_receive_hash

해싱한 비밀번호를 저장한다. 

짜잔

 

참고로 이렇게 저장하게 되면 비번 까먹으면 망한다. 저걸 원래대로 되돌릴 수가 없거든… 그리고 로그인 할 때도 비밀번호 일치 여부를 확인하기 위해 사용자가 입력한 비밀번호를 해시화한 다음 대조하게 된다.

 

로그인

일단 로그인은 사용자가 ‘로그아웃을 하기 전까지’ 그 상태가 유지된다. 로그아웃 버튼을 누르건 창을 닫건 사용자가 자의로 로그아웃 하기 전까지 유지되는데, 이거는 일단 토큰 관련 처리가 필요하고… 여기서는 로그인 로직만 일단 구현해보자.

바에 이렇게 세 개가 있는데, 얘가 토큰 유무에 따라 로그인과 로그아웃만 보이게 된다. 토큰이 있으면 로그아웃, 토큰이 없으면 로그인.

 

로그인 모달창은 구조도 간단하고, 서버를 거치지 않는 유효성 검사도 공란인가만 보면 된다.

 

아이디랑 비밀번호 일치 여부는 서버를 거쳐야 하기 때문에 여기서는 일단 패스.

function login_user() {
    let id = $('#loginID').val()
    let password = $('#loginPW').val()
    console.log(id, password)
    if (id.length == 0) {
        $('#login-id-required').css('display', 'block')
        $('#loginID').css('border', '2px solid #cc0000')
    }
    if (password.length == 0) {
        $('#login-pw-required').css('display', 'block')
        $('#loginPW').css('border', '2px solid #cc0000')
    }
}

이제 Ajax를 끼얹어보자.

$.ajax({
    type: "POST",
    url: "/login",
    data: {
        'login_id_give': id,
        'login_pw_give': password
    },
    success: function (response) {
        console.log(response)
    }
})

Ajax를 통해 플라스크로 보내는 건 아이디랑 비밀번호가 끝이다. 그러면 서버에서

@app.route('/login',methods=['POST'])
def login_user():
    login_id_receive = request.form['login_id_give']
    login_pw_receive = request.form['login_pw_give']
    m = hashlib.sha256()
    m.update(login_pw_receive.encode('utf-8'))
    login_pw_hash = m.hexdigest()
    id_find = list(db.register.find({'login id':login_id_receive}, {'_id': False,'login id':True}))
    pw_find = list(db.register.find({'password':login_pw_hash}, {'_id': False,'password':True}))

    if len(id_find) == 0:
        msg = '존재하지 않는 아이디입니다!'
    elif len(pw_find) == 0:
        msg = '비밀번호가 일치하지 않습니다! '
    else:
        name_find = list(db.register.find({'login id': login_id_receive}, {'_id': False, 'name': True}))
        name_find = name_find[0]['name']
        msg = '{}님 어서요세요!'.format(name_find)
    return jsonify({'msg': msg})

비밀번호와 아이디를 DB에서 검색해서 일치 여부에 따른 메시지를 반환하게 되고, JS에서는 그 메시지를 그대로 띄우면 된다.

그니까 이런거지.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

To-do list 바닐라JS로 만들기

Coding/JavaScript 2022. 8. 19. 01:47

쇼핑몰이요? 그거 로그인은 둘째치고 SSL 설정 개꼬여서 걍 인스턴스 엎고 만들라고… 아니 nginx 깔았는데 uWSGI에서 막혀서 하루종일 그거 찾았다니까. 아무도 안 알려줘 그걸…

 

깝깝해서 오라클 클라우드도 알아봐야 하나 생각중임 지금..


Reference

https://woojong92.tistory.com/entry/JS-%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A1%9C-ToDo-List-%EB%A7%8C%EB%93%A4%EA%B8%B0-1-%EA%B8%B0%EB%8A%A5%EC%A0%95%EC%9D%98-%EB%B0%8F-HTMLCSS

 

[JS] 바닐라 자바스크립트로 ToDo-List 만들기 - (1) 기능정의 및 HTML/CSS

목차 기능정의 및 HTML/CSS 할 일 추가하기 할 일 목록에서 할 일 삭제, 완료 처리 구현 할 일 수정하기 전체 완료 처리 및 남은 할 일 개수 하단 버튼 기능 구현 바닐라 자바스크립트를 이용해 todo-l

woojong92.tistory.com

단계별로 시리즈가 나뉘어져 있는데, 잘 따라하면 기본 기능정도는 구현할 수 있다.

 

To-do list의 CRUD

전에 쇼핑몰 얘기 하면서 설명했던 CRUD에 대해 잠깐 짚고 넘어가보자. CRUD는 생성/열람/수정/삭제 네 가지라고 했는데… 아니 거기도 크루드가? ㅇㅇ 있음. 어지간한 프로그램에는 다 있다. 여기서 구현할 최소한의 요소이기도 하고… 전에 유기체의 4대 요소(탄수화물/지질/단백질/핵산) 얘기하면서 비슷한거라고 했는데 ㄹㅇ 비슷하다.

 

그럼 여기서 CRUD는 뭘까? 

  1. Create: 할 일을 추가한다 
  2. Read: 추가한 할 일을 읽는다 
  3. Update: 할 일의 내용이나 상태를 수정한다
  4. Delete: 할 일을 삭제한다 

미리 스포하자면 3번이 제일 빡셌음. 

 

Create

할 일을 목록에 추가하는 기능이다. 일단 거두절미하고 짤로 보자.

아니 뭐야 이걸 어케해요!!! 아이 나도 했음. 

const todoText = document.querySelector('.todo-text')
const todoAdd = document.querySelector('.todo-add')
const todoDelete = document.querySelector('.delete')
const todoEdit = document.querySelector('.modify')
const todoCheck = document.querySelector('.checkbox')
const todoList = document.querySelector('.todo-list')

let todo = []
let id = 0;
//변수!! 

function setTodo(newTodo) {
    todo = newTodo
}

function getTodo() {
    return todo
}

function addTodo(text) {
    const newId = id++
    const newTodo = getTodo().concat({id: newId, isCompleted: false, content: text })
    setTodo(newTodo)
    displayTodo()
}

function displayTodo() {
    todoList.innerHTML = null;
        const allTodo = getTodo()

    allTodo.forEach(function(todo){
        const todoItem = document.createElement('li')
        const todoCheck = document.createElement('div')
        const todoCont = document.createElement('div')
        const todoBtn = document.createElement('div')
        const todoEdit = document.createElement('button')
        const todoDel = document.createElement('button')

        todoItem.classList.add('todo-item')
        todoCheck.classList.add('checkbox')
        todoCont.classList.add('todo')
        todoBtn.classList.add('todo-button-group')
        todoEdit.classList.add('modify')
        todoDel.classList.add('delete')
        console.log(todoDel)
        todoCont.innerText = todo.content
        todoEdit.innerHTML = '<i class="fa-solid fa-pen"></i>'
        todoDel.innerHTML = '<i class="fa-solid fa-trash-can"></i>'

        if(todo.isCompleted) {
            todoItem.classList.add('checked');
            todoCheck.innerHTML = '<i class="fa-solid fa-check"></i>'
        }

        todoList.appendChild(todoItem)
        todoItem.appendChild(todoCheck)
        todoItem.appendChild(todoCont)
        todoItem.appendChild(todoBtn)
        todoBtn.appendChild(todoEdit)
        todoBtn.appendChild(todoDel)
    })
    
}

function init() {
    todoText.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            addTodo(e.target.value);
            todoText.value ='';
        }
    })
}

init()

이게 문제의 코드. 근데 이렇게 했더니 추가가 이상하게 되는겨. 그래서 봤지.

<li class="todo-item">
<div class="checkbox"></div>
<div class="todo">삼시세끼 버터먹는 세토 혼내주기</div>
<div class="todo-button-group">
	<button class="modify"><i class="fa-solid fa-pen"></i></button>
	<button class="delete"><i class="fa-solid fa-trash-can"></i></button>
</div>
</li>

HTML 구조가 이렇게 되어 있는데, appendChild를 죄다 리스트에 줘버렸기 때문… 그러니 CSS도 적용이 안되고 개판 5분전인 리스트가 탄생한 것이다. (li가 아이템이고 ul이 리스트)

todoList.appendChild(todoItem)
todoItem.appendChild(todoCheck)
todoItem.appendChild(todoCont)
todoItem.appendChild(todoBtn)
todoBtn.appendChild(todoEdit)
todoBtn.appendChild(todoDel)

그래서 각각 구조에 맞게 appendChild를 다시 설정하니까 됐다. (박수)

 

Update/Delete

업데이트는 두개다. 하나는 상태를 수정하는것(다 했는지 아닌지)이고 하나는 내용을 수정하는 것. 근데 업데이트 어렵다면서요? 네, 내용 수정이요. 그럼 이번에도 짤로 한번 보자. 참고로 이번에 삭제와 함께 추가하는 업데이트는 상태 업데이트다. 

function deleteTodo(todoId) {
    const newTodo = getTodo().filter(todo => todo.id !== todoId )
    setTodo(newTodo)
    displayTodo()
}

function completeTodo(todoId) {
    const newTodo = getTodo().map(todo => todo.id === todoId ? {...todo,  isCompleted: !todo.isCompleted} : todo )
    setTodo(newTodo)
    displayTodo()
}

각 버튼에 이벤트 리스너를 추가할건데, 그 전에 이렇게 함수를 세팅하면 된다. 위에서 []로 선언했는데, 이게 뭔 소리냐면 할 일이 ‘배열’이라는 얘기다. 그래서 삭제는 삭제할 id를 제외한 다른 요소들을 보여주는 것. …근데 completed는 모르것음.

todoCheck.addEventListener('click',() => completeTodo(todo.id))
todoDel.addEventListener('click', () =>  deleteTodo(todo.id))

아무튼 각 버튼에 이벤트 리스너를 추가했다. click은 이 이벤트가 ‘클릭하면’ 실행되게 하는것이라고 보면 된다. Keypress는 키 입력. (추가가 Keypress이다)

function init() {
    todoText.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            addTodo(e.target.value);
            todoText.value ='';
        }
    })
}

이게 추가 코드. 잘 보면 keypress가 있고 밑에 e.key === ‘Enter’가 있는데, 키(엔터키)를 누르면 이 이벤트를 실행해줘라 이런 얘기.

 

Update(내용 수정)

일단 이것도 짤로 보자.

일단 내용 수정을 위해서는 두 가지가 있어야 하는데

  1. 버튼을 누르면 수정할 내용 입력을 위한 input창을 호출하고(다른 부분을 누르면 닫고)
  2. 수정한 내용을 반영한다

이렇게 두 개이다. 

function modifyTodo(text, todoId) {
    const currentTodo = getTodo()
    const newTodo = currentTodo.map(todo => todo.id === todoId ? ({...todo, content: text}) : todo)
    setTodo(newTodo)
    displayTodo()
}

그럼 함수 소환해주시고…

function summonTodo(e, todoId) {
    const todoElem = e.target
    const inputText = e.target.innerText
    const todoItemElem = document.querySelector('.modify')
    const todoEdit = document.createElement('input')

    todoEdit.value = inputText
    todoEdit.classList.add('todo-text-edit')
    todoEdit.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            modifyTodo(e.target.value, todoId)
            document.body.removeEventListener('click', onClickBody)
        }
    })

    const onClickBody = (e) => {
        if (e.target !== todoEdit) {
            todoItemElem.removeChild(todoEdit)
            document.body.removeEventListener('click', onClickBody)
        }
    }

    todoItemElem.appendChild(todoEdit)
    document.body.removeEventListener('click', onClickBody)
}

위쪽 if문은 엔터키가 눌렸을 때 수정한 내용을 반영하라는 얘기이고, 아래쪽에 const는 입력창이 아닌 다른 곳을 클릭하면 닫으라는 얘기다. todoEdit에 createElement를 주고 appendChild를 쓰면 원하는 곳에 생성할 수도 있다. 

todoEdit.addEventListener('dblclick',(event) => summonTodo(event,todo.id))

클릭하면 자꾸 1+1로 젠돼서 더블클릭 해놨음…


전체 코드

HTML

<html>

<head>
    <title>To-Do List</title>
    <link href="style.css" rel="stylesheet">
    <script src="https://kit.fontawesome.com/dc58858c96.js" crossorigin="anonymous"></script>
</head>

<body>
    <div class="todo-wrapper">
        <div class="todo-title">
            To-Do List
        </div>
        <div class="todo-body">
            <div class="todo-input">
                <input type="text" class="todo-text" placeholder="할 일을 입력하고 엔터키를 빡!!!(너무 세게 치면 컴퓨터 고장나요)">
                <button class="todo-add"><i class="fa-solid fa-plus"></i></button>
            </div>
            <div class="todo-post">
                <ul class="todo-list">
                    <!--<li class="todo-item checked">
                        <div class="checkbox"><i class="fa-solid fa-check"></i></div>
                        <div class="todo">니나브랑 발탄 찜갈비 먹기</div>
                        <div class="todo-button-group">
                            <button class="modify"><i class="fa-solid fa-pen"></i></button>
                            <button class="delete"><i class="fa-solid fa-trash-can"></i></button>
                        </div>
                    </li>
                    <li class="todo-item">
                        <div class="checkbox"></div>
                        <div class="todo">삼시세끼 버터먹는 세토 혼내주기</div>
                        <div class="todo-button-group">
                            <button class="modify"><i class="fa-solid fa-pen"></i></button>
                            <button class="delete"><i class="fa-solid fa-trash-can"></i></button>
                        </div>
                    </li>
                    <li class="todo-item">
                        <div class="checkbox"></div>
                        <div class="todo">알비온 밥주기</div>
                        <div class="todo-button-group">
                            <button class="modify"><i class="fa-solid fa-pen"></i></button>
                            <button class="delete"><i class="fa-solid fa-trash-can"></i></button>
                        </div>
                    </li>-->
                </ul>
            </div>
        </div>
        <p class="info">아, 근데 저장은 안됩니다. </p>
    </div>
    <script src="script.js"></script>
</body>

</html>

 

CSS

@font-face {
    font-family: 'DalseoHealingBold';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2207-01@1.0/DalseoHealingBold.woff2') format('woff2');
    font-weight: 700;
    font-style: normal;
}

* {
    margin: 0;
    padding: 0;
    font-family: 'DalseoHealingBold';
    font-size: 15pt;
    color: #2b2d42;
}

html {
    height: 100%;
}

body {
    display: flex;
    flex-wrap: nowrap;
    justify-content: center;
    background-color: #fcfcfc;
    min-height: 100%;
}

li {
    list-style-type: none;
}

button {
    background-color:transparent;
    border: 0;
}

.todo-wrapper {
    justify-content: center;
    margin-top: 3rem;
    min-width: 600px;
}

.todo-title {
    font-size:3em;
    text-align:center;
    padding:1em;
}

.todo-body {
    background-color: #fefefe;
}

.todo-input {
    display: flex;
    flex-wrap: nowrap;
    flex-direction: row;
    height: 3em;
    border: 1px solid #2b2d42;
    justify-content: center;
    align-items: center;
}

.todo-text {
    width: 80%;
    text-align: center;
    border: 0;
    outline: none;
    font-size: 1.3em;
    border-bottom:2px solid #E2DCC8;
    margin-right:20px;
}

.todo-text-edit {
    text-align: center;
    border: 0;
    outline: none;
    font-size: 1.1em;
    border-bottom:2px solid #E2DCC8;
    max-width:280px;
}

.todo-add {
    width: 1.3em;
    height: 1.3em;
    border-radius: 50px;
    cursor: pointer;
    font-size: 1.3em;
    border:2px solid #E2DCC8;
}

.todo-list {
    display:grid;
    grid-template-columns: repeat(3, 1fr);
    margin-top:1em;
    width:960px;
    gap: 10px;
}

.todo-item {
    background-color:#a9def9;
    padding:5px;
    box-shadow: 3px 3px 5px gray;
    border-radius:5px;
}

.todo-item:nth-child(2n) {
    background-color:#fcf6bd;
}

.checkbox {
    width: 1.5rem;
    height: 1.5rem;
    margin: 0.5rem 0.5rem;
    border-radius: 50px;
    border: 1px solid #2b2d42;
    cursor: pointer;
    text-align: center;
}

.checkbox > i {
    margin-top: 0.2em;
}

.todo-item .todo {
    padding:10px;
    font-size:1.1em;
}

.todo-item.checked .todo {
    font-style: italic;
    text-decoration: line-through;
    color:#354f52;
}

.todo-button-group {
    display:flex;
    flex-direction: row-reverse;
    flex-wrap: nowrap;
}

.modify, .delete {
    width: 1.3em;
    height: 1.3em;
    font-size:1.1em;
}

.info {
    text-align:center;
    margin-top:1.5em;
}

 

JS

const todoText = document.querySelector('.todo-text')
const todoAdd = document.querySelector('.todo-add')
const todoDelete = document.querySelector('.delete')
const todoEdit = document.querySelector('.modify')
const todoCheck = document.querySelector('.checkbox')
const todoList = document.querySelector('.todo-list')

let todo = []
let id = 0;
//변수!! 

function setTodo(newTodo) {
    todo = newTodo
}

function getTodo() {
    return todo
}

function addTodo(text) {
    const newId = id++
    const newTodo = getTodo().concat({id: newId, isCompleted: false, content: text })
    setTodo(newTodo)
    displayTodo()
}

function deleteTodo(todoId) {
    const newTodo = getTodo().filter(todo => todo.id !== todoId )
    setTodo(newTodo)
    displayTodo()
}

function completeTodo(todoId) {
    const newTodo = getTodo().map(todo => todo.id === todoId ? {...todo,  isCompleted: !todo.isCompleted} : todo )
    setTodo(newTodo)
    displayTodo()
}

function modifyTodo(text, todoId) {
    const currentTodo = getTodo()
    const newTodo = currentTodo.map(todo => todo.id === todoId ? ({...todo, content: text}) : todo)
    setTodo(newTodo)
    displayTodo()
}

function summonTodo(e, todoId) {
    const todoElem = e.target
    const inputText = e.target.innerText
    const todoItemElem = document.querySelectorAll('.todo')
    const todoEdit = document.createElement('input')

    todoEdit.value = inputText
    todoEdit.classList.add('todo-text-edit')
    todoEdit.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            modifyTodo(e.target.value, todoId)
            document.body.removeEventListener('click', onClickBody)
        }
    })

    const onClickBody = (e) => {
        if (e.target !== todoEdit) {
            todoItemElem.removeChild(todoEdit)
            document.body.removeEventListener('click', onClickBody)
        }
    }

    todoItemElem[todoId].appendChild(todoEdit)
    document.body.removeEventListener('dblclick', onClickBody)
}

function displayTodo() {
    todoList.innerHTML = null;
        const allTodo = getTodo()

    allTodo.forEach(function(todo){
        const todoItem = document.createElement('li')
        const todoCheck = document.createElement('div')
        const todoCont = document.createElement('div')
        const todoBtn = document.createElement('div')
        const todoEdit = document.createElement('button')
        const todoDel = document.createElement('button')

        todoItem.setAttribute("data-id","todo.id")
        todoCheck.addEventListener('click',() => completeTodo(todo.id))
        todoDel.addEventListener('click', () => deleteTodo(todo.id))
        todoEdit.addEventListener('dblclick',(event) => summonTodo(event,todo.id))

        todoItem.classList.add('todo-item')
        todoCheck.classList.add('checkbox')
        todoCont.classList.add('todo')
        todoBtn.classList.add('todo-button-group')
        todoEdit.classList.add('modify')
        todoDel.classList.add('delete')
        todoCont.innerText = todo.content
        todoEdit.innerHTML = '<i class="fa-solid fa-pen"></i>'
        todoDel.innerHTML = '<i class="fa-solid fa-trash-can"></i>'

        if(todo.isCompleted) {
            todoItem.classList.add('checked');
            todoCheck.innerHTML = '<i class="fa-solid fa-check"></i>'
        }

        todoList.appendChild(todoItem)
        todoItem.appendChild(todoCheck)
        todoItem.appendChild(todoCont)
        todoItem.appendChild(todoBtn)
        todoBtn.appendChild(todoEdit)
        todoBtn.appendChild(todoDel)
    })
    
}

function init() {
    todoText.addEventListener('keypress',function(e){
        if (e.key === 'Enter') {
            addTodo(e.target.value);
            todoText.value ='';
        }
    })
}

init()
Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

원페이지 쇼핑몰 보강하기-익스트림 어드레스+익스트림 CRUD(준비)

Coding/JavaScript 2022. 8. 19. 01:37

생각해보니 주소는 유효성 검사를 어떻게 해야 하지? 싶어서 물어봤더니 다음API가 잘 되어 있다고 함.

그래서 주소를 그걸로 검색해서 우편번호랑 함께 받고, 상세주소만 별도로 입력받는다. (상세주소까지 입력해야 하는 데도 있고 필수는 아닌데도 봤음)

아무튼 그래서 ㄱㄱ함.


익스트림 어드레스

일단 입력을 받으려면 준비를 해야 한다. 그 준비가 뭐냐면

  1. 입력란을 만들고
  2. 입력란 아이디를 수정하고
  3. API 가져와서 연결하기

이다. MongoDB는 콜렉션이 없으면 알아서 만들기때문에 콜렉션 청소만 한번 해 주면 되므로 DB는 작업이 다 끝나면 그냥 비우는걸로…

아무튼 이게 그 결과다. 우편변호와 주소는 직접 입력하는 게 아니라 검색하면 가져오는거고, 그 다음에 사용자가 입력하는 부분은 상세주소이다. 그니까 자바스크립트가 받을때 저걸 우편번호/주소/상세주소로 따로 받고, 상세주소가 없으면 없다고 하는거다. 아무튼 그것때문에 app.py도 손보고(DB에는 얘가 추가함) 개고생함…

function searchaddress() {
    new daum.Postcode({
        oncomplete: function (data) {
            // 팝업에서 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.

            // 각 주소의 노출 규칙에 따라 주소를 조합한다.
            // 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
            var addr = ''; // 주소 변수

            //사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
            if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
                addr = data.roadAddress;
            } else { // 사용자가 지번 주소를 선택했을 경우(J)
                addr = data.jibunAddress;
            }

            // 우편번호와 주소 정보를 해당 필드에 넣는다.
            document.getElementById('zipcode').value = data.zonecode;
            document.getElementById("address").value = addr;
            // 커서를 상세주소 필드로 이동한다.
            document.getElementById("detailed-address").focus();
        }
    }).open();
}

근데 그런것치고 주석 설명이 너무 잘 되어 있어서 예제 코드에서 적당히 쳐내고 getElementById부분 바꿔주니까 스무th하게 잘 됐다. 


익스트림 CRUD

CRUD는 Create, Read, Update, Delete의 준말로, 각각 생성/열람/수정/삭제를 의미한다. 무슨 유기체의 4대요소 이런건가 뭐 그렇게 거창한 건 아니지만, 우리가 사용하는 프로그램에는 이 네 가지 요소가 다 들어가 있다. …뭐야 이거 진짜 유기체의 4대요소 비슷한거였네? (유기체 4대요소: 탄수화물/단백질/지질/핵산) 비타민은요 걔는 영양소임 

뭔 소린지 잘 모르겠다고? 그렇다면 핸드폰에 있는 전화번호부를 예로 들어보자.

 

  1. 다이얼에 입력한 번호를 전화번호부에 추가(Create)할 수 있고
  2. 전화번호부에 있는 전화번호를 열람(Read)할수 있고
  3. 전화번호 주인이 바뀌거나 번호가 바뀌면 이걸 수정(Update)할 수 있고
  4. 빠이짜이찌엔 할 거면 삭제(Delete)할 수도 있다 보통 손절각 재면 삭제가 아니라 차단박고 스팸처리한다

이게 CRUD다.

 

그럼 지금 여기에 있는 요소는? C랑 R만 있다. 주문정보를 입력받아서 DB에 새로 쓸 수 있고, DB에 있는 주문 정보를 불러와서 열람할 수 있다. U랑 D에 관한 걸 추가하려면 밑작업이 필요하다. 세상천지 어느 쇼핑몰에서 다른 고객이 주문한 내역을 보여주겠는가? 그리고 주문 변경이나 취소도 남의 주문이 아니라 내 주문에 대해서만 취소할 수 있다. 서울사는 김서방이 주문한 걸 내가 열람하거나 취소할 수는 없잖음? 그래서 U와 D의 권한은 일반 게스트가 아닌 ‘관리자’에게 줄 것이다.

 

여기서 중요한 포인트는 관리자 이외에 다른 사용자의 CRUD는 ‘내가 주문한 것’에 한해서만 이루어져야 한다. 위에도 썼지만 옆집 김씨가 어디서 뭘 사든 나는 모르고, 뒷집 박씨도 내가 어디서 뭘 샀는지 모른다. 그리고 옆집 김씨가 주문한 걸 내가 임의로 취소시키거나 변경할 수도 없고(그래서도 안되고), 내가 주문한 걸 뒷집 박씨가 주문 취소하거나 변경할 수도 없다. 언더스탠? 

비로그인 상태의 화면이다. 저기가 로그인하면 내 정보와 로그아웃으로 바뀐다.

회원가입
로그인

그리고 로그인과 회원가입은 모달 윈도우를 통해서 진행하게 된다. 준비과정이 왜 이렇게 복잡하냐… 원래 권한이라는 건 그렇다… 쓸때는 쉬워보여도 막상 만들려고 하면 개빡센게 정상이다. 

 

일단 최종적으로 구현하고자 하는 기능은 다음과 같다.

  1. 로그인/로그아웃
  2. 회원가입(입력폼 유효성 검사)->시험용으로 관리자(판매자) 계정 하나와 게스트 계정 하나 생성 및 권한 분배
  3. 회원 정보 수정
  4. 주문 내역 확인 기능(계정 권한에 따라 상이)
  5. 주문 내용 수정(진행 상태를 수정)
Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

만들 수 있다! 이미지 슬라이더!

Coding/JavaScript 2022. 8. 19. 01:29

단점: 아직 자동재생이 안됨


기본편

참고 사이트: 

https://penguingoon.tistory.com/255

 

자바스크립트 이미지 슬라이드 구현하기

안녕하세요! 오늘은 라이브러리 없이 적은 양의 자바스크립트 코드만으로 간단한 이미지 슬라이더를 구현해보았습니다. 이미지 슬라이더 구현하는 방법이야 한트럭이지만, 그 중에 한 가지 방

penguingoon.tistory.com

하단에 있는 이전/다음버튼을 누르면 그림이 움직인다.

 

HTML

<!DOCTYPE html>
<html>

<head>
    <link href="style.css" rel="stylesheet">
    <title>저세상 이미지 슬라이더</title>
</head>

<body>
    <div class="container">
        <h1>Dotted wallpaper 둘러보기</h1>
        <div class="slider">
            <div class="image">
                <img src="https://i.imgur.com/ExRqDrt.png">
                <img src="https://i.imgur.com/XOLeZVs.png">
                <img src="https://i.imgur.com/7eHRY2s.png">
                <img src="https://i.imgur.com/WoZAGSa.png">
                <img src="https://i.imgur.com/ADHH2MR.png">
            </div>
        </div>
        <button class="prev">이전</button>
        <button class="next">다음</button>
    </div>
    <script src="script.js">
    </script>
    <div class="text">
        <p>본 이미지 슬라이더에 사용된 이미지는 전부 수제입니다. 도트찍느라 고생했습니다... </p>
    </div>
</body>

</html>

사실 얘는 뼈대라 특별히 설명할 건 없다. 응용편의 경우 밑에 뭐가 하나 더 들어간다는 것 빼곤 진짜 특별할 게 없다. 

 

CSS

@import url('https://fonts.googleapis.com/css2?family=Gaegu&display=swap');

* {
    font-family: 'Gaegu', cursive;
}

.container {
    width:972px;
    margin:0 auto;
}

.text{
    text-align:center;
    font-size:13pt;
}

h1 {
    text-align:center;
}

.slider {
    height:600px;
    width:972px;
    overflow:hidden; /*한 장만 보여주기 위해 필요*/
}

.image {
    position:relative;
    display:flex;
    height:600px;
}

img {
    width:972px;
    height:600px;
}

button, button:active, button:focus {
    width:100px;
    height:45px;
    font-size:16pt;
    border:1px dotted #00498c;
    color:#00498c;
    background-color:white;
    outline:none;
}

button:hover {
    background-color:#00498c;
    color:white
}

button:disabled {
    border:1px dotted gray;
    color:gray;
}

button:disabled:hover {
    background-color:gray;
    color:white;
}

.prev {
    float:left;
}

.next {
    float:right;
}

얘 응용편 코드는 네이버에서 짤리게 생겼어… ㄷㄷ

 

JS

let curPos = 0;
let position = 0; 
const IMAGE_WIDTH = 972;
const prevBtn = document.querySelector(".prev")
const nextBtn = document.querySelector(".next")
const images = document.querySelector(".image")
//const는 뭐지? 

function prev() {
    if (curPos > 0) {
        nextBtn.removeAttribute("disabled");
        position += IMAGE_WIDTH;
        images.style.transform = `translateX(${position}px)`;
        curPos = curPos - 1;
    }
    if(curPos == 0){
        nextBtn.removeAttribute("disabled");
        position -= (IMAGE_WIDTH * 5);
        images.style.transform = `translateX(0)px)`;
        curPos = 5;
    }
}
//이전 버튼

function next() {
    if (curPos < 5) {
        prevBtn.removeAttribute("disabled");
        position -= IMAGE_WIDTH;
        images.style.transform = `translateX(${position}px)`;
        curPos = curPos + 1;
    }
    if(curPos == 5){
        prevBtn.removeAttribute("disabled");
        position += (IMAGE_WIDTH * curPos);
        images.style.transform = `translateX(${position}px)`;
        curPos = 0;
    }
}
//다음 버튼

function init(){
    prevBtn.addEventListener("click", prev)
    nextBtn.addEventListener("click", next)
    }
init();

사실상 얘가 코어다. 그니까 대충 HTML은 뼈대, CSS는 살, JS는 작동을 담당한다고 보면 된다.

 

어? 근데 HTML코드에 onclick이 없네요? 그것은 init()이라는 함수에서 addEventListener로 prev와 next 버튼을 클릭하면 해당 함수를 불러라~ 라고 설정했기 때문이다. 원래는 이전버튼 비활성화도 같이 했었는데 뺑뺑이 돌리면서 그건 빼버렸다. 그럼 로직이 어떻게 되느냐…

function next() {
    if (curPos < 5) {
        prevBtn.removeAttribute("disabled");
        position -= IMAGE_WIDTH;
        images.style.transform = `translateX(${position}px)`;
        curPos = curPos + 1;
    }
    if(curPos == 5){
        prevBtn.removeAttribute("disabled");
        position += (IMAGE_WIDTH * curPos);
        images.style.transform = `translateX(${0}px)`;
        curPos = 0;
    }
}

이건 Next버튼의 코드이다. transform은 CSS에서 어떤 요소를 변형하는것. 즉, Next 버튼을 누르면 position에서 이미지의 가로 길이(고정값)만큼 X좌표를 옮겨간다. 그럼 curPos가 5인 건? HTML 소스에 이미지 다섯 장 있었음.

 

즉, 이미지가 맨 마지막이 아닐 때는 다음 장으로 넘어가고, 이미지가 맨 마지막이면 다시 처음 이미지로 돌아간다. Prev 버튼은 반대로 맨 처음 이미지일 때 마지막으로 가고, 아니면 한장 뒤로 돌아가는 것. 

 

Prev와 next 버튼이 각각 맨 처음, 맨 끝일 때 처리는 참고문헌에 없었다. 그냥 내가 버튼 연타로 누르기 귀찮아서 추가했지… 


응용편

근데 이제 그 어디서 많이 본 그런…

 

HTML

<html>

<head>
    <link href="style.css" rel="stylesheet">
    <script src="https://kit.fontawesome.com/dc58858c96.js" crossorigin="anonymous"></script>
    <title>저세상 이미지 슬라이더 시즌 2</title>
</head>

<body>
    <div class="wrap">
        <h1>돌아온 이미지 슬라이더 feat. 폰트어썸</h1>
        <h2>만드느라 정말 고생했습니다... 특히 밑에 저 점 움직이는거... </h2>
        <div class="container">
            <button class="prev">
                <i class="fa-solid fa-angle-left" id="prev"></i>
            </button>
            <div class="slider">
                <div class="image">
                    <img src="https://i.imgur.com/girxIea.jpg">
                    <img src="https://i.imgur.com/vSDcWOZ.jpg">
                    <img src="https://i.imgur.com/PutJ2re.jpg">
                    <img src="https://i.imgur.com/sexakO0.jpg">
                    <img src="https://i.imgur.com/oLXNhqu.jpg">
                    <img src="https://i.imgur.com/3l9IcG3.jpg">
                    <img src="https://i.imgur.com/yAacpMb.jpg">
                    <img src="https://i.imgur.com/kPCC0eD.jpg">
                    <img src="https://i.imgur.com/oS0TfYm.jpg">
                    <img src="https://i.imgur.com/gxr9C6X.jpg">
                </div>
            </div>
            <button class="next">
                <i class="fa-solid fa-angle-right" id="next"></i>
            </button>
            <div class="bar">
                <div class="activate"></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
                <div></div>
            </div>
        </div>
        <script src="script.js"></script>
    </div>
</body>

</html>

잘 보면 slider 밑에 bar라는 div가 하나 더 추가됐는데, 이게

이거다. 왜 사이트에서 이벤트 공지나 배너같은 거 슬라이더에 올려두고 빙빙 돌릴 때 이미지가 쇽쇽 돌아갈때마다 저 점도 같이 움직이잖음?

 

CSS

@font-face {
    font-family: 'Goyang';
    src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/Goyang.woff') format('woff');
    font-weight: normal;
    font-style: normal;
}

* {
    margin: 0;
    padding: 0;
    font-family: 'Goyang';
    color:#2C3333;
    background-color:#E7F6F2;
}

h1, h2 {
    text-align:center;
    margin-bottom:20px;
}

.prev, .next {
    text-align: center;
    font-size:60pt;
}

button {
    border:none;
}

button:disabled > i {
    color:#A5C9CA;
}

.wrap {
    width: 1280px;
    margin:0 auto;
}

.container {
    width:1280px;
    display: grid;
    grid-template-columns: auto 960px auto;
    align-items: center;
}

.slider {
    width:960px;
    overflow:hidden;
}

img {
    width:960px;
}

.image {
    display:flex;
    position:relative;
    transition: all 1s;
}

.bar {
    grid-column: 2 / span 1;
    text-align:center;
    margin: 20px auto 0 auto;
    display:flex;
}

.bar div {
    width:20px;
    height:20px;
    background-color:#A5C9CA;
    margin: 0 10px 0 15px;
    border-radius:15px;
    transition: all 0.5s;
}

.bar .activate {
    background-color:#2C3333;
}

워… 안 짤린 게 기적이다… 아래쪽 bar에 대해서 CSS가 추가된 부분은 모양과 grid 위치 관련(상위 요소가 grid이다). activate 클래스는 현재 화면에 보이는 그림 위치이다. transition은 스무th하게 움직일 수 있게 설정해주는 거라고 보면 된다. 

 

JS

let curPos = 0;
let position = 0;
const IMAGE_WIDTH = 960;
const prevBtn = document.querySelector(".prev");
const nextBtn = document.querySelector(".next");
const images = document.querySelector(".image");
const dot = document.querySelectorAll('.bar > div');

function prev() {
    if (curPos > 0) {
        nextBtn.removeAttribute("disabled");
        position += IMAGE_WIDTH;
        images.style.transform = `translateX(${position}px)`
        dot[curPos].classList.remove("activate")
        curPos = curPos - 1; 
        dot[curPos].classList.add("activate")
    }
    else if (curPos == 0) {
        nextBtn.removeAttribute("disabled");
        position -= (IMAGE_WIDTH * 6);
        images.style.transform = `translateX(0)px)`
        dot[curPos].classList.remove("activate")
        curPos = 6;
        dot[curPos].classList.add("activate")
    }
}

function next() {
    if (curPos < 9) {
        prevBtn.removeAttribute("disabled");
        position -= IMAGE_WIDTH;
        images.style.transform = `translateX(${position}px)`
        dot[curPos].classList.remove("activate")
        curPos = curPos + 1
        dot[curPos].classList.add("activate")

    }
    else if (curPos == 9) {
        prevBtn.removeAttribute("disabled");
        position += (IMAGE_WIDTH * curPos);
        images.style.transform = `translateX(${position}px)`
        dot[curPos].classList.remove("activate")
        curPos = 0;
        dot[curPos].classList.add("activate")
    }
}

function init() {
    prevBtn.addEventListener("click", prev)
    nextBtn.addEventListener("click", next)
    dot[curPos].classList.add("activate")
}

init();

위에 그 점같은 게 그림이 움직일 때 같이 움직여야 한다고 했잖음? 그래서 이전-다음버튼을 누를 때 이 점같은 게 색깔이 바뀌어야 하는데, 그걸 클래스 추가/제거를 통해서 하게 된다. 그래서 init 함수에서 0번 도트에 클래스를 추가하게 된다. (저게 배열로 가져오더라고…)

 

아무튼 그래서 이미지가 스무th하게 움직이는 어디서 많이 본 슬라이더가 탄생했다. 

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

원페이지 쇼핑몰 보강하기-유효성 검사와 공백처리

Coding/JavaScript 2022. 8. 19. 01:24

내 드디어!!! 서버에 올렸어!!! 여러분 이거 해써여!!! 하고 올렸는데… 피드백 온 것 중에

  1. 어? 이거 다 입력 안 해도 주문됐다고 뜨는데요?
  2. 유효성 검사 추가해주세요.

그게 뭔데요 감마펑션때 온갖가지 경우의 수 다 따져놓고 뭐한겨… (사실 몰랐음) 그래서 보강함. 아 그 HTTP HTTPS로 바꾸는거는… 내가 해봤어요 해봤는데… 그거 따라했는데 접속 안돼서 인스턴스 버렸어…


스크립트파일 분리

이게 과제 할 때는 스크립트고 CSS고 다 HTML파일에 박혀있었는데… 이게 코드 규모가 작으면 그래도 되요 그래도 되는데… 코드 규모가 커지면 인제 골치아퍼… 수정하다 욕해요… 그래서 분리할거다.

파이참에서 Flask project를 만들면 이렇게 된다. 이게 Flask 프로젝트의 국룰인데 HTML파일은templates 폴더에, CSS와 JS는 static 폴더에 넣는다. (프로젝트 제한효소는 CSV파일도 저기 있다)

그니까 이렇게 분리를 하고, HTML파일에서 해당하는 것들을 해당 파일로 분배해주면 된다. 아 근데 이러면 되냐고? 이렇게만 해 주면 HTML파일 입장에서는 소스가 있었는데 없었습니다가 된다. 그래서 HTML파일에게 여기 있던거 일로 뺐으니까 이 파일에서 갖다 쓰라고 해 줘야 하는데

<link rel="stylesheet" href="/static/style.css">
<script src="https://kit.fontawesome.com/dc58858c96.js" crossorigin="anonymous"></script>
<script src="/static/script.js"></script>

그게 이거다. 이걸 head태그 사이에 넣어주면 HTML파일이 찰떡같이 있었는데 없어질뻔한 스타일시트와 자바스크립트를 찾아온다. 두번째줄에 저거는 Fontawesome 관련된거니까 맨 위랑 맨 아래 두 개 넣자. 

 

공백과 형식 불일치 처리

저 빨간 텍스트는

  1. 빈 칸일 때 출력할 것
  2. 형식이 안 맞을 때 출력할 것(전화번호)

이다. span으로 넣었더니 줄바꿈이 안돼서 p태그 썼다.

function formcheck() {
    let ifvalidphone = /\d{3}-\d{4}-\d{4}/;
    let name = $('#name').val();
    let amount = $('#inputGroupSelect01').val();
    let addr = $('#address').val();
    let phone = $('#phone').val();
    let addrMsg = $('#address-required');
    let phoneMsg_empty = $('#phone-required');
    let phoneMsg_invalid = $('#phone-invalid');
    if (name.length == 0) {
        $('#name-required').css('display','block')
        $('#name').css('border','2px solid red')
    }
    if (addr.length == 0) {
        $('#address-required').css('display','block')
        $('#address').css('border','2px solid red')
    }
    if (phone.length == 0) {
        $('#phone-required').css('display','block')
        $('#phone').css('border','2px solid red')
    }

}

여기는 전체 코드가 jQuery라 팔할이 제이쿼리가 될 예정… 저 이프문이 뭐냐면

  1. 각 폼별 입력값을 받아와서
  2. 이름이랑 주소, 핸드폰이 공백일 때(입력값 길이가 0일때)
  3. 위에 있는 p태그를 보여주고
  4. 문제가 되는 입력폼을 강조해라

이런 얘기다. 근데 저렇게만 하면 스페이스 하나만 써도 뭔가 쓴 거라 저장이 된다. 에반데?

function formcheck() {
    let phone_valid = RegExp(/^010-\d{4}-\d{4}/)
    let name_space = RegExp(/^[가-힣]{2,6}$/)
    let mail_valid = RegExp(/^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/)
    let name = $('#name').val();
    let amount = $('#inputGroupSelect01').val();
    let addr = $('#address').val();
    let phone = $('#phone').val();
    let email = $('#email').val();
    let addrMsg = $('#address-required');
    let phoneMsg_empty = $('#phone-required');
    let phoneMsg_invalid = $('#phone-invalid');
    if (name.length == 0) {
        $('#name-required').css('display','block')
        $('#name-invalid').css('display','none')
        $('#name').css('border','2px solid red')
    }
    else if (!name_space.test(name)) {
        $('#name-required').css('display','none')
        $('#name-invalid').css('display','block')
        $('#name').css('border','2px solid red')
    }
    else {
        $('#name-required').css('display','none')
        $('#name-invalid').css('display','none')
    }
    if (email.length == 0) {
        $('#email-required').css('display','block')
        $('#email-invalid').css('display','none')
        $('#email').css('border','2px solid red')
    }
    else if (!mail_valid.test(email)) {
        $('#email-required').css('display','none')
        $('#email-invalid').css('display','block')
        $('#email').css('border','2px solid red')
    }
    else {
        $('#email-required').css('display','none')
        $('#email-invalid').css('display','none')
    }
    if (addr.length == 0) {
        $('#address-required').css('display','block')
        $('#address').css('border','2px solid red')
    }
    else {
        $('#address-required').css('display','none')
    }
    if (phone.length == 0) {
        $('#phone-required').css('display','block')
        $('#phone-invalid').css('display','none')
        $('#phone').css('border','2px solid red')
    }
    else if (!phone_valid.test(phone)) {
        $('#phone-required').css('display','none')
        $('#phone-invalid').css('display','block')
        $('#phone').css('border','2px solid red')
    }
    else {
        $('#phone-required').css('display','none')
        $('#phone-invalid').css('display','none')
    }
    if (!name.length == 0 && name_space.test(name) && !email.length == 0 && mail_valid.test(email) && !addr.length == 0 && !phone.length == 0 && phone_valid.test(phone)) {
        order()
    }

}

그래서 코드가 길어진거다. 아무튼 그렇다.

if (!name.length == 0 && name_space.test(name) && !email.length == 0 && mail_valid.test(email) && !addr.length == 0 && !phone.length == 0 && phone_valid.test(phone)) {
        order()
    }

}

이게 조건이 많은데, 요약하자면 모든 폼이 공백이 아니면서 기입을 제대로 했으면 order() 함수를 실행하고 DB로 넘겨라가 된다.

let phone_valid = RegExp(/^010-\d{4}-\d{4}/)
let name_space = RegExp(/^[가-힣]{2,6}$/)
let mail_valid = RegExp(/^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/)

여기서도 정규식씨가 열일하신다. 참고로 예전에는 010 말고도 011, 017, 016, 019가 있었지만 요즘은 010이다. 사실 국번을 제대로 입력하려면 국번이 2자리인 경우도 있으니 \d{2–3}이 되겠지만. 아무튼 그래서

평-범

공백

형식 망함

 

주소…도 저걸 해야 되는데… 주소는 몇자까지 해야되나…

 

+모바일 UI도 잡긴 잡았는데 표는 가로크기를 조절하면 가독성이 내 글씨보다도 수직하락해서 가로로 스크롤 추가했다.

Lv. 35 라이츄

Lv. 35 라이츄

광고 매크로 없는 청정한 블로그를 위해 노력중입니다. 근데 나만 노력하는 것 같음… ㅡㅡ

방명록