웹에서 Javascript 만으로 텍스트 파일 생성

웹에서 스크립트만으로 텍스트 파일을 생성하기 위한 코드를 정리한다. 주로 사용하는 웹브라우져가 IE와 Chrome인데, 텍스트 파일을 생성하는 방식이 서로 다르다. 먼저 크롬의 경우에는 아래와 같다.

function saveToFile_Chrome(fileName, content) {
    var blob = new Blob([content], { type: 'text/plain' });

    objURL = window.URL.createObjectURL(blob);
            
    // 이전에 생성된 메모리 해제
    if (window.__Xr_objURL_forCreatingFile__) {
        window.URL.revokeObjectURL(window.__Xr_objURL_forCreatingFile__);
    }
    window.__Xr_objURL_forCreatingFile__ = objURL;

    var a = document.createElement('a');

    a.download = fileName;
    a.href = objURL;
    a.click();
}

다음은 IE에서 작동하는 코드이다.

function saveToFile_IE(fileName, content) {
    var blob = new Blob([content], { type: "text/plain", endings: "native" });

    window.navigator.msSaveBlob(blob, fileName);
    //window.navigator.msSaveOrOpenBlob(blob, fileName);
}

아래는 현재 사용하는 웹브라우저가 IE인지를 식별하는 함수이다.

function isIE() {
    return (navigator.appName === 'Netscape' && navigator.userAgent.search('Trident') !== -1) ||
        navigator.userAgent.toLowerCase().indexOf("msie") !== -1;
}

jetty를 이용한 WebSocket 서버 구현하기

어플리케이션에 Servlet Container 이외도 여러가지 서버 기능을 내장할 수 있는 jetty 라이브러리를 이용해 WebSocket 서버를 구현하는 코드에 대한 뼈대를 정리한다. WebSocket는 HTML5에서 지원하는 기능으로 서버와 클라이언트는 연결되어 있는 상태에서 상호간에 데이터를 주고니 받거니 할 수 있는 기술이다. WebSocket은 그 막강한 통신 기능에 비해 서버와 클라이언트의 코드가 매우 작다. 그래서인지 뭔가 부족한 느낌이 드는데, 일단 기본적인거 정리하고 부족한 느낌이 드는 이유는 나중에 파악해 보자.

먼저 서버 단의 코드이다. Server를 구동하는 클래스와 WebSocket를 통해 접속하는 클라이언트를 나타내는 Session 범위의 클래스인데, 먼저 Server를 구동하는 클래스는 아래와 같다.

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;

public class test_webSocket {
    public static void main(String[] args) {
        Server server = new Server(8080);
		
        WebSocketHandler wsHandler = new WebSocketHandler() {
            @Override
            public void configure(WebSocketServletFactory factory) {
                factory.register(MyEchoSocket.class);
            };
        };

        ContextHandler context = new ContextHandler();
        context.setContextPath("/echoServer");
        context.setHandler(wsHandler);
		
        server.setHandler(context);

        try {
            server.start();
            server.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

그리고 다음은 서버에 접속하는 클라이언트(Session 객체로 구분) 마다 생성되는 Echo 기능을 갖는 MyEchoSocket 클래스이다.

import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;

public class MyEchoSocket implements WebSocketListener {
    private Session outbound;
	
    public MyEchoSocket() {
        System.out.println("class loaded " + this.getClass());
    }

    @Override
    public void onWebSocketConnect(Session session) {
        this.outbound = session;		
    }

    @Override
    public void onWebSocketError(Throwable cause)
    {
        cause.printStackTrace(System.err);
    }

    @Override
    public void onWebSocketText(String message)
    {
        if ((outbound != null) && (outbound.isOpen()))
        {
            String echoMessage = outbound.hashCode() + " : " + message;
            outbound.getRemote().sendString(echoMessage, null);
        }
    }

    @Override
    public void onWebSocketBinary(byte[] payload, int offset, int len)
    {
        //.
    }

    @Override
    public void onWebSocketClose(int statusCode, String reason)
    {    	
    	System.out.println("Close, statusCode = " + statusCode + ", reasone = " + reason);
        this.outbound = null;
    }	
}

요즘 웹이 그렇듯이 WebSocket 기술은 Binary 데이터도 주고 받을 수 있다는 것을 알 수 있다. 중요한 것은 클라이언트 하나가 접속을 하면 위의 클래스가 매번 생성된다는 것이고, 클라이언트는 Session 객체를 통해 나타내지며, 이 객체를 통해 데이터를 주고 받을 수 있다.

서버를 살펴봤으니, 이제 클라이언트 코드를 살펴보면..




    
    


    

위의 13번 부분에 들어가는 js 코드는 다음과 같다.

        var webSocket = new WebSocket("ws://localhost:8080/echoServer/");
        var msgField = document.getElementById("messageField");
        var divMsg = document.getElementById("msg-box");

        function sendMsg() {
            var msgToSend = msgField.value;

            webSocket.send(msgToSend);

            divMsg.innerHTML += "
Client> " + msgToSend + "
"; msgField.value = ""; } webSocket.onmessage = function (message) { divMsg.innerHTML += "Server> : " + message.data; } webSocket.onopen = function () { alert("connection opened"); } webSocket.onclose = function () { alert("connection closed"); } webSocket.onerror = function (message) { alert("error: " + message); }

서버를 기동한 뒤, 클라이언트를 실행하고 메세지를 날리면, 해당 메세지가 메아리로 돌아오는 것을 볼 수 있다.

JavaScript의 Class 정의 정리

모던한(?).. 즉 현대적인 Javascript에서는 클래스를 정의하기 위한 class 키워드를 제공하기는 하지만, 현재의 IE에서 아직도 지원하지 않아 나름대로의 class 정의 방식을 사용하고 있습니다. 웹 기반의 GIS 엔진인 FingerEyes-Xr도 이러한 class 정의 방식으로 개발 되었습니다. 수백여개의 클래스를 이 방식으로 정의해 왔음에도 새로운 클래스를 정의할라치면 기존에 만들어진 소스를 Copy 해서 Paste 해 고치는 것이 세련미 떨어져.. 직접 키보드로 한땀 한땀 입력하고자 정리해 봅니다.

먼저 클래스 정의하는 최소한의 구문입니다.

MyClass = Xr.Class({
    /* name: "MyClass", */ // optional

    construct: function () { /* 생성자 */ }
});

private 변수를 추가하는 구문입니다. 반드시 생성자 안에서 밑줄(_)로 시작해서 정의합니다.

MyClass = Xr.Class({
    construct: function () {
        this._privateVariable = 0;
    }
});

맴버 함수를 추가하는 구문입니다. private는 밑줄로 시작하고, public은 밑줄이 아닌 영문 소문자로 시작합니다.

MyClass = Xr.Class({
    construct: function () {},

    methods: {
        _privateFunction: function() { },
        publicFunction: function() { },
    }
});

클래스 차원에서 접근할 수 있는 static 변수 정의입니다. 아래와 같다면, MyClass.STATIC_VARIABLE의 값은 0이 됩니다.

MyClass = Xr.Class({
    statics: {
        STATIC_VARIABLE: 0
    },

    construct: function () {}
});

[ToDo] 상속, 인터페이스에 대한 내용은 추후 필요하면 그때 정리할 것

[JavaScript] SVG로 PieChart를 위한 도형 그리기

JavaScript 언어를 이용해 SVG에 PieChart를 위한 도형을 생성하기 위한 코드를 함수로 정리해 둡니다. 최종 결과는 아래처럼 4개의 항목으로 구성된 가운데 구멍이 뚫린 모양입니다.

먼저 아래처럼 svg와 PieChart를 구성하는 4개의 항목을 path로 지정해 둡니다. (실제 활용시에는 이 부분을 모두 JavaScript 코드로 동적 생성하도록 해야 합니다)


    
    
    
    

위의 4개의 path에 대해 각각 파이차트의 구성 요소로 만들어 주는 함수는 아래와 같습니다.

function toPieChartItemPath(x, y, radiusIn, radiusOut, startAngle, endAngle) {
    function _toXY(cX, cY, r, degrees) {
        var rad = (degrees) * Math.PI / 180.0;

        return {
            x: cX + (r * Math.cos(rad)),
            y: cY + (r * Math.sin(rad))
        };
    }

    var startIn = _toXY(x, y, radiusIn, endAngle);
    var endIn = _toXY(x, y, radiusIn, startAngle);

    var startOut = _toXY(x, y, radiusOut, endAngle);
    var endOut = _toXY(x, y, radiusOut, startAngle);

    var arcSweep = (endAngle - startAngle) <= 180 ? "0" : "1";

    var d = [
        "M", startIn.x, startIn.y,
        "L", startOut.x, startOut.y,
        "A", radiusOut, radiusOut, 0, arcSweep, 0, endOut.x, endOut.y,
        "L", endIn.x, endIn.y,
        "A", radiusIn, radiusIn, 0, arcSweep, 1, startIn.x, startIn.y,
        "z"
    ].join(" ");

    return d;
}

이 함수의 인자를 설명하면, x와 y는 파이 차트의 중심점이고 radiusIn과 radiusOut은 파이차트의 내부 반지름과 외부 반지름 값입니다. 그리고 startAngle와 endAngle는 시작각도(3시 방향이 0도)와 종료각도(시계방향)입니다. 이 함수를 통해 앞서 구성한 svg와 path 요소를 파이차트로 구성하는 코드는 아래와 같습니다.

document.getElementById("arc1").setAttribute("d", toPieChartItemPath(250, 250, 100, 240, 0, 45));
document.getElementById("arc2").setAttribute("d", toPieChartItemPath(250, 250, 100, 240, 45, 90));
document.getElementById("arc3").setAttribute("d", toPieChartItemPath(250, 250, 100, 240, 90, 180));
document.getElementById("arc4").setAttribute("d", toPieChartItemPath(250, 250, 100, 240, 180, 360));

D3로 시계 만들기

“소프트웨어 개발이 좋은 사람”이라는 블로그에서 d3를 이용해 아날로그 시계를 만든 글을 보고 코드를 살펴 보았습니다. d3는 개인적으로도 관심이 많고, 상당이 깊이 있게 공부를 했던(하지만 활용력은 아직까지도 얇은..) 자바스크립트 기반의 데이터 시각화 라이브러리입니다. 그 결과는 아래와 같습니다.

[xyz-ihs snippet=”D3-Clock”]

위의 시계에 대한 코드를 d3의 학습겸 해서 정리해 보고자 합니다. 먼저 시계에 대한 뷰는 아래처럼 오직 svg 태그 하나입니다.


시계의 크기에 해당하는 이 svg의 크기를 지정하기 위해 다음처럼 스타일을 지정합니다.

#clock {
    width: 400px;
    height: 400px;
}

시계의 구성요소는 시계의 형태인 큰 동그라미 부분, 12개의 숫자 부분, 시분초에 대한 바늘 부분으로 구성됩니다. 시분초에 대한 바늘은 Timer를 사용해 1초마다 갱신해 진짜 시계처럼 움직이도록 합니다.

스크립트 코드를 작성해 볼 것인데요. 가장 먼저 앞서 추가해둔 svg 요소를 d3의 선택자를 이용해 선택해 둡니다.

var clock = d3.select("#clock");

var center = parseInt(clock.style("height")) / 2;
var radius = center;

위의 코드는 추가적으로 svg의 높이값의 반값으로 중심점을 위한 center 변수와 시계의 반지름 크기값을 위한 radius 변수도 정의되어 있습니다.

앞서 시계는 3개의 부분으로 구성된다고 하였는데요. 다시 언급하면 시계의 형태인 큰 동그라미 부분, 12개의 숫자 부분, 시분초에 대한 바늘 부분입니다. 먼저 시계의 형태인 큰 동그라미 부분을 그리는 함수 drawFace를 반지름값(radius 변수)을 인자로 해 호출합니다.

drawFace(radius);

function drawFace(radius) {
    clock.append("circle")
        .attr("cx", center)
        .attr("cy", center)
        .attr("r", radius)
        .attr("class", "face");

    clock.append("circle")
        .attr({ 
            "cx": center, 
            "cy": center, 
            "r": radius * 0.1, 
            "fill": "#000" 
        });
}

위의 코드에서 face라는 class를 통해 스타일을 지정하고 있는데요. 이 .face에 대한 스타일 정의는 아래와 같습니다.

.face {
    fill : #FFF;
    stroke-width: 2px;
    stroke: #000;
}

또 아래는 12개의 숫자 부분을 구성하는 코드입니다.

drawNumbers(radius);

function drawNumbers(radius) {
    var pos = radius * 0.85;
    for (var num = 1; num < 13; num++) {
        var deg = 30 * num;
        var x = pos * Math.cos(Math.PI * (deg - 90) / 180);
        var y = pos * Math.sin(Math.PI * (deg - 90) / 180);
        var cx = x + center;
        var cy = y + center;
        var text = clock.append("text")
            .attr({ "x": cx, "y": cy, "class": "number" })
            .text(num)
            .style("font-size", radius * 0.15 + "px")
            .attr("transform", "rotate(" + deg + ", " + cx + ", " + cy + ")");
    }
}

위의 코드에서 number라는 class를 통해 숫자의 문자 스타일을 지정하고 있는데요. 이 .number에 대한 스타일 정의는 아래와 같습니다.

.number {
    font-family: arial;
    text-anchor: middle;
    text-align: center;
}

또 아래는 시, 분, 초에 대한 바늘을 구성하는 코드입니다. 시침, 분침, 초침을 각각 hourHand, minuteHand, secondHand 변수에 저장하고 있는데요. 이 변수를 통해 1초마다 각 침의 형태를 변경해 주게 됩니다.

var hourHand = drawHand(0, 0, radius*0.07);
var minuteHand = drawHand(0, 0, radius*0.05);
var secondHand = drawHand(0, 0, radius*0.01);

function drawHand(x, y, width) {
    var hand = clock.append("line")
        .attr({ 
            "x1": center, 
            "y1": center, 
            "x2": x + center, 
            "y2": y + center, 
            "class": "hands" })
        .style("stroke-width", width);

    return hand;
}

위의 코드에서 hand라는 class를 통해 바늘의 스타일을 정의하고 있습니다. 이 .hand에 대한 스타일 정의는 아래와 같습니다.

.hands {
    stroke: #000;
    stroke-linecap: round;
}

이제 1초마다 제 시간에 맞게 바늘을 움직도록 하는 코드는 아래와 같습니다.

drawClock();

function drawClock() {
    drawTime(radius);
    setTimeout(drawClock, 1000);
}

위의 코드에서 1초마다 실행되는 drawTime의 함수가 보이는데요. 바로 이 drawTime이 3개의 바늘을 현재 시간에 맞게 그 형상을 변경해 주는 함수이며 아래와 같습니다.

function drawTime(radius){
    var now = new Date();
    var hour = now.getHours();
    var minute = now.getMinutes();
    var second = now.getSeconds();
	
    //hour
    var pos = radius * 0.5;
    x = pos * Math.cos(Math.PI*((hour*30)-90+30/60*minute+30/60/60*second)/180); 
    y = pos * Math.sin(Math.PI*((hour*30)-90+30/60*minute+30/60/60*second)/180);
    hourHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});

    //minute
    pos = radius*0.65;
    x = pos * Math.cos(Math.PI*((minute*6)-90+6/60*second)/180); 
    y = pos * Math.sin(Math.PI*((minute*6)-90+6/60*second)/180);
    minuteHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
	
    // second
    pos = radius*0.9;
    x = pos * Math.cos(Math.PI* ((second*6)-90)/180); 
    y = pos * Math.sin(Math.PI* ((second*6)-90)/180);
    secondHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
}

이상으로 d3를 이용해 svg 요소를 시계로 만들어 보는 코드를 살펴보았습니다.