https://kldp.org/node/102863

예제로 배우는 XE 북마크 모듈 만들기

- XE 오픈 소스 프로젝트 매니저중 한분이신 한승엽님이 발표한 자료 중에서 실제로 간단한 모듈을 만들어 보는 것이 있었는데 그것을 따라 해 본다. XE 메뉴얼(http://www.zeroboard.com/manual)에 똑같은 내용이 좀더 자세히 설명되어 있다. 이 글의 목적은 내 손으로 직접 해 본다는 의미 정도...

- 동영상 발표 자료: http://www.zeroboard.com/17103019#7

- 한승엽님 홈페이지 자료: http://seungyeop.kr/prog/24110

0. 목적

최종 목적은 XE 북마크 모듈을 만드는 것. 실제 작동되는 예제는 http://seungyeop.kr/?module=bookmark 에서 확인할 수 있다.

1. 모듈이란?

XE 에서 모듈은 독립적인 단위로 실행되는 서비스를 말한다. 예를 들면, 게시판, 쇼핑몰, 프로젝트 관리 서비스 등이 있겠다. XE는 기본적으로 모듈 단위로 구동이 되는데, XE 사이트에 접속하면 지정된 모듈이 실행된다. 아직 설치가 안되었다면, 설치 모듈이 실행된다. 기본 설정이 게시판이면 보드 모듈이 구동되면서 미리 정해진 스킨을 통해 컨텐츠를 출력한다.

2. 배경 지식

(1) MVC 모델

모듈은 MVC 모델을 따라서 구성된다. 즉, 하나의 모듈은 View, Model, Control에 해당하는 클래스들로 구성된다. 전형적인 MVC와는 좀 다르게 각각은 다음과 같은 작업을 한다.

- Model은 데이타를 다루는 작업에 해당. 주로 Database에서 데이타를 가져오는 역할.
- Control은 데이타를 입력하는 작업에 해당. Database에 insert나 update하는 작업.
- View는 화면 인터페이스 작업에 해당.

(2) Query XML

XE는 다양한 DBMS를 지원하기 위해서query문을 직접 사용하지 않고 XML 형태로 작성한다.

3. 북마크 모듈의 최종 구성도

최종적으로 만들어질 북마크 예제는 다음과 같이 {설치디렉토리}/modules/bookmark 밑에 생성된다.


./modules/
    bookmark/
        conf/info.xml, module.xml                    # 설정 파일들
        lang/ko.lang.php, jp.lang.php ...           # 언어 파일들
        queries/insertDocument.xml ...             # 쿼리 파일들
        schemas/documents.xml ...                  # 데이터베이스 스키마 파일들
        tpl/                                                   # 스킨으로 설정하지 않은 template 파일들
            filter/insert.xml,                             # AJAX을 이용하여 값 전달
            js/bookmark.js, bookmark_list.html)  # javascripts
        skins/                                                # 스킨 파일
        bookmark.class.php                             # 북마트 모듈의 기본 클래스
        bookmark.controller.php                       # 기본 동작 정의
        bookmark.view.php                              # 기본 동작 정의

이번 예에서는 빠졌지만 다음과 같이 관리자 모드에서의 동작을 정의하는 파일도 있을 수 있다.

  • bookmark.admin.controller.php
  • bookmark.admin.view.php
  • bookmark.admin.model.php

전체적 그림을 머리에 넣어 두고 하나씩 살펴보기로 하자.
참고로 소스코드는 http://www.zeroboard.com/17103019#7 의 댓글에서 구할 수 있다.

4. 모듈 선언하기

이번 예제는 북마크 모듈을 만드는 것이니 모듈 디렉토리 밑에 bookmark 디렉토리를 생성한다.

{설치디렉토리}/modules/bookmark

이 상태에서 XE 관리자 게시판의 "모듈 관리"로 가보면 다음처럼 bookmark 모듈 정보가 나타난다.

보다 자세한 정보를 보이고자 하면 다음과 같은 info.xml을 bookmark/conf 디렉토리에 생성한다.


<?xml version="1.0" encoding="UTF-8"?>
<module version="0.2">
    <title xml:lang="ko">bookmark</title>
    <description xml:lang="ko">bookmark</description>
    <version>0.1</version>
    <date>2008-07-26</date>
    <category>service</category>
    <author email_address="haneul0318@gmail.com" link="http://www.seungyeop.kr">
        <name xml:lang="ko">haneul</name>
    </author>
</module>

그러면, 관리자 게시판에 해당 정보가 표시된다.

자세히 보기를 선택하면 info.xml에서 설정한 내용들이 보여진다.

이제 실제로 작동하는 북마크 모듈을 만들어 보자.

5. DB 설정

모듈은 Database Schema를 갖고 자신만의 데이타를 사용하는 경우가 많다.
북마크 모듈에 필요한 Database Schema를 다음처럼 정의했다고 하자.


    -------------------------------------------------------
    bookmark_url        INT(11)                Primary Key
    -------------------------------------------------------
    link                VARCHAR(255)
    ------------------------------------------------------
    title               VARCHAR(255)
    -------------------------------------------------------
    description         LONGTEXT
    -------------------------------------------------------

이것을 XML 파일로 다음처럼 저장한 후에 bookmark/schemas 디렉토리 및에 bookmark.xml로 저장한다.


<table name="bookmark">
    <column name="bookmark_srl" type="number" size="11" notnull="notnull" primary_key="primary_key" />
    <column name="link" type="varchar" size="255" notnull="notnull" />
    <column name="title" type="varchar" size="255" notnull="notnull" />
    <column name="description" type="bigtext" />
</table>

그후 관리자 게시판을 다시 로드하면 "설치" 메뉴가 보인다. 일단, "설치"를 눌러서 DB table이 생성되면 "설치" 메뉴는 더이상 보이지 않게된다.

6. 간단한 껍질을 만들자.

(1) bookmark.class.php


<?PHP
    class bookmark extends ModuleObject
    {
        function checkUpdate()
        {
            return false;
        }
    }
?>

- 해당 모듈의 기본이 되는 클래스로 ModuleObject를 상속하고 있다.
- 모듈의 다른 클래스들(Controller나 View 등)은 이 기본 클래스를 상속 받아서 구현한다.
- 모듈이 인스톨될 때, 업데이트 될 때, 캐시 재생성할 때의 해야할 동작들을 정의하는 함수(즉, moduleInstall, checkUpdate 함수)들을 포함한다.

(2) bookmark.view.php


<?PHP
    class bookmarkView extends bookmark
    {
        function dispBookmarkList()
        {
            $this->setTemplatePath($this->module_path.'tpl');
            $this->setTemplateFile('bookmark_list');
        }       
    }
?>

좀더 복잡해 지기 전에 여기선 단순히 스킨대신에 Template을 사용하는데, tpl 디렉토리 및의 bookmark_list를 사용하기로 설정한다.

(3) tpl/bookmark_list.html


<p>Hello Wordl!</p>

초간단 예제...

(4) conf/module.xml


<?xml version="1.0" encoding="utf-8"?>
<module>
    <grants />
    <actions>
    <action name="dispBookmarkList" type="view" index="true" standalone="true" />
    </actions>
</module>

XE 동작의 기본 단위를 Action이라고 하는데 이것을 conf 디렉토리 밑의 module.xml에서 정의한다. 위의 예제에서는 dispBookmarkList라는 Action을 실행하도록 설정했는데 이게 앞의 bookmark.view.php에서 정의한 함수이다.

이제 XE에 모듈명(즉, .../?module=bookmark)으로 다시 접속해 보면, 다음과 같이 출력됨을 알 수 있다.

7. Database로 부터 북마크 정보 읽어 오기

(1) queries/getBookmarkList.xml


<query id="getBookmarkList" action="select">
    <tables>
        <table name="bookmark" />
    </tables>
    <columns>
        <column name="*" />
    </columns>
    <navigation>
        <index var="sort_index" default="bookmark_srl" order="desc" />
        <list_count var="list_count" default="5" />
        <page_count var="page_count" default="10" />
        <page var="page" default="1" />
    </navigation>
</query>

select 문을 써서 bookmark table로부터 읽어오고 있다.

(2) bookmark.view.php (다시)

앞에서 껍질만 만들었던 View를 그럴듯하게 수정한다. 예를 들어, 앞에서 만든 getBookmarkList Query를 실행해서 얻은 데이타를 template으로 넘긴다.


<?PHP
    class bookmarkView extends bookmark
    {
        function dispBookmarkList()
        {
            $args->page = Context::get('page');
            $output = executeQueryArray("bookmark.getBookmarkList", $args);
            if(!$output->data) $output->data = array();
            Context::set('bookmark_list', $output->data);
            Context::set('total_count', $output->total_count);
            Context::set('total_page', $output->total_page);
            Context::set('page', $output->page);
            Context::set('page_navigation', $output->page_navigation);
            $this->setTemplatePath($this->module_path.'tpl');
            $this->setTemplateFile('bookmark_list');
        }       
    }
?>

여기서 Context는 여러 환경 변수 등을 담고 있는 객체이다.

(3) tpl/bookmark_list.html (다시)

앞에서 받은 list를 출력하는 부분이다. 맨 앞의 import로 시작하는 두 줄과 하단의 Form 부분은 입력을 처리하는 부분이다. 가운데단이 list를 출력하는 부분인데, 이곳과 앞의 view부분은 다음에 좀더 살펴 봐야겠다는 생각이 든다. 암튼, 게시판에서 페이지 만들고 네비게이션 추가하는 것도 일맥상통하다.


<!--%import("filter/insert_bookmark.xml")-->
<!--%import("js/bookmark.js")-->
<table>
    <col width="200" />
    <col width="500" />
    <tr> <th> 제목 </th> <th> 설명 </th> </tr>
    <!--@foreach($bookmark_list as $val)-->
    <tr> <td align="center"><a href="{$val->link}">{$val->title}</a>
         <td align="center">{$val->description}</td></tr>
    <!--@end-->
 
    <tr> <td colspan="2" align="center">
    <a href="{getUrl('page','','module_srl','')}" class="goToFirst"><img src="../../admin/tpl/images/bottomGotoFirst.gif" alt="{$lang->first_page}" width="7" height="5" /></a>
    <!--@while($page_no = $page_navigation->getNextPage())-->
        <!--@if($page == $page_no)-->
            <span class="current">{$page_no}</span>
        <!--@else-->
            <a href="{getUrl('page',$page_no,'module_srl','')}">{$page_no}</a>
        <!--@end-->
    <!--@end-->
    <a href="{getUrl('page',$page_navigation->last_page,'module_srl','')}" class="goToLast"><img src="../../admin/tpl/images/bottomGotoLast.gif" alt="{$lang->last_page}" width="7" height="5" /></a>
    </td></tr>
 
    <tr> <td colspan="2" align="center">
    <form action="./" method="POST" onsubmit="return procFilter(this, insert_bookmark);">
        <label>link<input type="text" name="link" /></label>&nbsp;
        <label>title<input type="text" name="title" /></label>&nbsp;
        <label>description<input type="text" name="description" /></label>&nbsp;
        <input type="submit" value="입력"/>
    </td></tr>
</table>

화면 출력은 다음과 같다.

8. 북마크 정보를 입력받아서 Database에 저장하기

앞의 tpl/bookmark_list.html에서 Form 버튼을 누르면 insert_bookmark.xml Filter를 실행시킨다. 먼저 Filter에 대해서 살펴 본다.

(1) tpl/filter/insert_bookmark.xml

발 표자료를 보면 "Filter는 Form의 파라미터 값을 AJAX을 이용하여 전달, 다른 action을 수행하는데 사용"한다고 설명되어 있다. 좀 어려워보이는데 "XE 메뉴얼(http://www.zeroboard.com/?mid=manual&pageid=392369)"을 참고해 보면 "웹페이지에서 Form문에서 특정 field의 not null, min/max length, value 체크등을 쉽게 하고 AJAX로 서버의 특정 Module과 Action와 통신을 하는 과정까지 모두 제어를 할 수 있"다고 나온다. 이 예제에서는 AJAX으로 서버와 통신하여 bookmark 모듈의 procBookmarkInsertBookmark Action을 실행시키며 콜백함수(끝나면 불리는 함수)로 completeInsertBookmark를 설정하고 있다.


<filter name="insert_bookmark" module="bookmark" act="procBookmarkInsertBookmark" confirm_msg_code="confirm_submit">
    <form />
    <parameter />
    <response callback_func="completeInsertBookmark">
        <tag name="error" />
        <tag name="message" />
    </response>
</filter>

(2) conf/module.xml (다시)

앞에서 설정파일에 이미 View 관련된 Action을 설정했었는데, 여기서는 Controller에 관련된 Action, 즉, 방금 Filter에서 언급한 Action을 정의한다.


<?xml version="1.0" encoding="utf-8"?>
<module>
    <grants />
    <actions>
    <action name="dispBookmarkList" type="view" index="true" standalone="true" />
    <action name="procBookmarkInsertBookmark" type="controller" standalone="true" />
    </actions>
</module>

그러고, Controller Class에 이 함수를 정의한다.

(3) bookmark.controller.php (다시)

이제 Controller Class를 만들자. procBookmarkInsertBookmark 함수는 북마크 정보를 Database에 입력하는 Query인 insertBookmark를 실행하도록 설계된다.


<?PHP
    class bookmarkController extends bookmark
    {
        function procBookmarkInsertBookmark()
        {
            $obj = Context::getRequestVars();
            $obj->bookmark_srl = getNextSequence();
            executeQuery("bookmark.insertBookmark", $obj);
        }
    }
?>

그러면, insertBookmark Query를 만들어야지.

(4) queries/insertBookmark

bookmark table에 data를 추가하는 query가 되겠다.


<query id="insertBookmark" action="insert">
    <tables>
        <table name="bookmark" />
    </tables>
    <columns>
        <column name="bookmark_srl" var="bookmark_srl" notnull="notnull" />
        <column name="title" var="title" notnull="notnull" />
        <column name="link" var="link" notnull="notnull" />
        <column name="description" var="description" />
    </columns>
</query>

마지막으로 남은 것이 Filter에서 정의한 콜백함수 completeInsertBookmark를 만드는 일.

(5) tpl/js/bookmark.js

콜백함수 completeInsertBookmark는 Javascript으로 만들어진다.


function completeInsertBookmark(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var page = ret_obj['page'];
    alert(message);
 
    var url = current_url;
    location.href = url;
}

9. 실행 결과 보기

한승엽 님의 사이트(http://seungyeop.kr/?module=bookmark)에서 돌아가는 예제를 볼 수 있다.

10. 끝...

뒷부분에서 너무 간단히 넘어간 면이 없지 않지만 오늘은 여기까지.
다음엔 위젯 만들기에 도전.


 
댓글은 로그인 사용자만 작성 가능합니다. 로그인하기