Skip to content

Instantly share code, notes, and snippets.

@easylogic
Last active February 28, 2018 09:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save easylogic/17b9d6d07e32b9148f8c to your computer and use it in GitHub Desktop.
Save easylogic/17b9d6d07e32b9148f8c to your computer and use it in GitHub Desktop.
summernote 를 다른 UI 로 확장하자.

#Summernote 멀티 UI 삽질기

다른 UI 형태로 확장할려면 어떻게 해야할까?

  • UI 객체가 필요하다.
  • ICON 문자열을 변경할 수 있어야한다.
  • 나머지 모듈에서 UI 를 사용하는 부분에 대해서 바꿀 수 있어야한다.

Lite 버전은 ?

summernote.js   /* 메인 클래스 로드 */
summernote-lite.js /* lite 에서 사용되어지는 클래스로 업데이트 */ 
summernote-lite.css /* lite 용 css 로드 */
icons/font.css      /* 자체 폰트 아이콘 사용 */ 

라이트 버전은 bootstrap 과 유사한 구조의 css 로 되어 있다.

ui 객체를 조금만 손 보고도 거의 비슷하게 UI 를 사용할 수 있게 해준다.

나름 성공적?

Material UI 로 만들어보자.

http://materializecss.com/

가장 유명한 프레임워크를 기준으로 만들어보자.

적용해보다가 망했다.

망한 이유

  1. toolbar 에 대한 개념이 없다.
  2. toolbar 가 없다보니 button group 에 대한 개념이 없다.
  3. nav 형태의 메뉴를 제공하긴 한다.
  4. 하지만 전체적으로 넓은 여백을 쓰고 있어서 editor 의 메뉴바로 활용할 수 있을지 미지수다.
  5. bootstrap 으로 설계한 UI 함수를 정상적으로 사용하는 것은 불가능에 가깝다.
  6. 애초에 개념이 다르기 때문에 없는 요소가 너무 많다.

Semantic UI 도 해보자.

http://semantic-ui.com/

bootstrap 다음으로 많이 쓰고 있는 ui framework 이다.

이것도 하다가 망했다.

망한 이유

1 ~ 6까지 모두 같다.

가장 큰건 tag 를 생성하는 규칙 자체가 다르기 때문에 기존에 가지고 있던 ui 객체를 활용하는 방법이 전혀 맞질 않다.

특히나 버튼의 표시 유무에 의해서 커스터마이징이 되어야 하는데 기존 태그 구조로는 맞추기가 너무 힘들다.

이럴 바에는 그냥 통째로 다시 만드는게 나을 듯 하다.

External UI 를 만드는 방법을 설계해보자.

$.summernote.ui 를 재정의 한다.

$.summernote.ui = (function(renderer) { 
  return {
    createLayout : function () {
      //... 
    }
  }
})(renderer);

실험해본 바로는 $.summernote.ui 를 summernote 로드 이후에 어디든 정의해서 덮어쒸울 수 있다.

기존에 bs3 에서 가지고 있던 ui 객체 소스를 가지고 와서 작성한다.

결과물이 jquery 객체로 되어 있기 때문에 renderer 를 구현하지 않고 순수하게 jquery 만 가지고 작업해도 된다.

$.summernote.options 도 재정의 한다.

semantic ui 의 경우 자신의 icon 을 쓰는 규칙이 따로 있다.

그러므로 이 부분도 사전에 미리 정의를 한다.

$.extend($.summernote.options.icons, {
  font : 'font icon'    // semantic icon 정의 
});

실제 에디터 영역으로 써야할 UI 형태를 잘 설계해보자.

bootstrap, semantic, material 등은 사실 각자의 개성이 강하다.

스스로 가질 수 있는 형태의 화면을 가지기를 원한다.

즉, 각자에 어울리는 화면이 있다.

그런 상황에서 Bootstrap UI 의 기준으로 다른 UI 를 만드는 것은 맞지않다.

예를 들어

Bootstrap 의 경우 data 속성만으로도 tooltip을 제공할 수 있지만 semantic ui 의 경우 .popup() 이라는 함수를 통해서 따로 실행을 해줘야 한다. material 도 그에 맞는 방법이 있다.

각자 UI 의 화면에 맞고 스크립트도 그대로 사용할 수 있으면 더 좋을 것 같다.

이렇게 할려면 UI 에 대한 추상화가 명확해야할 것 같다.

각자 쓰는 방식대로 구현을 하되 summernote 라는 에디터안에서 자연스럽게 표시될 수 있도록 하는 일이다.

Button 의 정의(추상화)를 명확히 한다.

보통 버튼의 종류가 많은데 아래와 같이 있다고 한다면

  • Button : 클릭의 기능만 있는 버튼
  • DropdownButton : Dropdown List 를 가지는 버튼
  • CheckDropdownButton : Check 형식의 Dropdown List를 가지는 버튼
  • SplitButton : 현재 상태를 표시하고 다른 값을 선택할 수 있는 버튼
  • SplitDropdownButton : SplitButton 과 동일하고 Dropdown 리스트를 정의할 수 있는 버튼

각 버튼마다 정의하는 방식 자체가 다르다.

문제는 UI 별로 button 을 정의하는 개념이 다르기 때문에 더욱더 다른 코드가 되버린다.

UI 를 추상화 해야할필요가 생긴다.

기본적으로 Summernote에서 제공하는 Button 의 종류를 만들어두고 그 내부 구현은 ui.js 에서만 한다.

예를 들어 현재 dropdown 버튼을 만들려면 아래와 같이 정의를 하는데

// bootstrap 구조에 맞게 Buttons.js 에서 정의 
ui.buttonGroup([  // group 을 만들고 
  ui.button({ // 표시될 버튼을 생성하고 
    className: 'dropdown-toggle',
    contents: ui.icon(options.icons.magic) + ' ' + ui.icon(options.icons.caret, 'span'),
    tooltip: lang.style.style,
    data: {
      toggle: 'dropdown'
    }
  }),
  ui.dropdown({ // dropdown 형태로 표시될 리스트를 만든다. 
    className: 'dropdown-style',
    items: context.options.styleTags,
    click: context.createInvokeHandler('editor.formatBlock')
  })
])

이것을 ui 구조 자체를 몰라도 할 수 있게 바꾼다.

// 추상화된 dropdownButton 을 사용해서 속성만 지정한다. ui 생성에 관여하지 않는다. 
ui.dropdownButton({ // 표시될 버튼을 생성하고 
  className: 'dropdown-toggle',
  contents: ui.icon(options.icons.magic) + ' ' + ui.icon(options.icons.caret, 'span'),
  tooltip: lang.style.style,
  items : context.options.styleTags,
  itemClick : context.createInvokeHandler('editor.formatBlock')
})

추상화된 객체를 생성하고 그 구현은 모두 ui.js 내부에 맡긴다.

Buttons.js 나 기타 다른 모듈에서 버튼을 정의할때 UI 구조를 모르고 속성만 가지고 사용할 수 있게 해야한다.

이렇게 해야 서로 다른 UI 에 대해서도 우리가 표현하고자 하는 바가 명확해진다.

Buttons.js 를 없애고 버튼의 기능을 모두 모듈로 만든다.

Buttons 에 현재 많은 버튼이 들어가있다.

서로 연관성이 없는 기능에 대해서 모두 모아둔 것인데 버튼이 복잡해질 수록 전체 코드가 복잡해지게 된다.

예를 들어 Button 이라고 정의는 해놓았지만 실제로 ColorPicker 의 경우 거의 모듈에 해당할정도로 복잡한 UI 를 가지고 있다.

ui.js 에 pallete 생성 코드가 있는데 사실 이건 ColorPicker 위한 전용코드 이기 때문에 다른데서 쓰지 않는다.

이럴꺼면 ColorPicker라는 모듈을만들고 그 안에서 UI 를 구성하는게 맞을 듯 하다.

이런 코드들을 한곳에 모아두는 것은 좋지 않다.

각 영역에 대해서 모두 개별 모듈 형태로 빼는게 맞을 듯 하다.

UI 의 추상화를 잘 해놓으면 대 부분의 버튼 생성 로직을 가지고 있는 모듈은 공통으로 올릴 수 있을 것 같다.

모듈의 구성을 재정의를 한번 해본다면 Buttons 를 빼고 나머지는 category 형태로 묶을 수 있을 것 같다.

  • RemoveStyle - removeStyle
  • FontStyle - style, family, size, height
  • Font - bold, underline, strike, supscript, subscript
  • Paragraph - para
  • Listing - orderedlist, unorderedlist
  • Color - forecolor, backcolor
  • Table - table
  • Video - video
  • Image - image
  • Link - link
  • CodeView - codeview
  • FullScreen - fullscreen
  • Help - help

각자 내부에서 하나의 버튼이 또 너무 기능이 많아지면 다시 모듈 형태로 나눠도 될 것 같다.

이렇게 해야 하나의 모듈이 커지는 것을 방지할 수 있다.

Popover 도 모듈에서 ui 를 정의하지 않는다.

popover 를 구성하기 위해서는 body 에 붙이고 다시 content 를 지정할 수 있는 selector 를 검색한다.

this.$popover = ui.popover({
  className: 'note-image-popover'
}).render().appendTo('body');
var $content = this.$popover.find('.popover-content');

context.invoke('buttons.build', $content, options.popover.image);

이러면 매번 ui 마다 커스터마이징을 해야한다.

this.$popover = ui.popover({
  className: 'note-image-popover',
  target : 'body',
  items : options.popover.image
});

위와 같이 최소한의 추상화만 남기고 모두 ui.js 에서 구현한다.

Dialog 도 비슷하게 처리한다.

최소한의 추상화만 남기고 그 외의 다른 dom 처리를 하지 않는다.

this.$dialog = ui.dialog({
  title: lang.image.insert,
  body: body,
  footer: footer,
  target : $container 
});

bs3 에 있는 모듈을 모두 개별 구현한다.

Dialog나 Popover 의 경우는 코드 구성만 맞으면 몇줄 수정하지 않고 기능 그대로 복원할 수 있다.

다만 추상화가 잘 되어 있다면 그 마저도 필요없을 듯 하다.

기본적으로 모듈마다의 최소한의 추상화된 UI 함수만 사용하고 개별 UI 생성 로직에 관여하지 않기 때문에 공통 모듈형태로 들어갈 가능성이 커진다.

결론

모든 UI 에 대해서 완벽히 대응하기는 어렵겠지만 UI 추상화를 통해서 어느정도 풀어갈 수 있지 않을까 한다.

외부에서는 최소한의 기능 정의만 하고

실제로 UI 를 구현하는 쪽에서 해당 기능에 맞는 UI 를 구현해서 주게 되면 좀 더 기능 개발에 집중할 수도있고

다른 UI 로의 확장도 쉬워질 것 같다.

관건은 모든 모듈에서 UI 작업을 하지 않는 것이다.

기대사항

추상화를 잘 해서 추후에 모바일용 UI.js 만 붙여도 되도록 구성하고 싶다.

ionic이나 jqueryui 같은 것도 붙일 수 있게 말이다.

@easylogic
Copy link
Author

샘플로 구성해봤는데 나름 괜찮은 것 같다.

Buttons.js 에 속성만 남길 수 있으므로 거의 공통으로 올라갈 수 있다.

Buttons.js 가 공통이 된다는 말은 나머지도 대부분 공통으로 올라갈 수 있다는 말이 된다.

Dialog 의 본문을 어떻게 할지만 정하면 될 듯 하다.

과연 ?

@easylogic
Copy link
Author

순간적으로 생각이 났는데.

Dialog 모듈에서 정의되어 지는 Template 코드를 재사용 가능한 코드로 만들면 어떨까?

예를 들어

function Module1 { 
   this.getTemplate = function() { 
      return str; 
     } 
}

이놈을 바꿔치기 하는거지

function Module2(context) {

    var module = new Module1(context); 
        module.getTemplate = function() {
          return str; 
        }
     return module;
}

이렇게 할 때 아래와 같이 하면 기존 모듈을 사용하면서 특정 메소드만 덮어쒸우는거다. 어떠냐?

$.summernote.plugins , {
     module1 : function (context) {
         var m = $.summernote.options.modules.module1(context);
         m.getTemplate = function() { 

         };

        return m; 
     }
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment