window.open 으로 dialog 컴포넌트 개선처리
대박이다. 쇼모달 제거하면서 알게된 게 엄청 많다.
일단 <쇼모달>이란 자바스크립트에서 새 창을 띄우는 명령어이다. 쇼모달의 특징은 새 창이 뜨면서 뒤쪽의 창이 클릭 불가 상태가 된다는 것이다. 이것을 제거해야 하는 이유는 현재 IE만이 쇼모달을 지원하며, 크롬과 파이어폭스가 오래전 쇼모달 지원을 중단했기 때문이다. 그러므로 크로스 브라우징(여러 브라우저에서 사용)을 지원하려면 쇼모달을 제거해야 한다.
쇼모달을 대체할, 새 창을 띄우는 명령어가 하나 더 있다. 윈도우오픈(window.open)이다. 윈도우오픈은 새 창이 떠도 뒤쪽을 클릭 가능하다는 특징이 있다. 그러니까 쇼모달을 윈도우오픈으로 바꾸면서, 사이드 이펙트가 생기게 된다.
크게 2가지다.
(1) 쇼모달은 창이 닫힐 때까지 다음 로직 진행 중지됨. 윈도우오픈으로 바꾸면 동기식 -> 비동기식이 되어 콜백 구현 필요.
(2) 자식창을 열어도 부모창 클릭이 가능해짐
치명적인 문제는 1번이었는데 툴을 만들어서 어느 정도 해결을 했고, 2번이 문제가 되었다. 2번은 치명적이진 않지만 까다로웠다. 여기서는 2번에 대해서만 기록하겠다. 자식창을 열어도 부모창 클릭이 가능해지면서, 자식창을 n개 띄우는 문제가 발생하였다. 이것을 1개로 제한하기 위한 코드가 필요했다.
기존에 공통 컴포넌트로 다이얼로그 js가 있는데, 아래와 같은 방식으로 구현되어 있었다.
var 전역변수 = null;
(중략)
전역변수 = openDialog(“전역변수명”, …);
보다시피 리턴받는 변수와 첫 번째 아규먼트의 이름을 동일하게 만들었다. 기억해두자.
그리고 openDialog 함수는 아래와 같은 방식으로 구현되어 있었다.
window.open(주소, 전역변수명, 크기및상태바옵션);
여기서 2번째 아규먼트에 전역변수명을 넘기는 이유는, 2번째 아규먼트가 target값이기 때문이다. 윈도우오픈은 target값으로 중복 없는 윈도우를 지원한다. target값이 같으면 같은 윈도우를 공유한다. 한 마디로 윈도우의 고유이름이라고 보면 된다. 정리하면,
window.open(주소, “aaaWin”, “”); 식으로 명령을 쓰면 n번을 띄워도 창 1개만 뜬다. 즉 새로고침만 되고 창이 늘어나지 않는다.
window.open(주소, “”, “”); 식으로 2번째 아규먼트를 비우면 n번이 뜨게 된다.
그렇다면 자식창에서 부모창을 어떻게 제어하는가? 정확히 표현하면, 부모창이 갖고 있는 자식창 객체를, 자식창 쪽에서 어떻게 얻을 수 있는가? 자식창의 window.open 함수 안쪽(윈도우가 로딩될 때 실행되는 부분)에 아래와 같이 코딩하면 된다.
// window.name == “aaaWin”
var 부모가가진자식창 = window.opener.[window.name];
window.opener 혹은 window[“opener”]라고 쓰면 부모창을 가져올 수 있다. 둘은 같은 말이다.
그리고 window.opener.aaaWin 혹은 window[“opener”].[“aaaWin”]이라고 쓰면 부모창에 선언된 aaaWin이라는 전역변수를 가져온다. 이게 <부모창이 갖고 있는 자식창 객체를, 자식창이 얻은 상황>이다.
문제는 이렇게 이름을 정해두면, 항상 1개의 윈도우를 유지한다는 것이다. 이게 왜 문제가 되는지 살펴보자.
예를 들어 A라는 윈도우가 있다. 이 A 윈도우는 사용자 요구로 인해 n번을 띄워야 하는 창이다. 그런데 A라는 창 안에 어떤 버튼을 눌러서 B라는 자식창을 띄워야 한다고 해보자. 이 B라는 자식창은 n번이 떠서는 안되고 딱 1번만 떠야 하는 상황이다.
그런데 A 윈도우를 3개 띄우고, 각각의 창에서 B라는 창을 띄운다고 해보자. A 윈도우는 3개인데, B라는 창은 1개의 윈도우로 공유하게 된다. 이렇게 되면 곤란하다. B 윈도우가 여러개 떠서는 안되는 것은 사실이지만, 그것은 A 윈도우가 1개일 때 얘기다. A가 5개가 떴다면, B는 5개로 유지가 되어야 하고, 5개씩 A와 B 서로 1:1로 매핑되어야 한다.
위 문제를 해결하기 위해 유니크 키를 만들어서 다이얼로그 js 에 심었다.
// 유니크키 대입
var 유니크키 = makeUniqueKey();
// 유니크키 생성 함수
function makeUniqueKey() {
var rand = (Math.floor(Math.random() * 10000) + 1) + “”;
var todayObj = new Date();
var todayYear = todayObj.getFullYear();
var todayMonth = (todayObj.getMonth() + 1);
var todayDay = todayObj.getDate();
var result = todayYear + make2digit(todayMonth) + make2digit(todayDay) + “_” + rand;
return result;
// private function
function make2digit(_num) {
if (_num == null) {
return “00”;
} else {
_num = _num + “”;
}
if (_num.length == 1) {
return “0” + _num;
} else if (_num.length == 2) {
return “” + _num;
} else {
alert(_num);
return “” + _num.substring(0, 2);
}
}
}
보다시피 날짜와 시간 그리고 랜덤숫자를 유니크키라는 변수에 담는 내용이다. 부모창은 다이얼로그 js(위 내용이 담긴 js)를 임포트하고 있다. 이렇게 되면 윈도우마다 유니크 키를 갖는 효과가 있다.
그리고 윈도우를 띄울때 아래와 같이 띄우는 것이다.
window.open(주소, 윈도우이름 + 랜덤키, “”);
이렇게 되면 윈도우 A를 n개 띄웠을 때, 같은 윈도우 A지만 서로 다른 유니크 키를 가질 것이다. 이들이 초기화할 때 유니크키가 지정되므로, 각 윈도우에서 윈도우 B를 띄우면 각 윈도우마다의 유니크기가 붙기에 앞서 목표했던 1:1 매핑이 된다. 즉 윈도우 A가 5개라면 윈도우 B도 5개가 한계다.
그리고 자식창에서 부모창 가져올 수 있게 함수를 만들었다.
var g_DialogList = [];
function getDialogObj(_windowName) {
if (g_DialogList == null || g_DialogList.length == 0) {
alert(“g_DialogList == null || g_DialogList.length == 0”);
return null;
}
_windowName = _windowName + “”;
var winObj = null;
var len = g_DialogList.length;
for (var i=0; i<len; i++) {
winObj = g_DialogList[i].m_objDlgCtrl;
if (winObj == null) {
continue;
}
if ((winObj.name + “”) == _windowName) {
return g_DialogList[i];
}
}
return null;
}
이제 자식창에서는 window.onload 부분에서 아래와 같이 쓰면 된다.
var 부모가가진자식창 = window[“opener”].getDialogObj(window.name);
추가로, 이제부터가 정말 중요한데, 이렇게 끝나는 줄 알았는데 한 가지 이슈가 더 있었다.
간헐적으로 <액세스가 거부되었습니다> 라는 에러가 뜨는 것이다.
부모창과 자식창의 도메인이 상호 다를 때 발생하는 이슈이다. 다시 말해서 부모창은 naver.com/어쩌고저쩌고 인데 자식창은 daum.net/어쩌고저쩌고 일때 발생하는 에러다. 이럴 때 도메인을 맞춰줘야 하는데, 다시 말해 document.domain 과 window.opener.document.domain 를 맞춰줘야 한다. 실험결과 부모창에서 자식창의 도메인 값을 바꿔쳐넣을 수는 있지만, 자식창에서 부모창의 도메인을 바꿔치려고 하면 또 다른 에러가 난다(사용 권한이 없습니다).
그래서 부모창이 창을 여는 라인(윈도우오픈) 바로 밑에 도메인 맞춰주는 로직을 넣었다.
var 자식창 = window.open(어쩌고저쩌고);
(중략)
// 도메인 변경코드 : 액세스가 거부되었습니다 처리
try {
var dom = document.domain + “”;
if (dom.indexOf(“http://”) < 0) {
dom = “http://*.” + dom;
}
document.domain = dom;
자식창.document.domain = dom;
} catch (e) {}
이렇게 되어서 문제가 해결된줄 알았다. 그런데 간헐적으로 계속 문제가 생긴 것이, 도메인 변경코드가 자식창이 떠서 부모창에 액세스하기 전에 처리되어야 할텐데, 자식창의 소스가 작아서 빨리 뜰 경우에 랜덤하게 문제가 생겼다. 도메인을 먼저 변경해야 하는 것이다.
그래서 모든 과정을 다 롤백해야 하나 심각하게 고민하고 있었는데, 애초 문제 없이 잘 돌아가고 있었던 코드가 떠올랐다.
var 부모가가진자식창 = window.opener.[window.name];
이 코드는 윈도우 객체 안의 opener 객체 안의 window.name에 해당하는 객체를 가져오는 코드였다.
여기서 깨달은 것은, 결국 객체 접근은 오류가 나지 않는다. 함수를 사용했을 때 “액세스가 거부되었습니다”, “사용 권한이 없습니다”가 나는 것 같았다. 번개 같이 스친 생각에 배열을 임시로 만들어 돌려봤더니 접근이 된다!
그래서, 자식창에서 부모가가진자식창을 갖고 오기 위한 함수 – window.opener.getDialogObj(window.name); – 를 다음과 같이 대체했다.
var 부모가가진자식창 = window.opener.g_DialogMap[window.name];
자식쪽에 이렇게 깔끔하게 해결했다. 원래 eval을 사용하려 했으나 eval은 비권장이기에 대괄호를 사용했다.
부모쪽에는 var g_DialogMap = {}; 이렇게 객체 하나만 만들어주면 된다.
이렇게 하면 for문 돌면서 객체 찾아오는 짓도 안해도 괜찮다.