To Do List 앱 구현하기

할일을 메모해서 관리하는 간단한 앱을 제작합니다.

"to DO List" 앱은 할일 내용과 중요도를 선택하는 입력 폼 1개와 할일의 진행단계에 따라 구분해서 표시하는 목록 1개로 구성됩니다.

할일 목록은 각 할일을 단계별로 구분해서 표시하고, 할일->진행중->완료 로 단계가 구분됩니다.

각 할일에 표시되는 ">>" 버튼을 눌러 다음 단계 목록으로 이동하는 기능을 구현하며, 선택자로 현재 할일을 선택해 다음 단계 목록으로 이동시키는 방법을 이해하는 것이 이 앱의 핵심 학습 부분입니다.

UI는 모바일 환경에 맞게 좁은 화면 하나에 모두 보이도록 합니다.

할일 폼 제작

먼저 할일 등록 폼을 제작합니다.

중요도와 할일 입력 텍스트영역 2개로 구성된 간단한 폼입니다.

<div class="wrap">
  <h1>to DO List</h1>
  <div class="wrapform">
    <form name="todoform" id="todoform">
      <div class="label">할일</div>
      <div class="label">중요도</div>
      <div class="radio">
        <input type="radio" name="priority" value="1" />
        <label>1순위</label><input type="radio" name="priority" value="2" />
        <label>2순위</label><input type="radio" name="priority" value="3" />
        <label>3순위</label><input type="radio" name="priority" value="4" />
        <label>4순위</label><input type="radio" name="priority" value="5" />
        <label>5순위</label>
      </div>
      <div class="cmd">
        <input type="button" name="reset" value="내용지우기" />
        <inputtype="button" name="save" value="등록" />
      </div>
    </form>
  </div>
</div>

CSS로 폼 레이아웃을 완성합니다.

body {
    background-color: #e8e8e8;
}
a,
a:hover,
a:visited,
a:active {
    text-decoration: none;
    color: #000;
}
a:hover {
    color: #a00;
}
h1 {
    text-align: center;
    background-color: #fff;
    padding: 5px;
    margin: 0;
}
/* 레이아웃 외곽 너비 400px 제한*/
.wrap {
    max-width: 480px;
    margin: 0 auto;
    /* 화면 가운데로 */
}
/* 폼 */
.wrapform {
    padding: 20px;
    width: 100%;
    background-color: #f0f0f0;
    box-sizing: border-box;
}
.wrapform form[name=todoform] {
    margin: 0 auto;
}
form[name=todoform]>textarea {
    width: 100%;
}
form[name=todoform]>.label {
    background-color: #f8f8f8;
    margin: 10px 0 10px 0;
    padding: 10px;
    font-weight: 600;
}
form[name=todoform]>.radio label {
    margin-right: 20px;
}
form[name=todoform]>.cmd {
    margin-top: 20px;
    text-align: right;
}
form[name=todoform]>.cmd input[type="button"] {
    padding: 10px 20px;
    border: 1px solid #e8e8e8;
    background-color: #fff;
    background-color: #000;
    color: #fff;
}
form[name=todoform] input[type=button] {
    cursor: pointer;
}

자바스크립트로 함수 버튼 기능 처리를 위한 이벤트 리스너와 저장전 폼 필드를 체크하는 함수들을 추가합니다.

폼 필드 입력 체크는 2개의 함수로 구현했습니다.

validateItemForm() 함수는 폼 전체 필드를 체크하는 호출 함수가 되고, 실제 개별 필드 1개는 validateItemField() 함수에서 체크를 합니다.

예제에서는 폼 필드가 2개여서 이렇게 나눌 필요까지 없지만, 폼 필드가 여러개가 될 경우 중복 코드가 많이 발생하기 때문에 중복 코드를 최소한으로 줄일 수 있습니다.

document.addEventListener('DOMContentLoaded', function () {

    //폼 버튼 이벤트 리스너 추가
    document.getElementsByName('reset')[0].addEventListener('click', function () {
        resetForm();
    });

    // 리셋
    document.getElementsByName('save')[0].addEventListener('click', function () {
        saveItem();
    });

    // 저장
});

/* 할일 저장 */
function saveItem() {
    if (validateItemForm()) {
        // 폼 필드 체크 완료되었으면
        // 새 아이템 템플릿 HTML 얻기
        let form = document.getElementById('newitem');
        let elem = document.createElement('div');
        elem.innerHTML = form.text.trim();
        elem.firstChild.classList.add('newitem');
        document.querySelector('.todo .content').append(elem.firstChild);

        // 새 아이템 등록 날짜 얻기
        let date = new Date();

        // 새 아이템 내용 생성
        document.querySelector('.newitem .todo').textContent = document.getElementsByName('todo')[0].value;
        document.querySelector('.newitem .date').textContent = date.getFullYear() + '-' + (date.getUTCMonth() + 1).fillZero(2) + '-' + date.getDate().fillZero(2) + ', ';
        document.querySelector('.newitem .priority').textContent = '중요도 ' + document.querySelector('input[name="priority"]:checked').value;

        // 단계 이동 이벤트리스너 등록
        document.querySelector('.newitem .moveitem').addEventListener('click', function (e) {
            moveItem(e.target);
        });

        // 새 아이템 부착
        document.querySelector('.newitem').classList.remove('newitem');
        resetForm();
        // 할일 목록에 등록 후 폼 초기화
    }
}

/* 양식 초기화 */
function resetForm() {
    document.getElementsByName('todo')[0].value = '';
    let radios = document.getElementsByName('priority');
    radios.forEach(function (radio) {
        radio.checked = false;
    });
}

/* 폼 입력 체크 */
function validateItemForm() {
    let todo = validateItemField('todo', '할일', 'input');
    let priority = validateItemField('priority', '중요도', 'radio');
    if (todo == '' || priority == '') {
        return false;
    } else {
        return true;
    }
}

/* 개별 필드 입력 체크 */
function validateItemField(fieldname, label, fieldtype) {
    let val = '';
    switch (fieldtype) {
        case 'input': val = document.getElementsByName(fieldname)[0].value;
            if (val == '') {
                alert(label + ' 을/를 입력해 주십시오.');
                document.getElementsByName(fieldname)[0].focus();
            } break;
        case 'radio': if (document.querySelector('input[name="' + fieldname + '"]:checked') != null) {
            val = document.querySelector('input[name="' + fieldname + '"]:checked').value;
        } if (val == '') {
            alert(label + ' 를 선택해 주십시오.');
        } break;
    }
    return val;
}

// 숫자 앞에 자리수만큼 0으로 채운 문자열 반환. 프로토타입으로 숫자 메써드로 구현
Number.prototype.fillZero = function(width){
    let n = String(this);
    //문자열 변환
    return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n;
    //남는 길이만큼 0으로 채움
}

할일 목록 제작

추가한 할일들을 단계별로 표시하는 목록을 만들어 보겠습니다.

먼저 HTML 페이지에 목록 기본 틀을 추가합니다.

<section class="section entry-content">
    <div class="todo">
        <div class="title">할일</div>
        <div class="content"></div>
    </div>
    <div class="doing">
        <div class="title">진행중</div>
        <div class="content"></div>
    </div>
    <div class="done">
        <div class="title">완료</div>
        <div class="content"></div>
    </div>
</section>

폼에서 추가하는 할일 아이템 1개는 

<div class="item">
    <h3 class="todo">내일할일 정리</h3>
    <span class="date">2020-06-22, </span>
    <span class="priority">중요도 3</span>
    <span class="moveitem"> >> </span>
</div>

이런 형태로 추가되는 것으로 합니다.

실제로는 템플릿으로 동적으로 추가하게 되므로 CSS와 자바스크립트로 할일 아이템을 제어하는 기준 모양이라고 이해하면 됩니다.

HTML 페이지에 아이템 1개를 추가하는데 사용할 템플릿을 추가합니다.

보통 템플릿은 HTML 페이지 최하단 </body> 앞에 추가합니다.

<!– "TO DO 아이템 템플리트" -->
<script type="template" id="newitem">
<div class="item">
    <h3 class="todo"></h3>
    <span class="date"></span>
    <span class="priority"></span>
    <span class="moveitem">>></span>
</div>
</script>

CSS를 추가해 목록 레이아웃을 맞춥니다.

/* 목록 */.section{
    padding: 20px;
    box-sizing: border-box;
    background-color: #fff;
}
.entry-content > div{
    width: 100%;
    box-sizing: border-box;
}
.entry-content > div .title{
    background-color: #a00;
    padding: 10px;
    box-sizing: border-box;
    color: #fff;
    font-weight: 600;
}
.entry-content > div .content{
    min-height:50px;
    padding: 10px;
    box-sizing: border-box;
    color: #000;
    background-color: #f0f0f0;
}
.entry-content > div div.item{
    background-color: #D5E3E8;
}
.entry-content > div div.item .moveitem{
    background-color: #e8e8e8;
    float: right;
    padding: 5px;
    margin-top: -5px;
    box-sizing: border-box;
    line-height: 1;
    cursor: pointer;
}
.entry-content > div div.content > .item{
    padding: 10px;
    box-sizing: border-box;
    margin-top: 5px;
}

할일 아이템을 추가한 완성된 기본 형태는 다음과 같이 됩니다.

단계 이동 기능 추가

기본적인 기능은 모두 완성되었고, 할일 아이템을 다음 단계로 이동시키는 자바스크립트 함수를 추가합니다.

/* 진행단계 이동 */
function moveItem(elem){
    let section = elem.parentNode.parentNode.parentNode;
    let item = elem.parentNode;
    
    // 진행중 목록에 있을 경우 완료 단계로 이동하면서 단계 이동 화살표 삭제
    if(section.classList.contains('doing')){
        elem.remove();
    }
    
    // 이동할 할일 아잍템을 떼어내서 다음 단계 목록에 붙임
    let ditem = item.parentNode.removeChild(item); // 떼어낸 할일 아이템을 반환 받음
    document.querySelector('.'+section.nextElementSibling.className+' .content').append(ditem);
}