Extending Symfony Adjacent List
Programming 2007/01/08 14:421. schema.yml 수정
각 item 에 상태를 추가하기 위해서 status 테이블을 만들고, item 테이블을 status 테이블과 조인할것입니다.
# config/schema.yml원래, `status_id` 란 필드명을 사용하고 `status` 란 테이블이 존재할 경우에는 propel 이 알아서 foreign key 를 생성해 주는데, 이번 경우에서는 `_foreign_keys` 옵션이 있어서 그런지 자동으로는 안 되는군요. `status_id` 를 위한 foreign_keys 항목을 추가합니다.
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)
테스트 데이터는 다음과 같이 변경합니다.
# data/fixtures/fixtures.yml이제 `symfony propel-build-all-load item` 명령으로 테이블을 생성하고, 테스트데이터를 입력합니다. 이후 테이블을 살펴보면 데이터가 입력된 것을 확인하실 수 있습니다.
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
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이제 $item->setStatusId('opened') 와 같이 함으로써 상태를 지정할 수 있게 되었습니다.
public function setStatusId($value) {
if (is_numeric($value)) {
$status_id = $value;
}
else {
$status = StatusPeer::getOneByTag($value);
$status_id = $status->getId();
}
parent::setStatusId($status_id);
}
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()이제 `$item->getStatus()` 호출을 통해서 `status` 테이블의 `title` 필드를 출력할 수 있습니다. (이렇게 하지 않는다면 `$item->getStatus()->getTitle()` 을 해야겠지요..) 마지막으로 템플릿을 수정합니다.
{
return $this->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>
5. AJAX Interaction 추가
`listSuccess.php` 템플릿에 다음을 추가하여 AJAX 링크를 만든다.
<?php foreach ($items as $item): ?>그리고 `Item` 모듈에 `toggle` 액션을 추가하고, 클래스에 `toggle` 메쏘드를 추가하여 AJAX 결과를 처리할 수 있도록 한다.
<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; ?>
# 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 |
