development

A 7-post collection

Using ES6 Promises in Angular Apps

ที่จริงคอนเซ็ปต์เรื่อง promise ไม่ใช่เรื่องใหม่ (อ่านเพิ่มเติมเรื่อง promise) และ Angular เองก็ได้รับเอาคอนเซ็ปต์นี้เข้ามาเป็นส่วนหนึ่งของ framework ซึ่งเราจะเรียกใช้งานได้จากการ inject $q (วิธีการใช้ $q) โดยผมพบด้วยตัวผมเองว่าการใช้ promise ทำให้ผมสามารถทำงานกับ Angular ได้อย่างมีความสุขขึ้นอย่างมาก โค้ดที่เขียนออกมานั้นอ่านและเข้าใจได้ง่ายกว่าเดิมมาก จากเดิมต้องเขียนด้วยการใช้ callback ซึ่งก็เข้าใจยากด้วยตัวเองอยู่แล้ว ยังยากต่อการอ่านให้รู้เรื่องด้วยเนื่องจากการเขียน callback ไม่ได้ถูกออกแบบมาให้อ่านด้วยมนุษย์ได้ง่าย ๆ

แต่สำหรับคนที่ไม่เคยได้ยินคำว่า promise มาก่อน ผมจะอธิบยไว้ตรงนี้เสียหน่อยว่ามันก็เหมือนกับ "สัญญา" ดังความหมายของมันนั่นแหละ มันคือสิ่งที่จะมี "ค่า" ในอนาคต แม้ว่ายังไม่ใช่ตอนนี้ ลักษณะการใช้งานทั่ว ๆ ไปที่เราจะเห็นได้ก็เช่น

let a = some promise;  
a.then(  
    function afterA(promisedValue) {
        ... do something here ...
    });

จากตัวอย่างนี้ ... ฟังก์ชัน afterA จะถูกเรียกเมื่อ "สัญญา" ได้กลายเป็นความจริงแล้ว (ไม่เกิดขั้นทันที) และจะถูกส่งค่าที่ได้สัญญาเอาไว้ใส่ไปใน promisedValue ด้วย ถ้าดูตรงนี้แค่นี้ก็อาจจะคิดว่ามันก็ไม่ได้ต่างกับ callback หนิ แค่มีตัวอักษร .then ซึ่งก็จริง เพราะว่า .then เองก็ยังต้องใช้ callback เลย แต่เชื่อหรือไม่ว่าแค่มี .then ก็ทำให้อ่านโค้ดได้ง่ายขึ้นเป็นโขแล้ว

อีกหนึ่งตัวอย่างที่แสดงว่า promise มันน่าจะดีกว่า callback แน่ ๆ ก็เช่น

let a = some promise;  
a  
.then(
    function afterA(promisedValue) {
        ... do something here ...
        let b = some promise;
        return b;
    })
.then(
อ่านต่อ »

Poor scrolling performance using bootstrap-select on a large list

Bootstrap select (เว็บไซต์) เป็น plugin ที่ทำให้เราสามารถ "รับประกัน" ประสบการณ์การใช้งาน dropdown list ของผู้ใช้ได้ เนื่องจากโดยปกติแล้ว dropdown list หรือ <select> มีความแตกต่างขึ้นอยู่กับ browser และก็ยากต่อการกำหนดค่าต่าง ๆ เนื่องจากสามารถกำหนดค่า CSS ได้อย่างจำกัดอย่างยิ่ง จึงมีคนคิด bootstrap select ขึ้นมาโดยอาศัยการจำลอง "ปุ่มกด" ประกอบกับเทคนิคเล็กน้อย เพื่อใช้แทน <select>

หากเราท่านติดตั้ง bootstrap select และใช้งานมันอย่างที่ได้เขียนกำกับไว้แล้วนั้น ผลลัพธ์ก็จะงดงามอย่างไม่น่ามีปัญหาอะไร

ทว่าปัญหาจะเกิดขึ้นก็ต่อเมื่อ เราเริ่มใช้ list ที่มีขนาด "ใหญ่" นี่ที่นี้ผมกล่าวถึง ราว ๆ 60-70 ขึ้นไป เช่น จังหวัดในประเทศไทย หรือรายชื่อมหาวิทยาลัยในประเทศไทย ซึ่งผมได้ใช้ bootstrap select กับข้อมูลเหล่านี้ แล้วพบว่าในบางกรณีมีปัญหาการ "scroll หน้าเว็บที่ช้าเอามาก ๆ" และเป็นไปอย่างผิดปกติ กล่าวคือเมื่อเรา คลี่ select ออกมาการ scroll ก็ลื่นปรื้ดตามปกติ แต่เมื่อเรา หุบ select เข้าไปการ scroll กับช้าราวกับเต่าคลาน (framerate ที่ผมพบไม่น่าจะเกิน 5 ต่อวินาที)

เมื่อผมลองดูหน้าต่าง performance ของ google chrome ก็จะได้ภาพดังต่อไปนี้

จะเห็นว่า framerate น้อยมาก ๆ ตอนที่มีการ scroll เกิดขึ้นจะเห็นกราฟสีเขียวดรอปลงในช่วงนี้

ที่แปลกคือปัญหาการ scroll เกิดขึ้นเมื่อ หุบ list เข้าไปแล้วซึ่งหมายความมว่า display: none ซึ่งตามความเข้าใจของผม browser ก็ควรจะทำงานน้อยลง แม้ว่า list จะใหญ่มี items เยอะ แต่มันก็สามารถมองข้ามไปด้วยเพราะว่ามันไม่ต้อง render เลยแม้แต่น้อย (ซึ่งผมอาจจะผิด)

หากมองลึกลงไปหน่อยจะเห็นว่าส่วนที่ช้าที่สุด ที่ทำให้การ

อ่านต่อ »

Tracking Angular.js Apps with Google Analytics

Google Analytics is a great service. It provides us, web developers, variety of insights about our users. Obviously, the more we understand our clients the more we can push our websites to the better stage for them.

Usually, to put a Gogole Analytics service onto normal "refreshy" websites is not a hard work at all becasue Google has managed to create a very simple way to install it, just copy and paste the provided tracking code that's all.

However, that tracking code will be run only once per refresh. This works properly considering when we click on a hyperlink, the web page refreshes, then, of course, the tracking code is triggered and notices that you have got into a new page.

A problem arises when it comes to the world of "refreshless" or single-page websites (e.g. AngularJS) where the tracking code will be run only once at the load time. According to this, the tracking code will not be able to collect proper information about the user as before. Now, it is our duty to come up with solution to "live" with it.

So far, the cleanest solution I found has been provided with an answer to this very problem posted on Stackoverflow.com

The code snippet is as follows:
(Note: I use Angular UI router instead of the native router)

rootScope.$on '$stateChangeSuccess', () ->  
    $window.ga 'send', 'pageview', { page: $location.url() }

Put this snippet to where you can make sure that this snippet will be run only once right after the page loads.

Don't forget to inject $window and $location

The above snippet will listen to every route change and call the Google Analytics service to acknowledge and log that action.

And also put your tracking code (get it from Google Analytics website) on the main

อ่านต่อ »

A Fresh Approach to Deal with Browser's Caching

เว็บบราวเซอร์สมัยใหม่ถูกสร้างมาให้พยายาม แคช ข้อมูลที่เป็น static เช่น CSS, Javascript หรือแม้แต่รูปภาพ (มักไม่ cache ไฟล์ html) เพื่อให้การโหลดครั้งต่อไปทำได้เร็วขึ้นแน่นอนว่าก็เป็นเรื่องดี​ แต่ก็ย่อมมีข้อเสียตามมาคือ เมื่อเราแก้ไขเว็บ (แก้ทั้ง html, js, css) แล้วอัพโหลดผลการแก้ไขไปยังเซิฟเวอร์อาจจะทำให้ผู้ใช้เข้าไปใช้เว็บไม่ได้ หรือเข้าได้แต่ผลการแสดงไม่ถูกต้อง เนื่องก็ด้วย html ได้อันใหม่แต่ว่า js กับ css อาจจะยังได้ของที่อยู่ใน cache อยู่ ซึ่งแท้จริงแล้วผู้ใช้สามารถแก้ปัญหานี้ได้ด้วยการ refresh หน้าจอเพียงเท่านั้น browser ก็จะขอข้อมูลสดใหม่จาก server ซึ่งก็จะแก้ปัญหานี้ได้ แต่การแสดงหน้าเว็บที่ ไม่พร้อมใช้งาน ก็เป็นสิ่งที่รับไม่ได้เช่นกัน

เท่าที่ทราบเราไม่สามารถ บังคับ ให้ browser รีเฟรช ข้อมูลที่ cache จากคำสั่งทาง server โดยตรงได้ เพราะว่าสิทธิ์การแคชเป็นของ browser (และการ cache เป็นเรื่องดี เราก็ไม่อยากปิดระบบนี้) เช่น การโหลดไฟล์ สมมติ app.js ซึ่งได้ถูก cache ไว้ก่อนแล้ว ถ้า cache ยังไม่หมดอายุ browser จะไม่ถามไปยัง server ด้วยซ้ำว่าอันนี้เป็นของล่าสุดหรือเปล่า ? เพราะฉะนั้นในเมื่อ browser ไม่ถาม เราก็หมดสิทธิ์สั่งการ

แต่เดี๋ยวก่อน... เพราะว่ามีสิ่งหนึ่งที่ browser มักไม่ cache คือไฟล์ html นั่นเอง และการไม่ถูก cache ของ html นี่เองที่นำไปสู่การแก้ปัญหานี้! การแก้ปัญหาคือการใส่ query string ต่อท้าย resource ที่เราต้องการโหลดเช่น แทนที่จะเรียกแค่ app.js เราก็ต่อท้าย query string ไปด้วยเช่น app.js?version=10 ซึ่งแท้จริ

อ่านต่อ »

AngularJS Routing with Permissions

ไม่ว่าจะเขียนเว็บด้วยเทคนิคอะไรก็ตาม สิ่งที่เราต้องทำก็ไม่พ้นระบบ Routing และยิ่งมากกว่านั้นคือจำกัดว่าใครสามารถเข้าไปชมหน้าไหนได้บ้าง (authorization)

ปัญหานี้ไม่ใช่เรื่องใหญ่เลยเมื่อเราเขียนเว็บไซต์ที่จัดการเรื่องนี้จากทาง backend กล่าวคือหากเราใช้ PHP Framework เช่น Laravel เราก็สามารถจัดการเรื่องนี้ใน routes.php โดยอาจจะเขียน filter กำกับไว้ (ไม่ว่าจะเขียนไว้ที่ใดก็ตาม) ด้วยว่าใครสามารถเข้า route นี้ได้บ้าง ถ้าไม่ได้จะต้องทำอย่างไรต่อไป เราก็แค่เขียนฟังก์ชันกำกับไว้เท่านั้นเอง

แต่เมื่อเราเสกสรรค์เว็บไซต์ด้วยเทคโนโลยีทาง frontend อย่าง AnuglarJS แล้วปัญหานี้มันนุ่มลึกกว่านั้นอยู่พอสมควร ระลึกไว้เสมอว่าเรามาใช้ AngularJS เพื่อต้องการการตอบสนองอย่างทันที ราบรื่น สวยงาม

Basic approach

ผมจัดการ routing ใน AngularJS ด้วย angular-ui-router ซึ่งก็เป็น pagkage ที่หลายคนแนะนำว่าดีมีความสามารถหลากหลายกว่าตัวหลักของ AngularJS เอง ผมอาจจะเร่ิมเขียน หน้า routes.coffee ดังนี้

angular.module 'routes', []  
.config [
  '$stateProvider'
  (stateProvider) ->

    stateProvider
    .state 'user', {
      url: '^/user'
      templateUrl: "public/user.html"
      controller: 'userCtrl'
    }
]

ตอนนี้ผมได้กฎการ route โดยกำหนด state user ขึ้นมาโดยผูก state นี้กับ url /user (การเขียน ^ นำหน้าทำให้เป็น absolute url ไม่ขึ้นกับว่า state นี้เป็นรองจาก state ไหน)

ผมอาจจะมีไฟล์ user.coffee ไว้เขียนรายละเอียดของ userCtrl ด้วย โดยผมอยากให้ userCtlr เป็น controller เกี่ยวกับหน้าส่วนตัวของ user แต่ละคนในระบบ ดังนั้นหน้านี้จะต้องเป็นหน้าที่เฉพาะคนที่ login แล้วเท่านั้นจึงเข้าได้ หากเค้ายังไม่ก็จะถูกเตะออกไปที่อื่นเช่นหน้าแรก

โดยสมมติว่าผมมี userService ไว้สำหรับทำงานเช่น เช็คว่าผู้ใช้ login อยู่หรือเปล่า หรืองานอื่น ๆ

angular.module 'user', []  
.controller [
  '$
อ่านต่อ »

Adding CSS !important flag with jQuery

แน่นอนว่าในบางทีเราอาจจะต้องการกำหนดค่าบางอย่างแบบ dynamic ด้วย jQuery เช่นความสูงของหน้า ๆ หนึ่ง ซึ่งอาจจะไม่เท่ากันในแต่ละสถานการณ์การทำทุกอย่างด้วย CSS อาจจะซับซ้อนเกินไป หรืออาจจะทำไม่ได้ แต่ก็ติดที่ว่าหากมีการกำหนด !important flag ในกฎ CSS แล้วแน่นอนว่าก็ต้องใช้ !important ด้วยกันมาหักล้าง

ปัญหาคือ jQuery ไม่สามารถใส่ flag นี้เข้าไปในการกำหนดค่าต่าง ๆ ของ CSS ได้ เราไม่สามารถ $(somthing).css('height', '100px !important'); แล้วให้ผลลัพธ์ที่ถูกต้องได้ (อย่างน้อยก็ในช่วงเวลาที่ผมเขียนอยู่...)

เราจึงต้องใช้วิธีอื่น ... ซึ่งก็มีคนเสนอหลายวิธี ผมพบว่าวิธีที่ผมชอบที่สุด (แน่นอนว่าต้องเขียนง่ายมาก่อน ฮ่ะ ๆ) ก็คือนี่ ...

var elem = $("#elem");  
elem[0].style.removeAttribute('height');  
elem[0].style.setProperty('height', '100px', 'important');  

หยิบมาจาก stackoverflow.com จากคำตอบของคุณ BettaSplendens

อ่านต่อ »

Laravel Controller Can't Return in Boolean

"รีบ ๆ เรียน ดูแค่ตัวอย่างแล้วก็เอาไปทำเองบ้าง" เป็นวิธีที่ผมใช้เรียนการทำเว็บและเขียนโปรแกรมมาตลอด เพราะว่าตัวเองก็ค่อนข้างรีบ จะให้มานั่งอ่านละเอียดจน ซึ้ง แล้วค่อยมาเริ่มเขียนก็อาจจะทนไม่ไหวซะก่อน

แต่ถ้าพูดถึงเรื่อคงความประหยัดเวลาในการเรียนรู้แล้ว แม้วิธีนี้จะทำให้เราสามารถทำผลงานชิ้นแรกออกมาได้เร็ว แต่เราอาจจะต้องเสี่ยงต่อการสูญเสียเวลากับการแก้บัคที่ไม่ควรเกิดขึ้นอีกมาก

หลาย ๆ ครั้งที่ติดปัญหาและก็ต้อง google เพื่อหาวิธีแก้อยู่เป็นแรมหลายชั่วโมงเทียว สุดท้ายปัญหาจริง ๆ ก็คือผมไม่ได้อ่าน Documentation ส่วนที่เป็นเงื่อนไขที่เขียนอยู่ข้างล่างนั่นเอง เหตุการณ์เหล่านี้ไม่เกิดขึ้นครั้งเดียว แต่หากผมใช้เวลากับการอ่านเงื่อนไขด้านล่างบ้าง ก็คงประหยัด น้ำตา ไปมากทีเดียว

ปัญหาที่พบและกว่าจะหาต้นเหตุเจอได้กับเป็นข้อจำกัดง่าย ๆ ของ Laravel เอง ที่ผมควรจะรู้ (ผมไม่มั่นใจว่าได้เขียนไว้ใน Documentation หรือเปล่า) นั่นคือ ฟังก์ชัน Controller ใน Laravel ไม่สามารถรีเทิร์นเป็น Boolean ได้ !

เหตุการณ์

โดยปกติก็คงไม่มีใครเขียนให้ controller รีเทิร์นเป็น boolean หรอก (โดยปกติผมก็ไม่) แต่ตอนนั้นผมแค่ต้องการให้มันตัดจบที่ตรงนี้ ไม่มีทำคำสั่งส่วนข้างล่างต่อ เช่น

public function home() {  
    $a = 10;
    $b = $a + 5;

    var_dump($a);
    return true;

    return Response::json([ 'a' => $a, 'b' => $b ]);
}

ผมแค่ต้องการดูว่า ตัวแปร $a มีค่าเท่าไหร่ด้วย var_dump แต่ก็ต้องไม่ต้องการให้เกิดการรีเทิร์นให้ผลลัพธ์มาบดบัง var_dump ด้านบน ผมจึงตัดสินใจ return true; ก่อน ทั้ง

อ่านต่อ »