블로그 이미지

Rurony's Training Gym

Rurony의 트레이닝 도장! by Rurony


'Develop/CLIENT SIDE'에 해당되는 글 6건

  1. 2011.10.19 Aptana ExtJs + JQuery Code assist 방법
  2. 2010.12.16 CSS3 Transform & Matrix Filter (개체 회전)
  3. 2010.12.16 자바스크립트의 나쁜 점들 (더글라스 클락포드)
  4. 2010.12.16 정규 표현식 (Regular Expression)
  5. 2010.12.16 가상 클래스와 가상 요소
  6. 2010.12.16 선택자 (Selector)

Aptana ExtJs + JQuery Code assist 방법

1. Aptana Studio 설치
1) Aptana Studio 설치 URL :  http://www.aptana.com/products/studio3/download
2) Eclipse Plug-in Version 3.0.5 설치
Available Software Sites URL : http://download.aptana.com/studio3/plugin/install

2. Aptana Studio JavaScript Library Support
http://wiki.appcelerator.org/display/tis/JavaScript+Library+Support#JavaScriptLibrarySupport-ExtJS%2FSencha%28
(지원되는 JavaScript Library의 정보 및 사용법을 제공하고 있으며, 그중 ExtJS, JQuery 사용법 정리)

3. ExtJS Code Assist 방법
1) ExtJS 3.3.0 sdocml 파일 다운로드 : https://raw.github.com/aptana/sencha.ruble/master/support/ext-js-3.3.0.sdocml

2) ExtJS를 사용할 프로젝트의 루트 경로에 위치 시킴
(ExtJS 4는 아직 공식 지원 안하는 것 같음... 방법 있으면 알려주세요 ^^;;)

4. JQuery Code Assist 방법

1) Eclipse의 Commands --> Bundle Development --> Update User Bundles --> JQuery를 선택한 후 OK 버튼을 클릭하여 설치

2) JQuery를 사용할 프로젝트 우클릭 Properties --> Project Build Path 에서 사용할 JQeury version 선택


4) Aptana Studio 3.0.5 version 이하일 경우
Top

CSS3 Transform & Matrix Filter (개체 회전)

CSS3 Transform & Matrix Filter를 활용하여 크로스 브라우징되는 개체(이미지 등)의 중심점 회전 예제.

CSS3 transform
        - IE 브라우저에서는 아직 지원되지 않는다. (Matrix Filter 사용)
          - 크롬, 사파리 : -webkit-transform
          - 파이어폭스 : -moz-transform
          - 오페라 : -o-transform

Supported Transform Functions
        - rotate : 회전
          - scale : 크기
          - skew : 기울기
          - translate : 이동

#transformedObject {
border : 1px solid red;
text-size : 16px;
width: 220px;
height: 70px;
-moz-transform: rotate(15deg) translateX(230px) scale(1.5);
-o-transform: rotate(15deg) translateX(230px) scale(1.5);    
-webkit-transform: rotate(15deg) translateX(230) scale(1.5);    
transform: rotate(15deg) translateX(230px) scale(1.5);
/* IE Only */
filter: progid:DXImageTransform.Microsoft.Matrix( M11=1.4488887394336025, M12=-0.388228567653781, M21=0.388228567653781, M22=1.4488887394336025, SizingMethod='auto expand');
margin-left: 156px;
margin-top: -2px;
}

rotate로의 회전은 회전축이 중심에 있다. IE
Matrix Filter의 사용 시에 회전축을
중심축으로 개체를 이동시킨 후 다시 원래 좌표로 이동 시켜 해결함.

var pi = Math.PI;
var degToRad = function(x) { return ( x/(360/(2*pi)) ); }
var radToDeg = function(x) { return ( x*(360/(2*pi)) ); }
var setPosXY = function() {
$('poX').value = parseInt($('img').style.left) + parseInt($('img').offsetWidth)/2;
$('poY').value = parseInt($('img').style.top) + parseInt($('img').offsetHeight)/2;
};

var rotate = function(name) {
var angle = $('angle').value;
var el = $(name);

var rad = degToRad(angle);
var costheta = Math.cos(rad);
var sintheta = Math.sin(rad);
if (isIE) {
//중심점 이동
$('img').style.left = $('poX').value + "px";
$('img').style.top = $('poY').value + "px";
el.style.filter = 'progid:DXImageTransform.Microsoft.Matrix()';
el.filters.item('DXImageTransform.Microsoft.Matrix').SizingMethod = 'auto expand';
el.filters.item('DXImageTransform.Microsoft.Matrix').FilterType = 'bilinear';
el.filters.item('DXImageTransform.Microsoft.Matrix').M11 = costheta;
el.filters.item('DXImageTransform.Microsoft.Matrix').M12 = -sintheta;
el.filters.item('DXImageTransform.Microsoft.Matrix').M21 = sintheta;
el.filters.item('DXImageTransform.Microsoft.Matrix').M22 = costheta;
//원래대로 ..
$('img').style.left = parseInt($('img').style.left) - parseInt($('img').offsetWidth)/2 + 'px';
$('img').style.top = parseInt($('img').style.top) - parseInt($('img').offsetHeight)/2 + 'px';
setPosXY();
} else {
if (typeof el.style.MozTransform !== 'undefined') {
el.style.MozTransform = 'rotate('+angle+'deg)';
} else if (typeof el.style.WebkitTransform !== 'undefined') {
el.style.WebkitTransform = 'rotate('+angle+'deg)';
} else if (typeof el.style.OTransform !== 'undefined') {
el.style.OTransform = 'rotate('+angle+'deg)';
}
}
};

참고 사이트 :

Top

자바스크립트의 나쁜 점들 (더글라스 클락포드)


1. ==/!=
 
피연산자의 타입이 다를 경우 값들을 강제로 변환하여 비교합니다.

'' == '0' //false
0 == '' //true
0 == '0' //true
false == 'false' //false
false == '0' //true
false == undefined //false
false == null //false
null == undefined //true

데이터의 타입이 같고 같은 값인지를 비교하기 위해 ===/!== 를 사용해야 합니다.

2. with
자바스크립트는 객체 하나에 속한 속성들을 접근할 때 간편함을 제공할 목적으로 with 문이 있습니다.

다음과 같은 with 문은,

with (obj) { a = b; }

다음과 같은 의미입니다.

if (obj.a === undefined) { a = obj.b === undefined ? b : obj.b; } else { obj.a = obj.b === undefined ? b : obj.b; }

그래서 결국 이 with 문은 다음의 문장들 중에 하나와 같게 됩니다.

a = b;
a = obj.b;
obj.a = b;
obj.a = obj.b

이 문장들 중에 어떤 문장으로 실행될지는 프로그램을 봐서는 알 수 없습니다.

3. eval

eval 함수는 문자열을 자바스크립트 컴파일러에 넘긴 후 그 결과를 실행시킵니다. 
이는 심각한 성능저하의 요인이 됩니다. 또한 eval 함수는 보안상의 치명적인 문제점을 가지고 있습니다. 하지만, 서버로부터 전달된 JSON 텍스트를 파싱하기 위해 eval 함수를 사용할 수 있습니다. 서버로부터 문제없이 전달된 텍스트를 eval 함수로 파싱하는 것은 HTML이 로드되는 것처럼 안전합니다.

※ 지금은 그렇지 않겠지만, 자바스크립트를 처음 접했을때 객체를 얻기 위한 방법으로 eval 함수를 남용한 코드를 많이 보아왔습니다.

4. continue

continue 문은 루프의 처음으로 제어를 이동 시키고 이러한 방식은 성능저하의 원인이 됩니다.
리팩토링을 통해 continue문을 제거 했을때 성능이 향상되지 않았던 적이 없었다고 하네요.

5. switch/case

각각의 case절은 명시적으로 벗어나게 하지 않으면 다음 case절 까지 계속해서 실행됩니다.
언어에서 가장 나쁜 점은 명백하게 위험하거나 불필요한 속성이 아니라, 매력적으로 보이는 페단이 가장 나쁜 점이라고 하네요.

6. 블록이 없는 문장

아래와 같은 형식은 프로그램의 구조를 애매하게 만들어 후에 작업하는 사람이 쉽게 버그를 만들 수 있게 합니다.

다음의 코드는,

if (ok)
t = true;
advance();

다음의 의미가 아니라,

if (ok) {
t = true;
advance();
}

다음과 같은 의미입니다.

if (ok) {
t = true;
}
advance();

비단 자바스크립트에서만의 나쁜 점이 결코 아니라는 것에 크게 공감하는 부분입니다.

7. ++/--

증감 연산자를 사용하면 코드를 매우 간결하게 작성 할 수 있습니다.  하지만 심각한 보안 취약점을 만드는 대부분의 버퍼 오버런 버그는 이와 같은 스타일의 코드 때문에 발생합니다.

다음과 같은 코드는

var i = 1;
function() { return i + 1 }; //return 2
function() { return i++ }; //return 1

i++/++i의 동작방식(리턴방식)을 충분히 이해한다면 문제되진 않겠지만 일급 객체 표현에 있어서 의식적으로 사용하는 코딩 습관으로 인해 충분히 발생될 소지가 있다고 생각합니다.

8. 비트연산자

&, |, ^, ~, >>, >>>, << 등의 비트 연산자는 정수에 대해서만 동작합니다. 하지만 자바스크립트에는 정수형이 없고 부동 소수점 숫자형만이 존재하여 대상이 되는 숫자를 일단 정수형으로 변환한 다음에 비트 연산을 수행하고 다시 원래 타입으로 되돌립니다. 이와 같은 이유로 성능이 크게 나빠집니다.

9. 함수 문장 vs 함수 표현식

다음과 같은 함수 문장은,

function foo() {};

다음과 같은 의미 입니다.

var foo = function foo() {};

자바스크립트라는 언어를 잘 사용하기 위해서는 함수도 값(value)이라는 것을 이해하는 것이 중요합니다. 함수문장은 위로 끌어롤려지는 대상이 됩니다.(hoisting) 이 말은 함수가 위치한 곳과 관계 없이 함수가 정의된 곳의 유효범위 가장 상위로 이동된다는 뜻입니다. 이러한 특징은 함수를 사용하기 전에 반드시 미리 선언해야 한다는 요구사항을 경감시켜 구조를 엉성하게 만듭니다.

공식적인 문법은 function이라는 단어로 시작하는 문장을 함수 문장이라고 가정하고 있기때문에 문장의 첫 번째 부분에 함수 표현식을 사용할 수 없습니다. 이를 위한 대안은 함수 표현식을 다음의 예처럼 괄호로 묶는 것입니다.

(function () {
var hidden_varialble;
})();

함수표현식에서 익명 함수값은 IE에서도 미리 선언되지 않으면 에러를 발생시키지만 이름부에 함수명을 쓰게되면 IE는 함수 문장으로 인식되고 있습니다.

10. 데이터 타입 랩퍼

new Boolean, new Number, new String 등은 완전히 필요가 없습니다.
또한 new Object와 new Arrary의 사용도 피하고 {}, []를 사용하기 바랍니다.

11. new

자바스크립트의 new 연산자는 피연산자의 프로토타입 멤버들을 상속하는 객체를 생성하고 이 객체를 this에 바인딩하면서 피연산자를 호출합니다.

프로토타입에의한 OOP 상속에서 생성자를 통한 인스턴스의 사용을 위해 사용하게 됩니다. 이런한 방식에서의 this 누락으로 인한 잠재적인 프로그램상의 위험 요소를 줄이고자 스칼 표기법을 쓰라고 권고하고 있습니다. new 연산자를 사용하지 않으면 this는 새로운 객체에 바인딩 되지 않고 전역객체에 연결됩니다. 자바스크립트 OOP 상속의 구현 방식에있어서 프로토타입 기반의 자바스크립트의 근본과 명확한 OOP의 개념(closure 생성을 통한 캡슐화 포함) 구현 및 메모리 등에서 여러가지 이견들이 있는 것 같습니다. 클락포드는 new 연산자를 사용하지 않는 것이 더 나은 정책이라고 말하고 있습니다.
 
12. void

자바스크립트에서 void는 피연산자를 취한 후 undefined를 반환하는 연산자입니다. 이는 유용하지도 않고 혼동됩니다.

출처 : 더글라스 클락포드의 "자바스크립트 핵심가이드"
Top

정규 표현식 (Regular Expression)


정규 표현식(Regular Expression)

자바스크립트의  정규 표현식은 문자열에서 특정 내용을 찾거나 대체 또는 발췌하는데 사용.

 

사용하는 메소드

regexp.exec, regexp.test, string.match, string.replace, string.search, string.split 등

 

정규표현식 객체 생성

    정규 표현식 리터럴 사용

        var my_regexp = /"(?:\\.|[^\\\"])*"/g;

   

        정규 표현식 플래그

        g  : Global(여러번 일치함, 정확한 의미는 메소드에 따라 다름)
        i  : Insensitive (대소문자를 구분하지 않음)
        m  : Multiline(^과 $이 라인 끝 문자에 일치할 수 있음)

 

    RegExp 생성자 사용

        var my_regexp = new RegExp("\"(?:\\.|[^\\\\\\\"])*\"", 'g');

       

        RegExp 객체의 속성
        global  : g 플래그가 사용된 경우 true
        ignoreCase : i 플래그가 사용된 경우 true
        lastIndex : 다음 exec 실행을 위한 시작점을 나타냄. 초기값을 0
        multiline  : m 플래그가 사용된 경우 true
        source  : 정규 표현식의 소스 텍스트

 

구성 요소

^ : 문자열의 시작
$ : 문자열의 끝

 

수량자
{m, n} : m에서 n번 반복
? : 0 또는 한번 반복 {0,1}
* : 0번 이상 반복 {0,}
+ : 한번 이상 반복 {1,}

 

클래스
[-] : 범위 ex) [a-z]
[^] : 부정(제외) ex) [^?#] : '?', '#'를 제외한 모든 문자

이스케이프 (\)
\d : 숫자문자 [0-9]와 동일
\D : 숫자문자 아님 [^0-9]와 동일
\s : 공백문자
\S : 공백문자 아님

...

 

선택

| : ex) "into".match(/in|int)

 

그룹

\1,\2,... 의 표현으로 그룹에 대한 참조가능

캡처 : '()'로 묶인 정규 표현식 선택

비캡처 : '(?:)'

긍정형 룩어헤드 : '(?=)' [좋은점 아님]

부정형 룩어헤드 : '(?!)' [좋은점 아님]

 

사용 예제

String.prototype.trim = function() {
    return this.replace(/^\s+|\s+$/g, '');
}
var strTest = '   abcd     efgh   ';
document.writeln(strTest.trim());  //abcd     efgh

 

var isEmail = function (str) {
    var emailEx1 = /[A-Za-z0-9_\-]@[A-Za-z0-9_\-]+\.[A-Za-z]+/;
    var emailEx2 = /[A-Za-z0-9_\-]@[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z]+/;
    var emailEx3 = /[A-Za-z0-9_\-]@[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z]+/;
 
    if (emailEx1.test(str)) { return true; }
    if (emailEx2.test(str)) { return true; }
    if (emailEx3.test(str)) { return true; }
 
    return false;
}

 

Top

가상 클래스와 가상 요소


:active
요소가 활성화된 경우에 적용된다. 가장 일반적인 경우는 HTML문서에서 텍스트의 링크를 클릭하는 경우인데, 링크를 클릭하여 마우스 버튼을 누르고 있는 동안이 활성화된 상태이다. 다른 요소들도 이론적으로는 활성화가 가능하지만 실제로 CSS에서는 정의하고 있지 않다.

형식 : 가상 클래스
적용대상 : 활성화된 요소
예제 :
a:active {color: red;}
*:active {background: blue;}


:after
요소의 컨텐츠 끝부분에 생성된 컨텐츠를 추가한다. 가상 요소는 기본적으로는 인라인이지만 display 속성을 사용하여 바꿀 수 있다.

형식 : 가상 클래스
생성결과 : 요소의 컨텐츠 끝부분에 생성된 컨텐츠를 포함하는 가상 요소가 추가된다.
예제 :
a:external:after {content: " " url(/icons/globe.gif);}
p:after {content: " | ";}


:before
요소의 컨텐츠 시작부분에 생성된 컨텐츠를 추가한다. 가상 요소는 기본적으로는 인라인이지만 display 속성을 사용하여 바꿀 수 있다.

형식 : 가상 클래스
생성결과 : 요소의 컨텐츠 시작부분에 생성된 컨텐츠를 포함하는 가상 요소가 추가된다.
예제 :
a[href]:before {content: "[LINK]";}
p:before {content: attr(class);}


:first-child
특정 요소의 첫 번째 자식 요소만 선택해주는 가상 클래스이다. 예를 들어 p:first-child를 사용하면 특정 요소의 첫 번째 자식 요소인 p 요소를 선택한다. 일반적인 예상과 달리 문서요소에 있는 첫 번째 요소를 선택하는 것이 아니다. 이런 경우에는 p > *:first-child를 사용한다.

형식 : 가상 클래스
적용대상 : 특정 요소의 첫 번째 자식인 요소
예제 :
body *:first-child {font-weight: bold;}
p:first-child {font-size: 125%;}


:first-letter
요소의 첫 번째 글자에 스타일을 적용한다. 첫 번째 글자 앞에 나오는 구두점도 같이 스타일이 적용된다. 일부 언어에서는 하나의 문자로 다루어져야 되는 문자조합이 있기 때문에 사용자 에이전트는 :first-letter를 전부 적용해야 한다. CSS2.1 이전에는 :first-letter가 블록 레벨 요소에만 사용이 가능했었는데 CSS2.1에서 모든 요소에 적용이 가능하도록 확장했다. 적용 가능한 속성에는 한정이 있다.

형식 : 가상 클래스
적용대상 : 요소의 첫 번째 글자를 가지고 있는 가상 요소
예제 :
h1:first-letter {font-size: 125%;}
a:first-letter {text-decoration: line-through;}


:first-line
요소의 텍스트에서 단어의 숫자에는 관계없이 첫 줄에 스타일을 적용한다. :first-line은 블록 레벨 요소에만 사용이 가능하다. 적용 가능한 속성에는 한정이 있다.

형식 : 가상 클래스
적용대상 : 요소의 첫 줄을 가지고 있는 가상 요소
예제 :
p.lead:first-line {font-weight: bold;}


:focus
특정 요소가 포커스를 가지고 있는 동안 적용된다. HTML에서 텍스트 입력상자에 입력 커서가 생겨있는 경우, 즉 사용자가 글자를 입력하기 시작할 때를 의미한다. 링크 요소와 같은 다른 요소에도 포커스가 생길 수 있지만 CSS에서 정의하고 있지 않다.

형식 : 가상 클래스
적용대상 : 포커스를 가진 요소
예제 :
a:focus {outline: 1px dotted red;}
input:focus {background: yellow;}


:hover
요소가 머무르는 상태인 경우, 즉 사용자가 요소를 활성화하지 않은 상태로 지정하는 경우에 적용된다. 일반적인 예로는 HTML에서 링크위로 마우스를 이동시키는 경우이다. 다른 요소에도 머무르는 상태가 가능하지만 CSS에서 정의하고 있지 않다.

형식 : 가상 클래스
적용대상 : 머무르는 상태인 요소
예제 :
a[href]:hover {text-decoration: underline;}
p:hover {background: yellow;}


:lang
언어의 인코딩을 선택한다. 언어 정보는 CSS에서 지정되지 못하고, 문서 내에 지정되거나 문서와 연관되어 있어야 한다. :lang는 언어 속성 선택자와 같다.

형식 : 가상 클래스
적용대상 : 언어 인코딩 정보와 연관된 요소
예제 :
html:lang(en) {background: silver;}
*:lang(fr) {quotes: '<< ' ' >>';}


:link
아직 방문하지 않은, 즉 사용자 에이전트의 히스토리에 있지 않은 URI를 가진 링크에 적용된다. :visited 상태와 서로 반대가 된다.

형식 : 가상 클래스
적용대상 : 아직 방문하지 않은 링크
예제 :
a:link {color: blue;}
*:link {text-decoration: underline;}


:visited
이미 방문한, 즉 사용자 에이전트의 히스토리에 있는 URI를 가진 링크에 적용된다. :link 상태와 서로 반대가 된다.

형식 : 가상 클래스
적용대상 : 이미 방문한 링크
예제 :
a:visited {color: purple;}
*:visited {color: gray;}

Top

선택자 (Selector)


전체 선택자
이 선택자는 문서내의 모든 요소명을 선택한다. 규칙적인 선택자가 없을 경우에는 전체 선택자가 영향을 준다.

형식 : *
예제 :
* {color: red;}
div * p {color: blue;}


타입 선택자
이 선택자는 문서에서 요소의 이름을 선택한다. 해당하는 요소의 모든 경우가 선택된다. (CSS1에서는 요소 선택자라고 부른다.)

형식 : 요소1
예제 :
body {background: #fff;}
p {font-size: 1em;}


자손 선택자
특정 요소의 자손관계를 이용해서 요소를 선택할 수 있다. 조상 요소의 자식 요소나 자식의 자식 등을 선택할 수 있다. (CSS1에서는 문맥 선택자라고 부른다.)

형식 : 요소1 요소2
예제 :
body h1 {font-size: 200%;}
table tr td div ul li {color: purple;}


자식 선택자
특정 요소의 자식에 해당하는 요소를 선택할 수 있다. 이 선택자는 하나의 자식 요소만 선택이 가능하기 때문에 자손 요소보다 더 제한적이다.

형식 : 요소1 > 요소2
예제 :
div > p {color: cyan;}
ul > li {font-weight: bold;}


인접 형제요소 선택자
특정 요소의 인접한 형제 요소를 선택할 수 있다. 요소와 문서 구조상의 위치만 고려되고, 두 요소 사이의 텍스트는 무시된다.

형식 : 요소1 + 요소2
예제 :
table + p {margin-top: 2.5em;}
h1 + * {margin-top: 0;}


클래스 선택자
HTML이나 SVG, MathML같은 언어에서는 '점 표기법'을 사용하여 특정 클래스를 가지고 있는 요소를 하나이상 선택할 수 있다. 클래스명은 마침표에 바로 붙여서 사용해야 하고, 여러 개의 클래스도 함께 연결해서 사용할 수 있다. 마침표 앞에 요소명이 오지 않는 경우에는 그 클래스를 가진 모든 요소가 선택된다.

형식 : 요소1.classname  요소1.classname1.classname2
예제 :
p.urgent {color: red;}
a.external {font-style: italic;}
.example {background: olive;}


id 선택자
HTML같은 언어에서는 특정 id를 가지고 있는 요소를 선택할 수 있다. id는 '#' 기호 표시에 바로 붙여서 사용해야 한다. '#' 기호 표시 앞에 요소명이 오지 않는 경우에는 그 id를 가진 모든 요소가 선택된다.

형식 : 요소1#idname
예제 :
h1#page-title {font-size: 250%;}
body#home {background: silver;}
#example {background: lime;}


단순 속성 선택자
특정 속성을 가진 요소를 선택할 수 있다.

형식 : 요소1[attr]
예제 :
a[rel] {border-bottom: 3px double gray;}
p[class] {border: 1px dotted silver;}


정확한 속성값 선택자
특정 속성에 특정한 값이 있는 요소를 선택할 수 있다.

형식 : 요소1[attr="value"]
예제 :
a[rel="Home"] {font-weight: bold;}
p[class="urgent"] {color: red;}


부분 속성값 선택자
공백으로 속성이 나누어진 속성값에서 일부를 가진 요소를 선택할 수 있다. [class~="value"]는 .value와 같다.

형식 : 요소1[attr~="value"]
예제 :
a[rel~="friend"] {text-transform: uppercase;}
p[class~="warning"] {background: yellow;}


시작하는 속성값 선택자
속성값이 특정 값으로 시작하는 요소를 선택할 수 있다.

형식 : 요소1[attr^="substring"]
예제 :
a[href^="/blog"] {text-transform: uppercase;}
p[class^="test-"] {background: yellow;}


끝나는 속성값 선택자
속성값이 특정 값으로 끝나는 요소를 선택할 수 있다.

형식 : 요소1[attr$="substring"]
예제 :
a[href$=".pdf"] {font-style: italic;}


임의 속성값 포함 선택자
속성값이 임의의 값을 포함하는 요소를 선택할 수 있다.

형식 : 요소1[attr*="substring"]
예제 :
a[href*="oreilly.com"] {font-weight: bold;}
div[class*="port"] {border: 1px solid red;}


언어 속성 선택자
하이픈(-)으로 조합된 일련의 값이 있는 lang 속성에 특정 값을 포함하는 요소를 선택할 수 있다.

형식 : 요소1[lang|="lc"]
예제 :
html[lang|="en"] {color: gray;}


Top

prev 1 next