Monday, November 26, 2007

Dojo DateWidget in Thai [Dojo 1.0]

จากปีก่อน ที่เขียนถึง Dojo DateWidget in Thai สำหรับ version 0.4
วันนี้ได้ฤกษ์ลอง version 1.0 บ้าง



ใน version 1.0, widget สำหรับ date ใช้ชื่อว่า DateTextBox
ซึ่งประกอบด้วย widget 2 ตัวประกอบกัน ก็คือ TextBox กับ _Calendar
โดยตัว _Calendar จะเป็น popup ที่ทำหน้าที่แสดงปฎิทิน
(เครื่องหมาย _ นำหน้า หมายความว่า dojo ตั้งใจให้ widget นี้เป็น internal widget)

สิ่งที่เราต้องการ ก็คือ เราต้องการให้ internal state ของ DateTextBox
ยังเก็บค่า Date เป็น Date object ปกติของ javascript อยู่
แต่ต้องการให้เฉพาะส่วนการแสดงผลเท่านั้น ที่แสดงผลเป็นปี พ.ศ.
นอกจากนั้นยังต้องการให้ calendar ที่ popup ขึ้นมาแสดงผลเป็นภาษาไทยด้วย

ใน dojo 1.0, ตัว widget ซับซ้อนขึ้น
มีการ reuse เยอะขึ้น อย่างเช่น DateTextBox นั้นมี inherited structure ดังนี้

DateTextBox -> TimeTextBox -> RangeBoundTextBox ->
MappedTextBox -> ValidationTextBox -> TextBox

จากการไล่การทำงานของ DateTextBox เราจะพบจุดตัดที่เราสามารถ override
ส่วนการแสดงผลได้ที่ method format และ parse
สามารถนำมา declare class ตัวใหม่ที่ชื่อ ThaiDateTextBox ได้ดังนี้
dojo.declare("ThaiDateTextBox", [dijit.form.DateTextBox], {

// กำหนดตัว calendar ตัวใหม่ที่แสดงผลภาษาไทย
_popupClass: "ThaiCalendar",

// value ที่รับเข้ามา มี type เป็น date object
// เมื่อได้มาแล้วก็โยนไปให้ dojo.date.locale.format จัดการ format ให้ก่อน
// แล้วค่อยนำมาตัด ปี เพื่อ + 543 หรือ 43 เข้าไปอีกที (ขึ้นอยู่กับ format)
// note: มี bug กรณี format yy ที่จะมี overflow
format: function(value, constraints) {
if(!value || value.toString() == this._invalid){ return null; }
var sidx = constraints.datePattern.indexOf('yy');
var fvalue = dojo.date.locale.format(value, constraints);
var ycnt = 2;
var adj = 43;
if (/yyyy/.exec(constraints.datePattern)) {
ycnt = 4;
adj = 543;
}
return fvalue.substr(0,sidx) + (parseInt(fvalue.substr(sidx, ycnt)) + adj);
},

// value ที่รับเข้ามา จะอยู่ในรูป datePattern ที่เรากำหนดตอน declare widget
// เมื่อได้มา ก็ทำการ - 543 เพื่อแปลงเป็น ค.ศ. ก่อนส่งให้ dojo.date.locale.parse
parse: function(value, constraints) {
var sidx = constraints.datePattern.indexOf('yy');
if (/yyyy/.exec(constraints.datePattern)) {
nyear = parseInt(value.substr(sidx, 4)) - 543;
} else {
nyear = String(parseInt(value.substr(sidx, 2)) + 2500 - 543).substr(2,2);
}
var newValue = value.substr(0, sidx) + nyear;
return dojo.date.locale.parse(newValue, constraints);
}
});


ส่วนตัว Calendar ที่ popup ขึ้นมาให้ user เลือกนั้น
ถ้าไล่ดู code ภายใน จะเห็นได้ว่า เขามีกลไก i18n ในการแสดงผลพวกชื่อเดือน ชื่อวัน
แต่เนื่องจากเจ้า dojo 1.0 มันไม่ได้ bundle locale ของไทยมาให้ด้วย
เราจึงต้องสร้างขึ้นมาเอง

ในการที่จะสร้าง locale th
เจ้า dojo ได้เตรียมกลไกไว้เรียบร้อยแล้ว
โดย script ที่จะใช้สร้าง จะอยู่ใน directory util/buildscript/cldr
ซึ่ง dojo ได้นำข้อมูล xml จาก Common Locale Data Repository
และเตรียม xlst file ซึ่งใช้สำหรับ generate เป็น javascript code ให้เราแล้ว
แค่เราเข้าไปแก้ไข build.xml เพิ่ม "th-TH" เข้าไปในรายการที่ต้องการให้ generate

<!-- Arbitrary defaults. locales and currencies properties can be altered or eliminated to build the entire set -->
<property name="locales" value="en-us,th-th"/>


ประเด็นปัญหาถัดไปก็คือตัวปี พ.ศ.
ถึงแม้เราจะเลือกใช้ locale th-TH แล้วก็ตาม
มันก็ยังมีปัญหาว่า ปี ยังได้เป็นปี ค.ศ. อยู่ดี
ตรงส่วนนี้ เราต้องแอบเข้าไป override method ใน Calendar widget
ในใน dojo 0.4, จุดตัดสำหรับเรื่องนี้ค่อนข้างง่าย เพราะคนเขียนเขาแบ่ง method ออกเป็น method ย่อยๆ เพื่อให้อ่านง่าย
แต่ใน dojo 1.0 คนเขียน เขาเขียนส่วน render ยาวเป็นพรืด ทำให้เราเลือก override ยากหน่อย

สำหรับกรณีนี้ เราเลือก override method "_setText"
ซึ่งเป็นทางเลือกที่ไม่ค่อยดีนัก เพราะเป็นการ override แบบที่แอบไปใช้กลไกภายในของมัน
เพราะมันมีผลว่า ถ้ามันมีการ upgrade internal structure เมื่อไร
class ของเราก็จะมีโอกาส broken ได้โดยง่าย
dojo.declare("ThaiCalendar", [dijit._Calendar], {
_setText: function(node, text) {
if (/YearLabelNode/.exec(node.getAttribute("dojoattachpoint"))) {
arguments[1] = parseInt(text) + 543;
}
return this.inherited('_setText', arguments);
}
});


เวลานำไปใช้ ก็ใช้แบบนี้
<script type="text/javascript" src="./js/dojo/dojo.js"
djConfig="isDebug: true, parseOnLoad: true, locale: 'th'"></script>

...

<span id="d1" dojoType="ThaiDateTextBox"
constraints="{datePattern:'dd/MM/yy'}"/>

Related link from Roti

No comments: