Dandy Now!
  • [개발자의품격][부트캠프][1기][13차시] JavaScript 주요 포인트 #18 | DOM 패턴 - 추가, 저장, 삭제 페이지
    2022년 02월 22일 17시 44분 38초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    JavaScript 주요 포인트 #18

    DOM 패턴

    Step 1

    <!-- dom_add.html -->
    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
          .normal-table {
            border: 1px solid black;
            border-collapse: collapse;
            width: 100%;
          }
    
          .normal-table th,
          .normal-table td {
            border: 1px solid black;
            padding: 5px 10px;
          }
    
          .normal-table thead tr {
            background-color: yellow;
          }
    
          .hover tbody tr:hover {
            background-color: yellow;
          }
    
          .error {
            border: 2px dotted red;
          }
        </style>
      </head>
      <body>
        <button onclick="addLine();">추가</button>
        <button onclick="doSave();">저장</button>
        <button onclick="doDelete();" id="btnDelete" disabled>삭제</button>
        <table class="normal-table striped">
          <thead>
            <tr>
              <th><input type="checkbox" id="chks" onchange="checkAll()" /></th>
              <th>Name</th>
              <th>Company</th>
              <th>Gender</th>
              <th>Email</th>
              <th>Phone</th>
              <th>Address</th>
            </tr>
          </thead>
          <tbody id="tbBody"></tbody>
        </table>
        <script>
          // 행 추가
          function addLine() {
            const h = [];
            h.push("<tr>");
            h.push(
              `<td><input type="checkbox" name="chk" onchange="isChecked();" /></td>`
            );
            h.push(`<td><input type="text" name="name" /></td>`);
            h.push(`<td><input type="text" name="company" /></td>`);
            h.push(
              `<td><select name="gender"><option value="male">남자</option><option value="female">여자</option></select></td>`
            );
            h.push(`<td><input type="text" name="email" /></td>`);
            h.push(`<td><input type="text" name="phone" /></td>`);
            h.push(`<td><input type="text" name="address" /></td>`);
            h.push("</tr>");
    
            document
              .querySelector("#tbBody")
              .insertAdjacentHTML("beforeend", h.join("")); // beforeend는 #tbBody의 끝부분 전에 추가하라!
          }
    
          // 행 삭제
          async function doDelete() {
            const chks = document.querySelectorAll("[name=chk]:checked");
            // console.log(chks);
            chks.forEach((chk) => {
              let tr = chk;
              // while문을 써야 행 전체를 삭제할 수 있다. 그렇게 하지 않으면 체크박스만 삭제된다.
              while (tr.tagName !== "TR") {
                tr = tr.parentNode; // parentNode은 현재 node의 상위 node
              }
              // let tr = chk.parentNode.parentNode; // 깊이를 알수 있을 때는 while문 대신 이렇게도 쓸 수 있다.
              // console.log(tr);
              tr.remove();
            });
          }
    
          // 저장
          async function doSave() {
            const names = document.querySelectorAll("[name=name]");
            const companies = document.querySelectorAll("[name=company]");
            const genders = document.querySelectorAll("[name=gender]");
            const emails = document.querySelectorAll("[name=email]");
            const phones = document.querySelectorAll("[name=phone]");
            const addresses = document.querySelectorAll("[name=address]");
    
            const customers = [];
            const len = names.length;
            for (let i = 0; i < len; i++) {
              customers.push({
                name: names[i].value,
                company: companies[i].value,
                gender: genders[i].value,
                email: emails[i].value,
                phone: phones[i].value,
                address: addresses[i].value,
              });
            }
    
            if (confirm("정말 저장하시겠습니까?")) {
              for (const customer of customers) {
                const res = await fetch("http://localhost:3000/customers", {
                  method: "POST",
                  body: JSON.stringify(customer),
                  headers: {
                    "content-type": "application/json;charset=UTF-8",
                  },
                });
              }
            }
          }
    
          function checkAll() {
            const checkValue = document.querySelector("#chks").checked;
            const chks = document.querySelectorAll("[name=chk]");
            for (const chk of chks) {
              chk.checked = checkValue;
            }
    
            isChecked();
          }
    
          function isChecked() {
            const chks = document.querySelectorAll("[name=chk]:checked");
            if (chks.length > 0) {
              document.querySelector("#btnDelete").disabled = false;
            } else {
              document.querySelector("#btnDelete").disabled = true;
            }
          }
        </script>
      </body>
    </html>

     

    [그림 1] 추가 버튼 클릭 전

     

    [그림 2] 행 추가

     

    [그림 3] 체크된 행 삭제

     

    [그림 4] 저장 기능

     


     

    Step 2

    // dom_add.html
    <body>
      <button onclick="addLine();">추가</button>
      <button onclick="doSave();">저장</button>
      <button onclick="doDelete();" id="btnDelete" disabled>삭제</button>
      <table class="normal-table striped">
        <thead>
          <tr>
            <th><input type="checkbox" id="chks" onchange="checkAll()" /></th>
            <th>Name</th>
            <th>Company</th>
            <th>Gender</th>
            <th>Email</th>
            <th>Phone</th>
            <th>Address</th>
          </tr>
        </thead>
        <tbody id="tbBody"></tbody>
      </table>
      <script>
        function addLine() {
          const h = [];
          h.push("<tr>");
          h.push(
            `<td><input type="checkbox" name="chk" onchange="isChecked();" /></td>`
          );
          h.push(`<td><input type="text" name="name" /></td>`);
          h.push(`<td><input type="text" name="company" /></td>`);
          h.push(
            `<td><select name="gender"><option value="male">남자</option><option value="female">여자</option></select></td>`
          );
          h.push(`<td><input type="text" name="email" /></td>`);
          h.push(`<td><input type="text" name="phone" /></td>`);
          h.push(`<td><input type="text" name="address" /></td>`);
          h.push("</tr>");
    
          document
            .querySelector("#tbBody")
            .insertAdjacentHTML("beforeend", h.join(""));
        }
    
        async function doDelete() {
          const chks = document.querySelectorAll("[name=chk]:checked");
          // console.log(chks);
    
          chks.forEach((chk) => {
            let tr = chk;
            while (tr.tagName !== "TR") {
              tr = tr.parentNode;
            }
            // let tr = chk.parentNode.parentNode;
            // console.log(tr);
            tr.remove();
          });
        }
    
        async function doSave() {
          const names = document.querySelectorAll("[name=name]");
          const companies = document.querySelectorAll("[name=company]");
          const genders = document.querySelectorAll("[name=gender]");
          const emails = document.querySelectorAll("[name=email]");
          const phones = document.querySelectorAll("[name=phone]");
          const addresses = document.querySelectorAll("[name=address]");
    
          const customers = [];
          const len = names.length;
          let passRequired = true; // 통과 조건
          for (let i = 0; i < len; i++) {
            // 비어있는 값 체크
            if (
              names[i].value === "" ||
              companies[i].value === "" ||
              emails[i].value === "" ||
              phones[i].value === "" ||
              addresses[i].value === ""
            ) {
              passRequired = false;
            }
    
            customers.push({
              name: names[i].value,
              company: companies[i].value,
              gender: genders[i].value,
              email: emails[i].value,
              phone: phones[i].value,
              address: addresses[i].value,
            });
          }
    
          // 비어있는 값이 존재할 때
          if (!passRequired) {
            return alert("비어있는 값이 존재합니다. 모든 값을 입력하세요.");
          }
    
          if (confirm("정말 저장하시겠습니까?")) {
            for (const customer of customers) {
              const res = await fetch("http://localhost:3000/customers", {
                method: "POST",
                body: JSON.stringify(customer),
                headers: {
                  "content-type": "application/json;charset=UTF-8",
                },
              });
            }
          }
        }
    
        function checkAll() {
          const checkValue = document.querySelector("#chks").checked;
          const chks = document.querySelectorAll("[name=chk]");
          for (const chk of chks) {
            chk.checked = checkValue;
          }
    
          isChecked();
        }
    
        function isChecked() {
          const chks = document.querySelectorAll("[name=chk]:checked");
          if (chks.length > 0) {
            document.querySelector("#btnDelete").disabled = false;
          } else {
            document.querySelector("#btnDelete").disabled = true;
          }
        }
      </script>
    </body>

     

    [그림 5] 비어 있는 값 체크

     


     

    Step 3

    <!-- dom_add.html -->
    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
          .normal-table {
            border: 1px solid black;
            border-collapse: collapse;
            width: 100%;
          }
    
          .normal-table th,
          .normal-table td {
            border: 1px solid black;
            padding: 5px 10px;
          }
    
          .normal-table thead tr {
            background-color: yellow;
          }
    
          .hover tbody tr:hover {
            background-color: yellow;
          }
    
          .error {
            border: 2px dotted red;
          }
        </style>
      </head>
      <body>
        <button onclick="addLine();">추가</button>
        <button onclick="doSave();">저장</button>
        <button onclick="doDelete();" id="btnDelete" disabled>삭제</button>
        <table class="normal-table striped">
          <thead>
            <tr>
              <th><input type="checkbox" id="chks" onchange="checkAll()" /></th>
              <th>Name</th>
              <th>Company</th>
              <th>Gender</th>
              <th>Email</th>
              <th>Phone</th>
              <th>Address</th>
            </tr>
          </thead>
          <tbody id="tbBody"></tbody>
        </table>
        <script>
          function addLine() {
            const h = [];
            h.push("<tr>");
            h.push(
              `<td><input type="checkbox" name="chk" onchange="isChecked();" /></td>`
            );
            h.push(`<td><input type="text" name="name" /></td>`);
            h.push(`<td><input type="text" name="company" /></td>`);
            h.push(
              `<td><select name="gender"><option value="male">남자</option><option value="female">여자</option></select></td>`
            );
            h.push(`<td><input type="text" name="email" /></td>`);
            h.push(`<td><input type="text" name="phone" /></td>`);
            h.push(`<td><input type="text" name="address" /></td>`);
            h.push("</tr>");
    
            document
              .querySelector("#tbBody")
              .insertAdjacentHTML("beforeend", h.join(""));
          }
    
          async function doDelete() {
            const chks = document.querySelectorAll("[name=chk]:checked");
            // console.log(chks);
    
            chks.forEach((chk) => {
              let tr = chk;
              while (tr.tagName !== "TR") {
                tr = tr.parentNode;
              }
              // let tr = chk.parentNode.parentNode;
              // console.log(tr);
              tr.remove();
            });
          }
    
          async function doSave() {
            const names = document.querySelectorAll("[name=name]");
            const companies = document.querySelectorAll("[name=company]");
            const genders = document.querySelectorAll("[name=gender]");
            const emails = document.querySelectorAll("[name=email]");
            const phones = document.querySelectorAll("[name=phone]");
            const addresses = document.querySelectorAll("[name=address]");
    
            const regexpEmail = /^([a-z]+\d*)+(\.?[a-z]*)+@[a-z]+(\.[a-z]{2,3})+$/; // Email 형식 정규식
            const regexpPhone = /^010-\d{3,4}-\d{4}$/; // phone 형식 정규식
    
            const customers = [];
            const len = names.length;
            const blankRows = []; // 잘못된 형식의 입력 값 행 배열
            const wrongEmails = []; // 잘못된 형식의 Email 행 배열
            const wrongPhones = []; // 잘못된 형식의 phone 행 배열
            let passEmail = true; // 기본값 true
            let passPhone = true; // 기본값 true
            let passRequired = true; // 기본값 true
            for (let i = 0; i < len; i++) {
              if (
                names[i].value === "" ||
                companies[i].value === "" ||
                emails[i].value === "" ||
                phones[i].value === "" ||
                addresses[i].value === ""
              ) {
                passRequired = false;
                blankRows.push(i + 1); // alert 메시지에 행 번호 넣기. index가 0부터 시작하므로 +1
              }
    
              // Email 정규식 체크
              if (!regexpEmail.test(emails[i].value)) {
                passEmail = false;
                wrongEmails.push(i + 1);
              }
    
              // phone 정규식 체크
              if (!regexpPhone.test(phones[i].value)) {
                passPhone = false;
                wrongPhones.push(i + 1);
              }
    
              customers.push({
                name: names[i].value,
                company: companies[i].value,
                gender: genders[i].value,
                email: emails[i].value,
                phone: phones[i].value,
                address: addresses[i].value,
              });
            }
    
            // alert 메시지에 행 번호 넣기.
            if (!passRequired) {
              return alert(
                `${blankRows.join(
                  ","
                )}행에 비어있는 값이 존재합니다. 모든 값을 입력하세요.`
              );
            }
    
            // Email행 alert 메시지
            if (!passEmail) {
              return alert(
                `${wrongEmails.join(
                  ","
                )}행에 잘못된 형식의 이메일 값이 존재합니다. 올바른 형식으로 입력하세요.`
              );
            }
    
            // phone행 alert 메시지
            if (!passPhone) {
              return alert(
                `${wrongPhones.join(
                  ","
                )}행에 잘못된 형식의 전화번호 값이 존재합니다. 올바른 형식으로 입력하세요.`
              );
            }
    
            const failData = []; // 여러건을 선택해서 한꺼번에 저장할 경우 저장이 누락될 수도 있다. 이를 고려해야 한다.
            if (confirm("정말 저장하시겠습니까?")) {
              for (const customer of customers) {
                const res = await fetch("http://localhost:3000/customers", {
                  method: "POST",
                  body: JSON.stringify(customer),
                  headers: {
                    "content-type": "application/json;charset=UTF-8",
                  },
                });
    
                // 정상적인 경우는 201이다. 실패한 데이터를 push 한다.
                if (res.status !== 201) {
                  failData.push(customer);
                }
    
                // 저장되지 않은 데이터가 있음을 알림
                if (failData.length > 0) {
                  alert(`저장되지 않은 데이터가 ${failData.length}개 있습니다.`);
                } else {
                  alert("정상적으로 저장 되었습니다.");
                }
              }
            }
          }
    
          function checkAll() {
            const checkValue = document.querySelector("#chks").checked;
            const chks = document.querySelectorAll("[name=chk]");
            for (const chk of chks) {
              chk.checked = checkValue;
            }
    
            isChecked();
          }
    
          function isChecked() {
            const chks = document.querySelectorAll("[name=chk]:checked");
            if (chks.length > 0) {
              document.querySelector("#btnDelete").disabled = false;
            } else {
              document.querySelector("#btnDelete").disabled = true;
            }
          }
        </script>
      </body>
    </html>

     

    [그림 6] alert 메시지에 행 번호 표현

     

    [그림 7] Email alert 메시지

     

    [그림 8] phone alert 메시지

     


     

    Step 4

    <!-- dom_add.html -->
    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <style>
          .normal-table {
            border: 1px solid black;
            border-collapse: collapse;
            width: 100%;
          }
    
          .normal-table th,
          .normal-table td {
            border: 1px solid black;
            padding: 5px 10px;
          }
    
          .normal-table thead tr {
            background-color: yellow;
          }
    
          .hover tbody tr:hover {
            background-color: yellow;
          }
    
          /* 잘못된 형식에 대한 스타일 */
          .error {
            border: 2px dotted red;
          }
        </style>
      </head>
      <body>
        <button onclick="addLine();">추가</button>
        <button onclick="doSave();">저장</button>
        <button onclick="doDelete();" id="btnDelete" disabled>삭제</button>
        <table class="normal-table striped">
          <thead>
            <tr>
              <th><input type="checkbox" id="chks" onchange="checkAll()" /></th>
              <th>Name</th>
              <th>Company</th>
              <th>Gender</th>
              <th>Email</th>
              <th>Phone</th>
              <th>Address</th>
            </tr>
          </thead>
          <tbody id="tbBody"></tbody>
        </table>
        <script>
          function addLine() {
            const h = [];
            h.push("<tr>");
            h.push(
              `<td><input type="checkbox" name="chk" onchange="isChecked();" /></td>`
            );
            h.push(`<td><input type="text" name="name" /></td>`);
            h.push(`<td><input type="text" name="company" /></td>`);
            h.push(
              `<td><select name="gender"><option value="male">남자</option><option value="female">여자</option></select></td>`
            );
            h.push(`<td><input type="text" name="email" /></td>`);
            h.push(`<td><input type="text" name="phone" /></td>`);
            h.push(`<td><input type="text" name="address" /></td>`);
            h.push("</tr>");
    
            document
              .querySelector("#tbBody")
              .insertAdjacentHTML("beforeend", h.join(""));
          }
    
          async function doDelete() {
            const chks = document.querySelectorAll("[name=chk]:checked");
            // console.log(chks);
    
            chks.forEach((chk) => {
              let tr = chk;
              while (tr.tagName !== "TR") {
                tr = tr.parentNode;
              }
              // let tr = chk.parentNode.parentNode;
              // console.log(tr);
              tr.remove();
            });
          }
    
          async function doSave() {
            // 잘못된 Email, phone 스타일을 본래대로 돌리기. 클래스명이 error인 경우만 찾아서 처리.
            document
              .querySelectorAll(".error")
              .forEach((item) => (item.className = ""));
    
            const names = document.querySelectorAll("[name=name]");
            const companies = document.querySelectorAll("[name=company]");
            const genders = document.querySelectorAll("[name=gender]");
            const emails = document.querySelectorAll("[name=email]");
            const phones = document.querySelectorAll("[name=phone]");
            const addresses = document.querySelectorAll("[name=address]");
    
            const regexpEmail = /^([a-z]+\d*)+(\.?[a-z]*)+@[a-z]+(\.[a-z]{2,3})+$/;
            const regexpPhone = /^010-\d{3,4}-\d{4}$/;
    
            const customers = [];
            const len = names.length;
            const blankRows = [];
            const wrongEmails = [];
            const wrongPhones = [];
            let passEmail = true;
            let passPhone = true;
            let passRequired = true;
            for (let i = 0; i < len; i++) {
              if (
                names[i].value === "" ||
                companies[i].value === "" ||
                emails[i].value === "" ||
                phones[i].value === "" ||
                addresses[i].value === ""
              ) {
                passRequired = false;
                blankRows.push(i + 1);
              }
    
              if (!regexpEmail.test(emails[i].value)) {
                passEmail = false;
                wrongEmails.push(i + 1);
              }
    
              if (!regexpPhone.test(phones[i].value)) {
                passPhone = false;
                wrongPhones.push(i + 1);
              }
    
              customers.push({
                name: names[i].value,
                company: companies[i].value,
                gender: genders[i].value,
                email: emails[i].value,
                phone: phones[i].value,
                address: addresses[i].value,
              });
            }
    
            if (!passRequired) {
              return alert(
                `${blankRows.join(
                  ","
                )}행에 비어있는 값이 존재합니다. 모든 값을 입력하세요.`
              );
            }
    
            // 잘 못된 형식의 Email인 경우 스타일 적용
            if (!passEmail) {
              wrongEmails.forEach((item) => {
                document.querySelector(
                  `#tbBody tr:nth-child(${item}) [name=email]`
                ).className = "error";
              });
              return alert(
                `${wrongEmails.join(
                  ","
                )}행에 잘못된 형식의 이메일 값이 존재합니다. 올바른 형식으로 입력하세요.`
              );
            }
    
            // 잘 못된 형식의 phone인 경우 스타일 적용
            if (!passPhone) {
              wrongPhones.forEach((item) => {
                document.querySelector(
                  `#tbBody tr:nth-child(${item}) [name=phone]`
                ).className = "error";
              });
              return alert(
                `${wrongPhones.join(
                  ","
                )}행에 잘못된 형식의 전화번호 값이 존재합니다. 올바른 형식으로 입력하세요.`
              );
            }
    
            const failData = [];
            if (confirm("정말 저장하시겠습니까?")) {
              for (const customer of customers) {
                const res = await fetch("http://localhost:3000/customers", {
                  method: "POST",
                  body: JSON.stringify(customer),
                  headers: {
                    "content-type": "application/json;charset=UTF-8",
                  },
                });
    
                if (res.status !== 201) {
                  failData.push(customer);
                }
    
                if (failData.length > 0) {
                  alert(`저장되지 않은 데이터가 ${failData.length}개 있습니다.`);
                } else {
                  alert("정상적으로 저장 되었습니다.");
                }
              }
            }
          }
    
          function checkAll() {
            const checkValue = document.querySelector("#chks").checked;
            const chks = document.querySelectorAll("[name=chk]");
            for (const chk of chks) {
              chk.checked = checkValue;
            }
    
            isChecked();
          }
    
          function isChecked() {
            const chks = document.querySelectorAll("[name=chk]:checked");
            if (chks.length > 0) {
              document.querySelector("#btnDelete").disabled = false;
            } else {
              document.querySelector("#btnDelete").disabled = true;
            }
          }
        </script>
      </body>
    </html>

     

    [그림 9] 잘못된 형식에 지정된 스타일

     

    728x90
    반응형
    댓글