์ฃผ์š” ์ž‘์—…

  • ์นด๋“œ๊ฐ€ ์—†์„ ๋•Œ, ์นด๋“œ column์ด ๋œจ์ง€ ์•Š๋Š” ํ˜„์ƒ ๊ณ ์น˜๊ธฐ(์ฟผ๋ฆฌ๋ฌธ ์ˆ˜์ •) โœ… 2024-09-05
  • ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์‹œ db์— order ๋ฐ˜์˜ โœ… 2024-09-05
  • ์ •๋ ฌ ์• ๋‹ˆ๋ฉ”์ด์…˜ โœ… 2024-09-05

ํ•™์Šต ํ‚ค์›Œ๋“œ

  • sql join โœ… 2024-09-05

๊ณ ๋ฏผ ๋ฐ ํ•ด๊ฒฐ๊ณผ์ •

๋นˆ Column์„ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ•˜๋Š” ์—๋Ÿฌ

๊ธฐ์กด์˜ ๊ฒฝ์šฐ ๋ชจ๋“  ์นด๋“œ๋“ค์˜ ์ •๋ณด๋ฅผ ```sql select * from todo_cards where username = ? ``` ์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ๋ชจ๋“  ์นด๋“œ๋“ค์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ model์—์„œ ์ด๋ฅผ column ๋ณ„๋กœ ๋‚˜๋ˆ„์–ด ๊ฐ€๊ณตํ•œ ๋’ค ๋‹ค์‹œ๊ธˆ ํด๋ผ์ด์–ธํŠธ์— response body๋ฅผ ๋„ฃ์–ด response๋ฅผ ๋ณด๋‚ด์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์„ค๊ณ„ํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋Ÿฌํ•œ sql๋ฌธ์˜ ๋ฌธ์ œ๋Š” todo_cards์˜ ๋ชจ๋“  ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ๋งŒ ํ•  ๋ฟ, column๋“ค์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋”ฐ๋กœ ๊ฐ€์ ธ์˜ค์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ task_column ํ…Œ์ด๋ธ”์— ๋งŒ์•ฝ ์–ด๋– ํ•œ ์นด๋“œ๋„ ์—†์„ ๊ฒฝ์šฐ todo_cards์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ์—†๋Š” column์˜ ์ •๋ณด๋Š” ๋‚˜์˜ค์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ joinํ•˜๋Š” ๋ฐฉ์‹์„ ํ†ตํ•ด์„œ ํ•ด๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค.

      const query = `
          SELECT 
              todo_cards.task_column,
              todo_cards.card_id,
              todo_cards.author,
              todo_cards.title,
              todo_cards.detail,
              todo_cards.created_at,
              task_columns.username,
              task_columns.col_name
          FROM 
            todo_cards
          right OUTER JOIN
              task_columns
          ON 
              todo_cards.task_column = task_columns.col_name
          WHERE task_columns.username = ?
      `;

์ด๋Ÿฐ ์‹์œผ๋กœ task_column์„ right joinํ•˜์—ฌ task column์—๋Š” ์žˆ์ง€๋งŒ todo_cards์—๋Š” ์—†๋Š” ๊ฒƒ๋“ค ๋˜ํ•œ ๊ฐ€์ ธ์™€์„œ ๋‹ค์‹œ๊ธˆ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๋„๋ก ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์‹œ db์— order ๋ฐ˜์˜

์ €๋Š” ๋Œ€๋ถ€๋ถ„์˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ apiํ˜ธ์ถœ์ด ์ด๋ฃจ์–ด์ง„ ํ›„ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•ด์„œ๋Š” ๋‹ค์‹œ get์š”์ฒญ์„ ๋ถˆ๋Ÿฌ์™€ ๊ฐ€์ ธ์˜จ ์ •๋ณด๋กœ ๋ฆฌ๋ Œ๋”๋ง์„ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ „์ œ๋กœ ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ ์‹œ์— db์— order์„ ์–ด๋–ป๊ฒŒ ๋ฐ˜์˜ํ•ด์•ผ ํ• ์ง€ ๊ณ ๋ฏผ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋˜ ์ค‘ greenhopper ๋ฐฉ์‹์„ ๋ธ”๋กœ๊ทธ ๊ธ€์—์„œ ์ฐพ๊ฒŒ ๋˜์—ˆ๊ณ , ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด db์™€ ํด๋ผ์ด์–ธํŠธ ๋ชจ๋‘์— ์ ์šฉ์‹œ์ผœ ๋งค๋„๋Ÿฌ์šด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์„์ง€์— ๋Œ€ํ•ด์„œ ๊ณ ๋ฏผํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.

document
    .getElementById("sections")
    .addEventListener("drop", async (event) => {
      try {
        const cardId = event.dataTransfer.getData("data");
        const targetCard = document.getElementById(cardId);
        handleSoftMove(event, cardId);
        await handleMoveActionsToServer(cardId, targetCard);
      } catch (error) {
        console.log(error);
      }
    });

drop ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒ์ด ๋˜๋ฉด ํ•ด๋‹น ๋“œ๋ž˜๊ทธ์˜ ๋Œ€์ƒ์ด ๋˜๋Š” ํƒ€๊ฒŸ์œผ๋กœ๋ถ€ํ„ฐ dataTransfer๋ฅผ ์ด์šฉํ•ด ํ•ด๋‹นํ•˜๋Š” ์นด๋“œ์˜ ๊ณ ์œ  ์‹๋ณ„ ID๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณ€์ˆ˜์— ํ• ๋‹นํ–ˆ์Šต๋‹ˆ๋‹ค.

function handleSoftMove(event, cardId) {
    let closest = getEventTarget(event);
    if (!closest) return;
    closest.style.borderBottom = "none";
    const cardToMove = document.getElementById(cardId);
    if (closest.tagName === "SECTION")
      closest = closest.querySelector(".todoList");
    closest.className === "todoList"
      ? closest.insertAdjacentElement("afterbegin", cardToMove)
      : closest.insertAdjacentElement("afterend", cardToMove);
  }
  
  function getEventTarget(event) {
    let closest = event.target.closest("ul > div");
    if (!closest) closest = event.target.closest("section");
    return closest;
  }

๋ณ€์ˆ˜์— ํ• ๋‹น๋œ ๋’ค์—๋Š” ํƒ€๊ฒŸ์— ๋Œ€ํ•ด์„œ getEventTargetํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์š”์†Œ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 0์ˆœ์œ„๋กœ๋Š” ๊ฐ ์นด๋“œ์˜ ๊ฐ€์žฅ ํฐ container์ธ div element๋ฅผ closest๋ฅผ ํ†ตํ•ด ์ฐพ๊ณ , 1์ˆœ์œ„๋กœ๋Š” ํ•ด๋‹นํ•˜๋Š” ์œ„์น˜์˜ ์นผ๋Ÿผ์— ๋Œ€ํ•ด์„œ ์ฐพ์•„ ๋ฆฌํ„ดํ•ฉ๋‹ˆ๋‹ค. ์ฐพ์€ ์œ„์น˜์— ๋Œ€ํ•ด์„œ ์กฐ๊ฑด๋ฌธ์„ ํ†ตํ•ด

  • section(column)์˜ ๊ฒฝ์šฐ โ†’ ๊ฐ€์žฅ ์ฒซ๋ฒˆ์งธ ์œ„์น˜๋กœ ๋„ฃ๊ธฐ ์œ„ํ•ด ๋ฐ”๋กœ card๋“ค์„ ๋‹ด๋Š” ๋ถ€๋ชจ ์š”์†Œ์˜ ul๋กœ ์ง€์ •ํ•˜๋Š” ์š”์†Œ๋ฅผ ์žฌํ• ๋‹นํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ๊ณณ์ด card์ผ ๊ฒฝ์šฐ โ†’ ํ•ด๋‹น ์นด๋“œ์˜ ๋‹ค์Œ ์œ„์น˜์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ softmove๋ฅผ ๋จผ์ € ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

softmove๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ์ด์œ ๋Š” ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

function getPriortyAfterMove(target) {
    let next = 0,
      prev = 0;
 
    if (
      target.nextSibling &&
      target.nextSibling.nodeType === Node.ELEMENT_NODE
    ) {
      const nextElement = target.nextSibling;
      if (nextElement.tagName === "DIV") {
        next = parseInt(nextElement.dataset.priority, 10) || 0;
      }
    }
 
    if (
      target.previousSibling &&
      target.previousSibling.nodeType === Node.ELEMENT_NODE
    ) {
      const prevElement = target.previousSibling;
      if (prevElement.tagName === "DIV") {
        prev = parseInt(prevElement.dataset.priority, 10) || 0;
      }
    }
 
    if (next && prev) return Math.floor((next + prev) / 2);
    else if (prev) return prev + 100;
    else return 100;
  }
  • getPriorityAfterMoveํ•จ์ˆ˜์—์„œ ๊ฐ๊ฐ ์นด๋“œ์˜ element์— ์ €์žฅํ•ด๋‘์—ˆ๋˜ ์šฐ์„ ์ˆœ์œ„์ธ priority๋ฅผ ๊ฐ€์ ธ์™€ ๊ณ„์‚ฐํ•˜๊ธฐ ์œ„ํ•ด์„œ์ž…๋‹ˆ๋‹ค.
  • softmove๋ฅผ ํ†ตํ•ด ๋ฏธ๋ฆฌ db์— ๋ฐ˜์˜๋˜๊ธฐ ์ „์˜ ์ƒํƒœ๋ฅผ ๊ทธ๋ ค๋†“๊ณ  ๋‹ค์‹œ๊ธˆ ๋ Œ๋”๋ง์„ ์‹œํ‚ค๋ฉด ๋ณด๋‹ค ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ฆ์ง„์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ softmove๊ฐ€ ์ด๋ฃจ์–ด์ง„ ํ›„์—๋Š” api ํ˜ธ์ถœ โ†’ ์„œ๋ฒ„์˜ ๋ผ์šฐํ„ฐ โ†’ ๋ ˆํฌ์ง€ํ† ๋ฆฌ๋ฅผ ์ด์šฉํ•ด ์ด๋™ํ•œ column์˜ ์ด๋ฆ„๊ณผ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ณ€๊ฒฝํ•จ์œผ๋กœ์จ ์ ์ ˆํ•œ ์œ„์น˜์— ์šฐ์„ ์ˆœ์œ„๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ Œ๋”๋ง์‹œ์ผœ ๋ฆฌ๋ Œ๋”๋ง ์‹œ์—๋„ ์œ„์น˜๊ฐ€ ๋˜‘๊ฐ™์ด ๋‚˜์˜ค๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

taskify๋ฅผ spa ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ

๊ธฐ๋ณธ์ ์ธ ๋ ˆ์ด์•„์›ƒ ์ž์ฒด๋Š” ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์œผ๋กœ ๊ตฌํ˜„์„ ํ–ˆ์ง€๋งŒ, ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋“ค์€ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์œผ๋กœ ๊ตฌํ˜„์„ ํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ณผ์ •์—์„œ views ํด๋”์˜ index.jsํŒŒ์ผ์€ ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ , app์—์„œ ๊ฐ ํŽ˜์ด์ง€์— ๋Œ€ํ•ด์„œ ๋ Œ๋”๋ง์„ ์‹œํ‚ค๋„๋ก ํ–ˆ์—ˆ๋Š”๋ฐ ์ด ๊ณผ์ •์—์„œ ๋งŒ์•ฝ ๋กœ๊ทธ์ธ์ด ์ถ”๊ฐ€๋  ๊ฒฝ์šฐ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์—†๋Š” express.render์˜ ํŠน์„ฑ์ƒ ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง ๋ฐฉ์‹์œผ๋กœ ๋‹ค์‹œ๊ธˆ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™์‹œํ‚ค๋ ค๋ฉด index.js์™€ login.js ํŒŒ์ผ ๋ชจ๋‘๋ฅผ ๋‘๋Š” ๋ฐฉ๋ฒ•๋ฐ–์— ์ƒ๊ฐ์ด ๋‚˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ํ•˜๊ฒŒ ๋˜๋ฉด ๊ธฐ์กด์˜ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์ž์ฒด๊ฐ€ ๋ฌด์ƒ‰ํ•ด์งˆ ๋ฟ๋”๋Ÿฌ, ๊ธฐ์กด์— ์‚ฌ์šฉํ•˜๋˜ Pages ๋””๋ ‰ํ† ๋ฆฌ๋Š” ์ „ํ˜€ ํ•„์š”์—†๋Š” ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ๋˜์–ด๋ฒ„๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ํ™œ์šฉํ•  ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์ฃผ์†Œ๋ฅผ ๋ฐ›์•„ ๋™์ ์œผ๋กœ ํŽ˜์ด์ง€ ๋ Œ๋”๋งํ•˜๊ธฐ

// /app/app.js
import navigation from "../shared/utils/navigation.js";
 
function entryPoint() {
  const pathName = window.location.pathname;
  console.log(pathName);
  navigation.navigate(pathName);
}
 
window.onpopstate = navigation.onPageChanged;
document.addEventListener("DOMContentLoaded", entryPoint);

๊ธฐ์กด์— mainpage๋งŒ ์žˆ๋˜ ๋•Œ๋Š” ๋ฉ”์ธ ํŽ˜์ด์ง€๋งŒ์„ ๋ Œ๋”๋ง ์‹œํ‚ค๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚ค๋Š” ์ •๋„๋กœ app.js๋ฅผ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๊ฐ€ ์ถ”๊ฐ€๋จ์— ๋”ฐ๋ผ ์ด๋ฅผ pathname์— ๋”ฐ๋ผ ํ•„์š”ํ•œ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋ง ์‹œํ‚ค๋„๋ก ๋ฐฉ์‹์„ ๋ฐ”๊ฟจ์Šต๋‹ˆ๋‹ค.

app.get("/", isLoggedIn, (req, res, next) => {
  res.render("index");
});
 
app.get("/login", isNotLoggedIn, (req, res, next) => {
  res.render("index");
});

๋”ฐ๋ผ์„œ ์„œ๋ฒ„์—์„œ๋„ pathname์€ ๋‹ค๋ฅด์ง€๋งŒ, ๋ชจ๋“  ํŽ˜์ด์ง€๋“ค์ด entryPoint์—์„œ ์ผ์–ด๋‚˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ spa ๋ฐฉ์‹์œผ๋กœ ๋งŒ๋“ค๋ฉด ์ข‹์€ ์ ์€ ์•„๋ฌด๋ž˜๋„ index๋งŒ์„ ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ๋กœ ์žก๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค๋ฅธ pathname์œผ๋กœ ์ ‘์†ํ–ˆ์„ ๊ฒฝ์šฐ๋Š” ์ „๋ถ€ ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ์—์„œ ์ œ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋ฒ„๊ฐ€ ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์€ ๊ธฐ์กด index์˜ ๋ ˆ์ด์•„์›ƒ์— ๋ถˆ๊ณผํ•˜๊ธฐ ๋•Œ๋ฌธ์— api๊นŒ์ง€ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์„œ๋ฒ„์˜ ์—ญํ• ์ด ๋ถ„๋‹ด๋˜๋Š” ํšจ๊ณผ๋ฅผ ๊ฐ€์ง„๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์‹œ app.js๋กœ ๋Œ์•„์˜ค๋ฉด, pathname์— ๋”ฐ๋ผ spa๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” navigation ๊ฐ์ฒด๊ฐ€ ํ•„์ˆ˜์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์™œ๋ƒํ•˜๋ฉด

  • pathname ๊ด€๋ฆฌ
  • ๊ธฐ์กด ๋ ˆ์ด์•„์›ƒ์„ ์žฌ์‚ฌ์šฉํ•˜๋ฉด์„œ body์˜ ๋‚ด์šฉ๋งŒ ๋‹ฌ๋ผ์ง€๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹
  • ํŽ˜์ด์ง€ ์ด๋™์— ๋”ฐ๋ฅธ ํŽ˜์ด์ง€๋ณ„ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋ถ™์ด๊ธฐ/๋–ผ๊ธฐ ๋“ฑ์˜ ์กฐ๊ฑด์„ ๊ฐ–์ถฐ์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ navigation ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•˜๋Š” ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค
// /shared/utils/navigation.js
import Main from "../../pages/main/index.js";
import Login from "../../pages/login/index.js";
import EventManager from "../../feature/index.js";
 
export default (function navigation() {
  const navigator = {
    "/": Main.render,
    "/login": Login.render,
  };
  function navigate(pathname, prev) {
    document.body.innerHTML = "";
    history.pushState({ pathname: prev }, null, pathname);
    navigator[pathname]();
  }
  function onPageChanged(event) {
    document.body.innerHTML = "";
    EventManager.detachEvent(event.pathname);
    navigator[window.location.pathname]();
  }
  return {
    navigate,
    onPageChanged,
  };
})();
 

navigation ๊ฐ์ฒด๋Š” ์–ด๋””์„œ๋‚˜ ํŽ˜์ด์ง€ ์ด๋™์— ์“ฐ์—ฌ์•ผ ๋กœ์ง์ด๊ธฐ ๋•Œ๋ฌธ์— fsd ๊ณ„์ธต ์ค‘์— shared ๊ณ„์ธต์ด ์•Œ๋งž์€ ์ž๋ฆฌ์ผ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•ด์„œ ๋„ฃ์–ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค ํ•ด๋‹น navigation๊ฐ์ฒด๋Š” ๋“ฑ๋ก๋œ pathname์— ๋”ฐ๋ผ

  1. ๊ธฐ์กด ๋ ˆ์ด์•„์›ƒ์—์„œ body๋ฅผ ์ดˆ๊ธฐํ™”
  2. history.pushState ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ด์ „ ์ฃผ์†Œ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ๋’ค๋กœ๊ฐ€๊ธฐ๋ฅผ ๋ˆŒ๋ €์„ ๊ฒฝ์šฐ ์ด์— ๋Œ€ํ•ด์„œ ๋‹ค์‹œ๊ธˆ ์˜ฌ๋ฐ”๋ฅธ ํŽ˜์ด์ง€๋ฅผ ๋ Œ๋”๋ง ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋กœ์ง ๊ตฌ์„ฑ
  3. ํŽ˜์ด์ง€๋ณ„ ๋ Œ๋”๋ง ํ•จ์ˆ˜ ์‹คํ–‰ ์˜ ๊ณผ์ •์„ ๊ฑฐ์นฉ๋‹ˆ๋‹ค. onPageChanged๋Š” ๊ธฐ์กด ํŽ˜์ด์ง€๊ฐ€ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก window์— ๋“ฑ๋กํ•ด๋†“์€ ์ด๋ฒคํŠธํ•ธ๋“ค๋Ÿฌ์ธ๋ฐ, ์ด๋Š” ํŽ˜์ด์ง€๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ๋‹ค์‹œ๊ธˆ ์ดˆ๊ธฐํ™”์‹œํ‚ค๊ณ , ํ•ด๋‹น ํŽ˜์ด์ง€์˜ ํ•ด๋‹นํ•˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋“ค์„ ์ข…ํ•ฉํ•ด๋†“์€ ๋ชจ๋“ˆ์„ head์—์„œ ๋–ผ์–ด๋ƒ„์œผ๋กœ์จ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.
// /feature/index.js
export default (function EventManager() {
  const route = {
    "/login": "/feature/login/index.js",
    "/main": "/feature/main/index.js",
  };
  function attachEvent(pathname) {
    const script = document.createElement("script");
    script.src = route[pathname];
    script.type = "module";
    document.head.appendChild(script);
  }
  function detachEvent(pathname) {
    const script = document.head.querySelector(
      `script[src="${route[pathname]}"]`
    );
    if (script) script.remove();
  }
  return { attachEvent, detachEvent };
})();
 

๋ฉ”์ธํŽ˜์ด์ง€์˜ ์ด๋ฒคํŠธ๋ฅผ ์ข…ํ•ฉํ•˜์—ฌ ๋“ฑ๋กํ•˜๊ธฐ๋งŒ ํ•ด์ฃผ๋˜ feature/index.js๋Š” ํŽ˜์ด์ง€๊ฐ€ ์ถ”๊ฐ€๋จ์— ๋”ฐ๋ผ ํŽ˜์ด์ง€๋ณ„๋กœ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค์–ด depth๋ฅผ ํ•œ๋‹จ๊ณ„ ๋Š˜๋ฆฌ๋Š” ๋Œ€์‹ ์—, ํŽ˜์ด์ง€๋ณ„๋กœ index.js๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด ํ•ด๋‹น ํŽ˜์ด์ง€์— ๋“ฑ๋ก๋˜์–ด์•ผ ํ•  ์ด๋ฒคํŠธ ์œ„์ž„๊ณผ ํ•ธ๋“ค๋Ÿฌ๋“ค์„ ์ข…ํ•ฉํ•˜์—ฌ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ฃผ์—ˆ์œผ๋ฉฐ, ์ด๋ฅผ ์ ˆ๋Œ€๊ฒฝ๋กœ๋กœ ์„ค์ •ํ•˜์—ฌ head์˜ script๋กœ ๋„ฃ์–ด์ฃผ๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

์งˆ๋ฌธ์‚ฌํ•ญ

์ €๋ฒˆ ๋ฆฌ๋ทฐ์—์„œ ๋ง์”€ํ•ด์ฃผ์‹  history ํ…Œ์ด๋ธ”์„ ์ด์šฉํ•œ redo, undo ์กฐ์–ธ์„ ์ฐธ๊ณ ํ•˜์—ฌ history ํ…Œ์ด๋ธ”์„ ๋‹ค์‹œ ๊ตฌ์ƒํ•จ์— ์žˆ์–ด์„œ ์• ๋งคํ•œ ๋ถ€๋ถ„์ด ์žˆ์–ด ์งˆ๋ฌธ๋“œ๋ฆฌ๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

-- `history` ํ…Œ์ด๋ธ”
CREATE TABLE `history` (
	`history_id` int auto_increment,
	`username` varchar(10) NOT NULL,
	`action` varchar(10) NOT NULL,
    `column_name` varchar(20) NULL,
	`title` varchar(20) NULL,
    `detail` varchar(500) NULL,
    `from_column` varchar(20) NULL,
    `to_column` varchar(20) NULL,
    `created_at` timestamp NULL,
    `card_id` int NOT NULL,
    `prev_priority` int NULL,
    PRIMARY KEY(`history_id`),
	FOREIGN KEY (`username`) REFERENCES `user` (`username`) on delete cascade on update cascade
);
 

history๋Š” ๋ชจ๋“  action์— ๋Œ€ํ•ด ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ๊ฐ์˜ action๋“ค์— ๋Œ€ํ•ด์„œ ํ•„์ˆ˜์ ์œผ๋กœ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ์ •๋ณด๋Š” ์กฐ๊ธˆ์”ฉ ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

  • ์นด๋“œ ์ด๋™ โ†’ ์ด์ „ ์นผ๋Ÿผ, ์นด๋“œ id, ๊ฐ„ ํ›„์˜ ์นผ๋Ÿผ
  • ์นด๋“œ ์‚ญ์ œ โ†’ ์‚ญ์ œ ์ „ ์นด๋“œ์˜ ๋ชจ๋“  ๋‚ด์šฉ
  • ์นด๋“œ ์ถ”๊ฐ€ โ†’ ์นด๋“œ id
  • ์นด๋“œ ์ˆ˜์ • โ†’ ์ด์ „ ์นด๋“œ์˜ ๋ชจ๋“  ๋‚ด์šฉ ์ด๋ฅผ ์–ด๋–ป๊ฒŒ ๊ด€๋ฆฌํ•˜๋ฉด ์ข‹์„๊นŒ์— ๋Œ€ํ•ด์„œ ์ƒ๊ฐํ•ด๋ดค๋Š”๋ฐ, ํ•„์ˆ˜์ ์œผ๋กœ ๊ฐ€์ ธ์•ผ ํ•˜๋Š” ๊ฐ’๋งŒ NOT NULL๋กœ ๋‘๊ณ , ๋‹ค๋ฅธ ์นผ๋Ÿผ์— ๋Œ€ํ•ด์„œ๋Š” NULL๋กœ ์„ค์ •ํ•ด๋†” ์—†์–ด๋„ ๋˜๋Š” ๊ฒฝ์šฐ์—๋Š” NULL๊ฐ’์œผ๋กœ ๋‘˜ ์ˆ˜ ์žˆ๋„๋ก ํ•ด๋†จ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๊ฐ๊ฐ ํžˆ์Šคํ† ๋ฆฌ๊ฐ€ ๊ฐ€์ง€๋Š” ์ข…๋ฅ˜๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์ธ๋ฐ, ์ด๋Ÿฌํ•œ ์ •๋ณด๋ฅผ ํ•œ๊ฐ€์ง€ ํ…Œ์ด๋ธ”์— ๋ชจ๋‘ ๋‘๋Š” ๊ฒƒ์ด ๋งž์„๊นŒ ๊ณ ๋ฏผ์ด ๋ฉ๋‹ˆ๋‹ค.

์ด์ „์—๋Š” JSONํ˜•์‹์œผ๋กœ ์œ ๋™์ ์ธ ์ •๋ณด์— ๋Œ€ํ•ด์„œ ์ „๋ถ€ JSON์œผ๋กœ ์ €์žฅํ•˜๋„๋ก ํ–ˆ๋Š”๋ฐ, JSON์„ ํ†ตํ•ด ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ๊ธฐ๋ณธ ์›๋ฆฌ์™€๋Š” ์ข€ ๋ฒ—์–ด๋‚˜๋Š” ๋ฐฉ์‹์ด๋ผ๋Š” ํ”ผ๋“œ๋ฐฑ์„ ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด์„œ๋„ ์ด๋ฒˆ ์Šคํ„ฐ๋””๊ทธ๋ฃน ๋ฆฌ๋ทฐ ์‹œ๊ฐ„์—์„œ๋Š” ์ด์— ๋Œ€ํ•ด JSON์œผ๋กœ ํ•˜์‹  ๋ถ„๋“ค๋„ ๊ณ„์‹œ๋Š”๋ฐ, ์œ ๋™์ ์ธ ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋กœ ์ƒ๊ด€์ด ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•˜์…จ๋‹ค๋Š” ๋ถ„๋„ ๊ณ„์…”์„œ ๋ฌด์—‡์ด RDB์˜ ํ™œ์šฉ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์‹์ผ์ง€๊ฐ€ ํ—ท๊ฐˆ๋ฆฌ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ ๋ฉ˜ํ† ๋‹˜์˜ ๊ณ ๊ฒฌ์„ ์—ฌ์ญ™๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.