Dandy Now!
  • [개발자의품격][부트캠프][1기][16차시] 부트스트랩 #2 | 조회, 삭제
    2022년 03월 02일 09시 52분 44초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    부트스트랩 #2

    https://getbootstrap.com/

     

    1. Starter template 가져오기

    https://getbootstrap.com/docs/5.1/getting-started/introduction/#starter-template

    <!doctype html>
    <html lang="en">
      <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    
        <!-- Bootstrap CSS -->
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    
        <title>Hello, world!</title>
      </head>
      <body>
        <h1>Hello, world!</h1>
    
        <!-- Optional JavaScript; choose one of the two! -->
    
        <!-- Option 1: Bootstrap Bundle with Popper -->
        <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>
    
        <!-- Option 2: Separate Popper and Bootstrap JS -->
        <!--
        <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js" integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
        -->
      </body>
    </html>

     

    2. 네비게이션바 가져오기

    https://getbootstrap.com/docs/5.1/examples/carousel/

    <header>
      <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Carousel</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarCollapse">
            <ul class="navbar-nav me-auto mb-2 mb-md-0">
              <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="#">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Link</a>
              </li>
              <li class="nav-item">
                <a class="nav-link disabled">Disabled</a>
              </li>
            </ul>
            <form class="d-flex">
              <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
              <button class="btn btn-outline-success" type="submit">Search</button>
            </form>
          </div>
        </div>
      </nav>
    </header>

     

    [그림 1] carousel 네비게이션바 적용

     

    3. Inline forms 적용

    https://getbootstrap.com/docs/5.1/forms/layout/#inline-forms

    <main></main> 태그 안에 Inline forms을 넣는다.

    <form class="row row-cols-lg-auto g-3 align-items-center">
      <div class="col-12">
        <label class="visually-hidden" for="inlineFormInputGroupUsername">Username</label>
        <div class="input-group">
          <div class="input-group-text">@</div>
          <input type="text" class="form-control" id="inlineFormInputGroupUsername" placeholder="Username">
        </div>
      </div>
    
      <div class="col-12">
        <label class="visually-hidden" for="inlineFormSelectPref">Preference</label>
        <select class="form-select" id="inlineFormSelectPref">
          <option selected>Choose...</option>
          <option value="1">One</option>
          <option value="2">Two</option>
          <option value="3">Three</option>
        </select>
      </div>
    
      <div class="col-12">
        <div class="form-check">
          <input class="form-check-input" type="checkbox" id="inlineFormCheck">
          <label class="form-check-label" for="inlineFormCheck">
            Remember me
          </label>
        </div>
      </div>
    
      <div class="col-12">
        <button type="submit" class="btn btn-primary">Submit</button>
      </div>
    </form>

     

    4. Inline forms 보이게하기

    네비게이션바 뒤에 숨어있는 Inlin forms의 위치를 변경하여 보이게한다.

    4.1 위쪽 간격 조정

    <style>
      main {
        margin-top: 70px;
      }
    </style>

     

    4.2 왼쪽 간격 조정

    <div class="container">
      <!-- Inline forms 넣기 -->
    </div>

     

    [그림 2] style과 container 속성 이용해 Inline forms 위치 조정

     

    5. Inline forms 구성 변경

    셀렉트 박스에 라벨이 보이지 않으나 코드가 존재한다. label class="visually-hidden"을 주어 사용자 눈에는 보이지 않게 처리하고 ARIA를 만족시킨다.

    <form>을 쓰지 않으므로 <div>로 변경하고 셀렉트 박스 위치 변경 및 수정, 버튼 수정 및 추가, 간격 조정 등을 해주었다.

    <div class="row row-cols-lg-auto g-2 align-items-center">
      <div class="col-12">
        <label class="visually-hidden" for="gender"
          >성별선택</label
        >
        <select class="form-select" id="gender">
          <option value="" selected>전체</option>
          <option value="male">남자</option>
          <option value="female">여자</option>
        </select>
      </div>
    
      <div class="col-12">
        <label class="visually-hidden" for="name"
          >Username</label
        >
        <!-- <div class="input-group">
          <div class="input-group-text">@</div> -->
          <input
            type="search"
            class="form-control"
            id="name"
            placeholder="Username"
          />
        <!-- </div> -->
      </div>
    
      <!-- <div class="col-12">
        <div class="form-check">
          <input
            class="form-check-input"
            type="checkbox"
            id="inlineFormCheck"
          />
          <label class="form-check-label" for="inlineFormCheck">
            Remember me
          </label>
        </div>
      </div> -->
    
      <div class="col-12">
        <button class="btn btn-primary">조회</button>
        <button class="btn btn-success">생성</button>
        <button class="btn btn-danger">삭제</button>
      </div>
    </div>

     

    [그림 3] Inline forms 구성 변경

     

    6. table 생성

    Inline forms을 감싸고 있는 </div> 아래에 table을 추가한다. class="form-check-input"을 주어 체크박스 디자인 개선하였다.

    <table class="table table-bordered table-striped mt-2">
      <thead>
        <tr>
          <th>
            <!-- class="form-check-input"을 주어 체크박스 디자인 개선 -->
            <input
              type="checkbox"
              id="chks"
              class="form-check-input"
              onchange="checkAll()"
            />
          </th>
          <th>Name</th>
          <th>Company</th>
          <th>Gender</th>
          <th>Email</th>
          <th>Phone</th>
          <th>Addres</th>
        </tr>
      </thead>
      <tbody id="tbBody"></tbody>
    </table>

     

    [그림 4] table 생성

     

    7. JavaScript 함수 추가

    <script>
      async function doSearch() {
        const gender = document.querySelector("#gender").value;
        const name = document.querySelector("#name").value;
    
        let resource = "http://localhost:3000/customers";
        if (gender === "") {
          if (name !== "") {
            resource = `http://localhost:3000/customers?name_like=${name}`;
          }
        } else {
          if (name === "") {
            resource = `http://localhost:3000/customers?gender=${gender}`;
          } else {
            resource = `http://localhost:3000/customers?gender=${gender}&name_like=${name}`;
          }
        }
    
        // if (gender !== "") {
        //   resource = `http://localhost:3000/customers?gender=${gender}`;
        // }
    
        const res = await fetch(resource);
        const resJson = await res.json();
        console.log(resJson);
        renderTable(resJson);
      }
    
      function renderTable(data) {
        const h = [];
        for (const item of data) {
          h.push("<tr>");
          h.push(
            `<td><input type="checkbox" class="form-check-input" value="${item.id}" name="chk" onchange="isChecked();" /></td>` // class="form-check-input"으로 체크박스 디자인 개선
          );
          h.push(`<td>${item.name}</td>`);
          h.push(`<td>${item.company}</td>`);
          h.push(`<td>${item.gender}</td>`);
          h.push(`<td>${item.email}</td>`);
          h.push(`<td>${item.phone}</td>`);
          h.push(`<td>${item.address}</td>`);
          h.push("</tr>");
        }
    
        document.querySelector("#tbBody").innerHTML = h.join("");
      }
    
      async function doDelete() {
        const chks = document.querySelectorAll("[name=chk]:checked");
    
        if (chks.length > 0) {
          if (confirm("정말 삭제하시겠습니까?")) {
            for (const chk of chks) {
              await fetch(`http://localhost:3000/customers/${chk.value}`, {
                method: "DELETE",
              });
            }
            alert("데이터가 정상작으로 삭제 되었습니다.");
          }
        } 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;
        }
      }
    
      function goToCreate() {
        document.location.href = "dom_create.html";
      }
    
      function checkEnter(e) {
        // console.log(e);
        if (e.keyCode === 13) {
          doSearch();
        }
      }
    </script>

     

    조회/삭제 버튼에 onclick 이벤트를 적용한다.

    <button class="btn btn-primary" onclick="doSearch();">조회</button>
    <button class="btn btn-success">생성</button>
    <button class="btn btn-danger" onclick="doDelete();">삭제</button>

     

    [그림 5] 조회 화면

     

    8. 삭제기능 sweetalert 적용

    8.1 sweetalert 스크립트 추가

    https://sweetalert2.github.io/#download 에서 CDN을 복사해 추가한다. sweetalert을 쓸수 있는 상태가 된다.

    <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>

     

    8.2 sweetalert 추가

    A confirm dialog, with a function attached to the "Confirm"-button의 코드를 가져온다.

    Swal.fire({
      title: 'Are you sure?',
      text: "You won't be able to revert this!",
      icon: 'warning',
      showCancelButton: true,
      confirmButtonColor: '#3085d6',
      cancelButtonColor: '#d33',
      confirmButtonText: 'Yes, delete it!'
    }).then((result) => {
      if (result.isConfirmed) {
        Swal.fire(
          'Deleted!',
          'Your file has been deleted.',
          'success'
        )
      }
    })

     

    8.3 doDelete 함수에 sweetalert 적용

    async function doDelete() {
      const chks = document.querySelectorAll("[name=chk]:checked");
    
      if (chks.length > 0) {
        Swal.fire({
          title: "정말 삭제하시겠습니까?",
          text: "삭제된 데이터는 복원되지 않습니다!",
          icon: "warning",
          showCancelButton: true,
          confirmButtonColor: "#3085d6",
          cancelButtonColor: "#d33",
          confirmButtonText: "삭제",
          cancelButtonText: "취소",
        }).then(async (result) => {
          if (result.isConfirmed) {
            for (const chk of chks) {
              await fetch(`http://localhost:3000/customers/${chks[0].value}`, {
                method: "DELETE",
              });
            }
            Swal.fire(
              "삭제 성공!",
              "데이터가 정상적으로 삭제 되었습니다.",
              "success"
            );
          }
        });
      } else {
        Swal.fire("삭제할 아이템을 선택하세요.");
      }
    }

     

    [그림 6] 삭제기능 sweetalert 적용

     

     

    9. 삭제버튼에 효과 적용

    삭제 항목이 선택된 경우에만 버튼이 활성화 되도록 한다.

    <button class="btn btn-danger" id="btnDelete" onclick="doDelete();" disabled>
      삭제
    </button>

     

    [그림 7]&amp;amp;amp;amp;amp;amp;amp;nbsp; 삭제 버튼 비활성화

     

    [그림 8] 삭제 버튼 활성화

     

    10. 최종 코드

    네비게이션바의 이름을 "부트스트랩 실습"으로 변경해주었다.

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <!-- Required meta tags -->
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
    
        <!-- Bootstrap CSS -->
        <link
          href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
          rel="stylesheet"
          integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
          crossorigin="anonymous"
        />
    
        <title>Hello, world!</title>
        <!-- 4. Inline forms 보이게하기 - 위쪽 간격 조정 -->
        <style>
          main {
            margin-top: 70px;
          }
        </style>
      </head>
      <body>
        <!-- 2. 네비게이션바 가져오기 https://getbootstrap.com/docs/5.1/examples/carousel/ -->
        <header>
          <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
            <div class="container-fluid">
              <!-- 10. 네비게이션바 이름 변경 -->
              <a class="navbar-brand" href="#">부트스트랩 실습</a>
              <button
                class="navbar-toggler"
                type="button"
                data-bs-toggle="collapse"
                data-bs-target="#navbarCollapse"
                aria-controls="navbarCollapse"
                aria-expanded="false"
                aria-label="Toggle navigation"
              >
                <span class="navbar-toggler-icon"></span>
              </button>
              <div class="collapse navbar-collapse" id="navbarCollapse">
                <ul class="navbar-nav me-auto mb-2 mb-md-0">
                  <li class="nav-item">
                    <a class="nav-link active" aria-current="page" href="#">Home</a>
                  </li>
                  <li class="nav-item">
                    <a class="nav-link" href="#">Link</a>
                  </li>
                  <li class="nav-item">
                    <a class="nav-link disabled">Disabled</a>
                  </li>
                </ul>
                <form class="d-flex">
                  <input
                    class="form-control me-2"
                    type="search"
                    placeholder="Search"
                    aria-label="Search"
                  />
                  <button class="btn btn-outline-success" type="submit">
                    Search
                  </button>
                </form>
              </div>
            </div>
          </nav>
        </header>
        <!-- 3. Inline forms 적용 https://getbootstrap.com/docs/5.1/forms/layout/#inline-forms -->
        <main>
          <!-- 4. Inline forms 보이게하기 - 왼쪽 간격 조정 -->
          <div class="container">
            <!-- 5. Inline forms 구성 변경 -->
            <div class="row row-cols-lg-auto g-2 align-items-center">
              <div class="col-12">
                <label class="visually-hidden" for="gender">성별선택</label>
                <select class="form-select" id="gender">
                  <option value="" selected>전체</option>
                  <option value="male">남자</option>
                  <option value="female">여자</option>
                </select>
              </div>
    
              <div class="col-12">
                <label class="visually-hidden" for="name">Username</label>
                <input
                  type="search"
                  class="form-control"
                  id="name"
                  placeholder="Username"
                />
              </div>
              <div class="col-12">
                <!-- 7. JavaScript 함수 추가 - 조회/삭제 버튼에 onclick 이벤트를 적용 -->
                <button class="btn btn-primary" onclick="doSearch();">조회</button>
                <button class="btn btn-success">생성</button>
                <!-- 9. 삭제버튼에 효과 적용 -->
                <button
                  class="btn btn-danger"
                  id="btnDelete"
                  onclick="doDelete();"
                  disabled
                >
                  삭제
                </button>
              </div>
            </div>
            <!-- 6. table 생성 -->
            <table class="table table-bordered table-striped mt-2">
              <thead>
                <tr>
                  <th>
                    <!-- class="form-check-input"을 주어 체크박스 디자인 개선 -->
                    <input
                      type="checkbox"
                      id="chks"
                      class="form-check-input"
                      onchange="checkAll()"
                    />
                  </th>
                  <th>Name</th>
                  <th>Company</th>
                  <th>Gender</th>
                  <th>Email</th>
                  <th>Phone</th>
                  <th>Addres</th>
                </tr>
              </thead>
              <tbody id="tbBody"></tbody>
            </table>
          </div>
        </main>
    
        <!-- Optional JavaScript; choose one of the two! -->
    
        <!-- Option 1: Bootstrap Bundle with Popper -->
        <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>
    
        <!-- Option 2: Separate Popper and Bootstrap JS -->
        <!--
        <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js" integrity="sha384-7+zCNj/IqJ95wo16oMtfsKbZ9ccEh31eOz1HGyDuCQ6wgnyJNSYdrPa03rtR1zdB" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
        -->
    
        <!-- 8. 삭제기능 sweetalert 적용 -->
        <!-- https://sweetalert2.github.io/#download 에서 CDN을 복사해 추가한다. -->
        <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
    
        <!-- 7. JavaScript 함수 추가 -->
        <script>
          async function doSearch() {
            const gender = document.querySelector("#gender").value;
            const name = document.querySelector("#name").value;
    
            let resource = "http://localhost:3000/customers";
            if (gender === "") {
              if (name !== "") {
                resource = `http://localhost:3000/customers?name_like=${name}`;
              }
            } else {
              if (name === "") {
                resource = `http://localhost:3000/customers?gender=${gender}`;
              } else {
                resource = `http://localhost:3000/customers?gender=${gender}&name_like=${name}`;
              }
            }
            const res = await fetch(resource);
            const resJson = await res.json();
            console.log(resJson);
            renderTable(resJson);
          }
    
          function renderTable(data) {
            const h = [];
            for (const item of data) {
              h.push("<tr>");
              h.push(
                `<td><input type="checkbox" class="form-check-input" value="${item.id}" name="chk" onchange="isChecked();" /></td>` // class="form-check-input"으로 체크박스 디자인 개선
              );
              h.push(`<td>${item.name}</td>`);
              h.push(`<td>${item.company}</td>`);
              h.push(`<td>${item.gender}</td>`);
              h.push(`<td>${item.email}</td>`);
              h.push(`<td>${item.phone}</td>`);
              h.push(`<td>${item.address}</td>`);
              h.push("</tr>");
            }
    
            document.querySelector("#tbBody").innerHTML = h.join("");
          }
    
          // 8. 삭제기능 sweetalert 적용
          async function doDelete() {
            const chks = document.querySelectorAll("[name=chk]:checked");
            if (chks.length > 0) {
              Swal.fire({
                title: "정말 삭제하시겠습니까?",
                text: "삭제된 데이터는 복원되지 않습니다!",
                icon: "warning",
                showCancelButton: true,
                confirmButtonColor: "#3085d6",
                cancelButtonColor: "#d33",
                confirmButtonText: "삭제",
                cancelButtonText: "취소",
              }).then(async (result) => {
                if (result.isConfirmed) {
                  for (const chk of chks) {
                    await fetch(
                      `http://localhost:3000/customers/${chks[0].value}`,
                      {
                        method: "DELETE",
                      }
                    );
                  }
                  Swal.fire(
                    "삭제 성공!",
                    "데이터가 정상적으로 삭제 되었습니다.",
                    "success"
                  );
                }
              });
            } else {
              Swal.fire("삭제할 아이템을 선택하세요.");
            }
          }
    
          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;
            }
          }
    
          function goToCreate() {
            document.location.href = "dom_create.html";
          }
    
          function checkEnter(e) {
            // console.log(e);
            if (e.keyCode === 13) {
              doSearch();
            }
          }
        </script>
      </body>
    </html>

     

    [그림 10] 최종 화면

     

    728x90
    반응형
    댓글