PhantomJS로 GRID&CHART를 PDF 출력하기 (4편 : Event Listners)


안녕하세요. CX사업부 MD 태태입니다.

오래간만에 PhantomJS 를 이용하여 PDF를 생성하는 글을 포스팅합니다.

지난 3번의 포스팅을 통하여 기본적인 PhantomJS를 통한 PDF출력에 대해 살펴보았는데, 이번 글에서는 PhantomJS에서 제공하는 다양한 Event listener에대해 살펴보겠습니다.

웹화면을 PDF형식으로 출력하다보면 예상치않은 웹상의 오류에 대해 대처해야하거나, JavaScript로직의 실행시점을 알아야 하는등 다양한 문제가 발생할 수 있습니다. 이럴 때 사용할 수 있도록 PhantomJS는 몇가지 Event listener를 제공해주고 있는데, 이번 포스팅에서는 webPonent 제품을 개발할 때 유용하게 사용되었던 것들을 중심으로 소개하고 설명해드리도록 하겠습니다.


  • onError
PhantomJS로 웹페이지에 접속을 처음 시도할 때 가장 난감한 경우는 웹페이지 자체에서 나는 에러때문에 제대로 스크립트가 실행되지 않을 경우일 것입니다. 이 리스너는 이럴 경우 사용하는 리스너입니다. 글로 길게 설명하기 보다는 실제 예제코드를 통해서 설명해 드리겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var webPage = require('webpage');
var page = webPage.create();
 
page.onError = function(msg, trace) {
    var msgStack = ['ERROR: ' + msg];
 
    if (trace && trace.length) {
        msgStack.push('TRACE:');
        trace.forEach(function(t) {
            msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
        });
    }
 
    console.error(msgStack.join('\n'));
};
 
page.open('http://test.com/'function(status) {
    console.log('Status: ' + status);
    phantom.exit();
});
cs
간단한 PhantomJS script입니다. http://test.com(이 URL은 코드 이해를 돕기 위한 가상의 URL입니다.)로 접속해서 상태만 로그출력한 후 종료하도록 작성되어 있습니다. 그리고 page.onError 객체에는 메시지를 받아서 콘솔에 출력하도록 작성했습니다.

http://test.com/의 내용은 아래 코드와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
    <title></title>
</head>
<body>
<h1>테스트 페이지입니다.</h1>
<script>
    console.log(value);
</script>
</body>
</html>
cs
설명이 필요하지 않을정도로 간단한 HTML코드입니다. 단, 스크립트에서 콘솔로 value라는 변수를 출력하는데 코드 어디에도 value변수를 선언하지 않았지요. 스크립트 오류가 발생할 것입니다.

이제 결과를 확인해 보겠습니다.

콘솔창에 JavaScript에러 메세지가 출력된 것을 확인할 수 있습니다. 이렇게 onError 리스너는 스크립트 에러와 같은 페이지상의 에러가 발생했을 때 실행되는 이벤트리스너입니다.

  • onConsoleMessage
JavaScript의 console.log() 함수를 이용해서 콘솔을 출력 할 수 있다는 것을 알고계실 것입니다. onConsoleMessage는 그런 콘솔메시지가 출력될 때 호출되는 리스너입니다. 쉬운 개념이니 바로 간단한 예시를 보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var webPage = require('webpage');
var page = webPage.create();
 
page.onError = function(msg, trace) {
    var msgStack = ['ERROR: ' + msg];
 
    if (trace && trace.length) {
        msgStack.push('TRACE:');
        trace.forEach(function(t) {
            msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
        });
    }
 
    console.error(msgStack.join('\n'));
};
 
page.onConsoleMessage = function(msg) {
  console.log('CONSOLE: ' + msg);
};
 
page.open('http://test.com/'function(status) {
    console.log('Status: ' + status);
    phantom.exit();
});

cs


위에서 사용된 script에 onConsoleMessage에 대한 정의가 추가 되었습니다. 또한 HTML파일을 살펴보면
1
2
3
4
5
6
7
8
9
10
11
12
<html>
<head>
    <title></title>
</head>
<body>
<h1>테스트 페이지입니다.</h1>
<script>
    var value = "This is PhantomJS test page.";
    console.log(value);
</script>
</body>
</html>
cs
위에선 없었떤 value변수에 대한 선언이 추가되었습니다. 이제는 value 변수가 선언되지 않았다는 에러 대신에 value함수에 할당된 텍스트메시지가 로그에 출력되겠지요. 그럼 결과를 확인하겠습니다.

'This is PhantomJS test page.'라는 메시지가 출력되었지요. 위 페이지코드의 스크립트 실행결과와 완전히 동일한 결과입니다. 이 리스너또한 페이지의 로그를 확인하고 싶을 때 유용하게 사용되는 리스너입니다.


  • onInitialized
웹 페이지가 생성되었지만 아직 페이지 전체가 load되지 않았을 때 발생되는 이벤트 리스너입니다. 아래에서 설명되는 onLoadFinished와 함께 예제를 설명하겠습니다.

  • onLoadFinished
웹 페이지가 생성된 후 모든 객체가 load된 후에 발생되는 이벤트리스너입니다. 단 이 이벤트가 발생되는 시점이 JavaScript의 모든 로직이 다 실행되었다는 의미는 아니기 때문에 구분해서 사용해야 합니다.

그러면 이제 onInitialized 리스너와 함께 코드예시를 확인해 보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var webPage = require('webpage');
var page = webPage.create();
 
page.onError = function(msg, trace) {
    var msgStack = ['ERROR: ' + msg];
 
    if (trace && trace.length) {
        msgStack.push('TRACE:');
        trace.forEach(function(t) {
            msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
        });
    }
 
    console.error(msgStack.join('\n'));
};
 
page.onConsoleMessage = function(msg) {
    console.log('CONSOLE: ' + msg);
};
 
page.onInitialized = function() {
    console.log("onInitialized");
}
 
page.onLoadFinished = function (status) {
    console.log("onLoadFinished Status : " + status);    
}
 
page.open('http://test.com/'function(status) {
    console.log('Status: ' + status);
    phantom.exit();
});
cs

onInitialized, onLoadFinished 리스너를 등록한 후 이벤트 발생 시점의 차이를 확인 해 보겠습니다.

확인결과 onInitialized 이벤트가 먼저 발생되고 (Page opened) onLoadFinished 이벤트가 발생된 후 page.open() 함수의 콜백함수가 실행되는 것을 확인 할 수 있습니다.

이 결과에 따라서 onInitialized 이벤트 리스너에는 페이지가 로딩되기 전 등록해야 하는 웹 이벤트 리스너를 등록하거나 변수, 객체를 선언하는 용도로 사용하고, onLoadFinished 리스너에 페이지가 로딩된 후의 동작을 등록해서 사용하면 시점이 어긋나지 않고 사용할 수 있을 것입니다.


  • onCallback
마지막으로 onCallback 이벤트 리스너입니다. 개인적으로 webPonent 용 PhantomJS script를 작성할 때 가장 큰 도움을 받았고, 많은 분들이 가장 빈번하게 사용하지 않을까 하는 리스너입니다. 이 리스너는 사용자가 원하는 시점에 이벤트를 발생시킬 수 있습니다. window.callPhantom() 함수를 통해서 client-side에서 이벤트를 발생시킬 수 있고, server-side에서는 다시 return값을 발생시켜서 넘겨줄 수도 있습니다. webPonent CHART나 GRID의 복잡한 JavaScript 로직이 끝나는 시점에 이 이벤트를 발생시켜서 시간손실을 최소화하여 더 신속하게 PDF문서를 생성할 수 있는 로직을 작성할 수 있었습니다.
그럼 마찬가지로 예제를 보여드리겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<html>
<head>
    <title></title>
</head>
<body>
<h1>테스트 페이지입니다.</h1>
<script>
    if (typeof window.callPhantom === 'function') {
        var status = window.callPhantom({
            message: 'Hello'
        });
 
        console.log(status);
    }
</script>
</body>
</html>
cs

먼저 onCallback 리스너 테스트를 위한 HTML 코드입니다. 스크립트상에서 window.callPhantom 객체가 function type으로 존재하면 JSON Object를 인자로 넘겨서 호출합니다. 이 때의 결과값을 status변수에 저장한 후 console에 출력하는 로직이 작성되어 있습니다.


이 페이지를 여는 PhantomJS script를 보면 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var webPage = require('webpage');
var page = webPage.create();
 
page.onCallback = function(data) {
    if (data && data.message && (data.message.toLowerCase() === 'hello')) {
        return "World!";
    } else {
        return 'Say hello please.';
    }
};
 
page.onConsoleMessage = function(msg) {
  console.log('CONSOLE: ' + msg);
};
 
page.open('http://localhost:1010/'function(status) {
    phantom.exit();    
});
cs

onCallback 이벤트 리스너를 등록하여서 data 객체의 message가 'hello'이면 'World!'를 아니면 'Say hello please.'를 반환하는 로직을 작성했습니다. 이 로직의 결과를 살펴보겠습니다.

2번째 결과는 message 요소에 'hello'대신 다른 문자열을 대입하고 실행해본 결과입니다. onConsoleMessage를 통해 출력된 콘솔메세지를 확인하실 수 있습니다. 이 리스너를 통해 조금 더 세부적으로 시점을 알 수 있습니다.


이정도가 가장 유용하게 쓰일 수 있는 이벤트 리스너입니다. 이렇듯 리스너를 사용하면 훨씬 더 세부적이고 디테일한 시점에따른 기능을 추가할 수 있습니다. 비단 PDF출력 뿐 아니라도 PhantomJS의 여러기능들을 사용할 때 활용하실 수 있었으면 좋겠습니다.






New Multi-Channel Dynamic CMS