넷째 날: 진짜 쉬운 위지위그 에디터: summernote

저자

@keedi - Seoul.pm 리더, Perl덕후, 거침없이 배우는 펄의 공동 역자, keedi.k at gmail.com

시작하며

웹 응용을 제작하다보면 사용자의 필연적으로 사용자의 입력을 받아 들여야 합니다. 이런 입력 중 긴 문자열의 경우 보통 textarea 태그를 이용하는데, 아무래도 가장 기본적인 입력 도구인 만큼 서식이 필요한 문자열을 입력 받기에는 부족한 점이 많습니다. 그래서 서식있는 문자열 입력 및 편집을 위해 수 많은 HTML 편집기가 있습니다만, 이 중에서도 국내산(?) 오픈소스로 가벼우면서도, 꼭 필요한 기능들을 담고 있는 Bootstrap 기반의 잘 만들어진 summernote 편집기를 Mojolicious와 연동해서 사용해보도록 하죠.

준비물

필요한 모듈은 다음과 같습니다.

직접 CPAN을 이용해서 설치한다면 다음 명령을 이용해서 모듈을 설치합니다.

$ sudo cpan Mojolicious

사용자 계정으로 모듈을 설치하는 방법을 정확하게 알고 있거나 perlbrew를 이용해서 자신만의 Perl을 사용하고 있다면 다음 명령을 이용해서 모듈을 설치합니다.

$ cpan Mojolicious

뼈대 만들기

우선 기본 뼈대를 만듭니다.

$ mojo generate lite_app summernote-web.pl
[exist] /home/askdna/workspace/summernote
[write] /home/askdna/workspace/summernote/summernote-web.pl
[chmod] /home/askdna/workspace/summernote/summernote-web.pl 744
$

생성한 summernote-web.pl 코드를 약간 수정해서 우선 사용자의 입력을 받을 수 있도록 해보죠. http://.../e/<page_id> 형식의 주소에 접근할 수 있도록 컨트롤러를 만듭니다.

#!/usr/bin/env perl

use Mojolicious::Lite;

get "/e/:page_id" => sub {
    my $c = shift;

    my $page_id = $c->param("page_id");

    $c->render(
        template    => "edit",
        title       => "$page_id",
        markup_text => "<p>Hello Summernote!</p>",
    );
};

app->start;

__DATA__
...

summernote 편집기는 Bootstrap 기반의 HTML 편집기입니다. 따라서 사용하려면 우선적으로 jQueryBootstrapMojolicious 웹 응용 측에서 사전 적재를 해야 합니다. 편의를 위해 레이아웃 템플릿 쪽에서 필요한 라이브러리는 CDN 주소를 이용합니다. 더불어 Bootstrap의 기본 레이아웃을 따르기 위해 그리드로 적당히 요소를 배치합니다.

...
app->start;

__DATA__
@@ layouts/default.html.ep
<!DOCTYPE html>
%= tag html => ( lang => "ko" ) => begin
  %= tag head => begin
    %= tag meta => charset => "UTF-8"
    %= tag title => $title
    %= stylesheet "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css"
    %= javascript "http://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"
    %= javascript "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js"
    %= stylesheet begin
      [name="article"] {
        width: 100%;
        height: 320px;
      }
    % end
  % end
  %= tag body => begin
    %= tag div => ( class => "container" ) => begin
      %= tag div => ( class => "content" ) => begin
        <%= content %>
      % end
    % end
  % end
% end

컨트롤러를 통해 실제로 편집할 화면 부분도 다음과 갈이 추가합니다.

...
__DATA__
@@ layouts/default.html.ep
...
@@ edit.html.ep
% layout "default";
% title $title . " - Edit";

%= tag div => ( class => "row" ) => begin
  %= tag div => ( class => "col-xs-12" ) => begin
    %= tag h1 => $title
    <%= text_area article => begin %><%== $markup_text %><% end %>
    %= tag div => begin
      <button class="btn btn-danger" type="button" id="article-reset">지우기</button>
      <button class="btn btn-primary" type="button" id="article-save">저장</button>
    % end
  % end
% end

보시다시피 기본 뼈대 코드에 특별한 부분은 없습니다. 실행하면 로컬 장비의 3000번 포트에 웹 응용이 실행됩니다.

$ morbo summernote-web.pl
Server available at http://127.0.0.1:3000
...

HelloWorld 페이지를 수정한다고 가정하고 http://localhost:3000/e/HelloWorld로 접속해보죠.

기본 레이아웃 그림 1. 기본 레이아웃 (원본)

summernote 장착

jQuery와 Bootstrap을 적재한 상태라면 summernote를 장착하는 일은 식은 죽 먹기입니다. 우선 마찬가지로 CSS와 자바스크립트를 적재해야겠죠. 편의를 위해 summernote 역시 CDN 주소를 이용합니다. CDN 주소는 다음과 같습니다.

현 시점 기준 summernote의 최신 안정 버전은 0.8.2입니다. CDN을 이용한다면 URL에서 버전 번호만 적절하게 변경해서 다른 버전을 사용할 수 있습니다. 템플릿에 summernote의 스타일시트와 자바스크립트를 적재합니다.

@@ layouts/default.html.ep
...
    %= stylesheet "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.css"
    %= stylesheet "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.css

    %= javascript "http://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"
    %= javascript "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js"
    %= javascript "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.js
...

마지막으로 textarea 요소를 summernote화 시켜주면 됩니다. 기존 edit.html.ep 템플릿의 끝 부분에 자바스크립트 섹션을 추가합니다.

__DATA__

@@ edit.html.ep
...
%= tag div => ( class => "row" ) => begin
  %= tag div => ( class => "col-xs-12" ) => begin
    ...
  % end
% end

%= javascript begin
  $(document).ready(function() {
    $('textarea[name="article"]').summernote({
      height: 320
    });
  });
% end

해당 요소를 지칭하기 위해 textarea[name="article"] CSS 선택자를 사용했으며, 기존 CSS 영역의 높이가 적용되지 않으므로 별도의 인자로 높이를 지정합니다. 사실 summernote를 사용할 경우 입력 영역이 꼭 textarea 요소일 필요가 없습니다. div요소라 하더라도 알아서 입력 가능한 영역으로 바꿔주므로, 어떤 요소를 사용해서 입력을 받을지는 상황에 맞게 선택하세요.

summernote 장착 그림 2. summernote 장착 (원본)

라이브러리 적재에, 해당 HTML 요소를 초기화 시키는 것 만으로도 꽤 그럴듯한 편집 영역이 생성되었죠? :)

내용 저장하기

아무래도 자바스크립트 기반의 편집기인 만큼 자바스크립트와 분리하기 어려운 만큼, 기본적으로 자바스크립트를 사용하는 것을 전제로 하고 있습니다. 필수 라이브러리가 jQuery와 Bootstrap이니 이는 당연한 일입니다. 따라서 어짜피 자바스크립트를 사용하고 있는 만큼 내용 저장시에도 필요하다면 충분히 자바스크립트를 활용해서 다양한 사용자 경험을 제공하는 것에 부담가질 필요는 없을 것 같습니다. 우선 입력 영역 부분의 가장 기본적인 조작은 사용자가 입력한 자료를 불러오거나, 덮어쓰는 것이겠죠. 지우기 버튼을 클릭했을 경우 입력 영역의 내용을 지워봅시다. 입력 영역의 값을 쓰는 메소드는 code이며, 인자로 빈 문자열을 넘겨주면 내용을 모두 지웁니다.

@@ edit.html.ep
...
%= javascript begin
  $(document).ready(function() {
    $('textarea[name="article"]').summernote({
      height: 320
    });

    $("#article-reset").on("click", function () {
      $('textarea[name="article"]').summernote("code", "");
    });
  });
% end

저장 버튼을 클릭했을 경우 입력 영역의 내용을 확보하려면 어떻게 해야할까요? 값을 얻어오는 메소드 역시 code이며, 아무 인자도 넘겨주지 않으면 입력 영역의 내용을 반환합니다.

@@ edit.html.ep
...
%= javascript begin
  $(document).ready(function() {
    $('textarea[name="article"]').summernote({
      height: 320
    });

    $("#article-reset").on("click", function () {
      $('textarea[name="article"]').summernote("code", "");
    });
    $('#article-save').on("click", function () {
      var markupStr = $('textarea[name="article"]').summernote('code');
      // do something with markupStr
      // ...
    });
  });
% end

크리스마스 선물 #1: 지역 설정

summernote는 무려 40여개의 지역 언어를 지원합니다. 한국 발 오픈소스인 만큼 당연히 한국어도 지원하죠. 특정 지역 언어로 로컬을 변경하려면 해당 지역의 자바스크립트를 적재하고, 개체를 생성하는 시점에 lang 속성을 추가합니다. 간단하죠? :)

__DATA__

@@ edit.html.ep
...
%= javascript begin
  $(document).ready(function() {
    $('textarea[name="article"]').summernote({
      lang: 'ko-KR', // default: 'en-US'
      height: 320
    });
    ...
  });
% end

@@ layouts/default.html.ep
<!DOCTYPE html>
...
    %= javascript "http://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.js"
    %= javascript "http://netdna.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.js"
    %= javascript "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/summernote.js"
    %= javascript "http://cdnjs.cloudflare.com/ajax/libs/summernote/0.8.2/lang/summernote-ko-KR.js"
...

크리스마스 선물 #2: 붙여 넣기

summernote는 브라우저의 붙여 넣기 기능을 꽤 잘 지원하기 때문에 웹 상의 서식있는 문서를 긁어서 복사한 다음 붙여 넣을 경우에도 정말 딱 원하는 만큼의 결과로 보여줍니다. 다만, 브라우저의 클립보드 복사 및 붙여 넣기 동작의 특성 상 CSS 내용을 인라인으로 요소내에 일일이 붙여 넣기 때문에, 붙여넣은 자료의 HTML 내용이 꽤나 번잡하다는 단점이 있습니다.

서식있는 HTML 붙여넣기 그림 3. 서식있는 HTML 붙여넣기 (원본)

이를 해결하려면, 붙여넣는 시점에 각 요소의 속성을 점검해 그 중 인라인 스타일 시트를 제거해야 합니다. summernote는 이벤트를 구조적으로 잘 지원하고 있으므로, 붙여넣기 이벤트를 연결해서 불필요한 태그 요소의 속성 값을 바꾼다던가, 또는 태그 자체를 제거한다던가 등, 간편하게 후처리가 가능합니다.

__DATA__

@@ edit.html.ep
...
%= javascript begin
  $(document).ready(function() {
    $('textarea[name="article"]').summernote({
      lang: 'ko-KR', // default: 'en-US'
      height: 320,
      callbacks: {
        onPaste: function(e, ne) {
          var markupTextWithStyle = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('text/html');
          var $html = $($.parseHTML(markupTextWithStyle));
          $html.find("*").addBack().each(function(idx, val) {
            var $item = $(val);
            $item.removeAttr("style");
          });

          e.preventDefault();

          var markupText = $html[0].outerHTML;
          $('textarea[name="article"]').summernote('code', markupText);
        }
      }

    });
    ...
% end
...

조금 복잡해보이지만, 찬찬히 뜯어보면 그렇게 어려운 내용은 아닙니다. summernote의 붙여넣기 이벤트에 연결하기 위해 callbacks 속성을 추가했으며, 이 중 onPaste에 자바스크립트 함수를 연결합니다. 이 후 클립보드의 자료를 HTML 형식으로 가져오고, jQuery와 DOM을 활용해 각각의 요소에 있는 style 속성을 모두 제거합니다. 이 후 summernote의 기본 붙여넣기 이벤트 동작을 중단시키고 직접 변경한 내용을 입력 영역에 붙여 넣는 것입니다.

인라인 스타일시트 제거 그림 4. 인라인 스타일시트 제거 (원본)

완전 깔끔해졌죠? ;-)

정리하며

웹 응용을 작성하다보면 사용자의 입력을 받아들여야 하는 일은 일상입니다. HTML은 이미 이 모든 기능을 수십년 전부터 지원하고 있었지만, 세상은 너무도 빨리 변했고, 기본 기능만으로는 사용자의 요구를 만족시키지 쉽지 않은 것이 사실입니다. summernote는 간결하지만 미려하게 잘 만들어진 편집기로, 대부분의 경우 원하는 요구 수준을 잘 만족시켜 줄 것입니다. summernote와 Perl / Mojolicious를 조합해서 손쉽게 사용자의 입력을 받아보세요. :)

EOT

blog comments powered by Disqus