#GWC UI Library : Space

웹 UI 라이브러리인 GWC에서 제공하는 Space 컴포넌트에 대한 예제 코드입니다. 이 컴포넌트는 UI 간의 여백을 지정하기 위한 목적으로 사용됩니다.

먼저 DOM 구성을 위한 JS 코드는 다음과 같습니다.

const domLayout = document.createElement("div");
domLayout.classList.add("login");

domLayout.innerHTML = `
    
`;
document.body.appendChild(domLayout);

Space 컴포넌트는 gw-space라는 Tag로 구성될 수 있는데, 여백에 대한 크기를 10px 또는 20px,10px 등으로 나타낼 수 있습니다. 10px의 경우 가로와 세로 모두에 대한 여백의 크기이고 20px,10px은 가로와 세로에 대한 크기를 서로 다르게 지정합니다. 위의 코드에 대한 결과는 다음과 같습니다.

이 컴포넌트에 대한 CSS 코드는 필요치 않으나 위의 코드에서 사용된 CSS는 다음과 같습니다.
.login {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    background: black;
}

.login .login-form {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: inline-flex;
    flex-flow: column;
    gap: 0.5em;
    background: rgba(255,255,255,0.1);
    padding: 4em 4em 1.5em 4em;
    border-radius: 1em;
    box-shadow: -0.6px -0.6px 0.6px rgba(255,255,255,0.3);
}

.login .login-form gwc-textinput {
    width: 300px;
}

.login .login-form .login-title {
    font-family: Raleway;
    color: white;
    font-size: 2em;
}

.login .login-form .login-form-tools {
    display: flex;
    justify-content: center;
    align-items: center;
    zoom: 0.9;
}

GWC 라이브러리, UI 구성 예 (gwcCreateModalDialog, Tree 등)

다음과 같은 UI 구성을 목표라고 할때…

대화상자 형태의 레이아웃이므로 gwcCreateModalDialog API를 이용한 클래스를 작성합니다.

class DownloadLayerList {
    constructor() {
        const dlg = gwcCreateModalDialog("레이어 목록 불러오기");
        dlg.content = `
            <div class="download-layer-list-dialog h-center content">
                <div class="vertical-linear-layout">
                    <div class="horizontal-linear-layout v-center">
                        <gwc-label content="필터" outline-type="none"></gwc-label>
                        <gwc-textinput hint="필터링할 값을 입력하세요."></gwc-textinput>
                        <gwc-toolbutton class="refresh" icon="../images/reset.svg"></gwc-toolbutton>
                    </div>
                    <gwc-vscrollview>
                        <content>
                            <gwc-tree></gwc-tree>
                        </content>
                    </gwc-vscrollview>
                    <div class="horizontal-linear-layout h-center v-space"> 
                        <gwc-button class="btnConfirm" title="불러오기" disabled=true></gwc-button>
                        <gwc-button class="btnCancel" title="취소"></gwc-button>
                    </div>
                </div>                    
            </div>
        `;

        dlg.show();
        GeoServiceWebComponentManager.instance.update();

        ....

대화상자의 크기 조정이 필요하다면 아래의 코드를 추가합니다.

        dlg.width = "30em"
        dlg.resizablePanel.resizableLeft = true;
        dlg.resizablePanel.resizableRight = true;
        dlg.resizablePanel.resizableTop = true;
        dlg.resizablePanel.resizableBottom = true;
        dlg.resizablePanel.minWidth = 350;
        dlg.resizablePanel.minHeight = 300;
        dlg.resizablePanel.addEventListener("change", (event) => {
            const { mode, oldHeight, newHeight } = event.detail;
            if(mode === "BOTTOM" || mode == "TOP") {
                const vscrollview = dlg.content.querySelector("gwc-vscrollview");
                const height = parseFloat(window.getComputedStyle(vscrollview).getPropertyValue("height"));
                vscrollview.style.height = `${height - (oldHeight - newHeight)}px`;
                vscrollview.refresh();
            }            
        });
    }

    ....

CSS에 대한 코드는 다음과 같습니다.


.download-layer-list-dialog {
    padding: 0.5em;
}

.download-layer-list-dialog gwc-textinput {
    flex: 1; /* 대화상자의 가로 크기를 재조정 했을 때 gwc-textinput의 크기도 재조정됨  */
}

.download-layer-list-dialog gwc-vscrollview {
    position: relative;
    height: 16em;
    background: rgba(0,0,0,0.8);
    border-radius: 0.5em;
    overflow: hidden;
}

.download-layer-list-dialog gwc-tree {
    width: 100%; /* tree의 항목의 크기를 대화상자의 폭을 가득 채우도록 조정함 */
    padding: 0.5em 0.5em 0.5em 0.5em;
}

.download-layer-list-dialog gwc-tree .gwc-tree-folder-files-file {
    width: 100%;
}

끝으로 gwc의 tree 컴포넌트는 동일한 계층에 동일한 이름을 가진 항목을 추가할 수 없습니다. 이때 label 속성을 이용해 이름은 다르지만 표시되는 제목은 중복되게 변경해 줄 수 있습니다. 코드 예시는 다음과 같습니다.

data.forEach(item => {
    const rootFolder = this.#tree.rootFolder;
    const file = rootFolder.addFile(item.id, "url(../images/layers.svg)");

    const date = new Date(item.used_time);
    file.tag = `${date.getFullYear()}-${date.getMonth()+1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}`;
    file.label = item.title;
});

#GWC UI Library : Select의 Custom Draw (setCustomDrawingMethod)

웹 UI 라이브러리인 GWC에서 제공하는 Select 컴포넌트의 선택 항목을 사용자 정의 그리기로 정의하기 위한 예제 코드입니다.

먼저 DOM 구성은 다음과 같습니다.

그리고 CSS 구성은 다음과 같구요.

.center {
    display: flex;
    width: 100%;
    height: 100%;
    justify-content: center;
    align-items: center;
}

.panel {
    display: flex;
    gap: 2em;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

#select {
    width: 200px;
}

js 코드는 다음과 같습니다.

window.onload = () => {
    select.setCustomDrawingMethod([
        {v: "4 4"}, // 사용자 정의 아이템을 시각화하기 위한 임의의 데이터
        {v: "8 4"},
        {v: "8 8"},
        {v: "16 8"},
        {v: "16 4"}
    ], (item) => {
        const domSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        domSVG.setAttribute("width", "100px");
        domSVG.setAttribute("height", "20px");

        const svgLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
        svgLine.setAttribute("x1", 4);
        svgLine.setAttribute("y1", 10);
        svgLine.setAttribute("x2", 96);
        svgLine.setAttribute("y2", 10);
        svgLine.style = `stroke:white;stroke-width:4;stroke-dasharray:${item.v}`;

        domSVG.appendChild(svgLine);

        return domSVG;
    });

    select.selectedIndex = 1;

    select.addEventListener("change",  (event) => {
        const select = event.target;
        label.content = `"${select.value.v}"(으)로 변경되었습니다.`;
    });

    button.addEventListener("click", () => {
        gwcMessage(`select의 값: ${select.value.v}`);
    });

    GeoServiceWebComponentManager.instance.update();
};

실행 결과는 다음과 같습니다.

#GWC UI Library : gwcMessage 함수

웹 UI 라이브러리인 GWC에서 제공하는 gwcMessage 함수에 대한 예제 코드입니다.

먼저 DOM 구성은 다음과 같습니다.

그리고 CSS 구성은 다음과 같구요.

.center {
    display: flex;
    width: 100%;
    height: 100%;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    gap: 1em;
}

js 코드는 다음과 같습니다.

window.onload = () => {
    button1.onclick = event => { 
        gwcMessage(`성공적으로 처리하였습니다. (소요 시간 17초)`);
    };

    button2.onclick = event => { 
        gwcMessage(`네트워크 접속 불가`, true);
    };
};

실행 결과는 다음과 같습니다.

gwcMessage의 세 번째 인자는 확인 버튼을 클릭하면 호출되는 콜백 함수를 지정할 수 있습니다.

#GWC UI Library : Tree

웹 UI 라이브러리인 GWC에서 제공하는 Tree 컴포넌트에 대한 예제 코드입니다.

먼저 DOM 구성은 다음과 같습니다.

그리고 CSS 구성은 다음과 같구요.

.center {
    display: flex;
    width: 100%;
    height: 100%;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    gap: 1em;
}

gwc-vscrollview {
    width: 22em;
    height: 30em;
    background: rgba(0,0,0,0.3);
    border: 1px solid black;
}

gwc-tree {
    width: 100%;
    padding: 0.5em 0.5em;
}

.h-center {
    display: fex;
    justify-content: center;
    align-items: center;
}

js 코드는 다음과 같습니다.

window.onload = () => {
    // 최상위 폴더(root) 
    const rootFolder = tree.rootFolder;
    
    // 폴더 추가
    const gaFolder = rootFolder.addFolder("아카이브");
    const docFolder = rootFolder.addFolder("문서").open();

    rootFolder.addFolder("프로그램");
    const programFolder = rootFolder.getFolder("프로그램");

    const humanGeoFolder = gaFolder.addFolder("인문공간데이터");
    const adminGeoFolder = gaFolder.addFolder("행정경계");

    // 폴더에 파일 추가    
    humanGeoFolder.addFile("인구통계.zip", "url(../examples/images/icon7.png)");
    humanGeoFolder.addFile("유동인구.zip", "url(../examples/images/icon7.png)");
    humanGeoFolder.addFile("유아통계.zip", "url(../examples/images/icon7.png)");

    const koreaFolder = adminGeoFolder.addFolder("대한민국");
    koreaFolder.addFile("시도.zip", "url(../examples/images/icon3.png)");
    koreaFolder.addFile("시군구.zip", "url(../examples/images/icon3.png)");
    koreaFolder.addFile("읍면동.zip", "url(../examples/images/icon3.png)");

    adminGeoFolder.addFile("서울특별시.zip", "url(../examples/images/icon3.png)");
    adminGeoFolder.addFile("경기도.zip", "url(../examples/images/icon3.png)");

    docFolder.addFile("레포트1.pdf", "url(../examples/images/icon6.png)");
    docFolder.addFile("레포트2.pdf", "url(../examples/images/icon6.png)");
    docFolder.addFile("레포트3.pdf", "url(../examples/images/icon6.png)");

    programFolder.addFile("VisualSudio.Code.zip", "url(../examples/images/icon5.png)")
    programFolder.addFile("PhotoShop.zip", "url(../examples/images/icon5.png)")
    programFolder.addFile("리터널_PS5.zip", "url(../examples/images/icon5.png)")

    // 폴더 열고 닫기
    btnProgramOpenClose.addEventListener("click", () => {
        const folder = tree.rootFolder.getFolder("아카이브");
        if(folder.isOpen()) folder.close();
        else folder.open();
    });

    // 특정 폴더에 파일 추가
    btnAddFile.addEventListener("click", () => {
        const folder = tree.rootFolder.getFolder("문서");
        const file = folder.getFile("NEW 레포트.pdf");
        if(file) {
            file.remove();
        } else {
            const file = folder.addFile("NEW 레포트.pdf", "url(../examples/images/icon6.png)");

            // 파일 또는 폴더에 사용자 정의 데이터 추가
            file.setData("생성일자", "2022년 2월 10일");
            console.log(file.getData("생성일자"));
        }
    });

    // 파일(폴더+파일)에 대한 클릭 이벤트
    tree.addEventListener("fileClick", (event) => {
        const file = event.detail.file;
        
        const parentFolderName = file.parentFolder.name;
        const bFolder = file.isFolder();
        const bOpen = file.isOpen();

        label.content = `
            이름: ${file.name} 
            부모폴더: ${parentFolderName?parentFolderName:"없음"} 
            종류: ${bFolder?"폴더":"파일"} 
            ${bFolder?`상태: ${bOpen?"열림":"닫힘"}`:""}
        `;

        vscrollview.refresh(); // 폴더 열기로 인한 트리 컴포넌트 크기 변경에 따른 스크롤뷰 업데이트
    });

    // 트리 컴포넌트의 크기가 가변이므로 스크롤뷰를 업데이트 해줌
    vscrollview.refresh();

    GeoServiceWebComponentManager.instance.update();
};

실행 결과는 다음과 같습니다.

트리의 항목에 대한 팝업 메뉴 기능을 적용할 때에 대한 코드입니다.

// PopupMenu 생성 시작
const popupMenu = gwcCreatePopupMenu();
popupMenu.addMenuItem("menu1", {
    text: "생성",
    _icon: "images/icon1.png",
    onClick: (menuId) => { 
        gwcMessage(`폴더 생성(${menuId})`);
        popupMenu.hide();                
    }
});
popupMenu.addMenuItem("menu2", {
    text: "이름 변경",
    _icon: "images/icon2.png",
    onClick: (menuId) => { 
        gwcMessage(`${popupMenu.fileData.name} 이름 변경(${menuId})`);
        popupMenu.hide();                
    }
});
popupMenu.addMenuItem("menu3", {
    text: "삭제",
    _icon: "images/icon3.png",
    onClick: (menuId) => { 
        gwcMessage(`${popupMenu.fileData.name} 삭제(${menuId})`);
        popupMenu.hide();
    }
});
popupMenu.addMenuItem("menu4", {
    text: "잘라내기",
    _icon: "images/icon4.png",
    onClick: (menuId) => { 
        gwcMessage(`${popupMenu.fileData.name} 잘라내기(${menuId})`);
        popupMenu.hide();
    }
});
popupMenu.addMenuItem("menu5", {
    text: "붙여넣기",
    _icon: "images/icon5.png",
    onClick: (menuId) => { 
        gwcMessage(`${popupMenu.fileData.name}에 붙여넣기(${menuId})`);
        popupMenu.hide();
    }
});
// PopupMenu 생성 완료

// 파일(폴더+파일)에 대한 클릭 이벤트
tree.addEventListener("fileClick", (event) => {
    const file = event.detail.file;
    const contextMenu = event.detail.contextMenu;

    if(contextMenu)  {
        console.log(event.detail.originalEvent);
        popupMenu.fileData = file; // fileData는 임의로 부여한 속성으로 팝업 메뉴 실행 시에 참조됨
        popupMenu.show(event.detail.originalEvent.clientX, event.detail.originalEvent.clientY);
    }
});

위의 코드에 대한 실행 결과는 다음과 같습니다.

트리를 구성하는 항목에 대해서 선택된 항목이라는 피드백을 줄 수 있습니다. 다음 코드를 참고하기 바랍니다.

tree.clearSelection(); // 일단 기존의 선택된 항목에 대해 선택 해제
tree.rootFolder.getFolder("아카이브").selected = true; // 폴더를 얻고 해당 폴더를 선택된 상태로 표시

폴더 또는 파일 항목의 우측에 Tag 정보를 표시할 수 있습니다. 즉, getFolder 또는 getFile을 통해 얻은 item 객체의 tag 속성(get, set)을 설정하면 됩니다. 아래는 파일항목의 우측에 파일의 크기를 표시하는 예시입니다.

tree 컴포넌트는 동일한 계층에 동일한 이름을 가진 항목을 추가할 수 없습니다. 이때 label 속성을 이용해 이름은 다르지만 표시되는 제목만을 변경해 줄 수 있습니다. 코드 예시는 다음과 같습니다.

data.forEach(item => {
    const rootFolder = this.#tree.rootFolder;
    rootFolder.addFile(item.id, "url(../images/layers.svg)").label = item.title;
});