웹에서의 레이아웃을 위한 구세주, Flex

웹의 CSS를 이용해 가로에 대한 가운데 정렬은 그럭저럭 쉽게 지정할 수 있지만, 세로 정렬은 왠지 트릭같은 방식으로 지정하는 느낌을 받는다. 그렇다보니, 세로 정렬이 필요한 레이아웃을 구성할때마다 고충이 생기는데.. 이럴때 flex라는 비교적 최신의 CSS 방식을 활용해 이러한 고충을 제거하고자 이 글을 정리한다. 사실 flex 방식은 그 내용이 제법 방대하다. 하지만 체계적인지라 여러번 살펴보면 충분이 이해하고 실제 업무에 활용할 수 있다.

일단 앞서 언급한 가로와 세로에 대한 가운데 정렬을 위한 flex 방식을 이 글을 통해 간단이 언급해 둔다. 가운데 정렬이 필요할때마다 flex를 사용하다보면.. flex의 그밖의 요소들이 하나씩 접근하게 될 것이기 때문이다. 기술이란 그 필요에 의해 사용되고, 자연스럽게 사용되어질때 사용자가 보다 쉽게 이해할 수 있기 때문이다.

아래의 dom 구성에 대한 코드가 있다.

DIP2K
김형준

.container 안에 .item 항목을 가로와 세로로 가운데 정렬하고자 한다면 아래와 같은 스타일을 지정하면 된다.


실제 결과는 아래와 화면처럼 가로와 세로에 대해 가운데로 정렬된 것을 볼 수 있다.

[xyz-ihs snippet=”flex-layout-center-align”]

DIV 요소는 display가 block이다. 그러나 flex의 내부의 항목으로 들어가게 되면 inline-block으로 변경된다는 점을 알 수 있다. 끝으로 이 글을 작성하기 위해 읽었던 문서는 “flexbox의 기본 개념”이다.

웹 UI ㅡ Accordion

웹에서 제공하는 표준 UI는 상당히 투박해서 거의 사용하지 않고, jQuery UI 등과 같이 유명한 라이브러리를 사용해 웹 어플을 개발합니다. 그러나 필자는 기존의 웹 UI 라이브러리를 사용하지 않는데.. 이유는 내가 원하는 UI의 형태와 느낌을 표현하기 위해 상당이 많은 시간과 노력이 투자되어야 하거나, 불가능한 경우라고 포기하며 타협하게 되기 때문입니다.

이번에 웹에서 접고 펼치는 Accordion UI가 필요해서, 직접 만들어 사용하고 있는데요. 아래는 그 UI에 대한 샘플 결과 이미지입니다.

뭐, 그닥 이쁘다거나 세련된 모양은 아닙니다. 하지만 UI의 멋은 Content으로 완성된다고 믿는 저에겐 충분합니다. 아래는 위의 UI를 구성하기 위한 Javascript 코드입니다.

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

<script>
var ui = new XrUI.accordion("div",
    {
        width: "320px",
        height: "400px",
        top: "50px",
        left: "60px",
        title: "어코디언 UI"
    }
);

ui.addItem("item1");
ui.itemTitle("item1").innerHTML = "Title1";
ui.itemContent("item1").innerHTML = "Content1";

ui.addItem("item2")
ui.itemTitle("item2").innerHTML = "Title2";
ui.itemContent("item2").innerHTML = "Content2";

ui.addItem("item3")
ui.itemTitle("item3").innerHTML = "Title3";
ui.itemContent("item3").innerHTML = "Content3";
</script>

어코디언을 구성하는 항목의 제목과 내용을 직접 innerHTML 등으로 구성할 수 있도록 DOM 요소를 노출하고 있는데요. 이 부분이 향후 이 어코디언 UI를 내가 원하는 형태과 내용으로 자유롭게 구성할 수 있는 통로가 됩니다.

이렇게 만들어진 웹 UI는 컴포넌트로써 실제 프로젝트에서 아래와 같은 형태로 초기에 응용되어 활용되었습니다.

기본적이고 단순한 틀은 유지하면서, 최대한 컨텐츠를 활용하여 UI를 꾸미고 있습니다. 프로그램의 기능은 배포후 점진적으로 개선 발전되듯이 UI 역시 편의성 등의 개선을 이유로 변경됩니다. 결국 현재는 아래와 같은 형태로 활용되고 있습니다.

웹에서 Javascript 만으로 텍스트 파일 읽기

웹에서 JS 언어만으로 로컬에 저장된 텍스트 파일을 읽어 오는 코드를 정리한 글이다. 먼저 아래는 예제 코드 실행을 위한 DOM 구성에 대한 코드이다.


...

Open 버튼을 클릭하면 텍스트 파일을 선택할 수 있는 대화상자가 표시되도록 하며, 여기서 읽고자 하는 파일을 사용자가 선택하면 id가 output인 div에 텍스트 파일의 내용을 출력한다. 이에 대한 코드는 아래와 같다.

function openTextFile() {
    var input = document.createElement("input");

    input.type = "file";
    input.accept = "text/plain"; // 확장자가 xxx, yyy 일때, ".xxx, .yyy"

    input.onchange = function (event) {
        processFile(event.target.files[0]);
    };

    input.click();
}

function processFile(file) {
    var reader = new FileReader();

    reader.onload = function () {
        output.innerText = reader.result;
    };

    reader.readAsText(file, /* optional */ "euc-kr");
}

openTextFile은 Open 버튼 클릭시 호출하는 함수이다. processFile은 openTextFile에서 호출되는 함수로 선택된 파일을 읽어 div에 그 내용을 출력한다. IE와 Chrome 모두에서 정상적으로 작동하는 것을 확인했다.

웹에서 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); }

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