반응형

새로운 프로젝트를 들어가면서 새로운 텍스트 에디터를 적용해보기 위해 이것저것 찾던 도중 IE에서 잘 동작이되며(이놈의 IE...) 무료 라이센스인 것들을 고르고 고르다보니 기능이 없거나 이쁘지가 않거나 여러 이슈들이 있었는데, 만족할만한 에디터를 찾아서 공유해보고자 합니다.

 

텍스트 에디터 종류들은 WYSIWYG HTML Editor 형태로 검색해보시면 더 많은 에디터를 찾아 볼 수 있습니다.

 

TinyMce License로서 특별히 추가하는 plugin을 사용하지 않는다는 전제하에 무료입니다.

https://www.tiny.cloud/blog/tinymce-free-wysiwyg-html-editor/

 

The TinyMCE WYSIWYG HTML editor is free forever

Looking for a free WYSIWYG HTML editor? The TinyMCE core editor is free to use for commercial and noncommercial purposes. Fully functional with 44 plugins and ready to customize as needed.

www.tiny.cloud

 

Tiny Editor

https://www.tiny.cloud/docs/quick-start/

 

TinyMCE | Quick start

Get an instance of TinyMCE 5 up and running using the Tiny Cloud.

www.tiny.cloud

 

해당 글을 보고 먼저 테스트용 계정을 생성해서 api key를 받고 간단하게 http서버를 열어서 소스코드 테스트를 진행해보았습니다.

 

뭐 간단한 세팅은 진짜 간단합니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>

    <script>
      tinymce.init({
        selector: '#mytextarea'
      });
    </script>

  </head>

  <body>
  <h1>TinyMCE Quick Start Guide</h1>
    <form method="post">
      <textarea id="mytextarea">Hello, World!</textarea>
    </form>
  </body>
</html>

간단하게 에디터의 모습으로 보입니다.

 

하지만 좀 더 블로그에서 사용하는 에디터나 이쁘게 커스텀을 하고 싶어서 여러 옵션을 통해 좀 더 변경을 해보았습니다.

 

 

개인적으로 변경한 Tiny Editor

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.tiny.cloud/1/각자 발급받은 api key/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>
</head>
<body>
    <h1>TinyMCE Quick Start Guide</h1>
    <form method="post">
        <textarea id="editor">Hello, World!</textarea>
        <button type="button" id="save">게시글 저장</button>
    </form>
</body>

<script>
$(function(){
    var plugins = [
        "advlist", "autolink", "lists", "link", "image", "charmap", "print", "preview", "anchor",
        "searchreplace", "visualblocks", "code", "fullscreen", "insertdatetime", "media", "table",
        "paste", "code", "help", "wordcount", "save"
    ];
    var edit_toolbar = 'formatselect fontselect fontsizeselect |'
               + ' forecolor backcolor |'
               + ' bold italic underline strikethrough |'
               + ' alignjustify alignleft aligncenter alignright |'
               + ' bullist numlist |'
               + ' table tabledelete |'
               + ' link image';

    tinymce.init({
    	language: "ko_KR", //한글판으로 변경
        selector: '#editor',
        height: 500,
        menubar: false,
        plugins: plugins,
        toolbar: edit_toolbar,
        
        /*** image upload ***/
        image_title: true,
        /* enable automatic uploads of images represented by blob or data URIs*/
        automatic_uploads: true,
        /*
            URL of our upload handler (for more details check: https://www.tiny.cloud/docs/configure/file-image-upload/#images_upload_url)
            images_upload_url: 'postAcceptor.php',
            here we add custom filepicker only to Image dialog
        */
        file_picker_types: 'image',
        /* and here's our custom image picker*/
        file_picker_callback: function (cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');

            /*
            Note: In modern browsers input[type="file"] is functional without
            even adding it to the DOM, but that might not be the case in some older
            or quirky browsers like IE, so you might want to add it to the DOM
            just in case, and visually hide it. And do not forget do remove it
            once you do not need it anymore.
            */
            input.onchange = function () {
                var file = this.files[0];

                var reader = new FileReader();
                reader.onload = function () {
                    /*
                    Note: Now we need to register the blob in TinyMCEs image blob
                    registry. In the next release this part hopefully won't be
                    necessary, as we are looking to handle it internally.
                    */
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);

                    /* call the callback and populate the Title field with the file name */
                    cb(blobInfo.blobUri(), { title: file.name });
                };
                reader.readAsDataURL(file);
            };
            input.click();
        },
        /*** image upload ***/
        
        content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
    });


    $("#save").on("click", function(){
        var content = tinymce.activeEditor.getContent();
        console.log(content);
    });
    
});
</script>
</html>

* tiny 동작 script를 적용시 저는 개인적으로 발급받은 url주소를 입력하였습니다. 자신만의 api key를 발급받은 url로 연결하시면 될 것 같습니다.

 

변경 후에는 좀 더 깔끔하게 메뉴바를 지우고 툴바들로 위치시켰고, 필요한 테이블표, 이미지, 링크등을 추가하였습니다.

텍스트 제어 관련된 색상, 폰트, 컬러 등을 순차적으로 배치해보았습니다. 또한, language 옵션에서 한글번역이 가능하여 변경하였습니다.

 

form태그 내부에 게시글 저장이라는 button태그를 추가하였는데, 클릭하면 tiny자체에서 제공하는 getContent메소드를 통해 게시글 내부의 HTML 코드를 확인 할 수 있습니다.

 

해당 내용을 서버로 전송하여 저장하는 형태로 처리하면 될 것 같습니다.

 

 

 

여러가지 옵션 변경은 아래글을 참고하여주세요.

https://www.tiny.cloud/docs/

 

TinyMCE | Documentation

Official documentation for the most advanced and widely deployed rich text editor platform.

www.tiny.cloud

 

반응형
반응형

리액트를 공부하면서 객체 불변성을 지켜야한다는 컨셉을 지키기 위해 좀 더 확실하게 개념을 잡을 필요가 있어서 공부하다가 정리를 해봅니다.

자바스크립트에서 객체나 배열을 복사할때 단순하게 대입연산자를 통해 처리하면 아래와 같이 같은 주소값을 바라보게 된다.

 

대입연산자를 통한 복사(참조 복사)

let obj1 = {a:1, b:2};
let obj2 = obj1;
obj2.a = 3;
console.log(obj1.a === obj2.a) //true 같은 주소값을 바라보고 있어서 같이 값이 바뀌어있다.
console.log(obj1.a) //3

obj1 생성하면서 발생한 주소값을 obj2도 바라보게 되어 값이 같이 바뀌게 된다.

즉, obj1과 obj2는 그냥 같은 값이다.

 

 

얕은 복사

ex1)

let obj1 = {a:1, b:2};
let obj2 = {...obj1};
console.log(obj1 === obj2) //false 서로 같은 주소값을 바라보진 않는다.
obj2.a = 3;
console.log(obj1.a === obj2.a) //false
console.log(obj1.a) //1

es6 문법 중 전개 연산자(...obj)를 통해 얕은 복사를 진행하였다.

 

결과만 봤을때는?

깊은 복사가 진행되었다. 단순 배열이나 단순 객체의 경우에는 깊은 복사가 진행된다.

각각의 객체가 서로 다른 값을 유지하기 때문이다.

하지만 아래와 같이 배열안에 객체들이 존재하는 경우에는 어떨까?

 

ex2)

let obj1 = [{a:1, b:2}, {a:5, b:25}];
let obj2 = [...obj1];
console.log(obj1 === obj2) //false 서로 같은 주소값을 바라보진 않는다.
obj2[0].a = 3;
console.log(obj1[0].a === obj2[0].a) //true
console.log(obj1[0].a) //3

배열안에 각각 객체들이 들어간 obj1을 복사하였다.

obj1, obj2의 배열만 비교하였을땐 서로 다른 주소값을 보도록 된 것을 볼 수 있지만, 첫번째 배열 인자의 a값을 확인해보면 true로 발생한다.

첫번쨰 인자의 객체주소값을 동일하게 obj1[0].a 와 obj2[0].a가 공유하고 있기때문이다.

마지막행에서도 바꾼적 없는 obj1[0].a 값이 3으로 나오는것을 볼 수 있다.

 

이처럼 내부의 객체나 배열등이 원본의 값을 참조하는 경우 얕은 복사(shallow copy)라고 지칭한다.

 

 

깊은 복사

let obj1 = [{a:1, b:2}, {a:5, b:25}];
let obj2 = [...obj1];
console.log(obj1 === obj2) //false 서로 같은 주소값을 바라보진 않는다.
obj2[0] = {...obj2[0],
        a : 3}; //전개 연산자를 통해 내부 특정 속성값까지 참조값을 변경한다.
console.log(obj1[0].a === obj2[0].a) //false 내부 객체의 참조값까지 변경되었다.
console.log(obj1[0].a) //1

깊은 복사의 예제이다.

 

다른 방식도 있지만 이번에도 전개 연산자(...)를 통해 처리하였다.

 

이부분이 포인트일 것이다.

obj2[0] = {...obj2[0],
        a : 3};

변경하고자 하는 요소의 참조값을 변경하여 내부까진 변경을 해야한다.

 

 

물론 아래처럼 다른 요소는 당연히 얕은 복사가 되었을 것이다.

console.log(obj1[0].b === obj2[0].b) //true

실제로 값이 필요한부분만 깊은 복사처리를 한것이고, 애초에 배열이나 객체 내부의 객체등을 깊은 복사를 하고자 할때는 JSON.stringify() 메소드는 또는 반복문을 통해 재처리가 필요할 것이다.

반응형
반응형

웹 페이지를 제작하면서 아주 편리하게 사용하는 alert, confirm창을 차별점을 두기 위해 디자인하고 팝업 형태로 제작의뢰가 들어오는 경우가 종종 있습니다.

 

사실 alert경우에는 동작하면 모든 javascript가 멈춘다는 특징외에는 출력메시지로 간단하게 경고 박스 띄우는 정도이기에 구현하는데 어려움은 없지만 confirm의 경우 확인, 취소버튼이 존재하며 확인이 눌린 경우 이후의 행위를 정의해야 하기에 callback 형태로 구현을 해야합니다.

 

아무래도 동작을 위해 modal의 태그와 css를 어느정도는 집어넣어야 하는 번거로움이 있지만 적용을 하고 나면 이후에 개인적인 취향으로 커스텀이 가능해집니다.

 

아래는 제가 작성해본 alert, confirm을 동작시키는 메소드와 예제입니다.(prompt도 존재하지만 개인적으로 사용하지 않아서 구성하지 않았습니다.)

 

Confirm, Alert 동작 메소드 정의하기

/**
 *  alert, confirm 대용 팝업 메소드 정의 <br/>
 *  timer : 애니메이션 동작 속도 <br/>
 *  alert : 경고창 <br/>
 *  confirm : 확인창 <br/>
 *  open : 팝업 열기 <br/>
 *  close : 팝업 닫기 <br/>
 */ 
var action_popup = {
    timer : 500,
    confirm : function(txt, callback){
        if(txt == null || txt.trim() == ""){
            console.warn("confirm message is empty.");
            return;
        }else if(callback == null || typeof callback != 'function'){
            console.warn("callback is null or not function.");
            return;
        }else{
            $(".type-confirm .btn_ok").on("click", function(){
                $(this).unbind("click");
                callback(true);
                action_popup.close(this);
            });
            this.open("type-confirm", txt);
        }
    },

    alert : function(txt){
        if(txt == null || txt.trim() == ""){
            console.warn("confirm message is empty.");
            return;
        }else{
            this.open("type-alert", txt);
        }
    },

    open : function(type, txt){
        var popup = $("."+type);
        popup.find(".menu_msg").text(txt);
        $("body").append("<div class='dimLayer'></div>");
        $(".dimLayer").css('height', $(document).height()).attr("target", type);
        popup.fadeIn(this.timer);
    },

    close : function(target){
        var modal = $(target).closest(".modal-section");
        var dimLayer;
        if(modal.hasClass("type-confirm")){
            dimLayer = $(".dimLayer[target=type-confirm]");
            $(".type-confirm .btn_ok").unbind("click");
        }else if(modal.hasClass("type-alert")){
            dimLayer = $(".dimLayer[target=type-alert]")
        }else{
            console.warn("close unknown target.")
            return;
        }
        modal.fadeOut(this.timer);
        setTimeout(function(){
            dimLayer != null ? dimLayer.remove() : "";
        }, this.timer);
    }
}

action_popup 변수에 객체 기반의 메소드화로 구현하였습니다.

timer 속성은 모달이 노출되거나 닫힐때, 자연스러운 처리를 위한 애니메이션 속도이며

confirm 메소드는 confirm효과의 모달을 동작하는 메소드로 첫번째 파라미터는 노출시킬 텍스트, 두번째 파라미터는 callback을 정의합니다.

alert 메소드는 alert 효과의 모달을 동작시켜줍니다. 파라미터는 노출시킬 텍스트만 입력합니다.

open, close 메소드는 모달을 열고 닫는 처리를 위해 정의하였습니다.

 

간단한 설명은 이정도로 하고 사용 예제 및 결과는 아래를 참고해주세요.

 

사용 예제 소스 및 결과

See the Pen Alert And Confirm Custom by myhappyman (@myhappyman) on CodePen.

 

css와 사용을 위한 display: none처리된 모달형태의 태그들을 심어두고 요청에 따라 모달을 노출하는 형태입니다.

 

 

결과

반응형
반응형

이미지 뷰어형태로 당장 올릴 이미지를 미리보기형태로 작은모습으로 보여주는 기능을 만들고 있었는데, 스마트폰에서 찍은 사진을 테스트하다 보면 자동으로 회전되어 보이는 현상이 있었습니다.

 

감자♥ 정상적으로 나옵니다.

 

 

꼬미♥ 꼬미야 왜 돌아갔니...

 

업로드 하고 페이지에 노출하는 형태라면 서버단에서 저장할 때, 처리하면 되지만 업로드 뷰어의 경우에는 스크립트단에서 해결이 필요하였습니다.

 

 

스크립트 라이브러리 중 Load-Image의 힘을 빌려 해결하였습니다.

github.com/blueimp/JavaScript-Load-Image#image-loading

 

blueimp/JavaScript-Load-Image

Load images provided as File or Blob objects or via URL. Retrieve an optionally scaled, cropped or rotated HTML img or canvas element. Use methods to parse image metadata to extract IPTC and Exif t...

github.com

 

위 github에서 라이브러리를 받을 수 있습니다.

 

EXIF 

디지털 카메라와 스마트폰등에서 이미지 등의 정보를 기록할 수 있습니다.

해당 메타정보 중에 회전 방향 옵션을 받아올 수 있는데 이 회전 방향값인 orientation을 추출하고 처리하여 강제로 고정시키는 방식입니다.

단순하게 업로드 한 데이터에서는 회전정보를 알 수가 없다보니 blob을 통해 바이너리 데이터로 변환하여 데이터를 확인하는 절차를 따른다고 합니다.

 

Load-Image

html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
    integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="./load_image/load-image.all.min.js"></script>
<body>
    <input type="file" id="upload">
    <img src="" alt="" id="preview" style="width:300px;height:300px;">
</body>

</html>

 

javascript

$(function () {
    $("#upload").on("change", function (e) {
        var files = e.target.files;
        var fileType = files[0].type;
        var limg = loadImage(files[0], function (img, data) {
            img.toBlob(function (blob) {
                var rotateFile = new File([blob], files[0].name, { type: fileType });
                var reader = new FileReader();
                reader.onload = function (e) { $("#preview").attr("src", e.target.result); }

                reader.readAsDataURL(rotateFile);
            }, fileType)
        }, { orientation: 1 });
    });
})

file 태그에 이미지가 올라가면 change이벤트에 의해 파일 정보를 받아오고 라이브러리 함수 loadImage를 통해 데이터를 blob으로 읽고 image태그에 처리를 해줍니다.

 

여기서 포인트는 2번째 파라미터 object값에 orientation값을 1로 해주면 세로 이미지로 볼 수 있었습니다.

여기서 값을 2로 처리하시면 좌우 반전되어 나오는 것을 볼 수 있습니다.

 

 

처리 동작 후 미리보기 이미지가 정상적인 회전으로 나옵니다.

반응형
반응형

사실 단순한 pdf파일을 보이게만 하는건 몇몇 태그를 활용하여 쉽게 처리가 가능합니다.

 

iframe

<iframe src="/image/test.pdf" style="width:700px;height:700px;"></iframe>

IE를 제외하고 firefox, chrome, edge등 모두 정상적으로 뷰어 동작을 하는것을 볼 수 있습니다.

IE에서는 다운로드 이벤트가 발생합니다.

 

 

 

embed

<embed src="/image/test.pdf" type="application/pdf" width=700px height=700px/>

iframe과 비슷하지만 type과 넓이 높이를 따로 지정해줘야 합니다.

마찬가지로 IE를 제외하고 전부 정상 동작하는것을 볼 수 있습니다.

 

 

 

 

PDFObject

pdfobject.com/

 

PDFObject: A JavaScript utility for embedding PDFs

width [string]. Default: "100%" Will insert the width as an inline style via the style attribute on the element. If left unspecified, PDFObject will default to 100%. Is standard CSS, supports all units, including px, %, em, and rem. Tip: It's safer to spec

pdfobject.com

PDF를 읽어서 뷰어 역할을 해주는 훌륭한 js 라이브러리입니다.

해당 브라우저에서 PDF를 읽지못한다면 특정 메시지와 함께 다운로드를 제공하고 있습니다.

 

IE에서 동작이 안되는 브라우저는 친절하게 다운로드 링크를 제공한다.

 

사용법은 생각보다 간단합니다.

위의 URL을 통해 PDFObject를 다운로드 받은 후, PDFObject를 연결줍니다.

<div id="myPdf"></div>
<script type="text/javascript" src="/js/common/component/PDFObject/pdfobject.js"></script>
PDFObject.embed("/image/guide.pdf", "#myPdf");

첫번째 파라미터는 pdf파일 위치를 두번째 파라미터는 만들어줄 타겟 대상을 입력합니다.

 

 

full 버전으로 잘 읽힙니다.

 

세번째에는 옵션값을 넣을 수 있습니다.

var option = {
		height: "400px",
		pdfOpenParams: {view: 'FitV', page: '2'}
}
PDFObject.embed(RESOURCES_PATH + "/image/test.pdf", "#myPdf", option);

높이가 400으로 줄었습니다.

높이를 400으로 줄여보았습니다.

 

 

그럼 이쯤에서 IE에서는 pdf 뷰어를 못하나요? 궁금해하실텐데 IE11에서만 동작하긴 하지만 또 방법이 있습니다.

 

 

 

 

PDF.js + PDFObject.js

먼저 알아보았던 PDFObject에 Pdf.js를 활용하면 가능합니다.

뷰어처럼 동작하도록 뼈대의 프레임 html을 사용하고 각 태그별에 맞는 이벤트 동작을 제공합니다.

 

mozilla.github.io/pdf.js/

 

PDF.js

PDF.js A general-purpose, web standards-based platform for parsing and rendering PDFs. Download Demo GitHub Project

mozilla.github.io

해당 URL에서 PDF.js를 다운로드 하실 수 있습니다.

PDFObject와 같은 위치에 다운로드한 파일들을 옮겨줍니다.

 

이번에는 div태그에 사이즈를 입력해야합니다.

<div id="myPdf" style="width:550px;height:400px;"></div>
<script type="text/javascript" src="/js/common/component/PDFObject/pdfobject.js"></script>
var option = {
	pdfOpenParams: {
		navpanes: 0,
		toolbar: 0,
		statusbar: 0,
		view: "FitV",
		page: 1
	},
	width : "550px",
	height: "400px",
	forcePDFJS: true,
	PDFJS_URL: "/js/common/component/PdfJs/web/viewer.html"
}
PDFObject.embed("/image/test.pdf", "#myPdf", option);

 

 IE11기준에서만 동작하긴 하지만 아래와 같이 익스에서도 pdf뷰어 기능을 구현할 수 있습니다.

반응형
반응형

Javascript 기술은 점점 발전하여 단순 JS만으로도 많은 기능을 사용할 수 있는데, 이번엔 화면을 캡처하는 방법을 알아보겠습니다.

 

요청사항 중 특정 요소를 캡처하여 이미지 데이터를 서버에 전송하고 저장을 원하였습니다.

화면 캡처 후 다운로드 기능은 사실 이미 많이 공유되고 있는데, 제가 겪었던 문제점에 대한 대응책이나 이런부분이 잘 보이질 않아 추가적으로 적어볼까합니다.

 

일단 html2canvas를 사용하여 동작시킬 예정이므로 해당 라이브러리를 추가합니다.

 

 

html2canvas를 통한 캡처 후 전송하기

<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>

아래는 공식사이트입니다.

https://html2canvas.hertzen.com/

 

html2canvas - Screenshots with JavaScript

Try out html2canvas Test out html2canvas by rendering the viewport from the current page. Capture

html2canvas.hertzen.com

 

이제 전송을 위해 ajax를 활용하여 전송 예제를 만들어보겠습니다.

-HTML

<section id="chart_box">
    <div class="chart">
     ...//차트에 해당하는 요소들
    </div>
</section>

 

 

-JS

$("#btn").on("click", function() {
	sreenShot($("#chart_box"));
});

function sreenShot(target) {
	if (target != null && target.length > 0) {
		var t = target[0];
		html2canvas(t).then(function(canvas) {
			var myImg = canvas.toDataURL("image/png");
			myImg = myImg.replace("data:image/png;base64,", "");

			$.ajax({
				type : "POST",
				data : {
					"imgSrc" : myImg
				},
				dataType : "text",
				url : contextPath + "/public/ImgSaveTest.do",
				success : function(data) {
					console.log(data);
				},
				error : function(a, b, c) {
					alert("error");
				}
			});
		});
	}
}

넣고 싶은 요소 $("#chart_box")를 매개변수로 집어넣고 0번째 데이터를 html2canvas 첫번째 파라미터에 처리합니다.

html2canvas는 Promise를 사용하는것으로 보이는데, 요소 처리가 완료되면 다음 이행동작을 정의합니다.

 

매개변수로 나온 canvas의 toDataURL을 통해 이미지 바이너리 데이터를 받을 수 있습니다.

거기서 앞에 붙는 데이터타입과 인코딩타입을 제거하고 서버로 전송하면 js부분은 끝입니다.

전송은 ajax를 통해 처리하였습니다.

 

 

-Server(java)

@ResponseBody
@RequestMapping(value = { "ImgSaveTest" }, method = RequestMethod.POST)
public ModelMap ImgSaveTest(@RequestParam HashMap<Object, Object> param, final HttpServletRequest request, final HttpServletResponse response) throws Exception {
	ModelMap map = new ModelMap();
	
	String binaryData = request.getParameter("imgSrc");
	FileOutputStream stream = null;
	try{
		System.out.println("binary file   "  + binaryData);
		if(binaryData == null || binaryData.trim().equals("")) {
		    throw new Exception();
		}
		binaryData = binaryData.replaceAll("data:image/png;base64,", "");
		byte[] file = Base64.decodeBase64(binaryData);
		String fileName=  UUID.randomUUID().toString();
		
		stream = new FileOutputStream("E:/test2/"+fileName+".png");
		stream.write(file);
		stream.close();
		System.out.println("캡처 저장");
	    
	}catch(Exception e){
		e.printStackTrace();
		System.out.println("에러 발생");
	}finally{
		if(stream != null) {
			stream.close();
		}
	}
	
	map.addAttribute("resultMap", "");
	return map;
}

파라미터로 넘긴 데이터를 문자열로 전달받고 바이너리 데이터를 스트림을 통해 읽어서 파일쓰기를 통해 파일을 남깁니다.

 

 

사용예시

html Page

전송버튼을 누르고 지정한 test2 디렉토리에 저장되는지 확인해보겠습니다.

 

 

정상적으로 파일이 써진 모습을 볼 수 있습니다.

 

 

개발시 주의사항

*아래 기재한 주의사항은 개인적으로 적용과 테스트를 해보면서 겪은 문제들입니다.

 

1. html2canvas에 넣는 첫번째 요소가 jQuery 선택자인 경우 id값일지라도 배열의 인덱스가 필요합니다.

ex) $("#test")[0]

 

2. 다운받으려는 특정요소는 width, height등 요소의 영역이 정확하게 지정되어 있어야합니다.

지정되지 않은 요소를 받게되면 브라우저상에서는 잘 보일지라도 전송된 데이터는 비어있거나 잘렸거나 등등 여러가지 문제가 발생할 수 있습니다.

 

3. 다운받으려는 특정요소의 위치가 position등을 통해 억지로 맞춰진 경우 정상출력이 되지 않을 수 있습니다.

 

4. div태그 내부의 img태그등 정보를 받아오는 데이터가 외부인 경우 정상적으로 출력되지 않습니다.

다른 예시 블로그 글들을 보면 http~를 통해 이미지를 가져오고 다운로드 받는다고 되어있는데, 막상 따라해보면 흰색 화면만 받아졌습니다. 서버 내부에 존재하는 이미지를 넣고 다운로드시 정상 출력되었습니다.

 

5. 인터셉터 관련 서버 코드 문제

저같은 경우는 XSS악성 스크립트 방지를 위해 서버코드로 넘어가는 모든 파라미터를 검사하는 로직을 추가해놨습니다.

html2canvas로 읽는 데이터가 얼마 안되는 경우에는 큰 문제가 없지만 복잡한 이미지를 만들게되면 그만큼 바이너리 데이터 또한 매우 커집니다. 캡처의 바이너리만 해도 문자열 길이가 약 7만5천개정도가 되었습니다. 여기서 로직을 체크하는 함수와 문자열이 겹치면서 서버로 전송이 안되는 이슈가 있어서, 해당 파라미터만 검사를 안하거나 url에 따라 예외처리등 약간의 극복(?)이 필요했습니다.

 

6. 바이너리 데이터를 넘기다보니 더욱 복잡한 이미지를 이미지화하여 전송하게되면 데이터는 더욱 커지게 되고 tomcat의 maxPortSize를 강제로 키우거나 -1처리를 통해 무제한으로 변경해야 할 수도 있습니다.

 

7. 높이가 body보다 큰 영역을 처리할 때 잘릴 수가 있습니다. 처리하는동안 body태그의 값을 제어하여 영역과 동일하게 또는 그 보다 크게 처리하였다가 원래 사이즈로 처리하는 로직이 필요합니다. - 20201126 추가

반응형
반응형

new Date객체의 getDate()메소드를 통해 마지막 일자를 구할 수 있다.

var date = new Date(년, 월, 0);
date.getDate(); //마지막 날짜


//사용예
new Date(2020, 1, 0).getDate(); //31
new Date(2020, 2, 0).getDate(); //29
new Date(2020, 3, 0).getDate(); //31
new Date(2020, 4, 0).getDate(); //30
new Date(2020, 5, 0).getDate(); //31
new Date(2020, 6, 0).getDate(); //30

 

반응형
반응형

이미 작업이 끝난 페이지가 오류가 발생하거나 분석을 위해 찾다보면 사용한 함수 -> 함수 형태로 찾을일이 많은데,

웹에서 디버깅은 관리자도구만 잘 사용하면 빠르게 디버깅이 가능하고 위치를 찾아서 분석하고 수정을 할 수 있습니다.

 

예를 들어 함수가 JSP에도 정의되어 있고 따로 빼놓은 js에도 있고, include처리된 jsp에도 있고 그 jsp를 물고 있는 js에도 정의 되어있고... 직접 짠사람이라면 금방 알고 수정하겠지만, 처음보거나 다른사람이 보면 정의된 내용을 찾는게 여간 고역이 아닙니다. 여러사람이 개발하면서 제대로 된 개발 약속이 없다면 이런식으로 중구난방으로 막 들어가 있을텐데 관리자도구를 사용하면 해당 함수가 어디 있는지 바로바로 찾을 수 있습니다.

 

관리자 도구를 통해 함수가 적용된 페이지 찾기

1. F12버튼을 누르고 관리자도구를 열어줍니다.

 

2. 관리자도구 Console창에 해당하는 함수명을 입력합니다.

function test() {
    console.log("저를 찾았나요?");
}

라는 내용의 test함수를 찾을 경우 "test"만 입력합니다.

 

함수명만 입력

 

3. 그럼 관리자도구는 해당 함수가 존재할 경우 타입이 무엇인지 정의된 내용이 무엇인지 간단하게 알려줍니다.

이때 출력된 함수 내용을 클릭해주면 해당하는 페이지로 관리자 도구가 이동을 시켜줍니다.

 

반응형