저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의
OKKY 세미나에서 발표한 자료.


Posted by 최범균 madvirus
TAG Vue.js

댓글을 달아 주세요

앵귤러2 기반 웹앱에 파일 업로드 기능을 추가해야 할 일이 생겨서 검색을 했더니 다음의 두 라이브러리가 얻어 걸렸다.

  • ng2-file-upload (https://github.com/valor-software/ng2-file-upload)
  • ng2-uploader (https://github.com/jkuri/ng2-uploader)

둘 다 쉽게 파일 업로드를 붙일 수 있다. 두 라이브러리를 실험해보고 나서 ng2-file-upload를 선택했다. 그 이유는 구현하려는 기능에 조금 더 맞았기 때문이다.


ng2-file-upload의 기본 사용법


데모(http://valor-software.com/ng2-file-upload/) 사이트를 보면 ng2-file-upload의 기본 사용법을 볼 수 있는데, 진짜 쉽다. ng2-file-upload가 제공하는 FileUploader 클래스와 ng2FileDrop 디렉티브와 ng2FileSelect 디렉티브를 사용하면 된다.


나 같은 경우 앵귤러2 사이트의 튜토리얼 문서에 있는 과정을 따라서 프로젝트를 생성했다. ng2-file-upload를 사용하기 위해 package.json 파일에 ng2-file-upload에 대한 의존을 추가했다.


{

  ...

  "dependencies": {

    "@angular/common": "2.0.0-rc.5",

    ...

    "bootstrap": "^3.3.6",

    "ng2-file-upload": "1.0.3"

  },

  "devDependencies": {

    "typescript": "^1.8.10",

    "typings":"^1.0.4"

  }

}


추가한 뒤에 npm install 명령어를 사용해서 ng2-file-upload를 다운로드 받았다.


그리고, 모듈 설정을 위해 systemjs를 사용했기 때문에 systemjs.config.js 파일에 ng2-file-upload에 대한 설정을 다음과 같이 추가해서 모듈 이름으로 접근할 수 있도록 했다.


(function (global) {

    // map tells the System loader where to look for things

    var map = {

        'app': '/operation-app/app-testeditor', // 'dist',

        '@angular': '/node_modules/@angular',

        'rxjs': '/node_modules/rxjs',

        'ng2-file-upload': '/node_modules/ng2-file-upload'

    };

    // packages tells the System loader how to load when no filename and/or no extension

    var packages = {

        'app': {main: 'test-editor-main.js', defaultExtension: 'js'},

        'rxjs': {defaultExtension: 'js'},

        'ng2-file-upload': {defaultExtension: 'js'}

    };

    ...

    ...

    System.config(config);

})(this);


파일 업로드 기능이 필요한 앵귤러 컴포넌트는 다음과 같이 ng2-file-upload 모듈을 임포트하면 된다.


import {FILE_UPLOAD_DIRECTIVES, FileUploader} from "ng2-file-upload/ng2-file-upload";


@Component({

    selector: 'test-file-uploader',

    templateUrl: './test-file-uploader.component.html',

    directives: [FILE_UPLOAD_DIRECTIVES]

})

export class TestFileUploader {

    public uploader:FileUploader = new FileUploader({url: '/my/upload/path');


    ...

}


FileUploader 클래스는 업로드할 파일 목록을 관리하고 파일을 전송하는 기능을 제공한다. ng2-file-upload 사이트의 데모 코드를 보면 다음과 같이 템플릿 코드에 디렉티브를 사용해서 업로드할 파일을 추가할 수 있다.


<div ng2FileDrop [uploader]="uploader" ....>

    Base drop zone

</div>


<div ng2FileDrop [uploader]="uploader" ....>

    Another drop zone

</div>


Multiple

<input type="file" ng2FileSelect [uploader]="uploader" multiple  /><br/>


Single

<input type="file" ng2FileSelect [uploader]="uploader" />


ng2FileDrop 디렉티브로 지정한 영역에 파일을 드롭하거나 ng2FileSelect를 이용해서 파일을 선택하면, 해당 파일이 [uploader] 속성으로 지정한 FileUploader 객체에 추가된다.


이렇게 FileUploader에 추가한 파일을 업로드하려면 다음과 같이 FileUplaoder의 uploadAll()을 사용하면 된다. 또는 추가한 개별 파일별로 업로드를 할 수도 있다. 데모 사이트에서 완전한 코드를 확인할 수 있다.


<button type="button" class="btn btn-success btn-s"

        (click)="uploader.uploadAll()" [disabled]="!uploader.getNotUploadedItems().length">

    <span class="glyphicon glyphicon-upload"></span> Upload all

</button>


디렉티브 없이 FileUploader 직접 사용


ng2-file-upload가 그 자체로 좋지만 내가 필요한 건 ng2-file-upload가 제공하는 파일 업로드 기능이었고, 디렉티브를 통한 연동은 필요없었다. 그래서 디렉티브를 사용해서 파일 목록을 FileUploader에 추가하지 않고 직접 추가했다.


FileUploader 생성과 파일 추가

FileUploader 객체를 앵귤러 컴포넌트 생성자에서 직접 생성했다. <input type="file"> 버튼을 눌러 선택한 파일 목록을 받기 위한 메서드는 handleUploadFileChanged()이다.


@Component( ... )

export class TestFileUploader {

    public uploader:FileUploader;


    constructor() {

        let uploadUrl = window.location.protocol + "//" + window.location.host + "/testfile/upload";

        this.uploader = new FileUploader({url: uploadUrl});

        ...

    }


    handleUploadFileChanged(event) {

        this.uploader.clearQueue();

        let files:File[] = event.target.files;

        let filteredFiles:File[] = [];

        for (var f of files) {

            if (f.name.endsWith(".pdf")) {

                filteredFiles.push(f);

            }

        }

        if (filteredFiles.length == 0) {

            this.showGuide = true;

        } else {

            this.showGuide = false;

            let options = null;

            let filters = null;

            this.uploader.addToQueue(filteredFiles, options, filters);

        }

    }

    ...


요구사항은 파일을 하나만 업로드하는 것이었기에 handleUploadFileChanged()는 먼저 현재 uploader.clearQueue())를 이용해서 uplaoder에 추가되어 있는 파일 목록을 지운다. 이를 제거하지 않으면 파일을 선택할 때마다 업로드할 파일이 추가되기 때문에, 실제 업로드를 수행할 때 마지막 선택한 파일을 포함한 이전에 선택한 모든 파일을 업로드한다.


위 코드에서는 확장자가 pdf인 파일을 걸러낸 뒤에 uploader.addToQueue()를 이용해서 업로드할 파일 목록을 직접 uploader에 추가했다.


handleUploadFileChanged()를 실행하기 위한 템플릿 코드는 다음과 같다.


<input type="file" (change)="handleUploadFileChanged($event)">



업로드와 완료 처리


업로드할 파일을 선택했다면 uploader.uploadAll()을 이용해서 선택한 파일을 업로드할 수 있다. 업로드를 완료한 뒤에 성공/실패 유무에 따라 알맞은 후속 작업(안내 문구 출력 등)을 하고 싶다면 uploader에 이벤트를 리스너를 등록하면 된다.


@Component( ... )

export class TestFileUploader {

    public uploader:FileUploader;

    private uploadResult:any = null;


    constructor() {

        let uploadUrl = window.location.protocol + "//" + window.location.host + "/testfile/upload";

        this.uploader = new FileUploader({url: uploadUrl});

        this.uploader.onSuccessItem = (item, response, status, headers) => {

            this.uploadResult = {"success": true, "item": item, "response": 

                response, "status": status, "headers": headers};

        };

        this.uploader.onErrorItem = (item, response, status, headers) => {

            this.uploadResult = {"success": false, "item": item, 

                "response": response, "status": status, "headers": headers};

        };

        this.uploader.onCompleteAll = () => {

            this.handleUploadComplete();

        };

    }


    uploadFile() {

        this.uploader.uploadAll(); // 업로드 시작

    }


    private handleUploadComplete() {

        console.log("upload compete : " + this.uploadResult.response);

        if (this.uploadResult.success) {

            ...성공 메시지 출력

        } else {

            ...실패 메시지 출력

        }

    }


FileUploader에는 onSuccessItem, onErrorItem, onCompleteAll 등의 이벤트 리스너를 등록할 수 있다. 이들 리스너를 등록하면 업로드 성공/실패 여부를 이벤트로 받아 그에 알맞은 처리를 할 수 있다. 위 코드는 한 개 파일만 업로드하므로 onSuccessItem 리스너와 onErrorItem 리스너에서 업로드 결과를 uploadResult 필드에 할당했다. 그리고, 모든 업로드 처리가 끝나면 불리는 onCompleteAll 리스너에서 업로드 성공/실패 결과에 따라 알맞은 처리를 수행하도록 했다.


Posted by 최범균 madvirus

댓글을 달아 주세요

앵귤러2로 프로젝트를 진행하는 과정에서 입력한 내용에 따라 textarea의 높이를 자동으로 조절해주는 기능이 필요했다. 검색 결과 다음 디렉티브가 적당해 보여 적용을 했다.

  • https://github.com/stevepapa/angular2-autosize

angular2-autosize 디렉티브 사용법은 간단하다. 위 사이트에서 제공하는 샘플 코드처럼 템플릿에 autosize 디렉티브를 적용해주면 된다.


import {Component} from '@angular/core';

import {Autosize} from 'angular2-autosize';


@Component({

  template: `

    <textarea autosize >Hello, this is an example of Autosize in Angular2.</textarea>

  `,

  directives: [Autosize]

})

class App {


}


예제 사이트(https://stevepapa.com/angular2-autosize/)에서 <textarea>에 입력하면 자동으로 높이가 늘었다 줄었다하는 것을 할 수 있다. 



약간의 수정

angular2-autosize는 내용이 없을 경우 다음 그림처럼 textarea가 표시된다.



두 줄 정도 높이로 표시되는데, 내가 원하는 건 내용이 없거나 한 줄 분량인 경우 높이도 한 줄 높이로 맞춰지는 것이었다. 그래서 angular2-autosize의 코드(https://github.com/stevepapa/angular2-autosize/blob/master/src/autosize.directive.ts)를 다음과 같이 약간 수정했다. 추가한 부분을 굵게 표시했다.


import {ElementRef, HostListener, Directive, Input} from '@angular/core';


@Directive({

    selector: 'textarea[autosize]'

})

export class Autosize {

    @HostListener('input',['$event.target'])

    onInput(textArea: HTMLTextAreaElement): void {

        this.adjust();

    }


    @Input("autosize") initValue:string;


    constructor(public element: ElementRef){

    }


    ngOnInit(): void{

        this.element.nativeElement.rows = 1;

        this.element.nativeElement.value = this.initValue;

        this.adjust();

    }

    adjust(): void{

        this.element.nativeElement.style.overflow = 'hidden';

        this.element.nativeElement.style.height = 'auto';

        this.element.nativeElement.style.height = this.element.nativeElement.scrollHeight + "px";

    }

}


@Input을 이용해서 textarea에 보여줄 값을 초기화하도록 구현했다. 수정한 디렉티브의 사용은 다음과 같다.


<textarea class="form-control" cols="30" rows="1"

          [(ngModel)]="mytext"

          [autosize]="mytext"></textarea>


ngModel로 설정한 값과 연동하는 방법이 있을 것 같은데, angular2 자체를 아직 자세히 몰라 거기까진 진행하지 못했다.


실제 적용한 결과 화면은 다음과 같다.





Posted by 최범균 madvirus

댓글을 달아 주세요

Ajax로 읽어온 데이터 중에 숫자 데이터를 화면에 알맞게 포맷팅해서 보여줘야 할 일이 생겼는데, 이미 숫자를 포맷팅해주는 라이브러리가 있지 않을까 해서 찾아봤더니, 역시나 간단하게 사용할 수 있는 라이브러리가 있다. jquery-number 라는 라이브러로 jquery 기반의 숫자 포맷팅 기능을 제공한다. 다운로드는 https://github.com/teamdf/jquery-number 에서 받을 수 있다.


사용방법은 간단하다. 다음의 형식 중 한 가지를 사용하면 된다. 모두 세 자리 단위마다 콤마를 삽입한다.

  • $.number(숫자): 소수점 이하는 제거한 결과 문자열을 생성한다.
  • $.number(숫자, 소수자리): 소수점 이하 지정한 자리까지 포함한 문자열을 생성한다.
  • $(실렉터).number(숫자): 실렉터로 선택된 태그의 값으로 숫자를 포맷팅한 결과를 사용한다. 소수점 이하는 제거된다.
  • $(실렉터).number(숫자, 소수자리): 실렉터로 선택된 태그의 값으로 숫자를 포맷팅한 결과를 사용한다. 소수점 이하는 지정한 자리까지 포함된다.
  • $(실렉터).number(true): 실렉터로 선택된 태그의 값을 포맷팅한다. 소수점 이하는 제거된다.
  • $(실렉터).number(true, 소수자리): 실렉터로 선택된 태그의 값을 포맷팅한다. 소수점 이하는 지정한 자리까지 포함된다.

아래 코드는 예제 코드이다.


<script src="/js/jquery/jquery-1.9.1.min.js"></script>

<script src="/js/jquery/jquery.number.min.js"></script>

<script>

// 자바스크립트 숫자를 문자열로 포맷팅

var num = 12345.6789;

var a = $.number(num); // "12,345"

var b = $.number(num, 3); // "12,345.67"


// jquery 실렉터로 선택된 태그의 text 값으로 포맷팅한 결과를 설정

$("#someSelector").number(1003.1034); // <span id='someSelector'>1,003</span>

$("#anySelector").number(1003.1034, 1); // <span id='anySelector'>1003.1</span>


// jquery 실렉터로 선택된 태그의 숫자 값을 포맷팅

$(".myNumber").number(true); // <span class='myNumber'>12,345</span>

$(".youNumber").number(true, 2); // <span class='youNumber'>12,345.67

</script>


<span id='someSelector'></span>

<span id='anySelector'></span>

<span class='myNumber'>12345.6788</span>

<span class='youNumber'>12345.6788</span>



Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 임예준 2014.10.30 18:50 신고  댓글주소  수정/삭제  댓글쓰기

    아쉬운게... 음수는 지원 안되는 거 같아요.

  2. 임예준 2014.11.06 17:05 신고  댓글주소  수정/삭제  댓글쓰기

    오랫만이죠~
    혹시나 해서 찾아봤는데 2.1.5 버전에는 negative sign 지원한다고 되어 있네요.
    세상엔 참 편한게 많은거 같아요. ^^

  3. 임예진 2015.02.08 09:48 신고  댓글주소  수정/삭제  댓글쓰기

    균~ 오랜만이야~~.

Bootstrap을 사용한 웹 페이지에서 네이버 지도 API를 사용하면 지도 이미지가 웹 화면에 제대로 출력되지 않는 것을 확인할 수 있다. 그 이유는 Bootstrap에 설정되어 있는 img 태그의 css 값 때문이다.


네이버 지도 API가 사용하는 <div> 영역의 id가 map 이라고 할 경우, 다음과 같이 css를 설정하면 지도에서 사용되는 이미지가 제대로 출력된다.


#map img {

max-width: none;

height: auto;

border: 0

}


다음 지도 API도 동일한 이유로 지도 영역의 이미지가 제대로 출력되지 않는데, 위와 같이 처리하면 올바르게 출력된다.

Posted by 최범균 madvirus

댓글을 달아 주세요

다음 지도 서비스를 사용하면 웹 브라우저 크기가 바뀔 때 마다 아래 그림처럼 지도 영역의 높이가 함께 변경되는 것을 알 수 있다.



위 그림에서 지도의 높이는 다음과 같다.

  • 지도 영역의 높이 = 웹 브라우저 전체 높이 - 상단 메뉴 고정 높이

즉, 지도 영역의 높이는 상단 메뉴 높이를 뺀 나머지가 된다. 그런데, CSS에는 높이를 부모 태그에 대한 비율로 지정하는 방법만 있을 뿐, 일부 영역을 뺀 나머지 높이를 지정하는 방법은 존재하지 않는다. 그래서, 위 그림과 같이 특정 요소의 높이가 일부를 제외한 나머지 요소의 높이가 되도록 만들려면 다음과 같은 처리를 해 주어야 한다.

  • 자바 스크립트 이벤트 처리
    • 문서 로딩이나 윈도우 크기 변경 이벤트 발생시에, 해당 요소의 높이를 변경한다.
아래는 자바스크립트로 높이를 처리하기 전의 HTML 문서를 보여주고 있다.


<!DOCTYPE html>

<html>

<head>

<title>가변 높이 처리</title>

<style>

#top{ height: 50px; background-color: #eee; }

#left { background-color: yellow; float: left; width: 100px; }

#content { background-color: red; margin-left: 100px; }

</style>

<body>

<div id="top">헤드 높이</div>

<div id="left">좌측 메뉴</div>

<div id="content">내용</div>

</body>


위 문서를 웹 브라우저로 보면 다음과 같이 left와 content 영역이 표시된다.



우리가 하려는 건, content 영역이 웹 브라우저의 나머지 높이를 차지하도록 만드는 것이다. 자바 스크립트를 이용하면, 간단하게 웹 브라우저의 크기에 따라 content 영역의 높이가 설정되도록 할 수 있다. 여기서는 jquery를 이용해서 크기를 변경해주는 코드를 작성해보았다.


<!DOCTYPE html>

<html>

<head>

<title>가변 높이 처리</title>

<style>

body { height: 100%; margin: 0 0 0 0;}

#top { height: 50px; background-color: #eee; }

#left { background-color: yellow; float: left; width: 100px; }

#content { background-color: red; margin-left: 100px; }

</style>

<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>

<script>

$(document).ready(function() {

resizeContent();

});

$(window).resize(function() {

resizeContent();

});


function resizeContent() {

var windowHeight = $(window).height();

var topHeight = $("#top").height();

$('#content').css({'height':(windowHeight-topHeight)+'px'});

}

</script>

<body>

<div id="top">헤드 높이</div>

<div id="left">좌측 메뉴</div>

<div id="content">내용</div>

</body>


위 코드에서 $(window).height()와 $("#top").height()는 각각 아래 그림과 같은 높이 값을 구해준다.



따라서, windowHeight - topHeight 는 전체 윈도우 높이에서 헤드 높이를 제외한 나머지 높이가 되며, 아래 코드를 실행하면 "#content" 영역은 나머지 만큼의 높이를 차지하게 된다.


$('#content').css({'height':(windowHeight-topHeight)+'px'});


resizeContent() 함수는 문서가 준비되거나 윈도우 크기가 변경될 때 마다 실행되므로, 최초 문서 로딩이 완료되면 내용 부분이 나머지 높이를 차지하게 된다. 실제로 실행해보면 아래 그림과 같이 내용 부분이 나머지 높이를 차지하는 것을 확인할 수 있다.





Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 최소값 2013.08.05 09:34 신고  댓글주소  수정/삭제  댓글쓰기

    혹시 이소스에 Min 값을 넣을 수 있을까요
    실질적으로 컨텐츠에 800px이상을 넣었을때도 창화면에 맞쳐져 버려서
    스크롤이 안생겨버리네요 ㅠ

트위터, 페이스북, 유스트림 등은 페이지 이동 없이 한 페이지에서 사용자의 요청을 처리하는 1 페이지로 구성된 웹 어플리케이션이다. 크롬이나 파이어폭스에서 이들 서비스를 사용해보면  웹 브라우저의 주소는 변경되는 것 처럼 보이더라도 실제로는 페이지 이동 없이 Ajax 요청을 이용해서 웹 페이지의 특정 부분만 변경하는 방식으로 구현되어 있다. 인터넷 익스플로러로 실행할 경우 해시(#) 기호를 이용해서 전체 화면 변경 없이 필요한 부분만 교체하도록 구현하고 있다.


최근에 개인적으로 개발중인 웹 어플리케이션도  일부 화면들을 1 페이지 웹 어플리케이션으로 구현하고 있는데, HTML5의 History API 지원 브라우저와 비지원 브라우저 모두에서 동일한 코드를 사용하기 위해 History.js 사용하였다. History.js는 History/State API와 유사한 API를 제공하고 있으며, History/State API를 지원하는 브라우저(최신 버전의 크롬이나 파폭)에서는 이 API를 사용하며 미지원 브라우저(인터넷 익스플로러)에서는 해시(#)를 사용해서 기능을 구현하고 있다. 따라서, History.js의 사용자는 브라우저의 기능 지원 여부에 상관없이 동일한 코드로 1 페이지 웹 어플리케이션을 구현할 수 있다.


History/State API나 History.js 자체에 대한 설명은 https://github.com/browserstate/history.js 사이트와 HTML5 관련 서적/글을 참고하기 바라며, 본 글에서는 실제로 History.js를 이용해서 1 페이지 웹 어플리케이션을 구현하는데 사용하는 코드를 정리하였다.


1페이지 웹 어플리케이션의 구현 방식


1 페이지 웹 어플리케이션은 일반적으로 다음과 같은 방식으로 구현된다. (아래 표는 시간 순서대로 정리한 것이다. 그림으로 그리고 싶었지만, 게으름에 표로 대신한다.)


 순서

클라이언트 (웹브라우저) 

웹 서버 

 1최초에 웹 브라우저에서 주소를 입력
-> 웹 서버에 요청 전송
 
 2 

웹 서버의 단일 프로그램이 요청 받음

-> 1 페이지 웹 어플리케이션을 위한 HTML 응답 제공

 3

 응답 화면 출력

 
 4

<a> 태그 등의 링크 클릭/또는 웹 브라우저 주소 변경

-> 상태 변경 이벤트 캐치

-> 새로운 주소로 이동인 경우, 이벤트 전파 중지

-> Ajax로 웹 서버에 이동할 주소에 해당하는 데이터 요청

 
 5 

Ajax 요청과 매핑되는 프로그램이 요청 받음

-> 화면 구성에 필요한 데이터 응답

 6

 Ajax 응답 도착

-> Ajax 응답 데이터를 이용해서 화면 갱신

 


위 흐름을 보면 웹 브라우저의 조소가 변경될 때 다음의 두 가지 다른 상황이 있음을 알 수 있다.

  • 사이트에 최초로 들어오는 모든 요청은 서버의 단일 프로그램(스프링의 경우 컨트롤러)이 처리한다.
  • 이후 동일 사이트 내에서의 주소 변경(<a> 태그 클릭이나 브라우저 주소 변경)은 Ajax 요청으로 처리된다.
사이트에 대한 최초 진입은 URL에 상관없이 단일 컨트롤러가 처리한다. 이때 단일 컨트롤러는 1 페이지 웹 어플리케이션 HTML 응답을 제공한다. 이후, 뒤에서 살펴보겠지만, 페이지 이동 페이지 이동 요청이 발생할 때 마다 Ajax를 이용해서 서버에서 새로운 내용을 가져와 화면에 보여주며, 실제 페이지 이동은 발생하지 않는다. (페이지 이동 없이 한 개의 HTML 페이지에서 모든 기능이 제공되기 때문에, 1 페이지 웹 어플리케이션이라고 표현한다.)

위 방식을 처리하려면 요청 주소가 다음과 같이 두 가지로 구분된다.
  • 서버의 단일 프로그램으로 들어가는 요청 주소
  • Ajax 요청 주소
이 두 가지를 다른 방식으로 처리하도록 구성해주면 된다. 예를 들어, 단일 프로그램으로 들어가는 요청은 "/app/"으로 시작하고, ajax 요청 주소는 "/rest/"로 시작하는 식으로 규칙을 정하면 좋다.

History.js를 이용한 1 페이지 웹 어플리케이션 구현


이제 위 과정을 History.js가 제공하는 기능을 이용해서 실제로 구현해 보자. 먼저 최초 진입을 처리할 서버 프로그램이 필요하다. 이 서버 프로그램은 웹 브라우저에 1 페이지 웹 어플리케이션을 실행해주는 HTML을 제공하는 역할을 맡는다. 예를 들면, 다음과 같은 스프링 컨트롤러가 될 수 있다.


@Controller

public class AppController {


    @RequestMapping("/app/**")

    public String app() {

        return "app/app";

    }

}


이 AppController의 결과로 생성되는 뷰는 다음과 같이 1 페이지 웹 어플리케이션을 구동해주는 HTML 코드를 응답으로 제공하면 된다. 참고로, 아래 코드는 HTML5와 HTML4 브라우저 모두를 지원하고 jQuery를 지원하는 history.js를 사용하였다.


<%@ page contentType="text/html; charset=UTF-8" %>

<!DOCTYPE HTML >

<html>

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <title>홈</title>

    <script src="/js/jquery/jquery-1.9.1.min.js"></script>

    <script src="/js/history.js/bundled/html4+html5/jquery.history.js"></script>

    <script type="text/javascript">


    $(document).ready(function(){

        (function(window){

            var History = window.History;

            if ( !History.enabled ) {

                return false;

            }

            History.Adapter.bind(window,'statechange',function(){

                var State = History.getState();

                load(History.getState().url+".data", "#contentArea");

            });

            

        })(window);


        load(History.getState().url+".data", "#contentArea");

    }

    

    function load(url, target) {

        $.ajax({

            url: url,

            success: function(data) {

                $(target).html(data);

                $('a.state').click(function(evt) {

                    evt.preventDefault();

                    History.pushState(null, null, $(this).attr('href'));

                });

            }

        });

    }

    </script>

</head>

<body>

    <div id="contentArea"></div>

</body>

</html>


위 코드는 크게 두 부분을 구성된다.

  • 문서 로딩이 완료될 때 History API를 이용해서, 윈도우 상태 변경시 Ajax를 호출하는 부분
  • Ajax 호출해서 결과를 화면에 보여주는 부분

먼저 문서 로딩이 완료되면 History API를 이용해서 윈도우에 상태변경 이벤트 핸들러를 등록한다.


History.Adapter.bind(window,'statechange',function(){

var State = History.getState();

load(History.getState().url+".data", "#contentArea");

});


History.getState() 메서드는 변경된 최종 상태 값을 구하는데, 이 상태의 url 속성을 변경될 주소 값을 의미한다. 따라서, 위 코드는 window에서 statechage 이벤트가 발생하면, 변경될 주소를 구한 뒤 그 주소뒤에 ".data"를 load() 메서드를 호출한다. 예를 들어, 변경할 주소가 "/deal/detail" 이라면, load() 메서드에서 전달되는 값은 "/deal/detail.data"이다.


위 코드에서 statechange 이벤트는 History.js가 제공하는 API를 이용해서 상태를 변경했을 때 발생한다. 실제로 이 이벤트와 관련된 처리를 하는 코드는 load() 메서드에 있다.


function load(url, target) {

    $.ajax({

        url: url,

        success: function(data) {

            $(target).html(data);

            $('a.state').click(function(evt) {

                evt.preventDefault();

                History.pushState(null, null, $(this).attr('href'));

            });

        }

    });

}


load() 메서드는 파라미터로 전달받은 url에 ajax 요청을 날리고, 그 결과를 target의 HTML로 설정한다. 즉, target이 가리키는 HTML 태그의 내용을 Ajax 응답으로 교체한다. 그리고, css 클래스가 state인 <a> 태그에 클릭 이벤트를 적용한다. 이 이벤트 핸들러는 이벤트 전파를 중단하고, History.pushState()를 이용해서 a 태그의 href 속성 값을 새로운 상태로 넣어준다.


History.pushState()를 실행하면 statechage 이벤트가 발생하게 된다. 따라서, css 클래스가 state인 <a> 태그를 클릭하면, 앞서 작성한 windowstate 이벤트 핸들러가 실행되고, 따라서 <a> 태그의 href 값을 이용해서 load() 함수가 다시 호출된다.


또한, ready()에 전달한 함수를 보면 마지막에 load(History.getState().url+".data", "#contentArea"); 코드를 실행하는데, 이는 현재 웹 브라우저의 URL + ".data"를 이용해서 load() 메서드를 실행하게 된다. 따라서, 최초에 웹 브라우저로 사이트에 접속하면 해당 URL 뒤에 .data를 붙인 Ajax 요청을 한 뒤에 그 결과를 contentArea 영역에 출력하게 된다. 


전체 흐름 정리


위 내용을 실행 순서대로 정리해보면 다음과 같다.


내용 

관련 코드 또는 함수

최초에 웹 페이지가 로딩되어 ready()에 전달한 함수가 실행된다.

최초 주소를 "/home" 이라고 가정하자.

 

window.statechage 이벤트에 핸들러를 등록한다.

ready 관련 코드:

History.Adapter.bind(window,'statechange', ... );

load("/home.data", "#contentArea") 메서드를 실행한다.

ready 관련 코드의 끝

ajax를 이용해서 "/home.data"를 요청한다.

load() 함수 

ajax 응답이 도착하면 #contentArea에 응답 결과를 보여준다.

응답 결과 중 다음 코드가 있다고 가정하자.


<a href="/list" class="state">list</a>

load() 함수 내부:

$(tager).html(data);

css 클래스가 state인 a 태그에 클릭 이벤트 핸들러를 등록한다.

load() 함수 내부:
$('a.state').click(function(evt) { ... });

사용자가 페이지 내용 확인

 

사용자가 /list인 a 태그를 클릭한다.

 

앞서 등록한 a 태그 클릭 이벤트 핸들러가 실행된다.
- 이벤트 전파를 취소해서 웹 브라우저가 페이지 이동을 하지 않도록 한다.

- History.pushState를 이용해서 a 태그의 링크 값('/list")을 추가한다.

- History.pushState() 메서드가 실행되면, statechange 이벤트가 발생한다.

load() 함수 내부:
evt.preventDefault();
History.pushState(null, null, $(this).attr('href'));

statechange 이벤트 핸들러가 실행된다.

상태로부터 url 값을 읽어와 load()를 실행한다. 앞서 발생한 url 값이 "/list" 였으므로, 다음의 load() 코드가 실행된다.


load("/list.data", "#contentArea");

History.Adapter.bind(window,'statechange',
    function(){
        var State = History.getState();
        load(History.getState().url+".data",

             "#contentArea");

    }

);

앞과 동일한 과정을 거쳐, contentArea 영역에 "/list.data"의 ajax 응답 결과가 표시된다.

 


실제 1페이지 웹 어플리케이션을 만들려면 이 보다 더 복잡한 과정을 거치겠지만, 기본적인 골격은 앞서 살펴본 것과 같다. (예를 들어, 경우에 따라 일부 영역만 변경하거나 전체를 변경하거나 하는 식의 차이가 있거나, statechange 이벤트를 처리하는 코드가 조건에 따라 다른 기능을 실행하도록 바뀔 수 있을 것이다.)


1페이지 웹 어플리케이션의 장점


1페이지 웹 어플리케이션의 장점은 웹 브라우저가 페이지 이동을 하지 않기 때문에, 웹 페이지와 관련된 연결을 유지할 수 있다는 데에 있다. 예를 들어, 유스트림의 경우 현재 시청중인 방송 시청을 계속하면서 다른 컨텐츠 목록을 탐색할 수 있으며, 페이스북의 경우 클릭을 통해 화면 이동을 하면서 동시에 끊김없이 채팅을 진행할 수 있다.


참고 자료

  • History.js : https://github.com/browserstate/history.js


Posted by 최범균 madvirus

댓글을 달아 주세요

Ajax로 읽어온 JSON 데이터로부터 HTML을 생성해주어야 하는 경우에 사용하기 좋은 문자열 템플릿 처리 라이브러리를 찾다가, jQuery 플러그인인 nano를 발견했다. jQuery nano 플러그인은 매우 간단한 라이브러리로서 아래 사이트에서 다운로드 받을 수 있다.

  • https://github.com/trix/nano

jQuery 1.9.1을 사용하고 있는데, 동작하지 않아 다음과 같이 코드를 일부 수정하였다. (빨간색으로 표시한 부분 삭제 처리, 다른 jQuery 버전에서는 어떻게 동작하는지 확인해보진 않았다.)


/* Nano Templates (Tomasz Mazur, Jacek Becela) */


(function($) {

    var _regex = /\{([\w\.]*)\}/g;

    $.fn.nano = function(template, data) {

        return template.replace(_regex, function (str, key) {

            var keys = key.split('.'), value = data[keys.shift()];

            $.each(keys, function() { value = value[this]; });

            return (value === null || value === undefined) ? '' : value;

        });

    };

}(jQuery));


사용방법은 간단한다. 다음과 같이 jquery와 함께 jquery.nano.js를 <script>로 읽어온 뒤에 $.nano() 메서드를 사용해서 변환 처리를 하면 된다.


<script src="/js/jquery/jquery-1.9.1.min.js"></script>

<script src="/js/jquery/jquery.nano.js"></script>

<script>

var data = {

    content: {

        title: "제목",

        imageUrl: "/pds/1.png"

    },

    desc: "설명"

}

var html = $.nano("<div>{content.title}</div><img src='{content.imageUrl}' alt='{desc}' />", data);

</script>


위 코드에서 nano() 함수의 첫 번째 파라미터는 문자열 템플릿이며, 두 번째 인자는 템플릿에서 사용될 값을 갖는 JSON 객체이다. 문자열에 포함된 {변수명}은 JSON 객체의 해당하는 값으로 치환된다. 예를 들어, 위 코드에서 {content.title}은 data.content.title의 값으로 치환되며, {desc}는 data.desc로 치환된다. 따라서, 위 코드에서 $.nano() 함수의 실행 결과로 생성되는 문자열 html은 "<div>제목</div><img src='/pds/1.png' alt='설명' />"이 된다.


사용법이 매우 간단하면서도 Ajax로 읽어온 JSON 객체를 그대로 사용할 수 있다는 점에서 유용하다.

Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 간세 2013.03.28 09:54 신고  댓글주소  수정/삭제  댓글쓰기

    오~ 굉장히 유용하겠네요.
    $('<div/>').attr(...).html(..).appendTo(..)
    이런코드가 확 줄겠네요..
    좋은 정보 감사합니다. 꾸벅.

Ajax가 제공하는 편리한 UI 덕에, 점점 더 많은 기능들에 Ajax가 적용되고 있는 추세이다. (예를 들어, 다음 카페의 관리 메뉴에서 메뉴 관리나 회원 관리 등은 Ajax에 기반해서 구현되었다.) 이에 따라 자바 스크립트 코드의 규모도 커지고 하나의 페이지를 구현하는 데 필요한 자바 스크립트 모듈 개수도 증가하고 있다.

문제는 로딩해야 할 자바 스크립트 파일 개수가 점차 늘어나면서 웹 페이지가 로딩될 때 함께 로딩되는 파일 개수 및 크기가 증가한다는 점이다. 자바 스크립트 파일을 다운로드 하는 시간만큼 웹 브라우저가 초기 페이지를 실행하는 시간은 지연되고, 또한 전혀 사용되지 않을 수도 있는 자바 스크립트 코드인데도 불필요하게 모든 자바 스크립트 파일을 로딩하는 문제도 있다.

jsDynamicLoader 모듈

그래서 필요할 때 자바 스크립트 파일을 동적으로 로딩해서 관련 모듈을 실행하는 방법을 찾아봤는데, 코드를 정리해보면 다음과 같다.

코드: jsDynamicLoader.js

function loadJavascript(url, callback, charset) {
    var head= document.getElementsByTagName('head')[0];
    var script= document.createElement('script');
    script.type= 'text/javascript';
    if (charset != null) {
        script.charset = "euc-kr";
    }
    var loaded = false;
    script.onreadystatechange= function () {
        if (this.readyState == 'loaded' || this.readyState == 'complete') {
            if (loaded) {
                return;
            }
            loaded = true;
            callback();
        }
    }
    script.onload = function () {
        callback();
    }
    script.src = url;
    head.appendChild(script);
}

위 코드의 실행 순서는 다음과 같다.

  1. <script> 노드를 생성하고, type 속성과 charset 속성을 설정한다.
  2. 웹 브라우저가 스크립트 파일의 로딩을 완료했을 때, callback 함수를 호출하도록 설정한다.
    1. IE인 경우, script.onreadystatechange를 이용해서 로딩 완료 여부를 확인한다.
    2. Firefox, Chrome 등, script.onload를 이용해서 로딩 완료 여부를 확인한다.
  3. script.src 속성에 로딩할 자바 스크립트 파일을 설정한다.
  4. <script> 노드를 <head> 노드에 자식으로 추가한다.

IE를 처리하기 위해서 onreadystatechange 함수를 보면, readyState가 'loaded'인 경우와 'complete'인 경우를 모두 처리해주고 있는데, 그 이유는 IE가 로딩할 스크립트 파일을 서버로부터 변경 여부를 확인하지 않고 캐시로부터 읽어오는 지의 여부에 따라서 readyState의 값이 다르기 때문이다.

  • IE가 서버에서 파일을 읽어오는 경우 readyState의 값은 'loaded'가 되고, 'complete'는 되지 않는다.
  • IE가 캐시에서 바로 파일을 읽어올 경우 readyState의 값은 'loaded'가 되지 않고, 'complete'가 된다.

이런 이유로 위 코드에서는 readyState가 'loaded'인 경우와 'complete'인 경우를 알맞게 처리할 수 있도록 하였다.

모듈 사용법

jsDynamicLoader.js를 사용하는 방법은 아래와 같이 간단하다.

코드: loadingtest2.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=euc-kr" />
<meta>
<title>Test</title>
<script type="text/javascript" src="jsDynamicLoader.js"></script>
<script type="text/javascript">
function loadingModule1AndRun() {
    loadJavascript("module1.js", runModule1, "euc-kr");
}

function runModule1() {
    sayHello();
}
</script>
</head>
<body>
<input type="button" value="sayHello() 실행" onclick="sayHello()" />
<input type="button" value="모듈1 로딩 및 실행" onclick="loadingModule1AndRun()" />
</body>

위 코드에서 sayHello() 함수는 module1.js에 정의되어 있다. 따라서 module1.js를 로딩하기 전에 sayHello() 함수를 실행하면 에러가 발생하게 된다.

loadingModule1AndRun() 함수가 실행되면, 동적으로 module1.js 스크립트를 로딩한다. module1.js 스크립트가 완전히 로딩되면 콜백으로 전달한 runModule1() 함수가 실행된다. 일단 module1.js 스크립트가 로딩되면 sayHello() 함수를 실행할 수 있으므로, 콜백 함수로 실행되는 runModule1()에서 sayHello() 함수를 실행하면 정상적으로 동작하게 된다.

결론

동적으로 자바 스크립트를 로딩함으로써 얻을 수 있는 이점은 다음과 같다.

  • 초기 구동에 필요하지 않은 자바 스크립트 파일을 로딩하지 않기 때문에, 웹 페이지를 빠르게 로딩할 수 있다.
  • 자바 스크립트를 알맞은 단위로 모듈화하도록 유도한다.

주의해야 할 점은 동일한 자바 스크립트 파일을 중복해서 로딩하지 않게 주의해야 한다는 점이다. 이 부분은 한번 직접 고민해 보기 바란다. 힌트는 아래와 같다.

  • 동일한 함수명을 사용하는 방법
  • loadJavaScript 함수를 한번 래핑해서 방지하는 방법

관련 링크:

Posted by 최범균 madvirus

댓글을 달아 주세요

  1. Shrek 2008.12.13 17:24 신고  댓글주소  수정/삭제  댓글쓰기

    if (this.readyState == 'loaded' || this.readyState == 'complete') {
    if (loaded) { // 이건 언제 true가 되는건지?
    return;
    }
    loaded = true;
    callback();
    }

  2. 최범균 madvirus 2008.12.14 22:52 신고  댓글주소  수정/삭제  댓글쓰기

    위코드 보시면 if (loaded)를 실행한 이유는 'loaded'를 통해서 loaded가 true가 되었는데, 또 'complete'가 발생해서 함수가 다시 실행되는 경우를 대비한 코드에요.

  3. 지나가는 행인 2013.07.11 11:30 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 보여주신 코드 참고해서 css 동적로딩 함수를 만들어봤는데 이게 맞나요?
    -------------------------------------------------------------------------
    function loadCss(url, callback, charset) {
    var head= document.getElementsByTagName('head')[0];
    var css= document.createElement('link');
    css.type= 'text/css';
    css.rel = "stylesheet";
    if (charset != null) {
    css.charset = "euc-kr";
    }
    var loaded = false;
    css.onreadystatechange= function () {
    if (this.readyState == 'loaded' || this.readyState == 'complete') {
    if (loaded) {
    return;
    }
    loaded = true;
    callback();
    }
    }
    css.onload = function () {
    callback();
    }
    css.href = url;
    head.appendChild(css);
    }
    ---------------------------------------------------------------------

    • 최범균 madvirus 2013.07.12 14:05 신고  댓글주소  수정/삭제

      제가 이 방식으로 CSS도 동적으로 읽어와 적용할 수 있는지 여부를 몰라서, 뭐라고 정확하게 말씀을 못 드리겠습니다만,
      jQuery 이용시, 아래와 같은 코드를 사용해보라는 것을 어디선가 본 것 같습니다.

      <script>
      $(document).ready( function() {
      $('').appendTo('head').attr({rel: 'stylesheet',type: 'text/css',href: 'dynamic.css'});
      });
      </script>

  4. 토토땅 2014.09.05 10:27 신고  댓글주소  수정/삭제  댓글쓰기

    js 파일을 동적으로 로딩하는 방법이 필요했는데 정말 좋은 내용 감사합니다. 잘 보고 갑니다~

XMLHttpRequest에서 한글 파라미터를 전송할 때 인코딩을 처리하는 방법에 대해서 살펴본다.

XMLHttpRequest 사용시 한글 파라미터 전송 방법

자바캔에 실린 'XMLHttpRequest를 이용한 웹 채팅 구현'이란 글에서 한글 문제를 iframe을 사용하여 해결했는데, 그 방식 말고 자바캔의 댓글 추가에서 사용한 한글 처리 방식에 대해서 설명해보도록 하겠다.

XMLHttpRequest의 한글 파라미터 문제 해결 방법에 대해서 살펴보기 전에, 웹브라우저가 파라미터 값을 전송할 때 어떻게 인코딩하는 지 살펴보도록 하자. 대부분의 한글 사이트는 다음과 같이 캐릭터셋이 "euc-kr"인 HTML 문서를 사용할 것이다.

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko" lang="ko">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=euc-kr" />
        <title>test</title>
    </head>
    <body>
    <form name="test" action="form.jsp">
    <input type="text" name="name" /><input type="submit"/>
    </form>
    </body>
    </html>

위 문서는 name 파라미터를 form.jsp로 전송하는 FORM을 갖고 있는데, name 필드에 '한글'을 입력한 뒤 submit 버튼을 누르면 다음과 같이 파라미터 값이 변환되어 전송된다.

    euc-kr 캐릭터셋에 맞춰 '한글' 을 인코딩한 값    
    http://..../form.jsp?name=%C7%D1%B1%DB

위에서 '%C7%D1%B1%DB'는 '한글'을 euc-kr 캐릭터셋에 맞춰서 인코딩한 결과이다. IE나 파이어폭스 등의 웹 브라우저는 문서의 캐릭터셋에 맞춰서 파라미터를 인코딩하여 전송하기 때문에, 만약 문서의 캐릭터셋이 utf-8 이면 '한글' 파라미터를 다음과 같이 인코딩하여 전송하게 된다.

    utf-8 캐릭터셋에 맞춰 '한글' 을 인코딩한 값    
    http://..../form.jsp?name=%ED%95%9C%EA%B8%80

문서 캐릭터셋이 euc-kr인 경우와 utf-8인 경우 전송되는 파라미터의 인코딩된 값도 다른 것을 확인할 수 있다.

이제, 다시 본론으로 돌아와서 XMLHttpRequest가 전송하는 파라미터에 대해서 살펴보자. XMLHttpRequest도 파라미터를 웹서버에 전송하기 때문에 파라미터 값을 알맞게 인코딩 해 주어야 한다. 그런데, 아쉽게도 XMLHttpRequest 자체적으로 인코딩 처리를 지원해주지는 않는다. 따라서, 자바스크립트가 지원해주는 인코딩 처리 함수를 사용해야 한다. 자바 스크립트가 제공하는 인코딩 처리 함수는 escape()와 encodeURIComponent()의 두가지가 있다. 이 두가지는 동작 방식이 다른데, 다음표는 두 함수의 실행 결과를 보여주고 있다.

두 함수의 실행 결과는 문서 캐릭터셋이 euc-kr 이거나 utf-8 인 경우 모두 동일하다.
escape('한글') %uD55C%uAE00 유니코드 값을 표현
encodeURIComponent('한글') %ED%95%9C%EA%B8%80 utf-8로 인코딩. encodeURI() 함수도 동일한 결과 출력

두 함수의 실행 결과를 보면 encodeURIComponent() 함수가 utf-8로 인코딩한 결과를 보여줌을 알 수 있다. 따라서, XMLHttpRequest로 한글 파라미터를 전송할 때에는 다음과 같은 방법을 사용하면 된다.

  • 웹브라우저에서: 자바스크립트 encodeURIComponent() 함수를 사용하여 파라미터 값을 utf-8로 인코딩하여 전송한다.
  • 서버에서: 파라미터 값을 utf-8로 디코딩하여 읽어온다.
웹브라우저의 코드를 작성하면 다음과 같을 것이다. (POST 방식으로 전송할 때에도 같은 방법으로 파라미터를 인코딩하면 된다.

    <script type="text/javascript">
    function sendData() {
        var xmlHttp = null;
        if( window.XMLHttpRequest ){
            xmlHttp = new XMLHttpRequest();
        }
        else{
            xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
        }
        
        if( xmlHttp ){
            var nameValue = document.memberForm.name.value;
            var idValue = document.memberForm.id.value;
            var param = "name="+encodeURIComponent(nameValue)+"&id="+encodeURIComponent(idValue);
            xmlHttp.open('GET', 'http://www.some.com/receive.jsp?'+param, true);
            
            xmlHttp.onReadyStateChange = function(){
                if( xmlHttp.readyState == 4 ){
                    alert(xmlHttp.responseText);
                }
            }
            xmlHttp.send(null);
        }
    }
    </script>

서버에서는 파라미터를 utf-8로 읽어오기만 하면 된다. 예를 들어, JSP를 사용한다면 다음과 같이 파라미터의 인코딩을 utf-8로 지정하면 된다.

    <%
        request.setCharacterEncoding("utf-8");
        ...
        String name = request.getParameter("name");
    %>

PHP나 ASP.net과 같은 다른 서버 프로그래밍 언어에서도 JSP와 같은 방식으로 파라미터 값을 utf-8로 디코딩하여 읽어오면 파라미터를 알맞게 처리할 수 있다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요

  1. 김장현 2009.03.20 13:04 신고  댓글주소  수정/삭제  댓글쓰기

    ㅠ_ㅠ....... 아 감사합니다... ㅠㅠ 한글 문제는 언제나..ㅠㅠ 골치였는데..ㅠㅠ

    감사합니다+ㅁ+

틀 자바스크립트를 이용하여 HTML 폼 검증 코드를 쉽고 빠르게 작성하는 방법을 살펴본다.

틀 자바스크립트를 이용한 HTML 폼 검증 코드 작성

HTML 폼에 원하는 값을 입력하지 않을 경우 입력 오류를 경고창(alert)으로 알려주는 것은 이제 기본이 되었다. 예를 들어, 아래와 같은 HTML 폼 코드가 있다고 하자.

    <form name="f" ...>
        아이디: <input type="text" name="id">
        암호: <input type="password" name="password">
        <input type="submit" value="로그인">
    </form>

위 코드는 "id"와 "passowrd" 입력 요소를 갖는데, 이 둘이 모두 필수 입력요소라고 할 경우 이 폼을 검사하는 자바 스크립트 코드는 아래와 비슷한 형태를 취하게 될 것이다.

    <script type="text/JavaScript">
    function checkForm() {
        form = document.f;
        if (form.id.value == '') {
            alert("아이디를 입력하세요");
            form.id.focus();
            return;
        }
        if (form.password.value == '') {
            alert("암호를 입력하세요");
            form.password.focus();
            return;
        }
        form.submit();
    }
    </script>

검사해야 할 요소가 두개인 경우에는 위와 같이 비교적 간단(?)하게 폼 검증 자바 스크립트 코드를 작성할 수 있다. 하지만, 아이디에 영문자와 숫자만 입력할 수 있고, 암호의 길이는 3~15 바이트 이내여야 한다면 위 코드는 좀더 복잡해질 것이다. 입력해야 하는 요소가 상대적으로 더 늘어난다면 위 코드는 더욱 복잡해질 것이다.

이러한 폼 검증 코드의 복잡함을 없애기 위해서 필자는 틀 자바스크립트(TLEJavaScript)를 만들게 되었으며, 쉽고 빠르게 폼 검증 코드를 작성할 수 있도록 하는 것이 틀 자바스크립트의 목적이다.

틀 자바스크립트를 사용하기 위한 준비

틀 자바스크립트 라이브러리는 틀 프로젝트 사이트에서 다운로드 받을 수 있으며, 이 글을 쓰는 시점에서 1.0.0 버전을 출시한 상태이다. TLEJavaScript-1.0.0.zip 파일을 다운로드 받은 뒤 압축을 풀면 아래와 같이 두 파일이 존재하는 것을 확인할 수 있다.

  • TLE.js - 틀 자바스크립트 라이브러리. 폼 검증을 위한 자바스크립트 코드가 저장
  • test/test.html - 틀 자바스크립트 라이브러리의 사용 예제 코드
test.html을 실행하면 다음과 같은 화면을 볼 수 있다.


위 화면에서 [체크] 버튼을 눌러보면 값이 검증되는 결과를 확인할 수 있을 것이다. test.html은 TLE.js를 사용해서 폼을 검증한다. test.html코드를 보면 기존의 자바 스크립트 코드와 많이 다른 것을 확인할 수 있을 것이다. test.html에서 폼 검증과 관련된 코드 중 일부는 아래와 같다.

    <script language="JavaScript">
    var checker = new FormChecker(document.f);
    
    checker.checkRequired('id', '아이디를 입력하세요', true);
    checker.checkAlphaNum('id', '아이디는 알파벳과 숫자만 입력하세요', true);
    checker.checkMinLength('id', 2, '아이디는 최소 두글자 입력하세요', true);
    ...
    
    </script>
    
    function checkForm() {
        if (checker.validate()) {
            alert("값 검증 통과");
        }
    }
    

위 코드를 보면 이 글의 서두에서 살펴봤던 if-else가 사용된 자바스크립트 코드에 비해 훨씬 알아보기 쉬운 것을 알 수 있다. 폼 검증의 핵심은 틀 자바스크립트가 제공하는 FormChecker 객체인데, 이 객체의 사용방법에 대해서 살펴보도록 하자.

틀 자바스크립트의 FormChecker 객체를 사용한 폼 검증

틀 자바스크립트는 폼 검증을 위한 FormChecker 객체를 제공하는데, 이 객체를 사용하는 방법을 실제 코드를 통해서 살펴보도록 하자.

    <form name="f" onSubmit="return checkForm()">
        아이디: <input type="text" name="id">
        암호: <input type="password" name="password">
        <input type="sumit" value="로그인">
    </form>
    
    <script type="language/JavaScript">
    // 1. FormChecker 객체를 생성할 때 검증할 폼 객체(document.f)를 전달한다.
    var checker = new FormChecker(document.f);
    
    // 2. 검사 조건을 추가한다.
    checker.checkRequired('id', '아이디 입력하세요', true);
    checker.checkMinLength('id', 3, '아이디는 3글자 이상 입력하세요', true);
    checker.checkRequired('password', '암호를 입력하세요', true);
    
    function checkForm() {
        // 3. 폼 값을 전송하기 전에 폼을 검증한다.
        return checker.validate();
    }
    
    </script>

FormChecker는 검사 조건을 추가할 수 있는 함수를 제공하는데, 이들 함수는 checkRequired 와 같이 check로 시작한다. checkRequired() 함수의 경우는 입력 요소에 값을 입력했는지의 여부를 검사한다. 예를 들어, 위 코드에서는 'id' 요소에 값을 입력했는 지 검사하고, id 요소에 값을 입력하지 않았을 경우 alert() 함수를 사용해서 '아이디 입력하세요'라는 경고 메시지를 출력한다.

폼 값의 검사는 FormChecker의 validate() 함수를 실행할 때 수행된다. checkRequired 등의 함수를 사용해서 설정한 모든 검증 조건을 통과할 validate() 함수는 true를 리턴하고, 그렇지 않을 경우 false를 리턴한다. validate() 함수는 false를 리턴하기 전에 검증을 통과하지 못한 조건과 관련된 에러 메시지를 출력한다. 예를 들어, 위 검증 코드에서는 id를 최소 3글자 이상 입력하도록 검증조건을 추가했는데, 만약 id의 값을 두 글자만 입력했다면 '아이디는 3글자 이상 입력하세요'라는 오류 메시지를 경고창으로 보여주게 된다.

검증 조건을 추가할 때 사용되는 FormChecker의 함수는 다음과 같다. 아래에서 fieldName은 값을 검증할 입력 요소의 이름을 의미한다. errorMesage는 값 검증을 통과 못했을 때 alert()으로 보여줄 에러 메시지이며, focus가 true이면 값 검증에 실패한 요소에 포커스가 맞춰진다.

  • checkRequired(fieldName, errorMessage, focus)
    값을 입력했는지의 여부를 검사한다.
  • checkMaxLength(fieldName, maxLength, errorMessage, focus)
    값의 길이가 maxLength보다 작거나 같은지 검사한다.
  • checkMaxLengthByte(fieldName, maxLength, errorMessage, focus)
    값의 바이트 길이가 maxLength보다 작거나 같은지 검사한다.
  • checkMinLength(fieldName, minLength, errorMessage, focus)
    값의 길이가 minLength보다 크거나 같은지 검사한다.
  • checkMinLengthByte(fieldName, minLength, errorMessage, focus)
    값의 바이트 길이가 minLength보다 크거나 같은지 검사한다.
  • checkAlphaNum(fieldName, errorMessage, focus)
    값이 알파벳과 0~9사이의 숫자만 포함하는 지 검사한다.
  • checkOnlyNumber(fieldName, errorMessage, focus)
    값이 0~9 사이의 문자만 포함하는 지 검사한다.
  • checkDecimal(fieldName, errorMessage, focus)
    값이 숫자인지 검사한다.
  • checkEmail(fieldName, errorMessage, focus)
    값이 올바른 이메일 주소인지 검사한다.
  • checkSelected(fieldName, firstIdx, errorMessage, focus)
    <select>에서 선택한 옵션의 인덱스가 firstIdx보다 크거나 같은 지 검사한다.
  • checkAtLeastOneChecked(fieldName, errorMessage, focus)
    checkbox나 radio 입력 요소가 최소한 1개 이상 선택됐는지 검사한다.
  • checkRegex(fieldName, regex, errorMessage, focus)
    값이 정규표현식에 해당하는 지 검사한다.
위 함수에서 주의할 점은 checkRequire를 제외한 나머지 함수들은 값이 ''인 경우 검증에 통과한다는 것이다. 이렇게 만든 이유는 필수 요소가 아닌 경우에는 값을 입력하지 않아도 검증에 통과되어야 하기 때문이다. 필수요소라면 먼저 checkRequired() 함수를 사용해서 검증 조건을 추가해주어야 한다. 예를 들어, email 입력 요소가 필수이고 이메일 주소를 입력해야 한다면 다음과 같이 검증 코드를 작성해야 한다.

    var FormChecker checker = new FormChecker(document.memberForm);
    checker.checkRequired('email', '이메일 주소를 입력하세요', true);
    checker.checkEmail('email', '올바른 이메일 주소를 입력하세요', true);

결론

본 글에서는 틀 자바스크립트가 제공하는 FormChecker 를 사용해서 폼 값을 검증하는 방법에 대해서 살펴보았다. FormChecker를 통해서 쉽고 간단하게 그리고 더불어 가독성이 높은 폼 검증 코드를 작성할 수 있는 것을 알게 되었다. 앞으로 틀 자바스크립트는 좀더 편리한 기능이 추가될 예정인데, 만약 틀 자바스크립트의 개발에 참여하고 싶다면 틀 프로젝트 사이트(http://kldp.net/projects/tle)나 madvirus@madvirus.net을 통해서 알려주기 바란다.

관련링크:
Posted by 최범균 madvirus

댓글을 달아 주세요