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

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의

앵귤러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

댓글을 달아 주세요