Wednesday, October 03, 2007

Dojo Tree

การใช้ dijit.Tree
ใน dojo0.9, มีการ re-design Tree Widget ใหม่
โดย copy code เดิมมาจาก TreeV3 ใน dojo 0.4
แล้วปรับเปลี่ยนวิธี define item ใน tree เสียใหม่
โดยเปลี่ยนไปใช้ abstraction ที่ชื่อ Store
เป็นผู้ทำหน้าที่ provide data ให้กับ Tree

ลองดู use case แบบง่ายสุดก่อน
เริ่มด้วยการ define Data ที่จะใช้เป็น model ในการสร้าง Tree
      var treeData = {
label: "name",
identifier: "id",
items: [
{ id: 1, name: "Micro-X", children: [
{ id: 3, name: "Developement" },
{ id: 4, name: "R&D" }
] },
{ id: 2, name: "Ingres", children: [
{ id: 5, name: "Marketing" }
] }
]
}

var store = new dojo.data.ItemFileReadStore({ data: treeData });

Attributes หลักๆที่ Store ต้องการก็คือ
  • label จะใช้กำหนดว่า เวลา render Tree Node แล้ว
    ค่าที่แสดงเป็น label จะนำมาจาก attribute ที่ชื่ออะไร
  • identifier ใช้กำหนดว่า attribute name ไหน represent unique key ใน dataset
  • items ตัวเนื้อ data ที่ represent tree node

เมื่อ define Store แล้ว ก็สามารถนำไป feed ให้ Tree Widget ของเราได้
    <div dojoType="dijit.Tree" store="store" id="tree" label="Company">
</div>


แค่นี้ก็ได้ Tree หน้าตาแบบนี้ออกมา



ตัว Tree Widget นี้ มันมี extension point ให้เรา extend functional หลายตัว เช่น
ถ้าเราต้องการ ดัก click event
เราสามารถ override หรือ connect onClick ได้ดังนี้
    <div dojoType="dijit.Tree" store="store" id="tree" label="Company">
<script type="dojo/connect" event="onClick" args="item">
console.debug(item.name);
var name = store.getValue(item, "name");
console.debug(name);
</script>
</div>

ปัญหาก็คือ ตัว Store มันเปลี่ยนรูป Data ที่เรา feed เข้าไป (นัยว่าเพื่อ optimize อะไรบางอย่าง)
ถ้าเราใช้ item.name เพื่อดึงค่าขึ้นมา, ค่าที่ได้จะได้ datatype เป็น array แทนที่จะเป็น string ธรรมดา (ตามที่เรา declare ไว้)
ดังนั้นก่อนที่จะนำค่าจาก item ไปใช้ ก็เลยต้องมี wrapper code นี้พอกลงแบบนี้
store.getValue(item, "name")
Note: อันนี้นับว่าเป็นข้อเสียของ Tree api นี้อย่างมาก, implementation ควรจะซ่อนให้พ้นสายตาจากคนใช้

จุด extension อื่นๆ ก็มีอย่างเช่น
ถ้าต้องการ customize Label
    <div dojoType="dijit.Tree" store="store2" id="tree2" label="Company">
<script type="dojo/method" event="getLabel" args="item">
return store2.getValue(item, "name") + "_hi";
</script>
</div>

ข้อจำกัดอีกอย่างของ Tree Label ก็คือ มันแสดงได้เฉพาะ Plain Text เท่านั้น
ถ้าต้องการอะไรที่ดูดีขึ้น เช่นเปลี่ยนสี
ก็ให้ไปใช้ css class ช่วยแทน โดยทำการ override method getLabelClass
    <div dojoType="dijit.Tree" store="store4" id="tree4" label="Company" childrenAttr="children,staffs">
<script type="dojo/method" event="getLabelClass" args="item">
if (item) {
if (this.store.getValue(item, "type") == 'employee') {
return "employee";
}
}
return "";
</script>
</div>

Note: ใครที่จะ test ตรงนี้ ให้ระวังว่า ช่วงนี้มีการ refactor อยู่
ทำให้ code ในส่วนนี้หายไปจาก trunk, ผมกำลังรอ submit patch อยู่

ประเด็นที่น่าสนใจของ tree model ก็คือ เราสามารถ share node ได้
โดยการใช้ _reference attribute
      var treeData2 = {
label: "name",
identifier: "id",
items: [
{ id: 1, name: "Micro-X", children: [
{ id: 3, name: "Developement" },
{ id: 4, name: "R&D" }
] },
{ id: 2, name: "Ingres", children: [
{ _reference: { id: 3 }},
{ id: 5, name: "Marketing" }
] }
]
}

node "Development" ก็จะถูก share ใช้ร่วมกัน
แต่ข้อสงสัยที่ตามมาก็คือ use case ไหน? ที่มันจำเป็นต้องทำแบบนี้

นอกจากนี้ยังมี feature แปลกๆอีกเช่น
เราสามารถกำหนด attribute name ของ child element ได้มากกว่า 1 (default คือ "children")
เช่น data ชุดนี้ มี child element อยู่ 2 แบบ (children และ staffs)
เข้าใจว่า feature นี้มีไว้ สำหรับกรณี Tree ที่ mix content จากหลายๆ class
      var treeData4 = {
label: "name",
identifier: "id",
items: [
{ type: "company", id: 1, name: "Micro-X", children: [
{ type: "department",
id: 3, name: "Developement",
staffs: [
{type: "employee", id: 100, name: "pok"}
]
},
{ type: "department",
id: 4, name: "R&D",
staffs: [
{type: "employee", id: 101, name: "kob"}
]
}
] },
{ type: "company", id: 2, name: "Ingres", children: [
{ _reference: { id: 3 }},
{ type: "department", id: 5, name: "Marketing" }
] }
]
}

เวลาใช้ เราก็ต้องกำหนด childrenAttr ให้ Tree ด้วย
    <div dojoType="dijit.Tree" store="store3" id="tree3" label="Company" childrenAttr="children,staffs">
</div>

Related link from Roti

No comments: