(Javascript | mithril) Todo 앱 – 예제

mithril 관련 글

  1. mithril 앱의 기본 구성 및 m()
  2. m.render – 가상 DOM 렌더링하기
  3. m.mountmithril을 이용한 양방향 바인딩 & 템플릿 렌더링
  4. m.prop 양방향 바인딩을 위한 데이터 래퍼
  5. m.withAttr 양방향 바인딩을 위한 이벤트 매퍼
  6. m.componentmithril 앱을 컴포넌트화하기
  7. * Todo
  8. m.route – 단일페이지 애플리케이션 및 라우팅 규칙
  9. m.request – 서버 API와 통신하기

간단한 TODO앱을 작성해보도록하자. ToDo 앱은 MVC 패턴으로 구성할 수 있는 가장 간단한 앱의 형태이다. 이 앱을 만들기 위한 기본적인 스케치는 다음과 같다.

  1. 텍스트를 입력할 수 있는 input 박스와 버튼이 있다. 텍스트에 할일을 입력하고 버튼을 클릭하면 아래쪽으로 새로운 할일이 목록에 추가된다.
  2. 목록의 각각의 할일은 체크 박스를 가지고 있다. 완료한 일은 체크 박스에 체크하여 완료로 표시한다. 완료 상태가 되면 해당 할일은 취소선을 적용하여 표시한다.

MVC

먼저 모델이 되는 “할 일” 부터 디자인해보자. 하나의 작업은 작업의 이름과 상태, 두 가지 속성을 가지는 매우 간단한 형태이다. 이는 다음과 같이 작성한다.

Task = (desc) !->
  @title = m.prop desc
  @done = m.prop false # 새 할일은 항상 완료되지 않은 상태이다.

컨트롤러 부분 부터 생각해보자. 컨트롤러는 크게 두 가지 일을 담당한다.

  1. 뷰의 텍스트 필드와 바인딩하여 입력된 텍스트값을 얻어온다.
  2. 버튼이 클릭되면 입력된 텍스트를 제목으로 하는 새 할일을 추가한다.

그리고 추가된 할일들의 리스트를 유지해주면 된다. 이는 다음과 같이 작성할 수 있다.

app = {}
app.controller = !->
  @tasks = new Array!
  @description = m.prop ''
  @add-new = !~> #이벤트핸들러로 쓸 것이기 때문에 바운드 메소드로 작성
    if @description! != '' =>
      @tasks.push new Task @desription
      @description '' # 입력값 초기화

뷰는 제법 복잡할 수 있는데, 입력을 받는 폼 부분과 목록을 출력하는 부분이다. 우선 폼 부분부터 작성해보자.

app.view = (ctrl) ->
  m \.app.todo,
    m \div, 
    * m \input, do
        onchange: m.with-attr \value, ctrl.description
        value: ctrl.description!
      m \button, do
        onclick: ctrl.add-new
      , \add
    # <listing goes here...>

입력 필드의 값이 변경되면 onchange이벤트가 발생하고, 컨트롤러의 description 프로퍼티에 이 값이 전달되어 업데이트된다. 그리고 버튼은 클릭하면 add-new 가 호출되어 현재 입력된 필드값이 있을 때만 입력값으로 새 할일을 만들어 추가하고, 입력 필드의 내용을 리셋한다. 입력 필드의 값은 컨트롤러의 description과 바인딩되어 있으므로 이 값을 빈 문자열로 넣어주기만해도 자동으로 UI에 반영된다.

목록은 각 task 별로 체크박스와 내용을 출력한다. 이 부분은 별도의 컴포넌트로 만들어보자.

comp-listing =
  view: (ctrl, data) ->
    m \table,
      data.map (task) ->
        m \tr,
          m \td,
          * m 'input[type=checkbox]' do
              onclick: m.with-attr \checked, task.done
              checked: task.done!
            m \span, {style: text-decoration: if task.done! then 'line-through' else 'none'}
            , task.name!

주의할 것은 앞서 컴포넌트편 포스팅에서 설명했듯이, 컴포넌트는 반드시 루트 DOM을 리턴해야 한다는 점이다. 따라서 table 내의 반복문을 대체할 수 없다. 리스팅은 테이블을 하나 만들고 전달받은 데이터 배열에 대해 각 Task 값마다 완료 체크 박스와 그 타이틀을 출력한다. 그리고 체크 박스를 클릭하여 체크 상태를 반전시키면 이 값은 다시 맵핑된 해당 task 값의 done 프로퍼티와 연동한다.

리스팅 부분은 컴포넌트로 뺐으니, 이제 view 부분은 다음과 같이 수정한다.

app.view = (ctrl) ->
  m \.app.todo,
  * m \div, 
    * m \input, do
        onchange: m.with-attr \value, ctrl.description
        value: ctrl.description!
      m \button, do
        onclick: ctrl.add-new
      , \add
    m.component comp-listing, ctrl.tasks

이제 이 앱을 문서에 마운트하면된다. 총 40라인 이하의 분량으로 완성했다. 결과는 아래 codepen을 참고하자.

See the Pen todo on mithril by 56perc (@56perc) on CodePen.