เทมเพลต

3

แปลไปแล้ว

ในบทนี้ คุณจะได้

  • เรียนรู้วิธีใช้เทมเพลตด้วย Spacebars
  • สร้างเทมเพลตสามตัวแรกของคุณ
  • เข้าใจการทำงานของตัวจัดการใน Meteor
  • สร้างแอพต้นแบบที่ทำงานกับข้อมูลง่ายๆ
  • เพื่อให้สร้างแอพได้ง่ายขึ้น เราจะเริ่มด้วยการสร้างเปลือกนอกของแอพขึ้นมาก่อนด้วย HTML แล้วค่อยๆ เพิ่มจาวาสคริปต์เข้าไปข้างในจนแอพสามารถทำงานได้ หรือที่เรียกว่า วิธีการแบบนอกเข้าใน (outside-in )

    ซึ่งในบทนี้ เราจะแก้ไขและสร้างไฟล์เฉพาะที่อยู่ในโฟลเดอร์ /client เท่านั้น

    ขั้นแรกให้คุณเปิดไฟล์ main.html ที่สร้างไว้แล้วในโฟลเดอร์ /client แล้วป้อนโค้ดต่อไปนี้ลงไป :

    <head>
      <title>Microscope</title>
    </head>
    <body>
      <div class="container">
        <header class="navbar navbar-default" role="navigation"> 
          <div class="navbar-header">
            <a class="navbar-brand" href="/">Microscope</a>
          </div>
        </header>
        <div id="main">
          {{> postsList}}
        </div>
      </div>
    </body>
    
    client/main.html

    โดยเราจะใช้ไฟล์นี้เป็นเทมเพลตหลักของแอพ และโค้ดที่คุณเห็นในไฟล์ก็เป็น HTML เกือบทั้งหมด ยกเว้นตรงตำแหน่ง {{> postList}} ที่ Meteor จะนำเนื้อหาข้อมูลซึ่งได้จากเทมเพลต postsList แทรกเข้าไปแทน สิ่งที่เราจะทำต่อไปก็คือ สร้างเทมเพลทเพิ่มขึ้นอีกสองสามตัว

    เทมเพลทของ Meteor

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

    เริ่มกันต่อโดยการสร้างโฟลเดอร์ /templates ข้างใน /client เพื่อไว้เก็บเทมเพลททั้งหมด และสร้างโฟลเดอร์ย่อย /posts ไว้ใน /templates อีกชั้น เพื่อแยกเก็บเทมเพลทต่างๆที่เกี่ยวกับข่าวที่โพสท์

    การค้นหาไฟล์

    Meteor เก่งในเรื่องการค้นหาไฟล์ ไม่ว่าคุณจะสร้างไฟล์ไว้ที่ไหนในโฟลเดอร์ /client มันก็จะค้นหาจนเจอและจัดการคอมไพล์ได้อย่างถูกต้อง สิ่งที่เราได้คือเราไม่ต้องคอยผูกไฟล์เข้าไปในแอพอยู่ตลอด ไม่ว่าจะเป็นไฟล์จาวาสคริปต์หรือสไตล์ชีต (CSS)

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

    ตอนนี้เราก็พร้อมจะสร้างเทมเพลทที่สองแล้ว ให้คุณสร้าง posts_list.html ในโฟลเดอร์ client/templates/posts ด้วยโค้ดต่อไปนี้

    <template name="postsList">
      <div class="posts page">
        {{#each posts}}
          {{> postItem}}
        {{/each}}
      </div>
    </template>
    
    client/templates/posts/posts_list.html

    แล้วสร้าง post_item.html อีกตัวตามนี้

    <template name="postItem">
      <div class="post">
        <div class="post-content">
          <h3><a href="{{url}}">{{title}}</a><span>{{domain}}</span></h3>
        </div>
      </div>
    </template>
    
    client/templates/posts/post_item.html

    ให้สังเกตุตรงแอททริบิวท์ name="postsList" ในแท็ก template ซึ่งเป็นชื่อที่ Meteor ใช้เพื่อเชื่อมโยงเทมเพลทต่างๆเข้าด้วยกัน (โดยไม่สนใจชื่อไฟล์)

    และแล้วก็ถึงเวลาที่เราจะมาทำความรู้จักกับระบบเทมเพลทของ Meteor ที่เรียกว่า Spacebars ซึ่งจริงๆแล้วก็คือ HTML ที่มีส่วนขยายเพิ่มสามส่วน ได้แก่ ตัวแทนที่ (inclusions หรือ partials), นิพจน์ (expressions) และ บล็อกตัวช่วย (block helpers)

    ตัวแทนที่ มีรูปแบบเป็น {{> templateName}} ทำหน้าที่บอกให้ Meteor นำเทมเพลทตามชื่อที่ระบุมาแทนที่ (ในโค้ดของเราคือ postItem)

    นิพจน์ เช่น {{title}} เป็นได้ทั้งคุณสมบัติของอ็อบเจกต์ตัวที่กำลังใช้งาน หรือค่าที่คืนกลับมาจากตัวช่วยเทมเพลท ที่สร้างไว้ในตัวจัดการของเทมเพลทปัจจุบัน ดังจะได้อธิบายในลำดับต่อไป

    บล็อกตัวช่วย เป็นแท็กพิเศษที่ใช้ควบคุมลำดับการทำงานของเทมเพลท เช่น {{#each}}...{{/each}} หรือ {{#if}}...{{/if}}

    ต้องการข้อมูลเพิ่มเติม

    คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับ Spacebars ได้ที่ หน้าคู่มือการใช้งาน Spacebars

    รู้มาถึงตรงนี้ เราก็พอจะทำความเข้าใจกับสิ่งที่เกิดขึ้นได้ดังนี้

    สิ่งที่เกิดขึ้นในเทมเพลท postsList ก็คือ บล็อกตัวช่วย {{#each}}..{{/each}} จะทำงานซ้ำเป็นจำนวนครั้งเท่ากับจำนวนข้อมูลที่อยู่ในอ็อบเจกต์ posts โดยในแต่ละรอบของการทำงาน เนื้อหาจากเทมเพลท postItem จะถูกใส่เพิ่มเข้าไปในเทมเพลทหลัก

    แล้วอ็อบเจกต์ posts ได้มาจากไหน คำตอบคือ ได้มาจาก ตัวช่วยเทมเพลท ซึ่งทำหน้าที่คล้ายกับถังข้อมูลที่เตรียมไว้ให้เทมเพลทเรียกใช้

    ส่วนในเทมเพลท postItem ก็ไม่มีอะไรซับซ้อน มันใช้แค่ 3 นิพจน์ คือ {{url}} และ {{title}} ที่ดึงค่ามาจากข่าวที่โพสท์ และ {{domain}} ที่ได้ค่ามาจากตัวช่วยเทมเพลท

    ตัวช่วยเทมเพลท

    ถึงตรงนี้เราก็ได้ลองใช้ Spacebars กันมาบ้างแล้ว อันที่จริงมันก็คือ HTML บวกกับแท็กพิเศษอีกนิดหน่อย ที่แตกต่างจากภาษาอื่น เช่น PHP (หรือแม้กระทั่งหน้า HTML เองที่สามารถใส่จาวาสคริปต์เข้าไปได้) ก็คือ Meteor แยกส่วนที่เป็นโค้ดคำสั่งออกจากเทมเพลท โดยตัวเทมเพลทเองทำงานได้แค่คำสั่งพื้นฐานเท่านั้น

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

    มองอีกมุมหนึ่ง เทมเพลทจะทำหน้าที่แค่แสดงหรือทำงานซ้ำๆ ตามค่าตัวแปรที่ได้มา ส่วนตัวช่วยจะทำหน้าที่กำหนดค่าให้ตัวแปรก่อนที่เทมเพลทจะนำไปใช้

    มีคอนโทรลเลอร์ใน Meteor บ้างมั้ย

    เราอาจมองว่าตัวช่วยเทมเพลท เป็นเหมือนคอนโทรลเลอร์ แต่เอาเข้าจริงแล้วคอนโทรลเลอร์ ในมุมมองของระบบ MVC จะทำหน้าที่บางอย่างแตกต่างออกไป

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

    เพื่อให้เข้าใจง่าย เราจะตั้งชื่อไฟล์ที่เก็บโค้ดตัวช่วยเทมเพลท ตามชื่อเทมเพลท แต่เปลี่ยนนามสกุลเป็น .js แทน โดยเริ่มด้วยการสร้างไฟล์ posts_list.js ใน /client/templates/posts แล้วก็ใส่โค้ดตัวช่วยเทมเพลทตัวแรกของเราเข้าไป ดังนี้

    var postsData = [
      {
        title: 'Introducing Telescope',
        url: 'http://sachagreif.com/introducing-telescope/'
      }, 
      {
        title: 'Meteor',
        url: 'http://meteor.com'
      }, 
      {
        title: 'The Meteor Book',
        url: 'http://themeteorbook.com'
      }
    ];
    Template.postsList.helpers({
      posts: postsData
    });
    
    client/templates/posts/posts_list.js

    ถ้าคุณทำถูก คุณควรจะเห็นหน้าจอแอพคล้ายๆแบบนี้

    Our first templates with static data
    Our first templates with static data

    สิ่งที่เราทำไปมีสองอย่างคือ เราได้สร้างข้อมูลตัวอย่างในอาร์เรย์ postsData ซึ่งที่จริงควรจะดึงมาจากฐานข้อมูล แต่เพราะเรายังไม่รู้ว่าจะทำยังไง (เราจะสอนคุณในบทถัดไป !) เราก็เลยหลอกแอพด้วยการใช้ข้อมูลที่เราสมมุติขึ้นแทน

    และเราได้สร้างตัวช่วยเทมเพลทชื่อ posts ที่จะส่งคืนค่าอาร์เรย์ postsData โดยใช้ฟังก์ชัน Template.postsList.helpers() ของ Meteor

    ถ้าคุณยังจำได้ เรามีการเรียกใช้ตัวช่วย posts ในเทมเพลท postsList แบบนี้

    <template name="postsList">
      <div class="posts">
        {{#each posts}}
          {{> postItem}}
        {{/each}}
      </div>
    </template>
    
    client/templates/posts/posts_list.html

    การสร้างตัวช่วยเทมเพลท posts ทำให้เราสามารถเรียกใช้มันได้จากเทมเพลทของเรา โดยเทมเพลทจะทำงานซ้ำตามจำนวนข้อมูลในอาร์เรย์ postsData และส่งต่อข้อมูลแต่ละตัวนั้นให้กับเทมเพลท postItem ทำงานต่อไป

    คอมมิท 3-1

    Added basic posts list template and static data.

    ตัวช่วย domain

    ก็คล้ายๆกับที่เราทำมาแล้ว เราก็สร้างไฟล์ post_item.js เพื่อเก็บโค้ดคำสั่งของเทมเพลท ดังนี้

    Template.postItem.helpers({
      domain: function() {
        var a = document.createElement('a');
        a.href = this.url;
        return a.hostname;
      }
    });
    
    client/templates/posts/post_item.js

    จะเห็นว่าค่าที่ส่งคืนมาจากตัวช่วย domain ไม่ใช่อาร์เรย์แต่เป็นฟังก์ชัน ซึ่งเป็นวิธีการเขียนโค้ดแบบที่นิยมใช้กันมาก และใช้ได้ดีกว่าแบบที่เราเขียนก่อนหน้านี้

    Displaying domains for each links.
    Displaying domains for each links.

    ตัวช่วย domain นี้ จะรับค่า URL และส่งคืนค่าเป็นชื่อโดเมนด้วยการใช้คุณสมบัติพิเศษบางอย่างของจาวาสคริปต์ แต่ว่ามันได้ URL มาจากไหนกัน

    เพื่อที่จะตอบปัญหานี้ เราต้องย้อนกลับไปดูที่เทมเพลท posts_list.html ตรงบล็อคตัวช่วย {{#each}} ที่ไม่เพียงทำงานซ้ำตามข้อมูลในอาร์เรย์ มันยังทำการ กำหนดค่า this ภายในบล็อกให้มีค่าเป็นอ็อบเจกต์ที่ได้จากอาร์เรย์อีกด้วย

    นั่นก็หมายความว่า ในแต่ละรอบการทำงานของแท็ก {{#each}} ข้อมูลข่าวที่โพสต์ไว้แต่ละตัว จะถูกใส่เข้าไปในตัวแปร this และส่งต่อไปจนถึงตัวจัดการเทมเพลทใน post_item.js ด้วย

    ถึงตรงนี้เราก็รู้แล้วว่าทำไม this.url ถึงคืนค่ากลับมาเป็น URL ของข่าวนั้นได้ และยิ่งไปกว่านั้น ถ้าเราใช้ {{title}} และ {{url}} ในเทมเพลท post_item.html Meteor ก็รู้ว่าเราหมายถึง this.title และ this.url และส่งค่าที่ถูกต้องกลับมาให้

    คอมมิท 3-2

    Setup a `domain` helper on the `postItem`.

    คุณสมบัติพิเศษของจาวาสคริปต์

    แม้จะไม่เกี่ยวกับ Meteor แต่เราก็จะอธิบายเรื่อง “คุณสมบัติพิเศษของจาวาสคริปต์” ให้คุณรู้ โดยการทำงานของโค้ดที่ตัวช่วย domain นั้น เริ่มจากการสร้างแท็ก anchor (a) แบบว่างๆ ขึ้นมาหนึ่งตัว และเก็บค่าไว้ในตัวแปร

    จากนั้นเราก็นำค่า URL ของข่าวที่โพสต์ (ได้จากอ็อบเจกต์ this) ใส่เข้าไปที่แอตทริบิวต์ href ของตัวแปรที่เราสร้างไว้

    สุดท้ายก็เรียกใช้ประโยชน์จากคุณสมบัติพิเศษ hostname ของแท็ก a เพื่อนำค่าโดเมนของ URL นั้นส่งคืนกลับมาให้เทมเพลทใช้งานต่อไป

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

    การรีโหลดแบบอัตโนมัติ

    คุณอาจสังเกตุเห็นว่า เราไม่จำเป็นต้องรีโหลดหน้าเบราว์เซอร์เลยเมื่อคุณทำการเปลี่ยนแปลงโค้ด

    นั้นก็เพราะ Meteor จะติดตามการแก้ไขไฟล์ในแอพทั้งหมด และทำการรีเฟรชหน้าเบราว์เซอร์ให้คุณโดยอัตโนมัติ เมื่อตรวจพบการเปลี่ยนแปลงกับไฟล์ใดไฟล์หนึ่ง

    การรีโหลดแบบอัตโนมัติของ Meteor ทำงานได้อย่างชาญฉลาด ทั้งยังเก็บสถานะของแอพคุณในระหว่างการรีเฟรชไว้ให้ด้วย !