21세기소년, Javascript

(Javascript | mithril ) 예제 – mithril로 만드는 초간단 클라이언트 사이트 블로그

로컬 스토리지를 이용한 메모장 만들기

미스릴을 이용해서 간단한 블로그 혹은 메모장을 만드는 과정을 살펴보도록 하겠다. 예전에는 뭐 ROR가지고 15분 만에 블로그만들기… 같은 게 유행했는데, 암튼 제대로된 UI는 없겠지만 최소 기능만 구현하는 것으로 해보자.

미스릴의 view쪽 코드를 작성하는 게 바닐라 자바스크립트로는 여러 가지 애로사항이 꽃필 수 있어서 라이브스크립트를 사용하기로 한다. 서버쪽 코드를 작성할 일이없기 때문에 60라인 이내로 다듬어지게 된다.

먼저 html 코드는 다음과 같다. 미스릴과 라이브스크립트 번역기를 로드하고 ㅇ제부터 작성할 스크립트 파일을 불러온 후에 이를 실행하는 코드가 전부이다.

그리고 body에는 화면이 비어있는 경우에 표시할 loading...이라는 문구만 적혀있게끔 한다.

<!doctype html>
<html>
    <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/0.2.5/mithril.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/livescript/1.4.0/livescript-min.js"></script>
    <script src="./blog.ls" type="text/ls"></script>
    <script>require('LiveScript').go();</script>
    </head>
    <body>
        <div id="main">loading</div>
    </body>
</html>

스크립트 작성하기

위에서 적은대로 blog.ls라는 파일을 생성하고 작성을 시작한다. 먼저 각각의 글을 저장하게될 모델 클래스를 만들어보자. 별도의 서버나 DB가 없으므로 로컬 스토리지에 저장할 건데, 그러핟면 JSON 포맷으로 변환이 가능해야 한다. 사실 m.request를 이용해서 서버와 통신해서 별도의 서버나 DB에 저장할 수도 있고 이 때의 포맷도 JSON을 사용하므로 서버만 준비된다면 클라이언트쪽은 storage-manager 부분만 정리하면 나머지는 완전히 동일하게 가져갈 수 있으니 참고하자.

작성한 글을 구분할 키는 생성시점의 타임스탬프를 사용하도록 한다. 또 작성시에 UI와 바인딩되어야 하는 부분은 제목과 본문만 있으면 된다.

class Post 
  (args) ->
    @title = m.prop (args?.title || '')
    @content = m.prop (args?.content || '')
    @timestamp = m.prop (args?.timestamp || new Date!get-time!)

이제 작성한 포스트를 로컬 스토리지에 저장하고, 또 키를 이용해서 불러와 줄, 저장을 담당할 객체를 만들어보자.

storage-manager =
  save: (post) !->
    key = post.timestamp
    local-storage.set-item key, JSON.stringify post
  load: (key) ->
    new Post JSON.parse local-storage.get-item key

글을 넣었다 뺐다하는 부분은 벌써 다 만들었다. 이번에는 작성하는 UI를 만들어보자. 그 전에 저장되는 글 상태를 볼 수 있도록 하는 임시 렌러더를 하나 만들어본다.

status = 
  view: (ctrl, post) ->
    m \ul,
      * m \li, post.timestamp!
        m \li, post.title!
        m \li, post.content!

비슷한 모양으로 코드가 반복되므로 다음과 같이 변경하는 것도 방법이다. (아래코드는 세줄에 걸쳐 썼지만 한 줄로 합쳐도 되는 코드이다.)

status = 
  view: (ctrl, post) ->
    m \ul, <[ timestamp title content ]>.map -> m \li, post[it]!

위 임시 정보창을 붙여서 작성창을 만들어보자.

writer = 
  controller: -> post: new Post!
  view: (ctrl) ->
    m \div.writer,
      * \title
        m \input, 
          onkeyup: m.with-attr \value, ctrl.post.title
          value: ctrl.post.title!
        m \textarea,
          onkeyup: m.with-attr \value, ctrl.post.content
          value: ctrl.post.content!
        m \button,
          onclick: ~> storage-manager.save ctrl.post
        , \save
        m.component status, ctrl.post

이제 여기까지 중간 점검!

m.mount document.body, writer

를 맨 아래에 추가해보고 브라우저에서 해당페이지를 열어보자.

> python -mhttp.server 8888

파이썬으로 간단한 HTTP 서버를 실행하고 해당 페이지를 열면 UI는 조악하지만 제목과 내용을 입력하는 창이 나오며 키를 누를 때마다 내용이 갱신되고, 언제든지 저장할 수 있는 페이지가 만들어졌다. 우리는 m.route를 이용해서 하나의 페이지에서 목록/작성/상세를 모두 구현할 것이다. 각각의 루트는 다음과 같이 구분하자.

  • ‘/’, ‘/list’ : 글 목록 표시
  • ‘/add/:key’ : 글 작성, 편집.
  • ‘/view/:key’ : 글 보기

이제 저장된 리스트를 표시하는 컴포넌트를 만들어보도록 하자. 저장된 키들을 순회하여 제목을 뽑아낸 다음, 이 제목과 키를 이용해서 링크를 만든다. 여기서는 정적 HTML 페이지를 사용할거니까, m.route를 이용해서 링크를 처리한다.

또, 리스트 아래에는 새 글 쓰기로 연결되는 버튼도 만든다.

lister = 
  view: (ctrl) ->
    * m \ul, [0 til local-storage.length].map (i) ~> 
        t = storage-manager.load local-storage.key i
        m \li, 
          m "a[href=javascritp:;]", {onclick: ~> m.mount "/view/#k"}, \t
      m \button, {onclick: -> m.route '/add'}, \write

다음은 작성된 내용을 표시하는 부분이다. 끝에는 리스트로 돌아가는 버튼과 현재글 편집 버튼을 추가했다.

추가: 컴포넌트가 구성될 때 controller는 최초 한 번만 호출된다. 이 시점에서 키가 올바른지 아닌지를 판단하여 올바르지 않은 키가 들어왔다면 새 창으로 이동하도록 해보자.

viewer = 
  controller: -> if k = m.route.param \key then post: storage-manager.load k
    else: m.route '/add/new'
  view: (ctrl) ->
    m \div.wrapper,
      m \h1, ctrl.post.title!
      m \div.content, ctrl.post.content!
      m \button, {onclick: ~> m.route "/add/#{ctrl.post.timestamp!}"}, \edit
      m \button, {onclick: -> m.route "/"}, \list

저장된 글을 편집하는 경우에는 키가 넘어온 경우에는 기존 글을 불러오고 아니면 새 글을 시작하면 된다. 이미 모든 필드가 바인딩되어 있기 때문에 writercontroller 함수만 아래와 같이 바꿔주면 된다.

writer = 
  controller: -> if k = m.route.param \key then post: storage-manager.load k
    else post: new Post!
  view: (ctrl) -> ...

이제 모든 주소들을 등록해준다.

m.route document.body, '/', do
  '/list': lister,
  '/add/:key': writer,
  '/view/:key': viewer

다했다!!!

모든 코드를 한 데 합쳤을 떄의 모양은 아래와 같다.

https://gist.github.com/sooop/330ab9187f32c998eed9109cce456519