AEM Widget : Excel #1

Excel 을 활용한 Table Component 개발

김태형
hivelab-dev
12 min readJul 23, 2019

--

안녕하세요~ 하이브랩에서 AEM 개발을 맡고 있는 김태형입니다 :)
Excel 라이브러리를 이용한 Table Component 를 개발한 내용에대해 서술하려고합니다.

왜 Excel 파일을 이용하여 Component 를 연구 개발 하게 되었는가?

첫번째로는 고객의 니즈가 차지하는 비율이 컸습니다. 기획자 분들은 평소에도 엑셀을 자주 이용하고, 엑셀을 활용한 Component 개발 의뢰를 했었습니다. 팀 내부적으로 이번 기회에 외부라이브러리를 이용하여 Widget 을 개발하고, Widget 활용하여 Component 를 작업 해보자고 했습니다.

Excel Widget 개발

Step 1 ) 외부라이브러리 사용

순수 javascript 에서는 Excel 파일의 데이터를 접근 할 수 있는 방법이 없기때문에 외부라이브러리를 활용했습니다.
SheetJS 에서 제공하는 js-xlsx 라이브러리를 사용했습니다.

※ js-xlsx 는 오픈 소스 라이브러리로써
Apache License 2.0 규정에 따르고있어 자유롭게 활용 할 수 있습니다.

파일을 직접 임포트 방식으로 라이브러리 객체 참조합니다.

js-xlsx 에서 제공하는 라이브러리

실제 코드에서는 window객체 하위에 선언되어있습니다.

9번 라인에서 즉시 실행 함수에 파라미터로 xlsx 객체를 호출 하고 싶었지만, 간헐적으로 안되는 현상이 생겨 6번 라인처럼 생성자 함수가 실행할때 호출할 수 있게끔 처리했습니다.

Step 2 ) Excel 파일 데이터를 읽기 위한 전체적인 구조

사용자는 Excel 파일을 업로드 합니다. 업로드된 파일의 데이터는 타입이 file 형태인 input 태그에 저장이 됩니다.

change 이벤트가 발생 시 event 객체에 파일의 이름, 데이터, 크기 등 메타데이터에 접근이 가능합니다.
접근이 가능한것이지 메타데이터를 처리는 못합니다. 실제로 처리를 하기 위해서는 FileReader 클래스를 이용하여 파일을 읽기, 쓰기를 해야합니다.

MDN 문서에 따르면

readAsDataURL : 컨텐츠를 특정 Blob, File 로 부터 읽어오는 역할
onload : readAsDataURL 로부터 읽은 파일 데이터를 성공적으로 업로드 시 발생하는 이벤트

설명한 메소드와 이벤트를 이용하여 result 객체에 저장된 파일 데이터를 읽기,쓰기가 가능합니다.

Step 3 ) JSON 데이터 만들기

외부라이브러리에서는 기본적으로 json,csv,html 등 다양한 데이터 포맷 변환을 지원을 합니다.

XLSX.utils.sheet_to_csv : csv 로 변환
XLSX.utils.sheet_to_txt : UTF16 텍스트로 변환
XLSX.utils.sheet_to_html : HTML 변환
XLSX.utils.sheet_to_json : 객체의 배열로 변환
XLSX.utils.sheet_to_formulae : 수식 목록으로 변환

최초 작업 시 제공하는 메소드를 이용하여 json,html 을 생성했습니다. 하지만 엑셀에서 병합데이터를 깔끔하게 처리하지 못하는 현상이 나타났습니다.
rowspan과 colspan 을 지원 하기 위해서 json 데이터를 새로 작업 했습니다.

( AS-IS ) json 데이터
( TO-BE ) json 데이터

새로 작업한 json 데이터는 기존 코드를 참조하여 각 cell의 값과 병합된 크기를 알 수 있게 생성했습니다.
형식은 아래와 같이 구성했습니다.

{시트이름:[행번호:[
{cell의 값,rowspan,colspan},
{cell의 값,rowspan,colspan},
{cell의 값,rowspan,colspan},
{cell의 값,rowspan,colspan}
]]}

확장성을 고려하여 시트 이름을 최상위 key 값으로 설정(n 개의 시트 고려), value 값으로 각 행 번호를 가지는 배열로 설정했습니다.

31 Line
ㄴ workBook[‘!ref’] : A-1 기준의 시트 범위
ㄴ XLSX.utils.decode_range : 셀 범위를 변환

39 Line
ㄴ workBook[‘!merges’] : 병합된 셀의 범위

41 Line
ㄴ range.s.r : 행 번호의 시작점
ㄴ range.e.r : 행 번호의 끝점
ㄴ range.s.c : 열 번호의 시작점
ㄴ range.e.c : 열 번호의 끝점

makeJsonData 메소드는 기존 라이브러리를 활용하여 새롭게 json 데이터를 생성해줍니다.
3 중 Loop를 사용하여 각 행,열,병합의 유무성까지 검사합니다.
여러 개의 행이 존재 가능성이 있으므로 첫번째 Loop 를 수행 할 때마다 배열을 생성합니다.
각 cell 의 값, 병합의 데이터를 객체에 저장합니다. 여기서 병합의 데이터는 rowspan, colspan 으로 구성을 했으며, 기본값으로는 0 으로 설정했습니다.

Step 4 ) Excel 파일 및 데이터 서버에 저장하기

파일 및 데이터를 서버에 저장 하기 위해서는 통신을 해야겠죠?
Ajax 을 활용하여 서버와 통신을 준비합니다. jQuery 의 ajax 관련 함수가 아닌 순수 javascript 에서 지원하는 XMLHttpRequest 를 사용했습니다.

최초 작업 시 XMLHttpRequest 를 전역 객체를 활용하여 통신을 하다보니 간헐적으로 Internal Server 500 Error 가 발생 했습니다.
내용의 이해를 위해 간략하게 Http 통신에 대해서 서술하겠습니다.

HTTP 통신

HTTP통신은 클라이언트에서 서버로 요청을 하면 서버는 적절한 응답을 합니다. Client 에서 응답을 받으면 통신을 바로 종료합니다.

이러한 특징을 활용하여 서버와 통신이 필요할때마다 XMLHttpRequest 객체를 생성해 Error 를 해결했습니다.
HTTP 통신의 메소드는 아래와 같습니다.

GET : 정보를 가져올 시
PUT : 정보를 수정 시
DELETE : 정보를 삭제 시
POST : 정보를 새롭게 저장 시

Table Component 개발

Step 1 ) Dialog 설계

Table 에는 기본적으로 셋팅해야되는 요소와 Excel Widget 으로 구성했습니다.

Table Title : Component Headline

Table Caption : Table Headline

Table Headline Type : Select Headline Level
ㄴ Default : default
ㄴ Top 1 Line : 상단 1열
ㄴ Top 2 Line : 상단 2열
ㄴ Top 1 Line + Left 1 Line : 상단 1열과 왼쪽 1행
ㄴ Top 2 Line + Left 1 Line : 상단 2열과 왼쪽 1행
ㄴ Left 1 Line : 왼쪽 1행

Excel Upload : Excel Widget
ㄴ Preview Excel Data
ㄴ Download File
ㄴ Delete File

Step 2 ) Preview Layer 를 위한 HTML 생성

기능 설계 시 업로드된 Excel 파일의 데이터 확인을 위해 Preview Layer 를 제공하자라고 생각했습니다. 그러기 위해서는 json 데이터를 HTML 으로 생성해주어야 합니다.

2중 Loop 를 활용해 각 열,행의 대한 데이터를 배열에 저장합니다.
td 태그를 생성해주는 메소드에서는 rowspan, colspan 의 데이터를 객체에 담습니다. Object.keys() 를 활용하여 key 값을 먼저 도출 후 문자열로 변환하여 배열에 저장합니다.
Excel 파일을 업로드 후 Preview Excel Data 버튼을 클릭 시 아래와 같은 화면이 노출됩니다.

Preview Excel Data
Excel 파일에 저장된 데이터

Step 3 ) Download File 기능 구현

서버에 저장된 Excel 원본 파일을 다운로드 기능은 비교적 간단하게 작업했습니다.

가상의 a 태그를 생성 한 후 href,download 를 설정합니다.

href : 서버에 파일이 위치한 URL
download : 다운로드 시 파일이름

마지막으로 강제적으로 가상의 태그를 클릭하게 합니다.

Step 4 ) Sightly, JS 작업

하기 이미지는 전체적인 코드 흐름도를 나타낸 다이어그램입니다.

※ 다이어그램은 전체적인 코드 흐름을 표현한것이므로, 위 실제 코드와는 차이가 날 수 있습니다.

이번 AEM Table Component 변화를 줘서 작업을 진행했습니다.
그 변화에 관해서 중점적으로 서술하겠습니다. 상세한 코드 설명은 생략하겠습니다.

4–1) Deep Copy ( 깊은 복사 )

깊은복사는 javascript 의 내부 객체까지 복사하는 형태를 말하며, 독립적으로 객체를 가공 할 수 있습니다.
원본 Table 의 데이터를 유지 할 필요성이 있었습니다. filter, map 함수를 활용해서 유지시키려 했으나 json 데이터의 포맷이 복잡한 구조이기때문에 깊은 복사가 이뤄지지 않았습니다.
JSON.parse, JSON.stringify 메소드를 이용하여 깊은 복사 작업을 진행했습니다.

4–2) filter, map

javscript 의 배열의 메소드(push,pop,unshift,shift,slice,splice) 를 이용하여 배열의 데이터를 가공 할 수 있지만, 이번에는 ES5에 추가된 filter,map,some 메소드로 배열 요소를 가공했습니다.

MDN 문서에 따르면

filter : 함수의 테스트를 통과하는 요소를 모아 새로운 배열로 반환
map : 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환
some : 메서드는 배열 안의 어떤 요소라도 주어진 판별 함수를 통과하는지 테스트

설명을 보태자면
filter 은 조건에 따른 배열을 반환,
map은 배열 내의 요소를 가공 후 배열로 반환,
some 은 조건에 맞는 배열의 유효성 판단.

먼저 Table 의 thead 태그를 적용하는 computeHeaderAndBody 부터 살펴보겠습니다.
예를 들어 Top 1 Line 적용 시 endIndex = 1, header.list 는 배열의 첫번째 요소 반환합니다.
※endIndex : Top Line 데이터
body.tableArr 은 endIndex 를 제외한 나머지 배열 반환합니다.

왼쪽의 행의 th 태그를 적용하는 computeLeftHeaderAndBody 에는 병합 부분을 고려해서 some 메소드를 사용했습니다.
병합된 부분이 없다면 map 메소드를 활용하여 배열 내에 각 첫번째 요소들의 태그를 th로 적용시킵니다.

4–3) Callback 함수 활용하기

javascript 에서 Callback 함수를 많이 활용합니다. 함수를 리턴받아 2차적인 데이터의 가공이 가능합니다. 그래서 AEM Backend Javascript 에서도 활용 할 수 있지않을까해서 적용시켜봤습니다.

앞에 서술한 computeHeaderAndBody, computeLeftHeaderAndBody 의 마지막 파라미터가 해당하는 부분입니다.
호출 하는 쪽에서 함수를 넘겨주면 적절한 데이터를 리턴해줍니다.

마지막으로 넘겨 받은 데이터로 Table 을 구성하면 끝입니다.

결과

a. 병합 미 존재시

왼쪽) default, 중간) Top 1 Line, 오른쪽) Top 2 Line
왼쪽) Top 1 Line + Left 1 Line, 중간) Top 2 Line + Left 1 Line, 오른쪽) Left 1 Line

b. colspan > 1

왼쪽) default, 중간) Top 1 Line, 오른쪽) Top 2 Line
왼쪽) Top 1 Line + Left 1 Line, 중간) Top 2 Line + Left 1 Line, 오른쪽) Left 1 Line

c. rowspan > 1

왼쪽) default, 중간) Top 1 Line, 오른쪽) Top 2 Line
왼쪽) Top 1 Line + Left 1 Line, 중간) Top 2 Line + Left 1 Line, 오른쪽) Left 1 Line

마치며

Excel 파일을 이용한 AEM Table Component 개발 작업 과정에 대해서 작성해봤습니다.
데이터를 표현하는 수단으로써 Excel 활용도는 여전히 높아 이러한 Component 가 있다면 고객의 만족도를 높여 줄 수 있지않을까 예상됩니다. 이 외에도 Table 형태가 아닌 다른 Component 도 적용 할 수 있는지 검토 중입니다.
지금까지 읽어주신 분들께 감사합니다 :)

--

--