부랑나랑 프로젝트 회고 : Javascript 리팩토링 (22/10/10 ~ 22/10/13)

KIM KYUBIN·2022년 10월 13일
0

1차 프로젝트 (22/09/14 ~ 22/09/29) 완료 후 내가 쓴 JS 코드들이 너무 지저분해보였다.

2차 프로젝트는 쌩자바에서 스프링으로 전환하는 것을 중점적으로 할 예정이기 때문에 JS 코드는 더 이상 손을 대지 않을 것 같아 리팩토링이 필요했다.

내가 맡은 모달 페이지의 리팩토링 전 코드다.

// var closeBtn = document.querySelector(".modal_cancel");
var addBtn = document.querySelector(".modal_add");
var tagAddBtn = document.getElementById("tag_insert");
var modal = document.querySelector(".modal_zone");
var startDate, endDate;

// closeBtn.addEventListener("click", function () {
//   modal.classList.add("modal_hidden");
// });

tagAddBtn.addEventListener("click", function (e) {
  var tag = document.getElementById("tag").value;
  var tagId = tag + "_in";
  var tagArea = document.getElementById("tag_area");
  var addTag = document.createElement("span");
  var tagValue = document.getElementById("tag_value").value;
  var overLap = document.getElementById("overlap");
  var manyTag = document.getElementById("manytag");
  var longTag = document.getElementById("longtag");
  var noTag = document.getElementById("notag");
  var tagValueList = tagValue.split(" ");

  if (tag === "#") {
    noTag.className = "";
    manyTag.className = "hidden";
    longTag.className = "hidden";
    overLap.className = "hidden";
    return;
  } else if (tag.length > 11) {
    noTag.className = "hidden";
    longTag.className = "";
    manyTag.className = "hidden";
    overLap.className = "hidden";
    return;
  } else if (tagValueList.length > 10) {
    noTag.className = "hidden";
    manyTag.className = "";
    longTag.className = "hidden";
    overLap.className = "hidden";
    return;
  } else {
    longTag.className = "hidden";
    noTag.className = "hidden";
    manyTag.className = "hidden";
    overLap.className = "hidden";
  }

  if (!tagValue.includes(tag)) {
    if (tagValueList.length <= 10) {
      document.getElementById("tag_value").value += tag + " ";
      addTag.id = tagId;
      addTag.className = "highlight";
      addTag.innerText = tag;
      addTag.onclick = function () {
        removeTag(tagId);
      };
      tagArea.appendChild(addTag);
      document.getElementById("tag").value = "#";
      manyTag.className = "hidden";
    } else {
      manyTag.className = "";
    }
    noTag.className = "hidden";
    longTag.className = "hidden";
    overLap.className = "hidden";
  } else {
    document.getElementById("tag").value = "#";
    overLap.className = "";
    longTag.className = "hidden";
    noTag.className = "hidden";
    manyTag.className = "hidden";
  }
});

function getValue(e) {
  var result = e.target.innerText;
  var tagArea = document.getElementById("tag_area");
  var tagValue = document.getElementById("tag_value").value;
  var overLap = document.getElementById("overlap");
  var manyTag = document.getElementById("manytag");
  var longTag = document.getElementById("longtag");
  var noTag = document.getElementById("notag");
  var addTag = document.createElement("span");
  var tagValueList = tagValue.split(" ");

  if (!tagValue.includes(result)) {
    if (tagValueList.length <= 10) {
      var tagId = result + "_in";
      document.getElementById("tag_value").value += result + " ";
      addTag.id = tagId;
      addTag.className = "highlight";
      addTag.innerText = result;
      addTag.onclick = function () {
        removeTag(tagId);
      };
      tagArea.appendChild(addTag);
      manyTag.className = "hidden";
    } else {
      manyTag.className = "";
    }
    noTag.className = "hidden";
    longTag.className = "hidden";
    overLap.className = "hidden";
  } else {
    longTag.className = "hidden";
    noTag.className = "hidden";
    manyTag.className = "hidden";
    overLap.className = "";
  }
}

function removeTag(tag_id) {
  var tagValue = document.getElementById("tag_value").value;
  var result = tag_id.substring(0, tag_id.length - 3);
  console.log(result);
  var idx = tagValue.indexOf(result);

  document.getElementById("tag_value").value = tagValue.replace(
    tagValue.substring(idx, idx + result.length + 1),
    ""
  );

  document.getElementById(tag_id).remove();
  manyTag.className = "hidden";
  noTag.className = "hidden";
  longTag.className = "hidden";
  overLap.className = "hidden";
}

// 캘린더 호출
document.addEventListener("DOMContentLoaded", function () {
  // 캘린더 생성
  var calendarEl = document.getElementById("calendar");
  var calendar = new FullCalendar.Calendar(calendarEl, {
    initialView: "dayGridMonth", // 달력 형식
    height: 500, // 달력 높이
    titleFormat: "YYYY년 MM월", // 달력 제목 포맷

    // 달력 헤더 순서
    headerToolbar: {
      start: "",
      center: "title",
      end: "prev,next",
    },
    selectable: true, // 달력 날짜 선택 가능 여부

    /* 드래그하면 마지막 날짜가 지정한 날짜의 다음날까지 포함해서 출력된다.
       ex) 2022-08-20 ~ 2022-08-21 까지 캘린더에 드래그하면 결과값은 2022-08-20 ~ 2022-08-22
      우리가 의도했던 결과와 다르므로 결과값을 임의로 조정해주어야 한다. */
    select: function (info) {
      var startDate = info.start; // 시작일자 Date 형식으로 저장
      var endDate = new Date(info.end.setDate(info.end.getDate() - 1)); // 마지막 날의 day를 -1하여 Date 형식으로 저장
      var startYear = startDate.getFullYear();
      var startMonth = startDate.getMonth() + 1;
      var startDay = startDate.getDate();
      var endYear = endDate.getFullYear();
      var endMonth = endDate.getMonth() + 1;
      var endDay = endDate.getDate();

      // "YYYY-MM-DD" 형식으로 출력하게끔 만든다.
      // 한 자리 숫자를 두 자리 숫자로 만들기 위해 한 자리 수 앞에 0을 붙여줘야 한다.
      document.getElementById("start_schedule").value =
        startYear +
        "-" +
        (startMonth >= 10 ? startMonth : "0" + startMonth) +
        "-" +
        (startDay >= 10 ? startDay : "0" + startDay);
      document.getElementById("end_schedule").value =
        endYear +
        "-" +
        (endMonth >= 10 ? endMonth : "0" + endMonth) +
        "-" +
        (endDay >= 10 ? endDay : "0" + endDay);
    },
  });
  calendar.render();
});

function writeCheck() {
  var title = true;
  var schedule = true;

  if (scheduleForm.title.value.length === 0) {
    document.getElementById("notitle").className = "";
    title = false;
  } else {
    document.getElementById("notitle").className = "hidden";
  }

  if (scheduleForm.firstdate.value.length === 0) {
    document.getElementById("noschedule").className = "";
    schedule = false;
  } else {
    document.getElementById("noschedule").className = "hidden";
  }

  if (title === false || schedule === false) {
    return;
  }

  document.scheduleForm.submit();	
}

일단 쓰지 않는 변수들도 많고 반복되는 것이 너무 많았다.

한 파일 안에 유효성 검사 기능과 캘린더 기능까지 합쳐져 있는 것도 문제였다.

그래서, 안쓰는 변수들을 정리하고 세부 기능들을 함수로 빼놓는 작업을 위주로 진행했다.

캘린더 기능도 파일을 별도로 만들어서 처리했다.

writeCheck()는 작성 페이지에서 처리되기 때문에 빼야하는 상황이였다.

리팩토링 후의 코드다.

const tagArea = document.getElementById("tag_area");
const duplicateTag = document.getElementById("duplicate_tag");
const tooManyTag = document.getElementById("too_many_tag");
const tooLongTag = document.getElementById("too_long_tag");
const blankTag = document.getElementById("blank_tag");
const addTagBtn = document.getElementById("add_tag");

/**
 * 태그 입력란의 추가 버튼을 클릭하면 태그 직접 추가
 */
addTagBtn.addEventListener("click", function () {
    directlyInsertTag();
});

/**
 * 태그 직접 추가
 */
function directlyInsertTag() {
    if (tagValidation()) {
        const tagName = document.getElementById("tag").value;
        createTag(tagName);
        document.getElementById("tag").value = "#";
    }
}

/**
 * 추천 태그를 클릭하여 태그 추가
 * @param 추천 태그
 */
function clickInsertTag(id) {
    if (tagValidation(id)) {
        createTag(id);
    }
}

/**
 * 태그 Area 안에 태그 생성
 * @param 태그명
 */
function createTag(tagName) {
    const tagId = tagName + "_in";
    const createTagSpan = document.createElement("span");
    document.getElementById("tag_value").value += tagName + " ";
    createTagSpan.id = tagId;
    createTagSpan.className = "highlight";
    createTagSpan.innerText = tagName;
    createTagSpan.onclick = function () {
        removeTag(tagId);
    };
    tagArea.appendChild(createTagSpan);
}

/**
 * 태그 Area 안의 태그를 클릭하면 태그 삭제
 * @param 태그 요소의 ID 값
 */
function removeTag(tag_id) {
    const tagValue = document.getElementById("tag_value").value;
    const result = tag_id.substring(0, tag_id.length - 3);
    const idx = tagValue.indexOf(result);

    document.getElementById("tag_value").value = tagValue.replace(
        tagValue.substring(idx, idx + result.length + 1),
        ""
    );

    document.getElementById(tag_id).remove();
    validationInit();
}

/**
 * 태그 유효성 검사
 *
 * 호출시 기존의 유효성 검사 초기화
 *
 * 공통)
 * 1. 태그 Area에 태그가 10개 넘어갈 수 없다.
 * 2. 중복 추가 금지
 *
 * 직접 입력의 경우 -> directlyTagValidation()에서 유효성 추가 검사
 * @param (클릭 추가일 경우) ID 값
 * @returns 통과할 경우 true, 아니면 경고문 출력 후 false
 */
function tagValidation(...id) {
    const tagName = document.getElementById("tag").value;
    const tagValue = document.getElementById("tag_value").value;
    const tagValueList = document.getElementById("tag_value").value.split(" ");

    validationInit();

    if (id.length === 0 && !directlyTagValidation(tagName)) {
        return false;
    }

    if ((id.length !== 0 && tagValue.includes(id)) || (id.length === 0 && tagValue.includes(tagName))) {
        duplicateTag.classList.remove("hidden");
        return false;
    }

    if (tagValueList.length > 10) {
        tooManyTag.classList.remove("hidden");
        return false;
    }

    return true;
}

/**
 * 직접 입력 시의 태그 유효성 추가 검사
 *
 * 1. 초기값 # 추가 금지
 * 2. 11자 이상 태그명 금지
 *
 * @param 태그명
 * @returns 통과할 경우 true, 아니면 경고문 출력 후 false
 */
function directlyTagValidation(tagName) {
    if (tagName === "#") {
        blankTag.classList.remove("hidden");
        return false;
    }

    if (tagName.length > 11) {
        tooLongTag.classList.remove("hidden");
        return false;
    }

    return true;
}

/**
 * 유효성 검사 초기화
 */
function validationInit() {
    blankTag.classList.add("hidden");
    tooLongTag.classList.add("hidden");
    tooManyTag.classList.add("hidden");
    duplicateTag.classList.add("hidden");
}

아까전보다 훨씬 깔끔해졌다.

스코프를 명확히 하기 위해 변수 타입을 var에서 const로 바꿨다.

JQuery를 쓸까 고민했지만 바닐라가 더 익숙해서 그대로 썼다.

더 깔끔하게 정리하고 싶지만 욕심은 버리고 나중에 시간 남으면 더 해보기로 했다.

profile
상상을 현실로 만들기 위해 노력하는 개발자

0개의 댓글