본문 바로가기
JavaScript

[JavaScript] 아이템 동적추가 삭제

by teamnova 2024. 6. 7.
728x90

 

오늘은 아이템 동적추가 및 삭제를 해보겠습니다. 

아래코드를 참고해주세요.

 

 

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Grocery Bud</title>
    <!-- font-awesome -->
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css"
    />
    <!-- styles -->
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <section class="section-center">
      <!-- form -->
      <form class="grocery-form">
        <p class="alert"></p>
        <h3>grocery bud</h3>
        <div class="form-control">
          <input type="text" id="grocery" placeholder="e.g. eggs" />
          <button type="submit" class="submit-btn">submit</button>
        </div>
      </form>
      <!-- list -->
      <div class="grocery-container">
        <div class="grocery-list"></div>
        <button class="clear-btn">clear items</button>
      </div>
    </section>
    <!-- javascript -->
    <script src="app.js"></script>
  </body>
</html>

 

 

styles.css

/*
=============== 
Fonts
===============
*/
@import url("https://fonts.googleapis.com/css?family=Open+Sans|Roboto:400,700&display=swap");

/*
=============== 
Variables
===============
*/

:root {
  /* dark shades of primary color*/
  --clr-primary-1: hsl(205, 86%, 17%);
  --clr-primary-2: hsl(205, 77%, 27%);
  --clr-primary-3: hsl(205, 72%, 37%);
  --clr-primary-4: hsl(205, 63%, 48%);
  /* primary/main color */
  --clr-primary-5: #49a6e9;
  /* lighter shades of primary color */
  --clr-primary-6: hsl(205, 89%, 70%);
  --clr-primary-7: hsl(205, 90%, 76%);
  --clr-primary-8: hsl(205, 86%, 81%);
  --clr-primary-9: hsl(205, 90%, 88%);
  --clr-primary-10: hsl(205, 100%, 96%);
  /* darkest grey - used for headings */
  --clr-grey-1: hsl(209, 61%, 16%);
  --clr-grey-2: hsl(211, 39%, 23%);
  --clr-grey-3: hsl(209, 34%, 30%);
  --clr-grey-4: hsl(209, 28%, 39%);
  /* grey used for paragraphs */
  --clr-grey-5: hsl(210, 22%, 49%);
  --clr-grey-6: hsl(209, 23%, 60%);
  --clr-grey-7: hsl(211, 27%, 70%);
  --clr-grey-8: hsl(210, 31%, 80%);
  --clr-grey-9: hsl(212, 33%, 89%);
  --clr-grey-10: hsl(210, 36%, 96%);
  --clr-white: #fff;
  --clr-red-dark: hsl(360, 67%, 44%);
  --clr-red-light: hsl(360, 71%, 66%);
  --clr-green-dark: hsl(125, 67%, 44%);
  --clr-green-light: hsl(125, 71%, 66%);
  --clr-black: #222;
  --ff-primary: "Roboto", sans-serif;
  --ff-secondary: "Open Sans", sans-serif;
  --transition: all 0.3s linear;
  --spacing: 0.25rem;
  --radius: 0.5rem;
  --light-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
  --dark-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
  --max-width: 1170px;
  --fixed-width: 620px;
}
/*
=============== 
Global Styles
===============
*/

*,
::after,
::before {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  font-family: var(--ff-secondary);
  background: var(--clr-grey-10);
  color: var(--clr-grey-1);
  line-height: 1.5;
  font-size: 0.875rem;
}
ul {
  list-style-type: none;
}
a {
  text-decoration: none;
}
img:not(.logo) {
  width: 100%;
}
img {
  display: block;
}

h1,
h2,
h3,
h4 {
  letter-spacing: var(--spacing);
  text-transform: capitalize;
  line-height: 1.25;
  margin-bottom: 0.75rem;
  font-family: var(--ff-primary);
}
h1 {
  font-size: 3rem;
}
h2 {
  font-size: 2rem;
}
h3 {
  font-size: 1.25rem;
}
h4 {
  font-size: 0.875rem;
}
p {
  margin-bottom: 1.25rem;
  color: var(--clr-grey-5);
}
@media screen and (min-width: 800px) {
  h1 {
    font-size: 4rem;
  }
  h2 {
    font-size: 2.5rem;
  }
  h3 {
    font-size: 1.75rem;
  }
  h4 {
    font-size: 1rem;
  }
  body {
    font-size: 1rem;
  }
  h1,
  h2,
  h3,
  h4 {
    line-height: 1;
  }
}
/*  global classes */

.btn {
  text-transform: uppercase;
  background: transparent;
  color: var(--clr-black);
  padding: 0.375rem 0.75rem;
  letter-spacing: var(--spacing);
  display: inline-block;
  transition: var(--transition);
  font-size: 0.875rem;
  border: 2px solid var(--clr-black);
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  border-radius: var(--radius);
}
.btn:hover {
  color: var(--clr-white);
  background: var(--clr-black);
}
/* section */
.section {
  padding: 5rem 0;
}

.section-center {
  width: 90vw;
  margin: 0 auto;
  max-width: 35rem;
  margin-top: 8rem;
}
@media screen and (min-width: 992px) {
  .section-center {
    width: 95vw;
  }
}
main {
  min-height: 100vh;
  display: grid;
  place-items: center;
}
/*
=============== 
Grocery List
===============
*/
.section-center {
  background: var(--clr-white);
  border-radius: var(--radius);
  box-shadow: var(--light-shadow);
  transition: var(--transition);
  padding: 2rem;
}
.section-center:hover {
  box-shadow: var(--dark-shadow);
}
.alert {
  margin-bottom: 1rem;
  height: 1.25rem;
  display: grid;
  align-items: center;
  text-align: center;
  font-size: 0.7rem;
  border-radius: 0.25rem;
  letter-spacing: var(--spacing);
  text-transform: capitalize;
}
.alert-danger {
  color: #721c24;
  background: #f8d7da;
}
.alert-success {
  color: #155724;
  background: #d4edda;
}
.grocery-form h3 {
  color: var(--clr-primary-1);
  margin-bottom: 1.5rem;
  text-align: center;
}
.form-control {
  display: flex;
  justify-content: center;
}
#grocery {
  padding: 0.25rem;
  padding-left: 1rem;
  background: var(--clr-grey-10);
  border-top-left-radius: var(--radius);
  border-bottom-left-radius: var(--radius);
  border-color: transparent;
  font-size: 1rem;
  flex: 1 0 auto;
  color: var(--clr-grey-5);
}
#grocery::placeholder {
  font-family: var(--ff-secondary);
  color: var(--clr-grey-5);
}
.submit-btn {
  background: var(--clr-primary-8);
  border-color: transparent;
  flex: 0 0 5rem;
  display: grid;
  align-items: center;
  padding: 0.25rem;
  text-transform: capitalize;
  letter-spacing: 2px;
  border-top-right-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
  cursor: pointer;
  content: var(--clr-primary-5);
  transition: var(--transition);
  font-size: 0.85rem;
}
.submit-btn:hover {
  background: var(--clr-primary-5);
  color: var(--clr-white);
}

.grocery-container {
  margin-top: 2rem;
  transition: var(--transition);
  visibility: hidden;
}
.show-container {
  visibility: visible;
}
.grocery-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
  transition: var(--transition);
  padding: 0.25rem 1rem;
  border-radius: var(--radius);
  text-transform: capitalize;
}
.grocery-item:hover {
  color: var(--clr-grey-5);
  background: var(--clr-grey-10);
}
.grocery-item:hover .title {
  color: var(--clr-grey-5);
}
.title {
  margin-bottom: 0;
  color: var(--clr-grey-1);
  letter-spacing: 2px;
  transition: var(--transition);
}
.edit-btn,
.delete-btn {
  background: transparent;
  border-color: transparent;
  cursor: pointer;
  font-size: 0.7rem;
  margin: 0 0.15rem;
  transition: var(--transition);
}
.edit-btn {
  color: var(--clr-green-light);
}
.edit-btn:hover {
  color: var(--clr-green-dark);
}
.delete-btn {
  color: var(--clr-red-light);
}
.delete-btn:hover {
  color: var(--clr-red-dark);
}
.clear-btn {
  text-transform: capitalize;
  width: 10rem;
  height: 1.5rem;
  display: grid;
  align-items: center;
  background: transparent;
  border-color: transparent;
  color: var(--clr-red-light);
  margin: 0 auto;
  font-size: 0.85rem;
  letter-spacing: var(--spacing);
  cursor: pointer;
  transition: var(--transition);
  margin-top: 1.25rem;
}
.clear-btn:hover {
  color: var(--clr-red-dark);
}

 

 

app.js

// ****** 요소 선택 **********

const form = document.querySelector(".grocery-form"); // 폼 요소 선택
const alert = document.querySelector(".alert"); // 알림 요소 선택
const grocery = document.getElementById("grocery"); // 입력 필드 선택
const submitBtn = document.querySelector(".submit-btn"); // 제출 버튼 선택
const container = document.querySelector(".grocery-container"); // 목록 컨테이너 선택
const list = document.querySelector(".grocery-list"); // 목록 요소 선택
const clearBtn = document.querySelector(".clear-btn"); // 전체 삭제 버튼 선택
// 수정 옵션
let editElement; // 수정할 요소
let editFlag = false; // 수정 모드 플래그
let editID = ""; // 수정할 항목의 ID
// ****** 이벤트 리스너 **********

// 폼 제출 이벤트 리스너
form.addEventListener("submit", addItem);
// 전체 삭제 버튼 클릭 이벤트 리스너
clearBtn.addEventListener("click", clearItems);
// 페이지 로드 시 항목 표시
window.addEventListener("DOMContentLoaded", setupItems);

// ****** 함수들 **********

// 항목 추가 함수
function addItem(e) {
  e.preventDefault();
  const value = grocery.value; // 입력된 값 가져오기
  const id = new Date().getTime().toString(); // 고유 ID 생성

  if (value !== "" && !editFlag) { // 새로운 항목 추가
    const element = document.createElement("article");
    let attr = document.createAttribute("data-id");
    attr.value = id;
    element.setAttributeNode(attr);
    element.classList.add("grocery-item");
    element.innerHTML = `<p class="title">${value}</p>
            <div class="btn-container">
              <!-- 수정 버튼 -->
              <button type="button" class="edit-btn">
                <i class="fas fa-edit"></i>
              </button>
              <!-- 삭제 버튼 -->
              <button type="button" class="delete-btn">
                <i class="fas fa-trash"></i>
              </button>
            </div>
          `;
    // 두 버튼에 이벤트 리스너 추가
    const deleteBtn = element.querySelector(".delete-btn");
    deleteBtn.addEventListener("click", deleteItem);
    const editBtn = element.querySelector(".edit-btn");
    editBtn.addEventListener("click", editItem);

    // 자식 요소로 추가
    list.appendChild(element);
    // 알림 표시
    displayAlert("리스트에 항목이 추가되었습니다", "success");
    // 컨테이너 표시
    container.classList.add("show-container");
    // 로컬 스토리지에 저장
    addToLocalStorage(id, value);
    // 기본 설정으로 되돌리기
    setBackToDefault();
  } else if (value !== "" && editFlag) { // 기존 항목 수정
    editElement.innerHTML = value;
    displayAlert("값이 변경되었습니다", "success");

    // 로컬 스토리지 수정
    editLocalStorage(editID, value);
    setBackToDefault();
  } else { // 입력 값이 없는 경우
    displayAlert("값을 입력해주세요", "danger");
  }
}

// 알림 표시 함수
function displayAlert(text, action) {
  alert.textContent = text;
  alert.classList.add(`alert-${action}`);
  // 일정 시간 후 알림 제거
  setTimeout(function () {
    alert.textContent = "";
    alert.classList.remove(`alert-${action}`);
  }, 1000);
}

// 항목 전체 삭제 함수
function clearItems() {
  const items = document.querySelectorAll(".grocery-item");
  if (items.length > 0) {
    items.forEach(function (item) {
      list.removeChild(item);
    });
  }
  container.classList.remove("show-container");
  displayAlert("목록이 비워졌습니다", "danger");
  setBackToDefault();
  localStorage.removeItem("list");
}

// 항목 삭제 함수
function deleteItem(e) {
  const element = e.currentTarget.parentElement.parentElement;
  const id = element.dataset.id;

  list.removeChild(element);

  if (list.children.length === 0) {
    container.classList.remove("show-container");
  }
  displayAlert("항목이 삭제되었습니다", "danger");

  setBackToDefault();
  // 로컬 스토리지에서 제거
  removeFromLocalStorage(id);
}

// 항목 수정 함수
function editItem(e) {
  const element = e.currentTarget.parentElement.parentElement;
  // 수정할 항목 설정
  editElement = e.currentTarget.parentElement.previousElementSibling;
  // 입력 필드에 값 설정
  grocery.value = editElement.innerHTML;
  editFlag = true;
  editID = element.dataset.id;
  //
  submitBtn.textContent = "수정";
}

// 기본 설정으로 되돌리기 함수
function setBackToDefault() {
  grocery.value = "";
  editFlag = false;
  editID = "";
  submitBtn.textContent = "추가";
}

// ****** 로컬 스토리지 **********

// 로컬 스토리지에 항목 추가
function addToLocalStorage(id, value) {
  const grocery = { id, value };
  let items = getLocalStorage();
  items.push(grocery);
  localStorage.setItem("list", JSON.stringify(items));
}

// 로컬 스토리지에서 항목 가져오기
function getLocalStorage() {
  return localStorage.getItem("list")
    ? JSON.parse(localStorage.getItem("list"))
    : [];
}

// 로컬 스토리지에서 항목 제거
function removeFromLocalStorage(id) {
  let items = getLocalStorage();

  items = items.filter(function (item) {
    if (item.id !== id) {
      return item;
    }
  });

  localStorage.setItem("list", JSON.stringify(items));
}

// 로컬 스토리지에서 항목 수정
function editLocalStorage(id, value) {
  let items = getLocalStorage();

  items = items.map(function (item) {
    if (item.id === id) {
      item.value = value;
    }
    return item;
  });
  localStorage.setItem("list", JSON.stringify(items));
}

// ****** 항목 설정 **********

// 로컬 스토리지에서 항목 설정
function setupItems() {
  let items = getLocalStorage();

  if (items.length > 0) {
    items.forEach(function (item) {
      createListItem(item.id, item.value);
    });
    container.classList.add("show-container");
  }
}

// 항목 생성 함수
function createListItem(id, value) {
  const element = document.createElement("article");
  let attr = document.createAttribute("data-id");
  attr.value = id;
  element.setAttributeNode(attr);
  element.classList.add("grocery-item");
  element.innerHTML = `<p class="title">${value}</p>
            <div class="btn-container">
              <!-- 수정 버튼 -->
              <button type="button" class="edit-btn">
                <i class="fas fa-edit"></i>
              </button>
              <!-- 삭제 버튼 -->
              <button type="button" class="delete-btn">
                <i class="fas fa-trash"></i>
              </button>
            </div>
          `;
  // 두 버튼에 이벤트 리스너 추가
  const deleteBtn = element.querySelector(".delete-btn");
  deleteBtn.addEventListener("click", deleteItem);
  const editBtn = element.querySelector(".edit-btn");
  editBtn.addEventListener("click", editItem);

  // 자식 요소로 추가
  list.appendChild(element);
}

 

 

 

 

 

 

 

'JavaScript' 카테고리의 다른 글

[Javascript] 화면내에 공 키보드로 이동 시키기  (0) 2024.06.17
[JavaScript] 아이템 슬라이드  (0) 2024.06.13
[Javascript] 문단 동적생성  (0) 2024.06.01
[Javascript] count-down timer  (0) 2024.05.26
[Javascript] tabs  (0) 2024.05.20