백오피스나 내부 사용자를 위한 시스템은 Edge로 전환 후 I.E에 대한 고려를 이제 걷어내어도 되겠지만, 대고객 서비스를 하는 시스템들은 계속 I.E에 대해 고려할 필요가 있습니다.

 

JavaScript에서 일자 formatting이나 연산에 많이 사용하는 momentJs 사용 시 약간의 주의사항이 있습니다.

 

서버 API에서 일자 정보가 아래와 같이 내려온 경우 I.E에서는 정상적으로 처리가 되지 않습니다.

 

const curDate = '2022.06.22';
moment(curDate).format('YYYYMMDD');

위와 같은 코드를 실행할 경우 I.E에서는 "Invalid date"를 return 합니다.

 

I.E를 제외한 Chrome 등의 브라우저에서는 정상적으로 "20220622"를 return 합니다.

 

하여 서버 API에서 전달 받는 일자 데이터에 dot(.)이 포함되어 있을 경우 안전하게 데이터를 가공한 후 formatting 하면 됩니다.

 

const curDate = '2022.06.22';
moment(curDate.replace(/[.]/g, '')).format('YYYYMMDD');
300x250

보통 순번 채번이 필요한 경우 대부분 Sequence를 사용합니다.

하지만 아래와 같은 사유로 시스템을 운영하다 보면 MAX + 1로 처리된 순번도 눈에 많이 보입니다.

 

1. 오래 전에 작성했거나,

2. 컬럼 조합에 따른 순번이어야 하는 경우 (ex> 접수일자 + 접수순번)

 

위와 같은 경우이거나, 프로젝트를 한 시스템을 인계 받았을 때 개발 가이드를 준수하지 않은 코드 등에서는

아래와 같이 작성된 MAX + 1 Query도 종종 있습니다.

INSERT  INTO  TBL_A (
	REQ_DT
     ,  SEQ
     ,  ...
     )  VALUES (
     	'20220622'
     , (SELECT  MAX(SEQ) + 1
          FROM  TBL_A
         WHERE  REQ_DT = '20220622')
     ,  ...

대강 위와 같은 형태입니다.

 

위와 같은 형태도 사실 거래가 많지 않은 시스템은 크게 문제가 없습니다. 순서대로 채번도 잘되고 데이터 무결성에도 문제가 없습니다.

하지만 거래량이 어느 정도 이상이 되는 대용량 시스템의 경우에는 언제든지 해당 Query로 인해 이슈가 발생할 수 있습니다.

 

문제점

해당 Query를 사용하는 transaction이 단순히 저 Query 하나로만 동작을 한다면 그나마 문제가 좀 덜하겠지만 해당 transaction에서 타 시스템과의 인터페이스 등 시간이 소요되는 다른 코드들이 존재한다면 Unique constraints violation이 발생하게 됩니다.

 

현재 MAX(SEQ)의 값이 1이라고 하면

transaction A 00:01:01.111(start) 00:01:567(commit)
transaction B 00:01:01.234(start) 00:01:789(commit)

위 순서로 유입되는 transaction A, B가 있다면 A와 B는 모두 MAX(SEQ) + 1 값으로 2를 얻어갑니다. 하여 둘 중에 먼저 commit이 일어나는 A는 SEQ 값을 2로 하여 문제 없이 commit 되고 transaction이 종료되지만,

B는 동일한 REQ_DT에 동일한 SEQ 값이 이미 생성되었으므로 unique constraints violation이 발생합니다.

(물론 REQ_DT, SEQ가 Composit PK 혹은 Unique Index라는 가정입니다.)

 

Sequence로 변경

이런 내용을 가장 깔끔하게 처리하는 방법은 Sequence 입니다. Sequence의 경우는 nextval을 한 후 바로 DD 테이블의 값이 증가하고 rollback이 불가능하므로 데이터의 무결성이 유지됩니다.

 

하지만 위와 같이 REQ_DT 하위의 순번으로 데이터가 관리되어야 한다면 Sequence는 사용할 수가 없습니다.

또한 현재 운영 중인 시스템의 영향도를 모두 파악하여 재구성한 후 Sequence로 변경하는 것도 결코 쉬운 일은 아닙니다.

(해당 테이블을 타 시스템으로 전달하여 해당 시스템도 해당 순번을 Key 값으로 사용하고 있는 경우 등등)

 

채번 테이블 사용

이런 경우에는 좀 귀찮기는 해도 채번 테이블을 사용할 수 있습니다. 채번 테이블은 MAX 순번을 관리하는 별도의 테이블을 운영하고 해당 테이블에서 값을 읽을 때 SELECT ~ FOR UPDATE로 Lock 처리를 통해 무결성을 유지하는 방법입니다.

REQ_DT VARCHAR2(8)
MAX_SEQ Number

위와 같은 형태의 채번 테이블이 있다면 transaction을 시작할 때 채번 테이블에서 순번을 가져오면서 Row Lock을 획득합니다.

SELECT MAX_SEQ
  FROM TBL_SEQ
 WHERE REQ_DT = #{reqDt}
   FOR UPDATE

 가져온 순번으로 원장에 데이터를 INSERT 합니다.

INSERT INTO TBL_A (
       REQ_DT
     , SEQ
     , ...
     ) VALUES (
       #{reqDt}
     , #{maxSeq}
     , ...

채번 테이블의 MAX_SEQ 값에 +1 한 후에 transaction을 종료 처리합니다.

UPDATE TBL_SEQ
   SET MAX_SEQ = MAX_SEQ + 1
 WHERE REQ_DT = #{reqDt}

위와 같은 방법으로 동시성 문제를 해결하고 데이터 무결성을 유지할 수 있습니다.

300x250

JavaScript에서 여러 개의 조건을 나열할 때 아래와 같이 or를 길게 연결해서 사용합니다.

 

const code = data.code;

if (code === 'A'
||  code === 'B'
||  code === 'C'
||  code === 'D'
||  code === 'E') {
	//Do anything
}

보통 현업에서 코드를 길게 쓰다보면 해당 라인이 계속 늘어나게 되면 프로그램도 지저분해지고, 가독성도 떨어지게 됩니다. 이럴 경우 Array.prototype.indexOf나 Array.prototype.includes를 활용하여 코드를 개선해 볼 수 있습니다.

 

Array.prototype.indexOf

이 메소드는 배열에서 해당 요소를 찾을 수 있는 첫 번째 인덱스를 return 하거나, 찾지 못할 경우 -1을 return 합니다.

const code = data.code;
const conditions = ['A', 'B', 'C', 'D', 'E'];

if (conditions.indexOf(code) > -1) {
	//Do anything
}

 

Array.prototype.includes

이 메소드는 배열이 해당 요소를 포함하고 있는지 여부를 boolean 으로 return 합니다.

const code = data.code;
const conditions = ['A', 'B', 'C', 'D', 'E'];

if (conditions.includes(code)) {
	//Do anything
}

 

만약 개발하고 있는 애플리케이션의 지원 범위에 I.E가 포함된다면 indexOf를 사용하면 되고, I.E를 지원할 필요가 없다면 includes 메소드를 일반적으로 사용하면 됩니다.

자세한 브라우저 호환성은 JavaScript MDN 문서를 참고하시면 됩니다.

300x250

SI 업체에서 진행한 프로젝트를 인수인계 받아서 운영하던 초기에 간헐적으로 자신의 아이디가 중복 로그인되어 로그인이 끊어진다는 민원이 들어왔다. B2C 사이트이고 뭔가 보안에 문제가 있을 수도 있으니 얼른 프로그램들을 검토해 보았으나 특이점이 없었다.

 

일반적으로 동시에 동일한 아이디를 사용하지 못하게 하는 부분은 jSessionId와 사용자 아이디 등의 조합으로 체크하여 현재 접속해 있는 jSessionId와 다른 값으로 동일한 사용자 정보로 거래를 시도하면 먼저 로그인 해있던 접속을 종료시키는 방법을 흔히들 사용한다.

 

일단은 먼저 해당 현상을 재현해 보려고 하였으나 재현이 되질 않았다. 사실 프로그램을 봐도 너무 단순한 비교를 하고 있어서 의도와 다르게 동작하는 경우가 잘 떠오르질 않았다.

 

처음 의심이 된 부분은 제우스의 세션 클러스터링 부분 이었다. 대용량 서비스이다 보니 AP가 여러 대로 이중화되어 있었고 세션 복제가 정상적으로 이루어지지 않으면 해당 고객의 접속 인스턴스가 변경이 될 때 위와 같은 오류가 발생할 수 있을 것 같았다.

 

하여 프로젝트에서 철수하지 않은 AA와 엔지니어 쪽으로 문의하였으나 세션 클러스터링 기능은 정상적으로 설정되어 있고 동작도 확인이 되었다고 한다. 더구나 스위치 설정도 Sticky라 고객이 동일한 접속을 사용할 경우 접속 인스턴스가 변경되지도 않을 것 같았다.

 

그러던 차에 CS팀의 협조를 얻어 고객 한 분의 접속 시간 대와 IP 주소를 제공 받을 수 있었고, 해당 정보로 인터맥스로 특이사항을 찾아보니 해당 오류가 있었던 시간에 해당 고객의 접속 인스턴스가 변경되어 있었다!!

일반적으로 Sticky 설정에서 접속 인스턴스는 유지되지만 Class 적용으로 인한 WAS Restart나 운영 상의 이유로 Rstart가 필요한 경우 롤링 방식으로 진행하기 때문에 그 시간과 맞아 떨어지면 인스턴스가 변경이 되기도 한다.

 

하지만 인스턴스가 변경이 되더라도 세션 복제가 정상적으로 수행된다면 문제가 되지 않을 부분이었으므로 임시적으로 운영 서버에 관련 로그를 남기고 모니터링을 해서 원인을 찾을 수 있었다. 제우스는 인스턴스 별로 jSessionId 뒤 쪽에 .으로 구분해서 인스턴스의 아이디도 남긴다고 한다.

xxxxxxxxxxxxxxxx.yyyy 같은 식이다. 하여 . 앞 부분은 인스턴스가 변경되어도 동일하나 뒷 부분은 인스턴스 별로 관리되는 값이라고 한다.

 

프로그램을 변경하여 . 앞 부분만 비교하도록 변경하여 해당 건도 무사히 대응할 수 있었다.

300x250

'Trouble shooting' 카테고리의 다른 글

Sequence order 옵션에 따른 성능  (0) 2022.06.25
Partition truncate 시 온라인 거래 지연 이슈  (0) 2022.06.24
WAS Connection Pool 부족 현상  (0) 2022.06.24
MAX + 1 채번 이슈  (0) 2022.06.22
JS UI Rendering, Data bind  (0) 2021.10.06

몇 년 전 모 프로젝트에서 지원 요청이 와서 가서 보고, 얼마 전에 내 프로젝트에서도 발생한 걸 보면 아주 기초적인 사항임에도 생각보다 잘 지켜지지 않는 사항인가보다.

대기업 SI 프로젝트에 몸담고 있다보면 요즘 트렌드에 맞는 UI Framework는 써보기가 힘들고 매번 jQuery 기반으로 프로젝트를 하게 되는데 (인력 수급이 아직까지는 가장 용이함), jQuery 기반의 프로젝트에 업무 화면이 복잡해져서 하나의 프로그램에서 수행하는 XHR이 많아질 경우 생각보다 자주 발생하곤 한다.

 

'화면의 특정 영역에 데이터가 나오다가 말다가 해요'

 

A라는 해당 문서의 DOM을 초기화하는 함수가 있다.

B 함수는 서버에서 데이터를 받아와 DOM에 Binding 하는 함수이다.

 

function A() {

    //특정 조건에 따라 UI에 특정 영역을 더하거나 뺀다

}

 

function B() {

    //$.ajax 등의 함수로 서버와 통신하여 UI에 데이터를 Binding 한다.

}

 

생각보다 많은 개발자들이 아래와 같이 작성하고, 대부분의 경우에 올바르게 동작한다.

A();

B();

 

하지만 모바일 프로젝트를 하다보면 아주 많은 경우의 수가 발생한다.

휴대폰이 구형이라 브라우저 동작 자체가 느린 환경도 있고, 접속이 잘 안되는 Wi-Fi 를 사용해서 인터페이스가 느린 경우도 있다. 하여 순서가 반드시 보장되어야 하는 코드들은 정확한 타이밍에 callback 형식으로 작성하도록 한다.

위와 같은 경우에도 A에서 생성해야 할 Element가 생성되지 않은 상태에서 B 함수에서 Data Binding이 수행되는 경우가 발생하면 데이터가 표시되지 않는 경우가 발생한다.

 

function A(callback) {

    //특정 조건에 따라 UI에 특정 영역을 더하거나 뺀다.

    if (callback && typeof callback == 'function') callback();

}

 

function B() {

    //$.ajax 등의 함수로 서버와 통신하여 UI에 데이터를 Binding 한다.

}

 

A(B);

 

위와 같이 작성하면 UI Rendering이 완료된 후에 B 함수를 실행하므로 올바르게 동작한다.

300x250

물론 모든 프로그램은 기본적인 텍스트 에디터를 사용해도 되지만, 각자에게 편리한 IDE 환경으로 개발하면 생산성을 향상시킬 수 있습니다. 보통 Python은 Visual Studio로 개발하는 편이 가장 편리하다고 하지만, 우리는 ECLIPSE에 Python 개발 툴을 설치해보도록 합니다.


ECLIPSE 메뉴에서 [Help] - [Install New Software] 메뉴를 선택한다. PyDev 설치 URL은 아래와 같습니다.

-. http://pydev.org/updates


위 URL을 입력하면 PyDev 설치 목록이 표시되는데, 모두 체크하여 선택하고 설치를 진행합니다.



설치를 완료하면 아래 그림과 같이 Perspective 가 하나 추가됩니다.



또한 New Project Dialog 를 선택하여 보면 아래처럼 Python 프로젝트를 추가할 수 있는 메뉴가 표시됩니다.



프로젝트를 생성하여 개발하면 프로젝트도 아래와 같이 Python 프로젝트 양식으로 표시됩니다.








300x250

'Python' 카테고리의 다른 글

Python 개발환경구성 (2) - IDLE  (0) 2016.04.23
Python 개발환경구성 (1) - Anaconda 설치  (0) 2016.04.20

아나콘다를 기본 경로에 설치하면 C:\Anaconda3 경로에 설치됩니다. 해당 경로에 가보면 Lib\idlelib 이라는 폴더가 있습니다. 해당 폴더에 들어가보면 idle.bat 라는 파일이 있으며, 더블 클릭하면 파이썬 IDLE(Integrated Development Environment)가 실행됩니다.



IDLE를 실행한 모습은 아래와 같습니다.



실제로 IDLE에서 프로그래밍을 할 수도 있지만, 그다지 편리한 개발 환경은 아니므로 실제 개발 시에는 Visual Studio Code나 ECLIPSE 등을 사용하여 개발을 하지만 특정 코드에 대해서 빠르게 확인을 해 볼 경우에는 IDLE가 월등히 편리하니 개발 시에 유용하게 사용할 수 있습니다.


예를 들어 현재 시간을 HHMMSS 형식으로 가져와서 출력하는 코드를 테스트해 보려면 ILDE에서 아래 그림과 같이 해보면 됩니다.



물론 위 코드를 Visual Studio Code나 ECLIPSE 같은 IDE 환경으로 개발해도 되지만 별도로 빌드를 한 후 실행해야 하기 때문에 ILDE에서 인터프리터 방식으로 실행해서 확인하는 것이 매우 편리합니다.

300x250

'Python' 카테고리의 다른 글

Python 개발환경구성 (3) - PyDev 설치  (0) 2016.04.24
Python 개발환경구성 (1) - Anaconda 설치  (0) 2016.04.20

PC에서 Python을 개발할 수 있는 환경을 구성해보도록 하겠습니다. Python은 당연히 Windows OS와 MAC OS를 지원하지만, 지금부터 Python을 시작하는 목적은 "시스템 트레이딩" 이므로 Windows OS에 설치하도록 하겠습니다.


아직까지 국내 증권사에서 제공하는 시스템 트레이딩 용 API들은 MAC OS를 지원하지 않습니다.


Python의 최소한 만을 설치하고 여타 필요한 Package들을 별도로 설치하실 분은 Python 공식 홈페이지(http://www.python.org)에서 제공하는 파일을 다운로드하여 설치하도록 합니다. 최소한의 인터프리터 및 개발 환경을 설치해주지만 필요한 Package가 생길 때마다 별도로 설치해야 하므로 어렵습니다.


목적이 Python을 깊게 공부하고자 하는 것보단 단 시간에 시스템 트레이딩을 구현해보고자 함이므로, 대중적으로 많이 사용하는 "아나콘다"라는 배포판으로 설치하여 사용하도록 하겠습니다.


아나콘다의 다운로드 경로는 아래와 같습니다.

-. https://www.continuum.io/downloads



설치하고자 하는 PC에 설치된 운영 체제와 일치하는 Bit의 설치 파일을 다운로드 하여 설치하면 되고, 3.x 버전의 파이썬을 설치하겠습니다.. 현재까지는 2.7 버전의 Python에서 지원하는 라이브러리들이 많아 당장의 프로그램은 편할 수 있지만, 시간이 지남에 따라 최신 버전에 메리트가 있을 것이라 보고 3.x 버전의 Python을 설치하도록 합니다. 


설치하는 Python의 버전에 따라 나중에 추가하는 py2exe나 PyQt 등도 모두 Bit를 맞추어 설치해야 합니다.


아나콘다 설치는 다운로드한 파일을 실행시킨 후 [다음] 버튼만 누르면 되는 형태로 진행되므로 별도의 캡쳐는 첨부하지 않습니다.



300x250

'Python' 카테고리의 다른 글

Python 개발환경구성 (3) - PyDev 설치  (0) 2016.04.24
Python 개발환경구성 (2) - IDLE  (0) 2016.04.23

JavaScript 에는 Same-origin Policy 라는 규칙이 있다. 한국어로 하자면 동일 출처 정책인데, 한 출처에서 로드된 문서나 스크립트가 다른 출처의 자원과 상호작용하지 못하도록 규정하는 내용이다. JavaScript 에서 AJAX 로 개발한 후 Google Chrome 에서 테스트를 해보면 인터페이스가 정상적으로 되지 않는 것을 자주 볼 수 있는데, 그때는 개발자 도구를 켜서 메시지를 확인해야 한다.


특정 도메인에 있는 페이지에서 다른 도메인의 자원을 AJAX로 호출하는 경우 해당 도메인에서는 접근을 거부하게 된다. 이럴 경우에는 서버에서 다른 도메인에서의 접근을 막는 것이므로 Chrome의 보안 옵션과는 무관하며 서버에 CORS(Cross Origin Resource Sharing) 설정을 적용해야 타 도메인에서 현재 도메인의 자원을 Access 하는 부분을 허용할 수 있다.


하지만 서버에서 CORS 옵션을 적용하여 설정하였거나, Local의 HTML 페이지(Null 도메인)에서 서버의 자원에 접근하였는데 접근이 되지 않는 경우 Chrome의 보안 옵션을 확인해보면 된다. 


1. Windows PC

   - Chrome의 바로가기 옵션에 아래 옵션 적용

   - --disable-web-security

2. MAC 

   - Chrome 실행 시 아래처럼 옵션 적용

   - open "/Applications/Google Chrome.app" --disable-web-security



300x250

PC Web을 개발하던, Mobile Web을 개발하던 반드시 Network로 서버와 인터페이스 하여 데이터를 주고 받는 업무가 발생한다. 이때 편리한 Debugging을 할 수 있는 Chrome 개발자 도구의 [Network] 탭을 살펴보도록 하자.

 

Chrome 개발자 도구는 이전 포스트에서 언급한 바와 같이 [F12] Key나 마우스 바로가기 메뉴의 [요소검사]를 선택하면 불러올 수 있다. 그럼 [Network] 탭의 구성을 살펴보자.

 

 

위 화면은 Naver에서 뉴스 한 건을 조회했을 때의 [Network] 탭을 Capture한 모습이다. 해당 페이지를 Loading 하면서 발생한 Network 요소들을 모두 Recording 하여 보여준다. 개발자 도구의 [Network] 탭에 접근한 경우가 아니라면 Chrome은 Network 요소를 Recording 하지 않는다. 반드시 개발자 도구의 [Network] 탭을 열고 있는 상태에서만 Recording 하게 된다. 따라서 해당 탭을 열더라도 페이지를 한 번 더 새로고침해야만 Recording 된 내용을 볼 수 있다.

 

도구상자의 가장 앞에 위치하는  

 버튼은 Recording을 할지 말지 선택하는 버튼이다. 해당 버튼이 빨간색이라면 Recording 상태이며, 회색이라면 현재를 Recording 하고 있지 않은 상태이다. 

 

그 옆에 있는  

 버튼은 누가 봐도, 현재 Recording 된 내용을 삭제하는 버튼이다. 해당 버튼을 클릭하면 현재 창에 있는 내용들이 모두 지워진다.

 

그 다음 

 버튼은 각종 Filter 버튼들을 보여줄지 말지 선택하는 버튼이다. [Network] 탭에서는 각종 Filter들을 제공하여 편리하게 원하는 부분만 볼 수 있도록 해주지만... 결국 자주 보게 되는 부분은 XHR 밖에 없는 것 같다. 여튼 Filter 버튼을 Enable 한 모습은 아래와 같다.

 

 

기본적으로 All 이 선택되어 있고, 보고 싶은 Content Type을 선택할 수 있게 되어 있으며, 맨 앞 텍스트 박스에 값을 입력하여서도 Filtering 할 수 있다. 예를 들어 HTTP Method 가 POST 인 인터페이스만 Filtering 해보고 싶다면 method:POST 라고 입력하면 Filtering 된다. Filter 텍스트 박스에 입력할 수 있는 Keyword 들은 아래를 참고한다.

 

-. domain

-. has-response-header

-. is

-. larger-than

-. method

-. mime-type

-. scheme

-. set-cookie-name

-. set-cookie-value

-. set-cookie-domain

-. status-code

 

대강 의미가 명확한 용어들이라 구체적인 설명은 생략하며 위의 예제와 같이 keyword:value 형식으로 Filtering 할 수 있다.

 

Filtering 버튼 중 자주 사용하게 되는 XHR 버튼을 한 번 클릭해보면 Filtering 되어 아래와 같은 데이터가 표시된다. 

 

 

XHR 인터페이스만 Filtering 한 것이며, 해당 인터페이스의 Method나 HTTP Response code, Content-Type, 또 Capture에는 잘렸지만 Elapse time 등도 표시되어 Debugging에 편리하게 사용할 수 있다. 그럼 위 3개의 데이터 중 list.json 을 한 번 클릭해보면 아래 탭의 내용이 변경된다.

 

 

5개 탭이 있으며, 위 그림은 Headers 탭의 내용이다. Headers 탭에서는 URL, Method, Status Code 등의 정보와 Response, Request Header 및 서버로 송신한 내용을 볼 수 있다. Client 화면 개발 시 서버와 데이터가 잘 맞지 않는다면 서버로 보낸 최종 데이터를 확인할 수 있는 부분이기에 개발 시 상당히 많이 활용하게 되는 내용이다.

 

 

그 옆에 있는 Preview 탭은 서버에서 수신한 데이터를 예쁘게 Parsing 하여 보여주고, 그 옆의 Response 탭은 Parsing 하지 않고 날 것으로 보여준다. 두 탭 모두 각자 쓸모가 있으며 예제는 아래와 같다.

 

[Preview] 탭

 

[Response] 탭

 

당연히 보기에는 Parsing 한 형태인 Preview 탭이 보기 좋지만, 쭉 긁어서 데이터를 재활용한다거나 넘어온 날 것 그대로의 데이터를 봐야할 때 (뭐 간단한 예로 서버 오류로 올바른 XML, JSON 데이터가 넘어오지 않았을 경우) 유용하게 사용할 수 있다.

 

[Cookie] 탭에서는 페이지에서 사용한 Request / Response Cookie 정보를 조회할 수 있으며 Timing 은 현재 Application의 Performance 를 점검해 볼 수 있다. Timing 에 대한 내용은 이야기가 제법 길어질 수 있으므로 별도로 포스팅할 생각이다.

 

이야기가 어쩌다가... 이렇게 두서 없이 빠졌지만 다시 [Network] 탭의 도구상자를 살펴보면 아직 두 개의 버튼이 남았다. 

 옆의 두 개의 버튼이 남았지만, 해당 버튼들도 결국은 Timing 과 관련된 내용이므로 나중에 다루기로 한다.

 

[Network] 탭 데이터를 표시하는 Header 정보는 기본적으로 아래와 같이 구성되며, Header를 클릭하면 Header 기준으로 정방향/역방향 Sort하여 데이터를 볼 수 있다.

 

 

기본적으로는 위와 같은 데이터만 표시되지만, Header 부분을 마우스 우클릭하게 되면 표시하고 싶은 데이터를 선택할 수 있으며, 데이터 종류는 아래 그림과 같다.

 

 

위 Selectbox 에서 선택한 데이터만 목록으로 표시된다.

300x250

+ Recent posts