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 : ,
Trackback 0 : Comment 0
◀ PREV : [1] : ... [57] : [58] : [59] : [60] : [61] : [62] : [63] : [64] : [65] : ... [72] : NEXT ▶