본문 바로가기
프로그래밍/JavaScript

[자바스크립트] JSON 데이터를 이용한 컨텐츠 노출 스킨 변경 - #4

by zoo10 2010. 12. 27.

4번째 포스팅이네요. 이번에는 죽이되든 밥이되든 마무리 하려 합니다. 사실 많이 부족한 내용으로 될 거 같은데요. JSON데이터를 이용해서 다양한 모양의 스킨에 뿌리는 것이 주 목적이었기 때문에 일단은 그에 대한 내용만 구성하기로 했습니다.

안쪽에 위치한 콘텐츠에 대한 스크립트 까지 다 작성해서 포스트를 구성하려면 책 한권은 나오겠더군요. 물론 과장 쪼끔 했습니다. 하하. 그래서 이번 포스팅에서는 상세보기의 좌우 화살표, 썸네일보기의 하단 스크롤바에 대한 내용은 포함되지 않았습니다. 나중에 제가 작업을 한 후에 따로 링크를 걸어드리겠습니다.(사실 저도 이걸 써야 되거든요 ^^)

자 그럼 본론으로 돌아가서 저번 포스팅까지 왠만큼은 그럴싸하게 구성했었습니다. 못보신 분들을 위해 링크 올려드립니다.
2010/12/20 - [WEB/javascript] - [자바스크립트] JSON 데이터를 이용한 컨텐츠 노출 스킨 변경 - #1
2010/12/20 - [WEB/javascript] - [자바스크립트] JSON 데이터를 이용한 컨텐츠 노출 스킨 변경 - #2
2010/12/22 - [WEB/javascript] - [자바스크립트] JSON 데이터를 이용한 컨텐츠 노출 스킨 변경 - #3

이번에는 함수화 시키는 작업을 하겠습니다. 날코딩으로는 다른 곳에 적용하기가 좀 힘드니까요. 날코딩이 나쁜다고 생각하지는 않습니다. 한번 쓰고 말거라면 그냥 날코딩이 더 낫다고 할 수 있습니다. ㅎㅎ

마크업과 CSS는 여전히 동일합니다. 그런데 JSON 데이터의 구조가 좀 바뀌었습니다. 범용적으로 구성을 하려다 보니 설정값들이 조금 늘어났습니다. 일단 JSON 데이터를 보여드리겠습니다. 데이타(data) 부분은 생략하고 말씀드리겠습니다. 그쪽은 바뀐게 없으니까요. (가로 스크롤이 생깁니다.)

var datas = {
	config : {
		jsonName : "datas", //json 데이터명
		dtBody : "data", //data 변수명
	},
	skins :
	[
		{
			eId : "list", // element의 아이디값
			tabId : "list_a", //탭 element의 아이디값
			mType : "table", //마크업 타입 : table, ul, ol, none 등이 올 수 있음 - 대소문자 구분 주의
			isLoop : "true", //반복 여부 설정 : true / false
			replaceKey : "replace", //치환자 엘리먼트 명
			dtName : ["grName", "itName", "itMaker", "itNation", "itIssueDt"] //replace 대신에 들어갈 데이터 객체변수명
		},
		{
			eId : "detail",
			tabId : "detail_a",
			mType : "none",
			isLoop : "false",
			replaceKey : "replace", //치환자 엘리먼트 명
			dtName : ["itImages","itName", "grName", "itMaker", "itNation", "itIssueDt", "itExp"] //replace 대신에 들어갈 데이터 객체변수명
		},
		{
			eId : "thumbnail",
			tabId : "thumbnail_a",
			mType : "ul",
			isLoop : "true",
			replaceKey : "replace", //치환자 엘리먼트 명
			dtName : ["itNation", "itImages", "itName"]
		}
	],
	data : 
	[
                 .
                 .
                 .
                ]
};

보시면 이전 코딩에서는 listConfig, detailConfig, thumbConfig 라는 설정영역을 skins라는 배열객체 타입으로 바꿨습니다. 유동적으로 접근하기 위해서 일정하게 접근할 방법을 찾아야 해서 입니다. 우리는 skins[0]이 listConfig 라고 암묵적으로 알고 있지만 실제로는 모르는 상황일 겁니다. 단순히 마크업 스킨을 기반으로 하여 skins를 구성하면 됩니다. 물론 3개이상의 스킨이 있다면 더 구성해서 넣을 수도 있겠죠.

그리고 각 skins의 요소마다 eId, tabId, mType, isLoop, replaceKey 가 새로 생겼습니다. 위의 주석을 통하셔서 보시면 직관적으로 아실 수 있는 내용입니다. eId 는 노출되는 영역 태그의 id값입니다. tabId 는 상단 탭영역 태그의 id값입니다. 마크업을 보시면 확인하실 수 있습니다. mType은 실제 콘텐츠를 구성하는 대표 태그를 설정하는 부분입니다. 데이터가 루프를 돌면서 표시되야 할때 꼭 적어 주어야 합니다. 그래야 하위 요소(태그)를 찾을 수 있습니다. table 이라면 tr을 ul, ol 이라면 li를 찾을거라고 약속을 하는 거죠. 그런데 이 부분은 대소문자를 구분해야 합니다. table과 TABLE은 다른 결과를 내놓습니다. isLoop는 반복적인 리스트 형태의 데이터 구조인가를 나타냅니다. 쉽게 말해 isLoop가 true 이면 루프를 돌면서 다 표시해라라는 뜻이 되겠습니다. replaceKey 는 고민 끝에 넣은 건데요. 대체될 태그를 지정해 줄수 있도록 했습니다. 즉, replace 라는 태그를 안쓰고 change 라는 태그를 썼다면 이 위치에 change 라고 적어주면 되겠습니다.

어렵지 않으시죠? 앞의 포스트를 보지 못한 분이라면 이해가 잘 안 되실수도 있겠습니다. 앞의 내용을 잠깐이라도 보고 오시면 간단하게 이해하실 수 있을겁니다.

자 이제는 통합된 함수를 보여드리기 전에 <replace type="image"> 라는 태그에 대해서 잠깐 설명을 드려야 할 것 같습니다. 이전 포스팅에서 빼놓고 간 내용인데요. replace 태그에 type이라는 어트리뷰트 즉, 속성값을 세팅해 놓았습니다. 바로 image라고 세팅되어 있습니다. 이 내용은 이 위치에는 이미지가 와야 한다라고 알려주는 역할입니다. 아무값도 없다면 html형식의 텍스트 데이터가 들어오게 됩니다.

replace의 type이란 속성에 접근하기 위해 우리는 object.getAttribute("TYPE") 이라는 함수를 사용할 수 있습니다. 만약 type이 image라는 텍스트를 반환시킨다면 우리는 해당 위치에 <img src=""> 태그를 써서 데이터를 치환해야 합니다.

자 아래 통합형의 스크립트를 보시면 이해가 더 빠르실듯 합니다.

function changeViewWorker(json){
	var jName = json.config.jsonName; //json 데이터명
	var bName = json.config.dtBody;	  //data 변수명

	var skinLength = json.skins.length; //스킨의 갯수

	for(n=0; n<skinLength; n++){
		var skin = json.skins[n];

		//탭 메뉴 세팅
		var tab = document.getElementById(skin.tabId);
		addEvent(tab, "onclick", viewContent, json, skin);
		
		var tag = "";
		if(skin.mType == "table") tag = "TR";
		else if(skin.mType == "ul" || skin.mType == "ol") tag = "LI";
		else if(skin.mType == "none") tag = "none";
		
		var loopCnt = json.data.length;
		if(skin.isLoop == "false") loopCnt = 1;
		
		var area = document.getElementById(skin.eId);
		var parentEl = area.getElementsByTagName(skin.mType);
		var copyEl = null;
		if(tag != "" && tag != "none") copyEl = area.getElementsByTagName(tag);

		for (i=0; i<loopCnt; i++){
			var el = area.getElementsByTagName(skin.replaceKey);

			if(skin.isLoop == "true"){
				if(copyEl == null){
					alert("반복문이 있으나 복사할 요소가 없어서 더 이상 실행할 수 없습니다.");
					return;
				}
				var newEl = copyEl[i].cloneNode(true);
				if( i < loopCnt-1 ) parentEl[0].appendChild(newEl);
			}
			
			var dtLen = skin.dtName.length;
			for(k=dtLen-1; k>=0; k--){
				var dtName = skin.dtName[k];
				var replaceData = eval(jName+"."+bName+"["+i+"]."+dtName);

				if(el[k].getAttribute("TYPE")=="image"){
					el[k].parentNode.innerHTML = "<img src='"+replaceData[0]+"'>";
					continue;
				}
				el[k].parentNode.innerHTML = replaceData;
			}//for(k=dtLen-1; k>=0; k++)
		}//for (i=0; i<loopCnt; i++){
	}//for(n=0; n<skinLength; n++){
}

changeViewWorker(datas);

function addEvent(obj, evtName, method, p1, p2){
	if(obj.attachEvent){
		obj.attachEvent(evtName, function(){method(p1, p2);});
	}else{
		evtName = evtName.replace("on","");
		obj.attachEventListner(evtName, function(){method(p1, p2);}, false);
	}
}

//보기 선택 처리
function viewContent(json, skin){
	for(n=0; n<json.skins.length; n++){
		document.getElementById(json.skins[n].eId).style.display="none";
		document.getElementById(json.skins[n].tabId).style.color="#303030";
	}
	document.getElementById(skin.eId).style.display = "block";
	document.getElementById(skin.tabId).style.color = "red";
}

보기가 좀 어려워 졌습니다. 내용도 좀 긴것 같습니다. 하지만 앞의 포스팅을 하나에 묶어서 정리한 내용 말고는 별다른 것이 없습니다.

function changeViewWorker(json){
	var jName = json.config.jsonName; //json 데이터명
	var bName = json.config.dtBody;	  //data 변수명

	var skinLength = json.skins.length; //스킨의 갯수

	for(n=0; n<skinLength; n++){
		var skin = json.skins[n];

1라인을 보시면 함수의 인자로 json의 데이터를 넘겨 받습니다. 즉, datas라는 변수를 넘겨받게 되겠네요.
2,3라인은 설정값을 읽습니다.
5라인은 스킨의 수를 구합니다. 스킨의 수만큼 루프를 돌려서 스킨의 데이터를 치환해야 하기 때문입니다.
7라인은 스킨의 수만큼 Loop를 실행하는 부분입니다.
8라인은 스킨을 변수에 담아 처리하기 위한 구문입니다. 즉, listConfig, detailConfig, thumbnailConfig 를 skin이라는 변수에 담는것과 마찬가지입니다.

		//탭 메뉴 세팅
		var tab = document.getElementById(skin.tabId);
		addEvent(tab, "onclick", viewContent, json, skin);

11라인은 해당 영역의 탭 메뉴를 찾아 변수에 담습니다. 해당 탭에 이벤트를 부여하기 위해서 입니다.
12라인은 addEvent라는 함수를 호출하여 각 탭 메뉴에 onclick 이벤트를 동적으로 추가하는 부분입니다. 이 함수에 대한 설명은 아래에서 다시 하겠습니다. 12라인을 통해서 탭 메뉴에 onclick 이벤트가 일어나면 viewContent 라는 함수가 호출되게 됩니다.

		var tag = "";
		if(skin.mType == "table") tag = "TR";
		else if(skin.mType == "ul" || skin.mType == "ol") tag = "LI";
		else if(skin.mType == "none") tag = "none";

14라인은 반복되야 하는 태그를 찾아서 세팅을 위한 변수선언입니다.
15 ~ 17라인은 mType을 읽어서 각 세팅에 맞게 tag 변수에 찾아야 할 하위 태그를 세팅해 주는 부분입니다. 루프를 돌려야 할 태그를 찾아서 세팅하는 겁니다. none일 경우에는 반복이 없는 스킨이 되겠습니다. 상세보기와 같은 스킨이 이에 해당합니다.

		var loopCnt = json.data.length;
		if(skin.isLoop == "false") loopCnt = 1;

19라인은 data의 수를 구합니다. 표현해야 할 애니의 수입니다.
20라인은 해당 스킨이 리스트 형태인지 설정값을 읽어서 만약 아니라면 loopCnt에 1을 세팅해 줍니다. 루프는 1번만 돌아야 하기 때문입니다. 아래 27라인에 보면 해당 변수를 사용하고 있는 것을 알 수 있습니다.

		var area = document.getElementById(skin.eId);
		var parentEl = area.getElementsByTagName(skin.mType);
		var copyEl = null;
		if(tag != "" && tag != "none") copyEl = area.getElementsByTagName(tag);

22라인에서 실제로 보여질 콘텐츠 영역을 구합니다.
23라인은 mType에 세팅되어 있는 태그를 22라인에서 구한 영역 안에서 찾습니다.(사실 여기는 좀 보강해야 합니다. 무조건 하나라고 가정하고 코드를 짰습니다. 나중에 분명히 문제가 생길 요지가 있어 보이네요.)
23~24라인은 변수를 세팅하고 리스트 형태의 스킨이라면 복사되야할 영역을 미리 copy 해 놓습니다.

		for (i=0; i<loopCnt; i++){
			var el = area.getElementsByTagName(skin.replaceKey);

			if(skin.isLoop == "true"){
				if(copyEl == null){
					alert("반복문이 있으나 복사할 요소가 없어서 더 이상 실행할 수 없습니다.");
					return;
				}
				var newEl = copyEl[i].cloneNode(true);
				if( i < loopCnt-1 ) parentEl[0].appendChild(newEl);
			}

27라인은 19~20라인에서 구한 데이터의 수만큼 Loop를 합니다.
28라인에서는 치환자를 찾아서 변수에 담습니다. 여기서는 replace 태그를 찾고 있습니다. 우리가 replaceKey 에 replace 라는 문자열을 세팅했기 때문입니다.
30라인은 만약 리스트 형태의 스킨이라면 실행됩니다.
31~34라인은 반복될 영역이 복사된게 없다면 문제가 되기 때문에 체크하는 부분입니다. 반복을 해야하나 반복을 위해 복사된 영역이 없다는 뜻이 됩니다. 실제 서비스에서는 alert 구문을 주석으로 처리하시는게 좋을듯 합니다.
35~36라인에서 복사해 놓은 영역을 cloneNode로 담은 후 부모 영역에 다음 데이터 치환을 위해 복사를 합니다.

			var dtLen = skin.dtName.length;
			for(k=dtLen-1; k>=0; k--){
				var dtName = skin.dtName[k];
				var replaceData = eval(jName+"."+bName+"["+i+"]."+dtName);

				if(el[k].getAttribute("TYPE")=="image"){
					el[k].parentNode.innerHTML = "<img src='"+replaceData[0]+"'>";
					continue;
				}
				el[k].parentNode.innerHTML = replaceData;
			}//for(k=dtLen-1; k>=0; k--)

실제로 데이터를 치환하는 부분입니다.
39~40라인은 치환될 자료의 수를 스킨별로 구한 후에 Loop를 실행합니다.
40~41라인은 이전 포스팅에서도 말씀드렸듯이 문자열을 eval 함수를 이용해서 객체접근이 가능하도록 처리한 부분입니다.
43~46라인이 이미지 형태의 데이터로 치환해야 하는지를 찾는 부분입니다. type을 구해서 image 형태라면 일반 html 텍스트가 아닌 img 태그를 대신 치환해서 넣습니다. 그리고 continue 문을 넣어 아래쪽은 실행되지 않도록 처리했습니다.47라인은 일반 html 데이터 치환을 하는 부분입니다.

중요 부분은 #3 포스트에서 대부분 설명 드린 내용이라 별달리 드릴 말씀이 없었습니다. 함수화 된 부분의 설명은 끝입니다. 이외에 동적으로 이벤트를 추가하는 부분과 탭영역의 색깔 바꾸는 함수 정도 남았네요. 일단 동적 이벤트 부분을 보겠습니다.

function addEvent(obj, evtName, method, p1, p2){
	if(obj.attachEvent){
		obj.attachEvent(evtName, function(){method(p1, p2);});
	}else{
		evtName = evtName.replace("on","");
		obj.attachEventListner(evtName, function(){method(p1, p2);}, false);
	}
}

changeViewWorker 함수 내에서 호출된 addEvent 함수는 해당 객체에 이벤트가 발생되면 호출될 함수를 세팅하는 메서드입니다. 이 함수는 이미 여기저기 내용이 많이 있으며 해당 부분을 차용했습니다. 이 함수는 IE 용과 그외 브라우져 용으로 나누어 작성했습니다. if문이 IE용이고 else문이 그 외의 브라우져 용입니다. p1, p2는 호출될 함수에 넘겨줄 인자입니다. 저는 json과 skin 을 넘겼습니다. 좀더 많은 정보를 원하시면 구글신에서 attachEvent로 검색을 하시면 될 듯 합니다. 주의사항으로 attachEvent로 동적 추가된 이벤트는 가비지에 의해서 자동적으로 해제가 되지 않는다 합니다. 즉, 메모리 릭이 좀 발생한다 하네요. 관련 내용도 검색으로 확인하실 수 있습니다.

//보기 선택 처리
function viewContent(json, skin){
	for(n=0; n<json.skins.length; n++){
		document.getElementById(json.skins[n].eId).style.display="none";
		document.getElementById(json.skins[n].tabId).style.color="#303030";
	}
	document.getElementById(skin.eId).style.display = "block";
	document.getElementById(skin.tabId).style.color = "red";
}

이 함수는 탭에 따라 영역을 바꿔주는 함수입니다. addEvent 함수에서 탭 영역에 동적으로 호출된 메소드입니다. 그리 어려운 내용이 아니므로 넘어가도록 하겠습니다.

자 이렇게 이번 포스팅이 끝이 난것 같습니다. 과연 실용성이 있는 코드일까 의문이 듭니다. 실제로 이렇게 쓸 일이 얼마 없어 보이기도 하고 굳이 이런 형태로 코딩하지 않아도 가볍게 할 수 있기도 한데 말입니다. 그래도 저는 아이튠즈 UI를 좀 배낄려 한 내용입니다. skin의 변경을 JSON 데이터를 바탕으로 스크립트로만 해보자는게 제 의도였죠. 어찌됬든 두서없는 긴 글 읽으며 따라와 주시느라 수고 많으셨습니다. 다음에는 하나로 끝낼 수 있는 그런걸로 좀 해야 겠네요. 너무 빡십니다. ㅎㅎ;;

그럼 즐프하세요. 아 참.. 작성한 파일은 첨부하였습니다.