본문 바로가기

Web[웹]/ES6+ 함수형 언어

[ES6+: 응용] 프론트엔드에서 활용하기 (1)

 

#1 프론트엔드에서 함수형/이터러블/동시성 프로그래밍

 

ES6 템플릿 리터럴 활용

 

이후에 템플릿 리터럴을 통해 프로그래밍을 해볼 것인데, 템플릿 리터럴의 사용법은 아래와 같다.

 

const a = 10;
const b = 5;
console.log(`${a} + ${b} = ${a + b}`);
// 10 + 5 = 15

 


 

이미지 목록 그리기

 

실습할 CSS 코드

더보기

<style>

      .images {

        positionfixed;

        top0;

        left0;

        width100%;

        height100%;

        box-sizingborder-box;

        padding16px;

        overflowauto;

        text-aligncenter;

      }

      .image {

        positionrelative;

        displayinline-block;

        width160px;

        margin4px;

      }

      .image .remove {

        positionabsolute;

        top-8px;

        right-8px;

        width24px;

        height24px;

        padding3px 0 0;

        box-sizingborder-box;

        text-aligncenter;

        background#000;

        color#fff;

        font-weightbold;

        border-radius50%;

        cursorpointer;

      }

      .image .box {

        positionrelative;

        width160px;

        height160px;

        border1px solid #ccc;

        margin-bottom8px;

      }

      .image img {

        positionabsolute;

        top0px;

        left0;

        right0;

        bottom0;

        max-width90px;

        max-height90px;

        marginauto;

      }

      .image .name {

        text-aligncenter;

        height20px;

        overflowhidden;

      }

    </style>

 

실습할 JSON 데이터

더보기

[
  { name: "HEART", url: "https://s3.marpple.co/files/m2/t3/colored_images/45_1115570_1162087.png" },
  { name: "하트", url: "https://s3.marpple.co/f1/2019/1/1235206_1548918825999_78819.png" },
  { name: "2", url: "https://s3.marpple.co/f1/2018/1/1054966_1516076769146_28397.png" }, { name: "6", url: "https://s3.marpple.co/f1/2018/1/1054966_1516076919028_64501.png"},{"name":"도넛","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918758054_55883.png"},{"name":"14","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077199329_75954.png"},{"name":"15","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077223857_39997.png"},{"name":"시계","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918485881_30787.png"},{"name":"돈","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918585512_77099.png"},{"name":"10","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077029665_73411.png"},{"name":"7","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076948567_98474.png"},{"name":"농구공","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918719546_22465.png"},{"name":"9","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077004840_10995.png"},{"name":"선물","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918791224_48182.png"},{"name":"당구공","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918909204_46098.png"},{"name":"유령","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918927120_12321.png"},{"name":"원숭이","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919417134_80857.png"},{"name":"3","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076802375_69966.png"},{"name":"16","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077254829_36624.png"},{"name":"안경","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918944668_23881.png"},{"name":"폭죽","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919005789_67520.png"},{"name":"폭죽 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919027834_48946.png"},{"name":"박","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919062254_67900.png"},{"name":"톱니바퀴","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919302583_24439.png"},{"name":"11","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077078772_79004.png"},{"name":"핫도그","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919086961_23322.png"},{"name":"고기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919274214_33127.png"},{"name":"책","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919326628_13977.png"},{"name":"돋보기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919363855_26766.png"},{"name":"집","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919395033_19373.png"},{"name":"사람","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918696715_44274.png"},{"name":"연필","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919437239_32501.png"},{"name":"파일","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919468582_23707.png"},{"name":"스피커","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919495804_49080.png"},{"name":"트로피 ","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918438617_69000.png"},{"name":"카메라","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919847041_33220.png"},{"name":"그래프","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918521301_43877.png"},{"name":"가방","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918642937_11925.png"},{"name":"입술","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919886042_10049.png"},{"name":"fire","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920036111_19302.png"},{"name":"TV","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920054808_42469.png"},{"name":"핸드폰","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920109727_43404.png"},{"name":"노트북","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920142776_26474.png"},{"name":"전구","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920181784_14964.png"},{"name":"장미","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920264149_78607.png"},{"name":"맥주","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920312701_18073.png"},{"name":"마이크","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920397855_39832.png"},{"name":"별","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920420823_49166.png"},{"name":"와이파이","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920438005_35247.png"},{"name":"헤드폰","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920468136_82088.png"},{"name":"peace","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920538719_19072.png"},{"name":"계산기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920348341_40080.png"},{"name":"poo 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924259247_12839.png"},{"name":"poo 3","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924850867_72121.png"},{"name":"poo 4","url":"https://s3.marpple.co/f1/2019/1/1235206_1548925154648_40289.png"},{"name":"poo","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918988097_38121.png"},{"name":"모니터","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769774483.png"},{"name":"talk","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927111573_76831.png"},{"name":"keyboard","url":"https://s3.marpple.co/f1/2018/1/1054966_1516330864360_25866.png"},{"name":"daily 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548926169159_58295.png"},{"name":"daily","url":"https://s3.marpple.co/f1/2018/7/1199664_1531814945451_49451.png"},{"name":"편지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920087850_99421.png"},{"name":"sns 하단바 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548917218903_88079.png"},{"name":"sns 하단바","url":"https://s3.marpple.co/f1/2019/1/1235206_1548917192465_28365.png"},{"name":"sns 이모지 6","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927313417_99007.png"},{"name":"sns 이모지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927219485_18861.png"},{"name":"13","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077164559_59630.png"},{"name":"iphone","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769886837.png"},{"name":"아이패드","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769820297.png"},{"name":"컴퓨터","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769802862.png"},{"name":"5","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076888018_74741.png"},{"name":"poo 1","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924230868_28487.png"},{"name":"Sns icon_똥 안경","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211657799.png"},{"name":"Sns icon_똥 웃음","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211686108.png"},{"name":"4","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076850148_36610.png"},{"name":"Sns icon_똥 놀림","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211670017.png"},{"name":"달력","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919531014_35045.png"},{"name":"자물쇠","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918410738_59815.png"},{"name":"손 이모지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918353391_54897.png"},{"name":"Sns icon_손바닥","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210472038.png"},{"name":"Sns icon_검지","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210393226.png"},{"name":"Sns icon_롹","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210522978.png"},{"name":"Sns icon_하이파이브","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210538695.png"},{"name":"Sns icon_브이","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210508758.png"},{"name":"Sns icon_중지","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210428137.png"},{"name":"Sns icon_주먹","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210372629.png"},{"name":"Sns icon_박수","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210444994.png"},{"name":"Sns icon_따봉","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210488684.png"},{"name":"손 이모지 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548921736267_35010.png"},{"name":"손 이모지 3","url":"https://s3.marpple.co/f1/2019/1/1235206_1548922150829_10878.png"}
]

 

준비되어있는 JSON 데이터를 바탕으로, 이미지 목록을 HTML로 만들어서 화면에 나타내볼 것이다. 그 JSON 데이터를 이제 HTML로 만들어보자.

 

먼저, 데이터를 fetch를 통해 정리해주자.

 

const Images = {};

Images.fetch = () => [
  { name: "HEART", url: "https://s3.marpple.co/files/m2/t3/colored_images/45_1115570_1162087.png" },
  { name: "하트", url: "https://s3.marpple.co/f1/2019/1/1235206_1548918825999_78819.png" },
  { name: "2", url: "https://s3.marpple.co/f1/2018/1/1054966_1516076769146_28397.png" }, ...
];

 

위와같이 정리하면 "Images.fetch()"를 실행하면, 90개의 이미지 데이터가 나온다.

 

Images.fetch()
/*
▼(90) [{…}, {…}, ... , {…}, {…}, {…}]
 ▶0: {name: "HEART", url: "https://s3.marpple.co/files/m2...
 ▶1: {name: "하트", url: "https://s3.marpple.co/f1/2019/1...
 ▶2: {name: "2", url: "https://s3.marpple.co/f1/2018/1/1054...
   ...
 ▶87: {name: "Sns icon_따봉", url: "https://s3.marpple.co...
 ▶88: {name: "손 이모지 2", url: "https://s3.marpple.co/f1...
 ▶89: {name: "손 이모지 3", url: "https://s3.marpple.co/f1...
   length: 90
 ▶__proto__: Array(0)
 */

 

이를 서버에서  200ms에 걸쳐서 내려오는 것처럼 임시적으로 표현해보자.

 

Images.fetch = () => new Promise(resolve => setTimeout(() => resolve([
  { name: "HEART", url: "https://s3.marpple.co/files/m2/t3/colored_images/45_1115570_1162087.png" },
  { name: "하트", url: "https://s3.marpple.co/f1/2019/1/1235206_1548918825999_78819.png" },
  { name: "2", url: "https://s3.marpple.co/f1/2018/1/1054966_1516076769146_28397.png" }, ...
]), 200));

 

Images.fetch().then(console.log)
 /*
 ▶Promise {<pending>}
 (딜레이 200ms)
 ▶(90) [{…}, {…}, ..., {…}, {…}]
 */

 

fetch를 실행했을때 바로 결과가 떨어지지 않고, then을 통해서 200ms 후에 값이 떨어지는 것을 볼 수 있다.

이제 템플릿 리터럴을 통해서, HTML문자열로 바꿀 것이다. 간단하게 동작 테스트를 해보자.

 

Images.tmpl = imgs => `
  이미지 개수: ${imgs.length}
`;

_.go(
  Images.fetch(),
  Images.tmpl,
  console.log);
// 이미지 개수: 90

 

본격적으로 템플릿 리터럴로 HTML문자열로 바꿔보자.

 

Images.tmpl = imgs => `
  <div class ="images">
    ${_.map( img => `
      <div class="image">
        <div class="box"><img src="${img.url}" alt=""></div>
        <div class="name">${img.name}</div>
      </div>
    `, imgs)}
  </div>
`;

_.go(
  Images.fetch(),
  Images.tmpl,
  console.log);
/*
<div class ="images">
    
      <div class="image">
        <div class="box"><img src="https://s3.marpple.co/files/m2/t3/colored_images/45_1115570_1162087.png" alt=""></div>
        <div class="name">HEART</div>
      </div>
    ,
      <div class="image">
        <div class="box"><img src="https://s3.marpple.co/f1/2019/1/1235206_1548918825999_78819.png" alt=""></div>
        <div class="name">하트</div>
      </div>
    ,
    .
    .
    .
*/

 

위에서 map함수를 통해, 각 이미지 요소를 받아오는 HTML코드를 썼다. 허나, 위 그대로 쓰면 실행결과에서 한 이미지마다 쉼표가 찍히는 것을 볼 수 있다. 이것은 바로 Array를 출력했기 때문인데,바로 아래 처럼 문자열을 합치는 과정에서 ','이 그대로 드러난다.

 

[1,2,3,4].toString()
//"1,2,3,4"

 

그럼 이런 쉼표 없이 완전히 합치는 과정이 필요할 것이다. string이라는 함수를 생성해 a, b 를 문자열이건 정수형이건 합쳐주는 reduce를 만들어 준다.

 

const string = iter => _.reduce((a, b) => `${a}${b}`, iter);
...
string([1, 2, 3])
//"123"

 

그래서 이제 이 함수를 통해 string로 한번 더 감싸주면 쉼표없이 출력된다.

Images.tmpl = imgs => `
  <div class ="images">
    ${string(_.map( img => `
      <div class="image">
        <div class="box"><img src="${img.url}" alt=""></div>
        <div class="name">${img.name}</div>
      </div>
    `, imgs))}
  </div>
`;

 

이것을 좀더 정리하면 다음과 같이 할 수 있다.

 

_.strMap = _.pipe(L.map, string);

Images.tmpl = imgs => `
  <div class ="images">
    ${_.strMap( img => `
      <div class="image">
        <div class="box"><img src="${img.url}" alt=""></div>
        <div class="name">${img.name}</div>
      </div>
    `, imgs)}
  </div>
`;

 

여기까지하면, HTML문자열은 모두 만들었다. 이제 이것들을 엘리먼트로 만들어야한다.

 

$.el = html => {
  const wrap = document.createElement('div');
  wrap.innerHTML = html;
  return wrap.children[0];
};

...

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  el => document.querySelector('body').appendChild(el),
  console.log);

 

여기까지 하면 HTML 문자열이 정상적으로 엘리먼트로 바껴서 나온다. 코드를 좀 더 함수적으로 하기 위해 아래 코드를 바꿔보자.

 

/* 변경 전 */
_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  el => document.querySelector('body').appendChild(el), /* 바뀌는 부분 */
  console.log);
  
-----------------------------------------------------------------------
/* 변경 후 */
$.qs = (sel, parent) => document.querySelector(sel, parent);
/*$.qs = document.querySelector.bind(document);*/
$.append = (parent, child) => parent.appendChild(child);

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  el => $.append($.qs('body'), el), /* 바뀌는 부분 */
  console.log);

 

이것을 curry통해 더 간결하게 해줄 수 있다.

 

$.append = _.curry((parent, child) => parent.appendChild(child)); /* curry 적용 */

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')), /* 간결해짐 */
  console.log);

 

위 코드를 전부 정리해보면 다음과 같다.

 

const $ = {};
$.qs = (sel, parent) => document.querySelector(sel, parent);
$.append = _.curry((parent, child) => parent.appendChild(child));


$.el = html => {
  const wrap = document.createElement('div');
  wrap.innerHTML = html;
  return wrap.children[0];
};

const Images = {};
const string = iter => _.reduce((a, b) => `${a}${b}`, iter);

Images.fetch = () => new Promise(resolve => setTimeout(() => resolve(
/* JSON 데이터 */
), 200));

_.strMap = _.pipe(L.map, string);

Images.tmpl = imgs => `
  <div class ="images">
    ${_.strMap( img => `
      <div class="image">
        <div class="box"><img src="${img.url}" alt=""></div>
        <div class="name">${img.name}</div>
      </div>
    `, imgs)}
  </div>
`;

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')),
  console.log);

 

 


 

아이템 지우기

 

이번엔 버튼을 통해, 이미지를 지우고 실제로 엘리먼트가 삭제되도록 해보자. 그 전에 remove 클래스를 추가해서 버튼을 만들자.

 

Images.tmpl = imgs => `
  <div class ="images">
    ${_.strMap( img => `
      <div class="image">
        <div class="box"><img src="${img.url}" alt=""></div>
        <div class="name">${img.name}</div>
        <div class="remove">×</div>
      </div>
    `, imgs)}
  </div>
`;

 

먼저, 각 × 버튼을 조회하는 코드를 짜보자.

 

$.qs = (sel, parent) => document.querySelector(sel, parent); /* 하나의 엘리먼트를 찾음 */
$.qsa = (sel, parent) => document.querySelectorAll(sel, parent); /* 모든 엘리먼트를 찾음 */

 

모든 요소를 조회하는 qsa 를 통해 remove버튼을 모두 조회할 수 있다.

 

$.qsa('.remove')
// NodeList(90) [div.remove, div.remove, ... , div.remove]

 

하지만, 지금 찾아볼 것은 특정 엘리먼트에 있는 remove이다. 두번째 인자로 부모가 전달이 되면, 그 인자를 통해서  그 부모의 자식요소만 찾도록 해줘야 한다.

 

$.qs = (sel, parent = document) => parent.querySelector(sel);
$.qsa = (sel, parent = document) => parent.querySelectorAll(sel);

 

이렇게 만든 후, qsa에다가 첫번째 이미지하나만 찾아서 넣게 되면 그 이미지 안에 있는 name만 찾아질 것이다.

 

$.qsa('.name', $.qs('.remove'))
/*
▼NodeList []
   length: 0
 ▶__proto__: NodeList
*/

 

그리고 go함수에다 해당 구문을 추가해주면, 모든 이미지 태그를 찾을 수 있게 된다.

 

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')),
  el => $.qsa('.remove', el), /* 추가된 코드 */
  console.log);
  /*
  NodeList(90) [div.remove, div.remove, ..., div.remove]
  */

 

이 코드 역시 curry하고 싶은데, 해당 코드는 인자가 가변으로 되어있는데, 인자가 가변일 경우는 curry가 적용될 수 없다. 그래서, 따로 인자 2개만을 이용하게 하는 함수를 추가적으로 만들어 주면 된다.

 

$.find = _.curry($.qs);
$.findAll = _.curry($.qsa);

...

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')),
  $.findAll('.remove'),
  console.log);

 

이제 이벤트리스터를 통해 click하면 지워지게끔 만들어보자. 먼저 지우고자하는 이미지의 버튼을 클릭하면 해당 <div>가 출력 되게끔 해줘야 한다.

 

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')),
  $.findAll('.remove'),
  _.each(el => el.addEventListener('click', e => _.go(
    e.currentTarget,
    console.log
  ))));

 

여기까지 하면 지울 이미지 ×버튼을 클릭했을때, "<div class="remove">×</div>" 문구가 나올 것이다. 그럼 그 나온 remove버튼을 기준으로 그 remove버튼이 포함된 이미지 엘리먼트를  closest를 통해 찾고 지우면 된다.

 

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')),
  $.findAll('.remove'),
  _.each(el => el.addEventListener('click', e => _.go(
    e.currentTarget,
    el => el.closest('.image'), /* 추가된 코드 */
    el => el.parentNode.removeChild(el), /* 추가된 코드 */
    console.log))));

 

여기까지하면, 실제로 × 를 클릭 했을 때, 해당 엘리먼트가 삭제된다. 이 코드 역시도 간결하게 정리해주자.

 

$.closest = _.curry((sel, el) => el.closest(sel));
$.remove = el => el.parentNode.removeChild(el);
$.on = (event, f) => els =>
  _.each(el => el.addEventListener(event, f), _.isIterable(els) ? els : [els]);


...

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')),
  $.findAll('.remove'),
  $.on('click', e => _.go(
    e.currentTarget,
    $.closest('.image'),
    $.remove)));

 


커스텀 "확인" 창과 Promise

 

이번엔 확인창을 만들어 볼것이다. 먼저 기본적으로 사용하는 경고창을 코드에 적용해보았다.

 

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')),
  $.findAll('.remove'),
  $.on('click', e => {
    if(confirm('정말 삭제하시겠습니까?')){
  _.go(
    e.currentTarget,
    $.closest('.image'),
    $.remove);
    }
}));

 

위 코드를 그대로 사용해보면, 아래 처럼은 기본적인 디자인이 나오는데, 이를 원하는 커스텀 디자인 할수 없다.

 

 

이를 커스텀 디자인 해보자.

 

커스텀 디자인 CSS

더보기

.confirm {

        positionfixed;

        top0;

        left0;

        right0;

        bottom0;

        background: rgba(0000.2);

        z-index2;

      }

      .confirm .body {

        positionabsolute;

        top0;

        left0;

        right0;

        bottom0;

        marginauto;

        width300px;

        height160px;

        background#fff;

        border-radius8px;

        text-aligncenter;

      }

      .confirm .msg {

        padding0 24px;

        margin-top56px;

        margin-bottom16px;

      }

      .confirm button {

        padding8px;

        width60px;

        border0;

        background#eee;

        border-radius8px;

        margin3px;

      }

      .confirm button.ok {

        border0;

        color#fff;

        background#000;

      }

 

아래 코드를 통해 커스텀으로 확인창을 만들어 놓았다.

 

const Ui = {};

Ui.confirm = msg => _.go(
  `
  <div class="confirm">
    <div class="body">
      <div class="msg">${msg}</div>
        <div class="buttons">
          <button type="button" class="cancel">취소</button>
          <button type="button" class="ok">확인</button>
        </div>
    </div>
  </div>
  `,
  $.el,
  $.append($.qs('body')),
  $.find('.ok'),
  $.on('click', e => _.go(
    e.currentTarget,
    $.closest('.confirm'),
    $.remove
  ))
);

Ui.confirm('정말 삭제하시겠습니까?');

 

 

여기까지 하면, 확인을 눌렀을 때 창이 꺼지는데 취소했을때 역시 그 동작을 하게끔 코드를 짜주자. 이때 이용해야할 함수가 바로 "tap"함수이다. "tap"함수는 "tap"함수내에서 어떠한 일이 일어나던지 간에 자기 동작만 하고, 그전 동작들을 이어서 연결해준다.

 

const Ui = {};

Ui.confirm = msg => _.go(
  `
  ... HTML 코드
  `,
  $.el,
  $.append($.qs('body')),
  /* 확인 */
  _.tap(
    $.find('.ok'),
    $.on('click', e => _.go(
      e.currentTarget,
      $.closest('.confirm'),
      $.remove
  ))),
  /* 취소 */
  _.tap(
    $.find('.cancel'),
    $.on('click', e => _.go(
      e.currentTarget,
      $.closest('.confirm'),
      $.remove
  )))
);

Ui.confirm('정말 삭제하시겠습니까?');

 

지금 현재까지 나온 코드로는, 확인이나 취소 버튼을 눌렀을때 그 창이 뜨지만, 다음 코드 동작으로 이어가버린다. 즉, 삭제확인을 누르기도 전에 삭제가 된다는 뜻이다. 이를 막기 위해 Promise가 resolve 될때까지 다음 코드로 진행이 되지 않게끔 해주면 된다.

 

const Ui = {};

Ui.confirm = msg => new Promise(resolve => _.go( /* 추가된 코드 */
  `
   ... HTML 코드
  `,
  $.el,
  $.append($.qs('body')),
  _.tap(
    $.find('.ok'),
    $.on('click', e => _.go(
      e.currentTarget,
      $.closest('.confirm'),
      $.remove,
      resolve /* 추가된 코드 */
  ))),
  _.tap(
    $.find('.cancel'),
    $.on('click', e => _.go(
      e.currentTarget,
      $.closest('.confirm'),
      $.remove,
      resolve /* 추가된 코드 */
  )))
));

Ui.confirm('정말 삭제하시겠습니까?');

 

이제 위에서 기본확인창을 생성하는 코드를 수정해주자.

 

_.go(
  Images.fetch(),
  Images.tmpl,
  $.el,
  $.append($.qs('body')),
  $.findAll('.remove'),
  $.on('click', async ({currentTarget}) => {
    if(await Ui.confirm('정말 삭제하시겠습니까?')){
    _.go(
      currentTarget,
      $.closest('.image'),
      $.remove);
      }
}));

 

async, await을 통해 원하는 시점에 멈추고 코드를 원하는 시점에 실행시켜서 동작하게끔 했다. 하지만, 위처럼 하면 확인버튼이건 취소버튼이건 같은 동작을 할 것 이다. 그래서 if절에서 Ui.confirm으로 오는 값을 true, false를 통해 구분 지어주자.

 

...
_.tap(
    $.find('.ok'),
    $.on('click', e => _.go(
      e.currentTarget,
      $.closest('.confirm'),
      $.remove,
      _ => resolve(true)
  ))),
  _.tap(
    $.find('.cancel'),
    $.on('click', e => _.go(
      e.currentTarget,
      $.closest('.confirm'),
      $.remove,
      _ => resolve(false)
  )))
...

 

 

코드 정리

더보기

const $ = {};

 

$.qs = (sel, parent = document) => parent.querySelector(sel);

$.qsa = (sel, parent = document) => parent.querySelectorAll(sel);

$.find = _.curry($.qs);

$.findAll = _.curry($.qsa);

 

$.closest = _.curry((sel, el) => el.closest(sel));

$.remove = el => el.parentNode.removeChild(el);

$.on = (event, f) => els =>

  _.each(el => el.addEventListener(event, f), _.isIterable(els) ? els : [els]);

 

$.append = _.curry((parent, child) => parent.appendChild(child));

 

$.el = html => {

  const wrap = document.createElement('div');

  wrap.innerHTML = html;

  return wrap.children[0];

};

 

const Images = {};

const string = iter => _.reduce((a, b) => `${a}${b}`, iter);

 

Images.fetch = () => new Promise(resolve => setTimeout(() => resolve([

  { name: "HEART", url: "https://s3.marpple.co/files/m2/t3/colored_images/45_1115570_1162087.png" },

  { name: "하트", url: "https://s3.marpple.co/f1/2019/1/1235206_1548918825999_78819.png" },

  { name: "2", url: "https://s3.marpple.co/f1/2018/1/1054966_1516076769146_28397.png" }, { name: "6", url: "https://s3.marpple.co/f1/2018/1/1054966_1516076919028_64501.png"},{"name":"도넛","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918758054_55883.png"},{"name":"14","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077199329_75954.png"},{"name":"15","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077223857_39997.png"},{"name":"시계","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918485881_30787.png"},{"name":"돈","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918585512_77099.png"},{"name":"10","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077029665_73411.png"},{"name":"7","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076948567_98474.png"},{"name":"농구공","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918719546_22465.png"},{"name":"9","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077004840_10995.png"},{"name":"선물","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918791224_48182.png"},{"name":"당구공","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918909204_46098.png"},{"name":"유령","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918927120_12321.png"},{"name":"원숭이","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919417134_80857.png"},{"name":"3","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076802375_69966.png"},{"name":"16","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077254829_36624.png"},{"name":"안경","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918944668_23881.png"},{"name":"폭죽","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919005789_67520.png"},{"name":"폭죽 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919027834_48946.png"},{"name":"박","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919062254_67900.png"},{"name":"톱니바퀴","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919302583_24439.png"},{"name":"11","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077078772_79004.png"},{"name":"핫도그","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919086961_23322.png"},{"name":"고기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919274214_33127.png"},{"name":"책","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919326628_13977.png"},{"name":"돋보기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919363855_26766.png"},{"name":"집","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919395033_19373.png"},{"name":"사람","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918696715_44274.png"},{"name":"연필","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919437239_32501.png"},{"name":"파일","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919468582_23707.png"},{"name":"스피커","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919495804_49080.png"},{"name":"트로피 ","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918438617_69000.png"},{"name":"카메라","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919847041_33220.png"},{"name":"그래프","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918521301_43877.png"},{"name":"가방","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918642937_11925.png"},{"name":"입술","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919886042_10049.png"},{"name":"fire","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920036111_19302.png"},{"name":"TV","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920054808_42469.png"},{"name":"핸드폰","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920109727_43404.png"},{"name":"노트북","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920142776_26474.png"},{"name":"전구","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920181784_14964.png"},{"name":"장미","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920264149_78607.png"},{"name":"맥주","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920312701_18073.png"},{"name":"마이크","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920397855_39832.png"},{"name":"별","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920420823_49166.png"},{"name":"와이파이","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920438005_35247.png"},{"name":"헤드폰","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920468136_82088.png"},{"name":"peace","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920538719_19072.png"},{"name":"계산기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920348341_40080.png"},{"name":"poo 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924259247_12839.png"},{"name":"poo 3","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924850867_72121.png"},{"name":"poo 4","url":"https://s3.marpple.co/f1/2019/1/1235206_1548925154648_40289.png"},{"name":"poo","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918988097_38121.png"},{"name":"모니터","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769774483.png"},{"name":"talk","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927111573_76831.png"},{"name":"keyboard","url":"https://s3.marpple.co/f1/2018/1/1054966_1516330864360_25866.png"},{"name":"daily 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548926169159_58295.png"},{"name":"daily","url":"https://s3.marpple.co/f1/2018/7/1199664_1531814945451_49451.png"},{"name":"편지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920087850_99421.png"},{"name":"sns 하단바 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548917218903_88079.png"},{"name":"sns 하단바","url":"https://s3.marpple.co/f1/2019/1/1235206_1548917192465_28365.png"},{"name":"sns 이모지 6","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927313417_99007.png"},{"name":"sns 이모지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927219485_18861.png"},{"name":"13","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077164559_59630.png"},{"name":"iphone","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769886837.png"},{"name":"아이패드","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769820297.png"},{"name":"컴퓨터","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769802862.png"},{"name":"5","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076888018_74741.png"},{"name":"poo 1","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924230868_28487.png"},{"name":"Sns icon_똥 안경","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211657799.png"},{"name":"Sns icon_똥 웃음","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211686108.png"},{"name":"4","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076850148_36610.png"},{"name":"Sns icon_똥 놀림","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211670017.png"},{"name":"달력","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919531014_35045.png"},{"name":"자물쇠","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918410738_59815.png"},{"name":"손 이모지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918353391_54897.png"},{"name":"Sns icon_손바닥","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210472038.png"},{"name":"Sns icon_검지","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210393226.png"},{"name":"Sns icon_롹","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210522978.png"},{"name":"Sns icon_하이파이브","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210538695.png"},{"name":"Sns icon_브이","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210508758.png"},{"name":"Sns icon_중지","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210428137.png"},{"name":"Sns icon_주먹","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210372629.png"},{"name":"Sns icon_박수","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210444994.png"},{"name":"Sns icon_따봉","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210488684.png"},{"name":"손 이모지 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548921736267_35010.png"},{"name":"손 이모지 3","url":"https://s3.marpple.co/f1/2019/1/1235206_1548922150829_10878.png"}

]), 200));

 

_.strMap = _.pipe(L.map, string);

 

Images.tmpl = imgs => `

  <div class ="images">

    ${_.strMap( img => `

      <div class="image">

        <div class="box"><img src="${img.url}" alt=""></div>

        <div class="name">${img.name}</div>

        <div class="remove">×</div>

      </div>

    `, imgs)}

  </div>

`;

 

_.go(

  Images.fetch(),

  Images.tmpl,

  $.el,

  $.append($.qs('body')),

  $.findAll('.remove'),

  $.on('click'async ({currentTarget}) => {

    if(await Ui.confirm('정말 삭제하시겠습니까?')){

    _.go(

      currentTarget,

      $.closest('.image'),

      $.remove);

      }

}));

 

const Ui = {};

 

Ui.confirm = msg => new Promise(resolve => _.go( /* 추가된 코드 */

  `

  <div class="confirm">

    <div class="body">

      <div class="msg">${msg}</div>

        <div class="buttons">

          <button type="button" class="cancel">취소</button>

          <button type="button" class="ok">확인</button>

        </div>

    </div>

  </div>

  `,

  $.el,

  $.append($.qs('body')),

  _.tap(

    $.find('.ok'),

    $.on('click', e => _.go(

      e.currentTarget,

      $.closest('.confirm'),

      $.remove,

      _ => resolve(true)

  ))),

  _.tap(

    $.find('.cancel'),

    $.on('click', e => _.go(

      e.currentTarget,

      $.closest('.confirm'),

      $.remove,

      _ => resolve(false)

  )))

));