'Ajax'에 해당되는 글 2건

  1. 2007/01/08 Extending Symfony Adjacent List
  2. 2006/12/29 AJAX Post-It 만들기 (1)

Extending Symfony Adjacent List

Programming 2007/01/08 14:42
지난번에 만들었던 Adjacent List 를 확장해볼 생각입니다. 추가할 기능은, 체크박스를 넣고 상태 토글 기능을 넣어보겠습니다.

1. schema.yml 수정
각 item 에 상태를 추가하기 위해서 status 테이블을 만들고, item 테이블을 status 테이블과 조인할것입니다.
# config/schema.yml
propel:
  item:
    _attributes: { phpName: Item }
    id:
    parent_id:    integer
    thread:       integer
    depth:       { type: tinyint, default: 0 }
    title:        varchar(255)
    description:  longvarchar
    lft:         { type: integer, default: 1 }
    rgt:         { type: integer, default: 2 }
    status_id:    tinyint
    _foreign_keys:
      - self_joined_keys
        foreign_table: item
        on_delete: cascade
        references:
          - { local: parent_id, foreign: id }
      - status_joined_keys
        foreign_table: status
        on_delete: setnull
        references:
          - { local: status_id, foreign: id }

  status:
    _attributes: { phpName: Status }
    id:
    tag:          varchar(255)
    title:        varchar(255)
원래, `status_id` 란 필드명을 사용하고 `status` 란 테이블이 존재할 경우에는 propel 이 알아서 foreign key 를 생성해 주는데, 이번 경우에서는 `_foreign_keys` 옵션이 있어서 그런지 자동으로는 안 되는군요. `status_id` 를 위한 foreign_keys 항목을 추가합니다.

테스트 데이터는 다음과 같이 변경합니다.
# data/fixtures/fixtures.yml
Status:
  opened
    tag: opened
    title: Opened
  closed
    tag: closed
    title: Closed
   
Item:
  first:
    title: first item
    description: first list item
    status_id: opened
    depth: 0
  second:
    title: second item
    description: second list item
    status_id: opened
    depth: 0
  third:
    title: third item
    description: child of first item
    status_id: opened
    parent_id: first
  fourth:
    title: fourth item
    description: child of first item
    status_id: closed
    parent_id: first
  fifth:
    title: fifth item
    description: child of third item
    status_id: closed
    parent_id: third
  sixth:
    title: sixth item
    description: child of fifth item
    status_id: opened
    parent_id: fifth
  seventh:
    title: seventh item
    description: child of fourth item
    status_id: opened
    parent_id: fourth
이제 `symfony propel-build-all-load item` 명령으로 테이블을 생성하고, 테스트데이터를 입력합니다. 이후 테이블을 살펴보면 데이터가 입력된 것을 확인하실 수 있습니다.

사용자 삽입 이미지

item 테이블

사용자 삽입 이미지

status 테이블



2. Item 클래스의 `->setStatusId()` 메쏘드 오버라이드
BaseItem 클래스의 `->setStatusId()` 메쏘드는 `status_id` 필드에 저장될 아이디를 지정합니다. 하지만 이 자료는 숫자형이기 때문에, 나중에 상태 변경을 할때 문제가 생길 수 있습니다. 따라서 여기서는 `status` 테이블의 `tag` 필드값을 바탕으로 `item` 의 `status` 를 지정할 수 있도록 해보겠습니다.
# lib/model/StatusPeer.php
  static function getOneByTag($value)
  {
    $c = new Criteria;
    $c->add(self::TAG, $value);
    return self::doSelectOne($c);
  }
# lib/model/Item.php
  public function setStatusId($value) {
    if (is_numeric($value)) {
      $status_id = $value;
    }
    else {
      $status = StatusPeer::getOneByTag($value);
      $status_id = $status->getId();
    }
    parent::setStatusId($status_id);
  }
이제 $item->setStatusId('opened') 와 같이 함으로써 상태를 지정할 수 있게 되었습니다.


3. status 테이블과 조인
`item/list` 액션에서 `status` 관련 정보는 `$item->getStatus()` 를 함으로써 얻을 수 있습니다. 하지만 이는 추가적인 데이터베이스 쿼리를 발생시킵니다. 따라서 미리 조인을 하여서 이런 추가적인 쿼리를 하지 않도록 할 수 있습니다. `lib/model/itemPeer.php` 의 `getAllByThread()` 를 아래와 같이 수정합니다.
  static function getAllByThread()
  {
    $c = new Criteria;
    $c->addDescendingOrderByColumn(self::THREAD);
    $c->addAscendingOrderByColumn(self::LFT);
    return self::doSelectJoinStatus($c);
  }

4. View 수정
View 를 수정하기 위해, 먼저 `lib/model/Status.php` 에 `__toString()` 메쏘드를 추가합니다.
  public function __toString()
  {
    return $this->getTitle();
  }
이제 `$item->getStatus()` 호출을 통해서 `status` 테이블의 `title` 필드를 출력할 수 있습니다. (이렇게 하지 않는다면 `$item->getStatus()->getTitle()` 을 해야겠지요..) 마지막으로 템플릿을 수정합니다.

<style>
.closed
{
  text-decoration: line-through;
}
</style>

<p style="margin-top:20px">
<h1>Adjacent List Items</h1>

<table>
<thead>
<tr>
  <th>Id</th>
  <th>Parent</th>
  <th>Thread</th>
  <th>Depth</th>
  <th>Title</th>
  <th>Description</th>
  <th>Lft</th>
  <th>Rgt</th>
  <th>Status</th>
</tr>
</thead>
<tbody>
<?php foreach ($items as $item): ?>
<tr class="<?php echo $item->getStatus()->getTag() ?>">
    <td><?php echo link_to($item->getId(), 'item/show?id='.$item->getId()) ?></td>
      <td><?php echo $item->getParentId() ?></td>
      <td><?php echo $item->getThread() ?></td>
      <td><?php echo $item->getDepth() ?></td>
      <td><?php echo $item->getTitle() ?></td>
      <td><?php echo $item->getDescription() ?></td>
      <td><?php echo $item->getLft() ?></td>
      <td><?php echo $item->getRgt() ?></td>
      <td><?php echo $item->getStatus() ?></td>
  </tr>
<?php endforeach; ?>
</tbody>
</table>
사용자 삽입 이미지

List 템플릿



5. AJAX Interaction 추가
`listSuccess.php` 템플릿에 다음을 추가하여 AJAX 링크를 만든다.
<?php foreach ($items as $item): ?>
<tr class="<?php echo $item->getStatus()->getTag() ?>" id="item_<?php echo $item->getId() ?>">
    <td><?php echo link_to($item->getId(), 'item/show?id='.$item->getId()) ?></td>
      <td><?php echo $item->getParentId() ?></td>
      <td><?php echo $item->getThread() ?></td>
      <td><?php echo $item->getDepth() ?></td>
      <td><?php echo $item->getTitle() ?></td>
      <td><?php echo $item->getDescription() ?></td>
      <td><?php echo $item->getLft() ?></td>
      <td><?php echo $item->getRgt() ?></td>
      <td><?php echo link_to_remote($item->getStatus()->getTitle(), array(
        'url'       => 'item/toggle?id='.$item->getId(),
        'method'    => 'post',
        'complete'  => "$('item_".$item->getId()."').className=request.responseText;",
      )) ?></td>
  </tr>
<?php endforeach; ?>
그리고  `Item` 모듈에 `toggle` 액션을 추가하고, 클래스에 `toggle` 메쏘드를 추가하여 AJAX 결과를 처리할 수 있도록 한다.
# apps/item/modules/item/actions/actions.class.php
  public function executeToggle ()
  {
    $item = ItemPeer::retrieveByPk($this->getRequestParameter('id'));
    $this->forward404Unless($item);
    
    $status_tag = $item->toggleStatus();
    $this->status_tag = $status_tag;
  }
# lib/model/Item.php
  public function toggleStatus()
  {
    switch ($this->getStatus()->getTag())
    {
      case 'opened':
        $status_tag = 'closed';
        break;
      default:
        $status_tag = 'opened';
    }
    $this->setStatusId($status_tag);
    $this->save();
    return $status_tag;
  }
# apps/item/modules/item/templates/toggleSuccess.php
<?php echo $status_tag ?>

6. Next Steps
 - Nested status toggle: If a parent node is toggled to closed, its child nodes would be closed. And if every child nodes are closed, their parent node should be closed also.
 - Reduce database query: In toggle action, there are three queries that we can reduce.

'Programming' 카테고리의 다른 글

FLEX 구매대행 요금 계산기  (3) 2007/01/11
Flex Gnuboard Frontend  (0) 2007/01/09
Extending Symfony Adjacent List  (0) 2007/01/08
Symfony Adjacent List  (0) 2007/01/03
AJAX Post-It 만들기  (1) 2006/12/29
Building Protein List using BeautifulSoup and BioPython  (0) 2006/12/27
tags : Ajax, Symfony
Trackback 0 : Comment 0

AJAX Post-It 만들기

Programming 2006/12/29 13:37
PHP 웹 프레임워크인 Symfony[각주:1] 를 이용하여 AJAX 기반의 어플리케이션을 만들어보겠습니다. 그 중에서도 특히 Script.aculo.us[각주:2] 의 draggable_element 와 droppable_element 를 활용하여 Post-It 스타일의 메모장을 만들어볼 계획입니다.

1. 먼저 symfony 명령을 활용하여 프로젝트를 준비합니다. 프로젝트명은 note, 어플리케이션 이름도 note 로 하겠습니다.
# mkdir note
# cd note
# symfony init-project note
# symfony init-app note

2. 아파치 설정을 변경하시거나 해당 디렉토리를 웹이 접근가능한 폴더로 변경합니다. 그리고 테스트를 하시면 'Symfony Project Created' 페이지를 만나실 수 있습니다. 참고로 제 웹 설정을 올려보겠습니다.
# /etc/httpd/users/sunhwan.conf
<Directory "/usr/local/lib/php/data/symfony/web/sf">
    AllowOverride All
    Allow from all
</Directory>

<VirtualHost *:80>
    ServerName note
    DocumentRoot "/Users/sunhwan/working/note/web"
    DirectoryIndex index.php
    Alias /sf /usr/local/lib/php/data/symfony/web/sf

    <Directory "/Users/sunhwan/working/note/web">
        AllowOverride All
        Allow from all
    </Directory>
</VirtualHost>
사용자 삽입 이미지

좀 더 상세한 프로젝트 생성방법은 심포니 홈페이지[각주:3] 을 참고하시면 됩니다.

3. 이제 우리가 사용할 어플리케이션이 사용할 데이터베이스를 설정합니다. 지금 우리가 만들 어플리케이션은 규모도 크지 않고 다른 어플리케이션들과의 연동도 별로 없기때문에 XML 이나 다른 파일 형태로 저장하는 것도 나쁘지 않을 것 같았지만, 사실 Symfony 에서 이를 어떻게 구현할지 익숙하지 않아서 일단 데이터베이스를 사용하기로 했습니다.

note/config 디렉토리에 있는 database.yml, propel.ini, schema.yml 파일을 수정합니다.
# database.yml
all:
  propel:
    class:          sfPropelDatabase
    param:
      dsn:          mysql://root:@localhost/Note

# propel.ini
propel.database.url        = mysql://root@localhost/note

# schema.yml
propel:
  note:
    _attributes: { phpName: Note }
    id:
    title:    varchar(255)
    contents: longvarchar
    x:        smallint
    y:        smallint
    z:        tinyint
    width:    smallint
    height:   smallint
사실 이러면 안 되지만, 저는 방화벽이 설치된 로컬에서 MySQL 을 돌리는 관계로, root 비밀번호가 없습니다. database.yml 이나 propel.ini 파일의 아이디 뒷부분에 자신에 해당하는 비밀번호를 입력하면 됩니다.

4. 이제 데이터베이스를 생성합니다. Symfony 는 schema.yml 에 입력된 정보를 바탕으로 데이터베이스 구조를 생성할 수 있습니다.
symfony propel-build-model
symfony propel-build-sql
mysqladmin -u root create database note
symfony propel-insert-sql
데이터베이스에 접속해보시면 note 테이블이 생성된 것을 확인하실 수 있습니다. 또한 데이터베이스 자료와 PHP 자료형을 연결시켜주는 역할을 하는 모델들이 note/lib/model 디렉토리에 생긴것을 확인하실 수 있습니다.

5. 이제 생성된 모델을 이용해서 기본적인 데이터 입력, 호출, 수정, 그리고 삭제 (CRUD - Create, read, update, and delete) 를 하는 모듈을 만들어 보겠습니다. 모델명은.. 역시 note 입니다.
symfony propel-generate-crud note note Note
참고로 앞에서부터 어플리케이션, 모듈, 그리고 데이터모델 순서입니다. 즉, 만약 blog 라는 어플리케이션을 만들고, 그 아래 Article 이라는 데이터모델을 이용하여 post 라는 모듈을 만든다면 'symfony propel-generate-crud blog post Article' 이 되는 것입니다.
 
사용자 삽입 이미지

CRUD - List


사용자 삽입 이미지

CRUD - Show


사용자 삽입 이미지

CRUD - Create/Edit


6. 이제 템플릿을 수정할 차례입니다. 다른 부분이야 그대로 두고, note/list 액션의 템플릿을 바꿔서 노트형식으로 보이도록 할 계획입니다. 또한 이부분 draggable/dropperable element 들을 설정하여 만약 개별 노트의 위치가 변경되는 경우에 데이터베이스를 변경하도록 할 것입니다.

먼저 note/apps/note/modules/note/templates/listSuccess.php 파일을 수정합니다.
<div id='notepad' class='notepad' style='position: absolute; top: 5px; left: 5px;'>

<h1>note</h1>

<?php foreach ($notes as $note): ?>

  <div id='note_<?php echo $note->getId() ?>' class='note' style="
    position: absolute;
    left: <?php echo $note->getX() ?>px;
    top: <?php echo $note->getY() ?>px; ">
    <h2><?php echo link_to($note->getTitle(), 'note/show?id='.$note->getId()) ?></h2>
   
    <?php echo $note->getContents() ?>
  </div>
 
<?php endforeach; ?>

<?php echo link_to ('create', 'note/create') ?>

</div>
note/web/main.css 에 아래 내용을 추가합니다.
.note
{
  background-color: #FFFFCC;
  width: 200px;
  height: 200px;
}
그리고 테스트를 해보신다면 아래와 같은 화면을 보실 수 있습니다. 각각의 노트는 데이터베이스에 있는 X, Y 값을 사용하여 위치가 결정될 것입니다.
사용자 삽입 이미지

Post It!?


7. 이제 마지막으로 AJAX 액션을 추가할 차례입니다. Symfony 에는 Prototype 과 Script.aclo.us 가 기본적으로 내장되어 있기 때문에 따로 설치할 필요는 없습니다. 개체를 드래그 앤 드랍이 가능하도록 만들어주는 명령은 draggable_element 입니다. 또한 어떠한 개체가 자신의 위에 드랍되었을때 특정한 액션을 일으킬 수 있도록 하기 위해서 drop_receiving_element 를 사용합니다.

apps/note/modules/note/templates/listSuccess.php 를 여시고, 다음 내용을 맨 위쪽에 입력합니다.
<?php echo use_helper('Javascript') ?>
아래 내용은 'endforeach' 위에 입력합니다.
<?php echo draggable_element('note_'.$note->getId(), array()) ?>
그리고 아래내용은 파일의 제일 아래쪽에 입력합니다.
<?php echo drop_receiving_element('notepad', array(
  'url' => 'note/translate',
  'with' => "'id='+encodeURIComponent(element.id)+'&x='+element.style.left+'&y='+element.style.top")
) ?>
파일 전체를 다시 살펴보시려면 클릭하십시오.

more..


7. 마지막으로 apps/note/modules/actions/action.class.php 에 'executeTranslate' 을 추가하면 끝입니다.
  public function executeTranslate ()
  {
    $tmp = split('_', $this->getRequestParameter('id', ''));
    $note_id = $tmp[1];
   
    $note = NotePeer::retrieveByPk($note_id);
    $this->forward404Unless($note);

    $note->setId($note_id);
    $note->setX($this->getRequestParameter('x', 0));
    $note->setY($this->getRequestParameter('y', 0));
    $note->setZ($this->getRequestParameter('z', 0));

    $note->save();

    return true;
  }


  1. http://www.symfony-project.com [본문으로]
  2. http://script.aculo.us [본문으로]
  3. http://www.symfony-project.com/book/trunk/project_creation [본문으로]

'Programming' 카테고리의 다른 글

Extending Symfony Adjacent List  (0) 2007/01/08
Symfony Adjacent List  (0) 2007/01/03
AJAX Post-It 만들기  (1) 2006/12/29
Building Protein List using BeautifulSoup and BioPython  (0) 2006/12/27
Symfony Form Helper - object_select_tag  (0) 2006/12/24
Adapter Pattern in PHP  (0) 2006/12/20
tags : Ajax, Symfony
Trackback 1 : Comment 1