modal noteList

woolee의 기록보관소·2022년 11월 9일

FE 기능구현 연습

목록 보기
19/33

HTML

.add-note
note 추가 버튼
버튼 내부에 a링크 생성
+-- a 링크는 id 속성으로 내부 링크 기능을 구현할 수 있다. 같은 페이지 내 명시된 id를 가지고 있는 요소를 연결함. (a 요소 href에 #id 삽입)
버튼을 누르면 > 해당 id로 이동하고, 이동하는 순간 :target 발동되므로 여기에 css로 opacity:1;을 주면 열리는 인터랙션 구현 가능함.

.modal
.add-note 버튼을 누르면 등장할 모달창
+-- .header 모달창 제목
+-- title input창 1개
+-- note input창 1개
+-- submit input창 1개
+-- cancel 링크 버튼 1개

.note-table
작성한 타이틀/노트가 들어갈 공간 (table 구조)

<div class="add-note">
    <a href="#myModal" class="add-note">Add Note</a>
  </div>

  <div id="myModal" class="modal">
    <div class="modal-content">
      <h1 class="header">
        Add Note
      </h1>
      <form class="note-form" action="">
        <label for="">Title</label><br>
        <input type="text" class="title"><br>
        <label for="">Note</label><br>
        <input class="note" type="text"><br>
        <input class="submit" type="submit">
        <div class="cancel">
          <a href="#close" class="cancel-btn">Cancel</a>
        </div>
      </form>
    </div>
  </div>

  <table class="note-table">
    <tr>
      <td class="header">Title</td>
      <td class="header">Note</td>
    </tr>
  </table>

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

CSS

.add-note
add-note 버튼 스타일링
좌측 상단에 위치하도록
:hover도 잡아주기

input
입력창 스타일링
입력창 하단에 밑줄 스타일링 넣기

h1
모달창 제목 스타일링

.modal
모달창 스타일링
모달창은 사라지면 안 되므로 fixed; => 위치 배치 기준은 항상 뷰포트임.
.add-note의 a링크와 .modal의 id가 연결되어 있으므로(myModal라는 이름으로) add-note 버튼을 클릭하는 순간 모달창이 열리면 :target선택자 활성화됨.

.modal-content
모달창 내부 콘텐츠 영역 스타일링

.cancel, .submit
취소 제출 버튼 가운데 정렬

.note-table
값 저장될 공간 스타일링

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Montserrat', sans-serif;
}

a {
  text-decoration: none;
  color: #ffffff;
}

.add-note {
  position: relative;
  background-color: rgb(133, 251, 255);
  color: #000000;
  width: 8rem;
  padding: 0.5rem;
  border-radius: 5px;
  left: 10%;
  top: 5%;
}
.add-note:hover {
  opacity: 0.7;
}

body, 
html {
  width: 100%;
  height: 100vh;
  background: url('./modernArt.jpg');
  background-size: cover;
  color: #ffffff;
}

input {
  margin-bottom: 1rem;
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid #ffffff;
  color: #ffffff;
}

h1 {
  position: relative;
  text-align: center;
}

.modal {
  position: fixed;
  top: -20%;
  right: 0;
  bottom: 0;
  left: 0;
  padding: 1.5rem;
  pointer-events: none;
  z-index: 9999;
  opacity: 0;
  border: 1px solid rgba(0,0,0,0.192);
  transition: 0.2s ease-in;
  border-radius: 5px;
}

.modal:target {
  opacity: 1;
  top: 0;
  pointer-events: auto;
  background: rgba(0,0,0,0.5);
}

.modal-content {
  width: 400px;
  position: relative;
  margin: 10% auto;
  border: 1px solid rgba(0,0,0,0.192);
  padding: 2rem;
  border-radius: 5px;
  background-color: #ffffff5b;
  backdrop-filter: blur(10px);
}

.cancel, .submit {
  text-align: center;
  border: none;
  color: #ffffff;
  font-size: 1rem;
  cursor: pointer;
}

.note-table {
  position: relative;
  width: 40%;
  left: 10%;
  background: #000000b7;
  top: 10%;
  border-radius: 5px;
}

.header {
  color: rgb(133, 251, 255);
  padding: 1rem;
}

.noteItem {
  height: 2rem;
  z-index: 1;
}

td {
  padding: 1rem;
}

.delete-item {
  font-size: 1.5rem;
}

.delete-item:hover {
  color: rgb(133, 251, 255);
  cursor: pointer;
}

JS

// 변수

modal : 모달창 전체
noteForm : 전송창
noteTable : 전송된 값들 보여주는 공간
cancel : cancel 버튼

전송한 노트 데이터는 noteList 변수로 관리.
=> localStorage.setItem('변수명', 값)으로 '변수명'에 데이터를 저장할 수 있고, localStorage.getItem('변수명')으로 데이터 가져올 수 있음. 여기서는 'notes' 변수로 관리함.

// 이벤트

addNote()
noteForm에 submit 이벤트를 걸어주고 submit제출하면 addNote(e) 함수 실행

addNote(e)함수는,
e.preventDefault();로 기본 브라우저 동작 막아주고

사용자가 입력한 title, note의 value를 newNote 객체에 저장한 후에 title.value와 note.value 값은 초기화해주고

이 newNote 객체를 localStorage인 noteList에 push해준다. 그리고 제출했으면 창을 닫아야 하므로 cancel.click() 처리해준다. (a 요소에 대해 click()함수를 실행하면 자동으로 클릭처리해준다.)

+-- 여기서 cancel이 cancel-btn 클래스를 가진 a 요소로 연결되는데, 얘의 href가 #close로 적어놓음.

=> 근데 이게 #으로 시작하면 close라는 id를 가진 요소로 이동을 하는데, close 라는 id를 가진 요소가 있든 없든 전체 모달창의 :target이 해제되므로 opacity:0;으로 변경돼서 마치 취소 버튼 기능을 하는 것처럼 보이는 듯하다.

그리고 나서 appendNotes() 함수를 실행한다.

appendNotes()
appendNotes()는
noteList가 비어 있거나, addNote()로 값을 추가하거나, deleteNote()로 값을 삭제하거나 할 때 계속 실행되는 함수이다.
얘는 기본적으로 업데이트해주는 함수이므로, 기존 요소를 전부 삭제하고 localStorage에 있는 데이터로 다시 요소에 넣어서 업데이트 해주는 함수임.

remove()는 DOM 요소 제거하는 메서드

localStorage인 noteList 변수에 사용자가 입력한 값이 저장되어 있는 상태.

noteList를 map으로 순회하면서, tr과 td 요소를 각각 생성하고, 여기에 classList로 클래스를 붙여준다. 이때 목록에 대한 삭제 버튼인 tdDelete 요소도 생성해준다. (×는 html entities에서 x 버튼 모양을 의미함.)

tr 요소에 생성한 td들을 appendChild해준다.
그리고 noteTable에 tr 요소를 appendChild 해준다.

그리고 localStroage 'notes' 변수에 noteList를 생성해서 넣는다

그리고 getDeleteButtons() 함수를 실행해준다.

getDeleteButtons()
노트 데이터가 저장된 공간에서 x버튼을 누르면
deleteNote() 함수 실행

deleteNote(noteTitle)

let modal = document.querySelector('.modal');
let noteForm = document.querySelector('.note-form');
let noteTable = document.querySelector('.note-table');
let cancel = document.querySelector('.cancel-btn');

let noteDeleteButtons;
// let noteList = [];
let noteList = JSON.parse(localStorage.getItem('notes')) || [];

if (noteList !== null) {
  appendNotes();
}

noteForm.addEventListener('submit', (e) => {
  // console.log('submit');
  addNote(e);
});

function addNote(e) {
  e.preventDefault();
  
  let newNote = {};

  let title = document.querySelector('.title');
  let note = document.querySelector('.note');

  if (title.value == '' || note.value == '') {
    return alert('Please enter both fields.');
  } else {
    newNote.title = title.value;
    newNote.note = note.value;
  }

  title.value = '';
  note.value = '';

  noteList.push(newNote);
  appendNotes();
  cancel.click();
}

function appendNotes() {
  let notes = Array.from(document.querySelectorAll('.noteItem'));
  if (notes.length > 0) {
    notes.forEach(note => {
      note.remove();
    })
  }

  noteList.map(note => {

    // Create table cells
    let tr = document.createElement('tr');
    tr.classList = 'noteItem';
    let tdTitle = document.createElement('td');
    tdTitle.innerText = note.title;
    let tdNote = document.createElement('td');
    tdNote.innerText = note.note;
    let tdDelete = document.createElement('td');
    tdDelete.innerHTML = '&times';
    tdDelete.classList.add('delete-item');

    // Append cells to table row 
    tr.appendChild(tdTitle);
    tr.appendChild(tdNote);
    tr.appendChild(tdDelete);

    // Append row to table
    noteTable.appendChild(tr);
    getDeleteButtons();
    localStorage.setItem('notes', JSON.stringify(noteList));
  })
}

function getDeleteButtons() {
  noteDeleteButtons = Array.from(document.querySelectorAll('.delete-item'));

  noteDeleteButtons.forEach(button => {
    let noteTitle = button.previousSibling.previousSibling.innerText;
    console.log(noteTitle);

    button.addEventListener('click', () => {
      deleteNote(noteTitle);
    })
  })
}

function deleteNote(noteTitle) {
  for (let i=0; i<noteList.length; i++) {
    if (noteList[i].title == noteTitle) {
      noteList.splice(i, 1);
    }
  }
  localStorage.setItem('notes', JSON.stringify(noteList));
  appendNotes();
}

참고

Vanilla JavaScript: Creating a Glassmorphic Modal Note List App

profile
https://medium.com/@wooleejaan

0개의 댓글