ระบบ Auto Complete ที่อยู่ไทย อย่างที่มันควรเป็น
Update 24 เมษา 60: ตอนนี้มีอัพเดตเพิ่มขึ้นเยอะมากๆ นับตั้งแต่วันที่ปล่อยบทความนี้ก็มีหลายๆ ท่านที่เข้ามาช่วยปรับปรุง ทั้งช่วยส่งโค้ดและช่วยออกไอเดีย ตอนนี้อะไรๆ ก็ดีขึ้นจากเดิมมาก อย่างเช่นฐานข้อมูล รวมตัวแกะ zip เราทำให้มันเล็กกว่า 100KB ได้สำเร็จแล้วครับ — คุณสามารถอ่านเพิ่มเติมเหล่านี้ได้ที่ท้ายสุดของบทความครับ
เบื่อครับ เบื่อเต็มที เว็บในไทยหลายเว็บมีการเก็บที่อยู่ของผู้ใช้ แต่กลับไม่มีใครพยายามทำให้มันกรอกง่ายขึ้นเลย ไม่มี!
เคยเจอไหมครับเว็บที่พยายามทำตัวฉลาด ให้เราเลือกรหัสไปรษณีย์ก่อน จากนั้นมันจะไป filter รายชื่อจังหวัดมาให้ พอเลือกจังหวัด มันก็จะไป filter อำเภอมาให้เลือกต่อ พอเลือกอำเภอ มันก็จะไป filter ตำบลมาให้เลือก หรือบางเว็บก็สิ้นสุดแค่อำเภอ ตรงตำบลนั่นให้พิมพ์เองตั้งแต่แรก
นี่ยังไม่นับชาวบ้านตามเกาะต่างๆ ที่ไม่มีรหัสไปรษณีย์นะ เจอแบบนี้เข้าไปจบเลย ไปไหนต่อไม่ได้ เช่น ตำบลเกาะทะลุ จังหวัดชุมพร
สังเกตไหมครับ สิ่งที่เกิดขึ้นคือ ผมต้องกรอกเลขที่บ้าน ชื่ออาคาร ถนน จากนั้นก็ไปกรอกรหัสไปรษณีย์, จังหวัด, อำเภอ และกรอกตำบลเป็นลำดับสุดท้าย
เฮ้ย นี่มันขัดกับสามัญสำนึกชาวไทยสุดๆ เลยนะ!
ตั้งแต่ไทยมีระบบที่อยู่ เราก็เรียงลำดับจากเล็กไปใหญ่มาตลอด นี่อะไร เดี๋ยวเล็กเดี๋ยวใหญ่ แล้วก็กลับไปเล็ก
ถึงจะเป็นเรื่องเล็กๆ แต่คุณเชื่อเถอะ ถ้าคุณสนใจ UX ให้มากกว่านี้ มันจะเป็นผลดีต่อเว็บของคุณ
แล้ว… มันควรเป็นยังไงล่ะ?
ประเด็นแรกเลยครับ กลับไปใช้วิธีกรอกที่อยู่แบบเล็กไปหาใหญ่ จากนั้นประเด็นที่สอง ทำยังไงก็ได้ ให้ผู้ใช้กรอกได้ง่าย และกรอกได้เร็วที่สุด
ผมสงสัยมาก ใครมันเป็นคนริเริ่มให้กรอกข้อมูลที่อยู่จากใหญ่ไปเล็ก ในเมื่อการกรองข้อมูลจากเล็กไปใหญ่มันช่วยให้กรอกได้ง่ายกว่ามาก
จากภาพ จะเห็นได้ว่า ผมกรอกไปเพียง “บ้านกลา” ยังพิมพ์ไม่เสร็จเลย ผมก็สามารถเลือกบ้านกลางที่อยู่ที่อำเภอสันป่าตองได้ทันที พิมพ์ไปแค่นี้ บวกกับอีก 1 คลิก ผมก็กรอกแบบฟอร์มครบ ทั้งตำบล อำเภอ จังหวัด และรหัสไปรษณีย์
เห็นไหม? ไม่เห็นต้องไปทำอะไรให้ยุ่งยากเลย
Talk is cheap. Show me the code. — Torvalds, Linus
ผมทำมาให้ลองเล่นแล้ว เชิญเลย!
ง่ายอย่างที่โม้จริงไหม ถามใจเธอดู
สังเกตให้ดี เว็บนี้ไม่มีการติดต่อไปยัง Server Side เลยแม้แต่น้อย นั่นแปลว่าเอาไปใช้ทำ Mobile App แบบ HTML5 ออฟไลน์ได้ด้วยครับ
โดยความลับที่ทำให้ไม่ต้องมี Server Side นั่นก็เพราะผมยัดฐานข้อมูลที่อยู่ประเทศไทยลงไปในหน้านี้ ทั้งหมด 7,477 ตำบล ฟังดูขนาดน่าจะใหญ่ ในหัวข้อถัดไปผมจะเผยความลับที่ทำให้ไฟล์มันเล็ก
ทำยังไง?
ขอเล่าตั้งแต่ต้น ผมได้ฐานข้อมูลที่อยู่ประเทศไทยมาจากแหล่งข้อมูลที่ไม่ประสงค์ออกนาม ซึ่งอัพเดตล่าสุดประมาณเดือนตุลา 2016 ได้มาในรูปแบบของไฟล์ Excel ผมจึงนำมาแปลงให้อยู่ในรูปของ JSON เพื่อให้นำไปใช้งานได้ง่ายมากขึ้นและมีขนาดเล็กลง
แต่มันก็ยังใหญ่มากอยู่ดี ตั้ง 759KB
ณ จุดนี้ผมมีทางเลือก 2 ทาง
- หาทางแปลงไปเป็นข้อมูลที่ Compact มากกว่านี้
- ต้องมี Server Side แล้วล่ะ
เมื่อ JSON มันใหญ่ไป ต้องหาตัวเลือกอื่น
โปรแกรมเมอร์ทั้งหลายล้วนรู้ดีว่า ถ้าไม่ใช่ JSON ทางเลือกอื่นก็เหลือเพียง 3 ตัวเลือก CSV, BSON และ MessagePack
ผมลองแปลงข้อมูลชุดนี้ไปทั้งสามแบบ ปรากฎว่า CSV มีขนาดที่เล็กที่สุด ที่ 560KB ตามมาด้วย BSON และ MessagePack ที่ก็ยังมีขนาดกว่า 700KB
กำ ยังใหญ่มากอยู่เลยว่ะ หรือต้องมี Server Side จริงๆ วะ?
มองหา Server Side ที่ตอบโจทย์
มันเป็นความตั้งใจอย่างใหญ่หลวงที่จะเปลี่ยนแปลงระบบกรอกที่อยู่ในเว็บไทย ฉะนั้นถ้าจะต้องมี Server Side มันต้องเป็นอะไรที่กระทบเว็บเดิมน้อย ถ้ายุ่งยาก คนไม่เปลี่ยนแปลงแน่นอน ฉะนั้นการทำ Server เองจึงเป็นอะไรที่ผมไม่เอาแน่นอน ดังนั้นจึงต้องมองหา BaaS
Firebase?
ด้วยความที่แพลนฟรีค่อยข้างโอเค และทำงานได้เร็ว ผมนึกถึงมันเป็นสิ่งแรก แต่ก็ต้องฝันสลายเมื่อพบว่าไม่ว่าจะทำอย่างไร มันก็ Query ไม่ได้อย่างที่ควรเป็น แถมตอบสนองช้าด้วย ใช้เวลามากกว่า 5 วินาที นานเกินไปสำหรับการนำมาใช้งานเป็น Auto-complete
Query ไม่ได้อย่างที่ควรเป็น?
ตัวอย่างเช่น “บ้านกลา” ตามตัวอย่างข้างต้น
ผลลัพธ์ที่ได้คือ Firebase จะนำข้อมูลทั้งหมดมาเรียง จากนั้นจะตัดก้อนข้อมูลออกไปเรื่อยๆ จนกว่าจะเจอ district แรกที่ขึ้นต้นด้วย “บ้านกลา”
ปัญหาที่เกิดขึ้นก็คือ มันไม่ได้ตัดข้อมูลที่ไม่ขึ้นต้นด้วย “บ้านกลา” ที่เหลือทิ้งไป
สิ่งที่ได้มาจึงเป็นข้อมูลตำบลที่ขึ้นต้นด้วย “บ้านกลา” ประมาณ 10 ตำบลและตามด้วยตำบลที่ไม่เกี่ยวข้องอีกหลายร้อยตำบลตามหลัง
ถ้า Firebase ไม่เวิร์ค ก็ไม่มี BaaS อะไรที่จะเวิร์คอีกแล้ว
กลับมาตั้งหลักครับ ผมต้องคิดให้ออกว่าจะทำอย่างไรให้พี่น้องโปรแกรมเมอร์ปรับตัวง่ายที่สุด การมี Server Side นั้นยังไงก็ไม่ควรด้วยประการทั้งปวง
กลับมาตั้งหลักที่ทางเลือกแรก “หาทางแปลงไปเป็นข้อมูลที่ Compact มากกว่านี้”
ไม่รู้อะไรดลใจ ผมจึงลองนำไฟล์ db.json ไปเข้า zip ดู ผลลัพธ์ที่ได้นั้นน่าตกใจมาก
เช็ดกระรอก แม่งเหลือ 65.2KB เองว่ะ!
ผมพูดเสมอว่าสักวัน JavaScript จะครองโลก เพราะมันทำได้ทุกอย่างเลย
ดังนั้นผมมีความเชื่ออย่างรุนแรงครับว่า JavaScript จะสามารถแกะ zip อ่านได้ในฝั่ง Client Side เลย ซึ่งก็ไม่ผิดหวังครับ JSZip ช่วยได้
ขนาดของ JSZip นั้นค่อนค้างใหญ่ อยู่ที่ 102KB แต่ยังไงก็เล็กกว่าไม่ใช้ zip มากมาย ซึ่งตัวเลขที่ยอมรับได้ในใจของผมคือ 200KB มันจึงเข้ารอบทันทีแบบไม่ต้องสงสัย (เพิ่มเติม ถ้า server รองรับ gzip ไฟล์ JSZip จะเหลือขนาดแค่ 34KB)
นอกเรื่อง: ปัจจุบันเว็บส่วนใหญ่มีขนาดเกิน 2MB ครับ 200KB จึงเป็นเรื่องจิ๊บจ๊อยมาก
ข้างล่างเป็นวิธีอ่านไฟล์ db.json ที่อยู่ใน zip ด้วย JSZip ให้ออกมาเป็น JSON String จากนั้นก็ parse เป็น Object Literal
ง่ายๆ แค่นี้เองครับ
ที่เหลือต่อไปก็ไม่ยากแล้ว การ Query ข้อมูลผมใช้ JQL.js ที่เขียนเอาไว้เมื่อหลายปีก่อน ส่วน autocomplete engine ผมเลือกใช้ Twitter Typeahead.js เพราะยืดหยุ่นสูงมากครับ เหมาะกับงานเราสุดๆ
โค้ดก็จะประมาณนี้
แค่นี้เอง ที่เหลือก็ไล่ทำให้ครบ เพราะอันนี้มันแค่ตำบลอย่างเดียว
แต่ช่างเหอะ ผมทำเป็น Lib มาให้ใช้ง่ายๆ แล้ว ไปโหลดมาใช้ได้เลยครับ
เชิญ Fork ไปตามสบาย ยินดีรับ Pull Request เป็นอย่างยิ่งครับ
วิธีใช้เขียนอธิบายไว้เรียบร้อยแล้ว หรือแกะจาก Demo ก็ได้ ผมเชื่อว่ามันง่ายจนเอาไปใช้กับเว็บปัจจุบันได้ทันที ใช้เวลาแก้ไม่ถึงชั่วโมงทุกเว็บ
ส่วนใครเกลียด jQuery จะพอร์ทไปเป็นแบบ VanillaJS ก็เชิญตามสบาย
ที่ผมใช้ jQuery ก็เพราะว่า Typeahead.js มันจำเป็นต้องใช้ jQuery น่ะ
ถ้าคุณมี Autocomplete Engine ที่ดีกว่าก็แนะนำกันเข้ามาได้ครับ :)
มาร่วมกันทำให้สังคมเว็บไทยดียิ่งๆ ขึ้นไปด้วยกันนะครับ
อัพเดต
- มี PR เข้ามาแล้วจากคุณ dtinth โดยเลิกใช้ข้อมูลแบบ zip แล้วเปลี่ยนโครงสร้างข้อมูลเป็นแบบต้นไม้เพื่อให้ขนาดเล็กลงแทน อ่านรายละเอียดได้ที่นี่ครับ PR#2
- มีการนำไอเดียไป implement ด้วย React แล้ว โดยคุณ zapkub ขอเชิญพี่น้องไป star โดยพลัน
https://github.com/zapkub/react-thailand-address-typeahead - คุณ saknarak ช่วยปรับโครงสร้างข้อมูลอีกครั้ง PR#7 ด้วยการรวบรวมคำซ้ำ 52 อันดับแรกแยกออกมาแทนที่ด้วยตัวอักษรภาษาอังกฤษ ทำให้ขนาดเล็กลงไปอีกกว่า 35% ปัจจุบันฐานข้อมูลที่เคยมีขนาด 759KB ลดเหลือเพียง 186KB
- ตอนนี้รองรับไฟล์ทั้ง 2 ฟอร์แมต คือทั้งแบบ json ตรงๆ เลย และแบบ zip
- เปลี่ยนไปใช้ตัวแกะ zip ที่เล็กกว่า JSZip (102KB) นั่นก็คือ zip.js (36.4KB)
การใช้งาน zip.js นั้นค่อนข้างยาก และไม่มีเอกสารสอนเลย กว่าผมจะทำได้ก็งงอยู่นานพอควร ดังนั้นผมจะแปะโค้ดเอาไว้ ผู้ที่สนใจจะได้นำไปลองใช้งานดูได้ครับ (ถ้ามีไฟล์ text ใน zip หลายไฟล์ก็ปรับเปลี่ยนโค้ดตามความเหมาะสมนะครับ เคสนี้ผมอ่านไฟล์ index ที่ 0 )
- ฐานข้อมูลแบบ json เหมาะกับเซิร์ฟเวอร์ที่เปิดใช้งาน gzip มีขนาดฐานข้อมูล อยู่ที่ 68.90 KB เท่านั้น
- แบบ zip เหมาะกับ server ที่ไม่มี/ไม่สามารถเปิดใช้ gzip ได้ มีฐานข้อมูลรวมตัวแกะ zip อยู่ที่ 87.50 KB
นอกจากนี้การใช้ฐานข้อมูลแบบ zip จะมีข้อเสียเพิ่มเติมอีกเล็กน้อย ดังต่อไปนี้
- ต้องใช้ resource ด้าน CPU และ RAM เพิ่มเล็กน้อย
- ต้องเสียเวลาในการโหลด dependencies สำหรับแกะ zip เพิ่มอีก 3 ตัว ถึงแม้จะมีขนาดเล็ก แต่ก็เสียเวลาไปกับ latency ที่เกิดจาก HTTP Request อีกเล็กน้อยอยู่ดี
จากภาพนี้จะเห็นได้ว่า หากเป็นฐานข้อมูลชนิด json
จะมีการโหลดไฟล์ db.json
เพียงไฟล์เดียว ใช้เวลาเพียง 123ms เท่านั้น ในขณะที่ฐานข้อมูลชนิด zip
จะต้องโหลดไฟล์หลายไฟล์ ใช้เวลารวมกันถึง 43+47+294+286 = 670ms ช้ากว่าแบบ json
ถึงครึ่งวินาที
- อ่านรายละเอียดเพิ่มเติมเรื่องที่ว่า ควรใช้อะไรดีได้ที่ README.md