#1 프론트엔드에서 함수형/이터러블/동시성 프로그래밍
ES6 템플릿 리터럴 활용
이후에 템플릿 리터럴을 통해 프로그래밍을 해볼 것인데, 템플릿 리터럴의 사용법은 아래와 같다.
const a = 10;
const b = 5;
console.log(`${a} + ${b} = ${a + b}`);
// 10 + 5 = 15
이미지 목록 그리기
실습할 CSS 코드
<style>
.images {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 16px;
overflow: auto;
text-align: center;
}
.image {
position: relative;
display: inline-block;
width: 160px;
margin: 4px;
}
.image .remove {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
padding: 3px 0 0;
box-sizing: border-box;
text-align: center;
background: #000;
color: #fff;
font-weight: bold;
border-radius: 50%;
cursor: pointer;
}
.image .box {
position: relative;
width: 160px;
height: 160px;
border: 1px solid #ccc;
margin-bottom: 8px;
}
.image img {
position: absolute;
top: 0px;
left: 0;
right: 0;
bottom: 0;
max-width: 90px;
max-height: 90px;
margin: auto;
}
.image .name {
text-align: center;
height: 20px;
overflow: hidden;
}
</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 {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.2);
z-index: 2;
}
.confirm .body {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
width: 300px;
height: 160px;
background: #fff;
border-radius: 8px;
text-align: center;
}
.confirm .msg {
padding: 0 24px;
margin-top: 56px;
margin-bottom: 16px;
}
.confirm button {
padding: 8px;
width: 60px;
border: 0;
background: #eee;
border-radius: 8px;
margin: 3px;
}
.confirm button.ok {
border: 0;
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)
)))
));
'Web[웹] > ES6+ 함수형 언어' 카테고리의 다른 글
[ES6+: 응용] 프론트엔드에서 활용하기 (2) (0) | 2019.11.05 |
---|---|
[ES6+: 응용] 시간을 이터러블로 다루기 (0) | 2019.11.01 |
[ES6+: 응용] 사용자 정의 객체를 이터러블 프로그래밍으로 다루기 (0) | 2019.11.01 |
[ES6+: 응용] 객체를 이터러블 프로그래밍으로 다루기 (0) | 2019.10.29 |
[ES6+: 응용] map과 filter로 하는 안전한 함수 합성 (0) | 2019.10.29 |