Wednesday, August 03, 2005

AJAX in Tapestry with Tacos

Post ก่อนหน้าที่ได้พูดถึง AJAX with Ruby on Rails ไปแล้ว
คราวนี้จะลอง implement feature แบบเดียวกันนี้ใน Tapestry บ้าง
โดยเราจะใช้ Component Library ของ Tacos

เริ่มที่ feature link_to_remote ของ Rails
ใน Tacos ก็มี component ที่ชื่อ PartialLink ที่ทำหน้าที่แบบเดียวกัน
ลองทำตัวอย่างแบบเดียวกับที่ทำใน Rails ก็จะต้องเขียน source code ดังนี้

เริ่มจาก Html Template File
จะประกอบด้วย Component PartialLink ที่ใช้ render html link
กับ Part Component ซึ่งใช้กำหนดบริเวณที่จะเกิด Partial Render
สังเกตุว่าเราจะ pass parameter refreshParts ให้กับ PartialLink ด้วยค่า "time_div"
ที่เป็น id ของบริเวณ Part ที่เราต้องการ render
<html>
<head jwcid="@Shell" title="Test Ajax Partial Link">
</head>

<body jwcid="@Body">
<a jwcid="@tacos:PartialLink" listener="ognl:listeners.refreshTime"
refreshParts="time_div"
>getTime</a>
<div id="time_div" jwcid="@tacos:Part">
<span jwcid="@Insert" value="ognl:currentTime">display time here</span>
</div>
</body>
</html>

ส่วน Page Descriptor File จะประกาศแค่ Property currentTime
มี datatype เป็น String และใช้เป็นแค่ temporary property
ในการเก็บค่าวันที่ที่ต้องการ render
...
<page-specification class="pok.test.TestPage">
<property-specification name="currentTime"
type="java.lang.String"/>
</page-specification>


ส่วนตัว Page Class นั้น จะ implement method ที่ชื่อ refreshTime
ที่จะถูกเรียกใช้เมื่อ user click บน PartialLink Component
package pok.test;

import java.util.Date;

import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.html.BasePage;

public abstract class TestPage extends BasePage {

public abstract void setCurrentTime(String timeStr);

public void refreshTime(IRequestCycle cycle) {
setCurrentTime(new Date().toString());
}
}


ข้อแตกต่างระหว่าง Tacos กับ Rails ที่เห็นชัดเจน
ก็คือ การ Render ผลลัพท์
ใน rails เราระบุ block ที่จะ render ได้แค่ 1 block
ส่วนใน Tacos เราสามารถระบุ block ได้มากกว่า 1 block
ตัวอย่างเช่น
<body jwcid="@Body">
<p>Test Partial Link</p>
<div id="time_div1" jwcid="@tacos:Part">
<span jwcid="@Insert" value="ognl:currentTime">display time here</span>
</div>
<a jwcid="@tacos:PartialLink" listener="ognl:listeners.refreshTime"
refreshParts="ognl:{'time_div1', 'time_div2'}"
>getTime</a>
<div id="time_div2" jwcid="@tacos:Part">
<span jwcid="@Insert" value="ognl:currentTime">display time here</span>
</div>
</body>

ในตัวอย่างข้างบน เราระบุว่าผลลัพท์ที่ได้จะ render บน
block time_div1 และ time_div2
สังเกตุว่าใน refreshParts parameter เราจะ pass parameter
เป็น Array

ข้อสงสัยอีกอย่างที่ผมสงสัยเจ้า Tacos ก็คือ วิธีการส่งผลลัพท์กลับไปที่ browser
ประเด็นก็คือ มันส่ง page กลับไปทั้งหมด แล้ว javascript ที่ฝั่ง browsser ค่อยเลือก render
เฉพาะที่ต้องการ หรือ ส่งเฉพาะส่วนที่เปลี่ยนแปลงกลับไปเท่านั้น
ก็เลยทดลอง sniff packet ที่รับส่ง
ผลปรากฎว่ามันส่งเฉพาะส่วนที่เปลี่ยนแปลง (ซึ่งดีต่อ traffic)

HTTP/1.1 200 OK
Date: Wed, 03 Aug 2005 10:19:44 GMT
Server: Jetty/5.1.4 (Mac OS X/10.4.2 ppc java/1.4.2_07
Content-Type: text/xml
Transfer-Encoding: chunked

a9
<?xml version="1.0" encoding="UTF-8"?><parts><part id="time_div2">
Wed Aug 03 17:19:45 ICT 2005
</part><part id="time_div1">
Wed Aug 03 17:19:45 ICT 2005
</part></parts>


ตัวอย่างที่ 2 ก็คือ กรณีที่ Rails มี feature form_remote_tag
เจ้า Tacos ก็มี component แบบเดียวกันที่ชื่อ PartialForm
ทดลอง Implement แบบเดียวกับที่ทำกับ Rails ได้ดังนี้



เริ่มจากส่วน Html Template
ในตัว Form จะมี TextField กับ Submit Button
ซึ่งกำหนดไว้ว่า เมื่อมีการ submit จะให้ method ที่ชื่อ partialSubmit ทำงาน
แล้วก็กำหนดให้มีการ render block div_item ใหม่
ส่วนใน block div_item ก็มีการใช้ Foreach component
ในการ render ​Items Array
สุดท้ายก็มี PartialLink ที่ใช้ในการลบข้อมูลใน list ทั้งหมด
<html>
<head jwcid="@Shell" title="Test Partial Form">
</head>

<body jwcid="@Body">
<form jwcid="@tacos:PartialForm" refreshParts="div_item"
listener="ognl:listeners.partialSubmit" direct="true">
<input type="text" jwcid="item@TextField" value="ognl:inputItem"/>
<input type="submit" jwcid="@Submit" label="Save"/>
</form>
<div id="div_item" jwcid="@tacos:Part">
<ul jwcid="@Foreach" source="ognl:items" value="ognl:item">
<li><span jwcid="@Insert" value="ognl:item"/>
</li>
</ul>
</div>
<a jwcid="@tacos:PartialLink" listener="ognl:listeners.clearAll"
refreshParts="div_item"
>Clear All</a>
</body>
</html>


ในส่วน Page Specification ประกอบด้วย
property items ซึ่งเป็น ArrayList
,item เป็นตัวแปรชั่วคราวสำหรับใช้ในการ render foreach loop
,inputItem เป็นตัวแปรที่ใช้รับข้อมูลจาก TextField ที่ user ป้อนเข้ามา
<page-specification class="pok.test.FormPage">

<property-specification name="items" type="java.util.ArrayList"
persistent="yes"/>

<property-specification name="item" type="java.lang.String"/>

<property-specification name="inputItem" type="java.lang.String"/>

</page-specification>


สุดท้ายส่วน Page Class
package pok.test;

import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.event.PageEvent;
import org.apache.tapestry.event.PageRenderListener;
import org.apache.tapestry.html.BasePage;

public abstract class FormPage extends BasePage implements PageRenderListener {

public abstract String getInputItem();

public abstract String getItem();

public abstract java.util.ArrayList getItems();
public abstract void setItems(java.util.ArrayList lists);

public void pageBeginRender(PageEvent event) {
if (getItems() == null) {
setItems(new java.util.ArrayList());
}
}

public void partialSubmit(IRequestCycle cycle) {
getItems().add(getInputItem());
}

public void clearAll(IRequestCycle cycle) {
getItems().clear();
}

}


ถ้าดูเปรียบเทียบกันระหว่าง Rails กับ Tapestry จะเห็นว่า
Rails ใช้จำนวนบรรทัดของคำสั่งและ config file น้อยกว่า
นอกจากนี้ก็ยังแตกต่างกันในแง่ concept ของการ render ที่ฝั่ง server ด้วย
โดยในส่วนของ Rails ที่ server code สามารถทำ Partial Render ได้
ไม่เหมือนกับ Tapestry ที่ฝั่ง Server จะทำการ Render ทั้ง Page
แต่ PartialService จะเลือกส่งกลับเฉพาะ Part ที่ระบุ

Related link from Roti

No comments: