Now.js Framework Documentation
ApiComponent - ส่วนห่อ UI สำหรับการเรียก API
ApiComponent - ส่วนห่อ UI สำหรับการเรียก API
เอกสารฉบับนี้อธิบาย ApiComponent ซึ่งทำหน้าที่เป็นส่วนห่อส่วนติดต่อผู้ใช้ เพื่อเรียกใช้งาน API และนำผลลัพธ์ไปแสดงบน HTML ได้อย่างสะดวก โดยไม่จำเป็นต้องเขียนโค้ด JavaScript เพิ่มเติม
📋 สารบัญ
- ภาพรวม
- การติดตั้งและนำเข้า
- การใช้งานพื้นฐาน
- คุณสมบัติ HTML
- ตัวเลือกการกำหนดค่า
- ระบบเทมเพลต
- การแบ่งหน้า
- การดึงข้อมูลเป็นช่วง
- อีเวนต์
- CSS State Classes
- JavaScript API
- ตัวอย่างการใช้งาน
- เอกสารอ้างอิง API
- แนวทางปฏิบัติที่ดี
ภาพรวม
ApiComponent เป็นคอมโพเนนต์สำหรับส่วนติดต่อผู้ใช้ที่ช่วยให้เรียก API แล้วนำข้อมูลมาเรนเดอร์ใน HTML ได้อย่างง่ายดาย โดยลดภาระการเขียน JavaScript ด้วยตนเอง
ฟีเจอร์หลัก
- ✅ การกำหนดผ่าน HTML: ตั้งค่าทั้งหมดผ่าน HTML attributes ได้ทันที
- ✅ รองรับเทมเพลต: ใช้ระบบเทมเพลตของ Now.js เพื่อจัดรูปแบบการแสดงผล
- ✅ โหลดอัตโนมัติ: ดึงข้อมูลทันทีเมื่อคอมโพเนนต์ถูกสร้าง
- ✅ ระบบแคช: เก็บข้อมูลไว้ชั่วคราวเพื่อลดการเรียก API ซ้ำ
- ✅ การแบ่งหน้า: รองรับทั้งการแบ่งหน้าปกติและการโหลดแบบเลื่อนต่อเนื่อง
- ✅ การดึงข้อมูลเป็นช่วง: อัปเดตข้อมูลตามช่วงเวลาที่กำหนดอัตโนมัติ
- ✅ ระบบอีเวนต์: ส่งอีเวนต์เมื่อมีการเปลี่ยนแปลงสถานะหรือข้อมูล
- ✅ ตรรกะการลองใหม่: พยายามเรียกซ้ำอัตโนมัติเมื่อเกิดข้อผิดพลาดที่กำหนดได้
- ✅ สถานะการโหลด: จัดการสถานะกำลังโหลด ข้อผิดพลาด และไม่มีข้อมูลให้อัตโนมัติ
เมื่อไหร่ควรใช้ ApiComponent
✅ ใช้ ApiComponent เมื่อ:
- ต้องการแสดงข้อมูลจาก API บน HTML โดยแทบไม่ต้องเขียนโค้ดเพิ่ม
- ต้องการโครงสร้างที่ใช้งานง่าย (ลดการเขียน JavaScript ด้วยตนเอง)
- ต้องการระบบแบ่งหน้าหรือการเลื่อนโหลดต่อเนื่อง
- ต้องการให้ข้อมูลรีเฟรชอัตโนมัติแบบเป็นช่วง
- ต้องการให้สถานะการโหลด ข้อผิดพลาด และกรณีไม่มีข้อมูลจัดการให้อัตโนมัติ
❌ ไม่ควรใช้ ApiComponent เมื่อ:
- ต้องการควบคุมการแสดงผลอย่างละเอียดทุกขั้น (แนะนำให้ใช้
ApiServiceคู่กับโค้ดที่เขียนเอง) - ต้องทำงานกับข้อมูลเรียลไทม์ที่ซับซ้อนมาก (พิจารณาใช้ WebSocket หรือโซลูชันเฉพาะ)
- ต้องการตรรกะที่สลับซับซ้อนและควบคุมทุกรายละเอียดของการเรียก API
การติดตั้งและนำเข้า
ApiComponent โหลดมาพร้อมกับ Now.js Framework และพร้อมใช้งานทันทีผ่าน window object:
// ไม่ต้อง import - พร้อมใช้งานทันที
console.log(window.ApiComponent); // อ็อบเจ็กต์ ApiComponent ที่ผูกไว้กับ windowสิ่งที่ต้องพึ่งพา
การใช้งาน ApiComponent ต้องอาศัยโมดูลต่อไปนี้:
- HttpClient หรือ simpleFetch – สำหรับจัดการคำขอ HTTP
- TemplateManager – สำหรับเรนเดอร์เทมเพลต (ทางเลือก หากต้องการใช้ระบบเทมเพลตขั้นสูง)
สองโหมดการทำงาน
ApiComponent รองรับ 2 โหมดการทำงาน ที่แตกต่างกัน โดยจะตรวจสอบอัตโนมัติว่าควรใช้โหมดไหน:
1. Simple Binding Mode (โหมดการผูกข้อมูลแบบง่าย)
เมื่อไหร่: เมื่อ ไม่มี <template> element ภายใน component
จุดประสงค์: แสดงข้อมูลแบบง่าย เช่น ชื่อผู้ใช้, รูปภาพ, ตัวเลข โดยอัพเดทค่าใน elements ที่มีอยู่แล้ว
ตัวอย่าง:
<!-- Simple Binding Mode - ไม่มี template -->
<div data-component="api"
data-endpoint="/api/v1/auth/me"
data-cache="true">
<img data-source-key="data.avatar.0.url" data-source-attr="src" class="avatar">
<span data-source-key="data.name">Loading...</span>
<span data-source-key="data.email">Loading...</span>
</div>คุณสมบัติที่รองรับ:
data-source-key- path ไปยังข้อมูล (dot notation)data-source-attr- attribute ที่จะอัพเดท (textContent, src, href, innerHTML, etc.)data-source-template- template แบบง่าย เช่น"Hello, {data.name}!"
เหมาะกับ:
- แสดงข้อมูลผู้ใช้ที่ login (topbar, profile card)
- Badge แสดงจำนวน (notifications, cart count)
- ข้อมูลเดี่ยวๆ ที่ไม่ซับซ้อน
2. Template Rendering Mode (โหมดการเรนเดอร์เทมเพลต)
เมื่อไหร่: เมื่อ มี <template> element ภายใน component
จุดประสงค์: สร้าง HTML ใหม่จาก template สำหรับข้อมูลที่ซับซ้อน เช่น lists, cards, dynamic content
ตัวอย่าง:
<!-- Template Rendering Mode - มี template -->
<div data-component="api"
data-endpoint="/api/users">
<template>
<ul>
<li data-for="user of data">
<img data-bind="user.avatar" data-attr="src">
<span data-bind="user.name"></span>
<span data-if="user.status === 'active'">Active</span>
</li>
</ul>
</template>
</div>คุณสมบัติที่รองรับ:
data-for- loop ข้อมูลdata-if- เงื่อนไขการแสดงผลdata-bind- ผูกข้อมูล- และ directives อื่นๆ ของ TemplateManager
เหมาะกับ:
- แสดง lists (users, products, posts)
- Dynamic components ที่มีหลาย items
- UI ที่ต้องการ loops และ conditions
เปรียบเทียบสองโหมด
| Feature | Simple Binding | Template Rendering |
|---|---|---|
| Template Element | ❌ ไม่มี | ✅ มี |
| Use Case | ข้อมูลเดี่ยว | Lists/Collections |
| Complexity | ง่าย | ซับซ้อน |
| HTML Output | อัพเดทค่าเดิม | สร้าง HTML ใหม่ |
| Loops | ❌ | ✅ |
| Conditions | ❌ | ✅ |
| Performance | เร็วกว่า | ช้ากว่า (สร้าง HTML ใหม่) |
การใช้งานพื้นฐาน
1. การใช้งานผ่าน HTML (แนะนำ) — HTML-Based
<div data-component="api"
data-endpoint="/api/users"
data-method="GET">
<template>
<div data-for="item in data">
<h3>{item.name}</h3>
<p>{item.email}</p>
</div>
</template>
</div>2. การใช้งานผ่าน JavaScript
const element = document.querySelector('#my-api-component');
const instance = ApiComponent.create(element, {
endpoint: '/api/users',
method: 'GET',
autoload: true
});3. การเริ่มทำงานอัตโนมัติ
ApiComponent จะสร้าง instance อัตโนมัติสำหรับ elements ที่มี data-component="api":
<!-- จะถูกสร้างอัตโนมัติเมื่อหน้าโหลด -->
<div data-component="api" data-endpoint="/api/products"></div>คุณสมบัติ HTML
ApiComponent รองรับ attributes เหล่านี้สำหรับการกำหนดค่า:
คุณสมบัติพื้นฐาน
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-endpoint |
string | - | URL ของ API (จำเป็น) |
data-method |
string | 'GET' |
วิธีการ HTTP (GET, POST, PUT, DELETE) |
data-base-url |
string | '' |
Base URL สำหรับ API |
data-autoload |
boolean | true |
โหลดข้อมูลอัตโนมัติเมื่อสร้าง component |
data-timeout |
number | 30000 |
เวลารอคำขอ (มิลลิวินาที) |
คุณสมบัติด้านแคช
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-cache |
boolean | false |
เปิดใช้งานการแคช |
data-cache-time |
number | 60000 |
ระยะเวลาแคช (มิลลิวินาที) |
คุณสมบัติเทมเพลต
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-template |
string | - | ID ของเทมเพลตภายนอก |
คุณสมบัติการแบ่งหน้า
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-pagination |
boolean | true |
เปิดใช้งานการแบ่งหน้า |
data-page-param |
string | 'page' |
ชื่อพารามิเตอร์หน้าที่ส่งไปยังเซิร์ฟเวอร์ |
data-limit-param |
string | 'pageSize' |
ชื่อพารามิเตอร์จำนวนรายการต่อหน้า |
data-page-size |
number | 10 |
จำนวนรายการต่อหน้า |
คุณสมบัติการดึงข้อมูลเป็นช่วง
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-polling |
boolean | false |
เปิดใช้งานการดึงข้อมูลเป็นช่วงๆ |
data-polling-interval |
number | 30000 |
ช่วงเวลาการดึงข้อมูล (มิลลิวินาที) |
คุณสมบัติอีเวนต์
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-refresh-event |
string | - | ชื่ออีเวนต์ที่ใช้ทริกเกอร์การรีเฟรช (คั่นด้วย comma) |
คุณสมบัติข้อมูล
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-params |
JSON | {} |
พารามิเตอร์สำหรับ query string |
data-data |
JSON | {} |
ข้อมูลสำหรับส่งใน body ของคำขอ |
data-headers |
JSON | {} |
เฮดเดอร์ที่กำหนดเอง |
data-data-path |
string | 'data' |
เส้นทางไปยังข้อมูลใน response |
data-meta-path |
string | 'meta' |
เส้นทางไปยัง meta ใน response |
การใช้คุณสมบัติแบบรวบยอด
สามารถใช้ data-props เพื่อกำหนดค่าทั้งหมดในรูปแบบ JSON:
<div data-component="api"
data-props='{
"endpoint": "/api/users",
"method": "GET",
"cache": true,
"cacheTime": 300000,
"params": {"status": "active"}
}'>
</div>ตัวเลือกการกำหนดค่า
เมื่อสร้าง instance ด้วย JavaScript สามารถกำหนดค่าได้ทั้งหมด:
const instance = ApiComponent.create(element, {
// การตั้งค่าทั่วไป
method: 'GET',
baseURL: '',
endpoint: '/api/users',
autoload: true,
timeout: 30000,
// การแคชข้อมูล
cache: false,
cacheTime: 60000,
// การลองใหม่
retry: 3,
// ข้อความสถานะ
loadingText: 'กำลังโหลดข้อมูล...',
errorText: 'เกิดข้อผิดพลาด',
emptyText: 'ไม่พบข้อมูล',
// ฟังก์ชันจัดการอีเวนต์
onSuccess: (data, response) => {
console.log('โหลดข้อมูลสำเร็จ:', data);
},
onError: (error) => {
console.error('เกิดข้อผิดพลาด:', error);
},
onEmpty: () => {
console.log('ไม่มีข้อมูลให้แสดง');
},
// การแบ่งหน้า
pagination: true,
pageParam: 'page',
limitParam: 'pageSize',
pageSize: 10,
// การดึงข้อมูลแบบเป็นช่วง
polling: false,
pollingInterval: 30000,
// อีเวนต์ที่ตั้งค่าให้ทริกเกอร์การรีเฟรช
refreshEvent: 'user:updated',
// ข้อมูลส่งออก
params: {},
data: {},
headers: {},
// เส้นทางภายในข้อมูลตอบกลับ
dataPath: 'data',
metaPath: 'meta',
// เทมเพลตที่ต้องการใช้
template: 'my-template-id',
// โหมดดีบัก
debug: false
});ระบบเทมเพลต
ApiComponent ใช้ TemplateManager ของ Now.js สำหรับแสดงผล
เทมเพลตแบบฝัง
<div data-component="api" data-endpoint="/api/users">
<template>
<div data-for="user in data">
<h3>{user.name}</h3>
<p>{user.email}</p>
<span data-if="user.status === 'active'">ใช้งาน</span>
</div>
</template>
</div>เทมเพลตภายนอก
<!-- กำหนดเทมเพลต -->
<template id="user-template">
<div class="user-card">
<h3>{name}</h3>
<p>{email}</p>
</div>
</template>
<!-- เรียกใช้เทมเพลต -->
<div data-component="api"
data-endpoint="/api/users"
data-template="user-template">
</div>ไดเรกทีฟของเทมเพลต
ApiComponent รองรับ directives ของ TemplateManager:
data-for
<template>
<ul>
<li data-for="item in data">
{item.name}
</li>
</ul>
</template>data-if
<template>
<div data-for="user in data">
<h3>{user.name}</h3>
<span data-if="user.status === 'active'" class="badge">ใช้งาน</span>
<span data-if="user.status === 'inactive'" class="badge">ไม่ใช้งาน</span>
</div>
</template>ลูปซ้อน
<template>
<div data-for="category in data">
<h2>{category.name}</h2>
<ul>
<li data-for="product in category.products">
{product.name} - {product.price}
</li>
</ul>
</div>
</template>การแบ่งหน้า
ApiComponent รองรับการแบ่งหน้าแบบ server-side
การแบ่งหน้าแบบพื้นฐาน
<div data-component="api"
data-endpoint="/api/products"
data-pagination="true"
data-page-size="20">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
<p>{product.price}</p>
</div>
</template>
<!-- ปุ่มควบคุมการแบ่งหน้า -->
<button onclick="this.closest('.api-component').apiInstance.loadMore()">
โหลดเพิ่ม
</button>
</div>กำหนดพารามิเตอร์แบ่งหน้า
<div data-component="api"
data-endpoint="/api/posts"
data-page-param="p"
data-limit-param="limit"
data-page-size="15">
</div>Request จะส่งเป็น:
GET /api/posts?p=1&limit=15รูปแบบ Response ที่คาดหวัง
{
"data": [
{"id": 1, "name": "Item 1"},
{"id": 2, "name": "Item 2"}
],
"meta": {
"total": 100,
"last_page": 10,
"current_page": 1,
"per_page": 10
}
}ปิดการแบ่งหน้า
<div data-component="api"
data-endpoint="/api/settings"
data-pagination="false">
</div>การดึงข้อมูลเป็นช่วง
ApiComponent สามารถ poll endpoint อัตโนมัติเพื่ออัปเดตข้อมูล
เปิดใช้งาน Polling
<div data-component="api"
data-endpoint="/api/notifications"
data-polling="true"
data-polling-interval="10000">
<!-- จะโหลดข้อมูลใหม่ทุก 10 วินาที -->
</div>ควบคุม Polling ด้วย JavaScript
const instance = ApiComponent.getInstance('#my-component');
// เริ่มการ Polling
ApiComponent.startPolling(instance);
// หยุดการ Polling
ApiComponent.stopPolling(instance);อีเวนต์ของ Polling
element.addEventListener('api:polling:start', (e) => {
console.log('เริ่ม Polling');
});
element.addEventListener('api:polling:stop', (e) => {
console.log('หยุด Polling');
});อีเวนต์
ApiComponent ส่งอีเวนต์ เมื่อมีการเปลี่ยนแปลง
ประเภทของอีเวนต์
| อีเวนต์ | เมื่อไหร่ | รายละเอียด |
|---|---|---|
api:init |
คอมโพเนนต์ถูกเริ่มใช้งาน | {instance} |
api:loaded |
โหลดข้อมูลสำเร็จ | {data, response, fromCache} |
api:error |
เกิดข้อผิดพลาด | {error, message} |
api:empty |
ไม่มีข้อมูลจาก API | - |
api:content-rendered |
แสดงผลคอนเทนต์เสร็จ | {data} |
api:polling:start |
เริ่ม Polling | - |
api:polling:stop |
หยุด Polling | - |
api:destroy |
ลบคอมโพเนนต์ออก | - |
การฟังอีเวนต์
const element = document.querySelector('#my-api-component');
element.addEventListener('api:loaded', (e) => {
console.log('โหลดข้อมูลสำเร็จ:', e.detail.data);
console.log('จาก cache หรือไม่:', e.detail.fromCache);
});
element.addEventListener('api:error', (e) => {
console.error('ข้อผิดพลาด:', e.detail.error);
});
element.addEventListener('api:empty', (e) => {
console.log('ไม่มีข้อมูลให้แสดง');
});รีเฟรชเมื่อมีอีเวนต์ที่กำหนดเอง
<div data-component="api"
data-endpoint="/api/cart"
data-refresh-event="cart:updated">
<!-- จะ refresh เมื่อมีการส่ง event 'cart:updated' -->
</div>
<script>
// ที่ไหนก็ได้ในโค้ด
EventManager.emit('cart:updated');
</script>CSS State Classes
ApiComponent จะเพิ่ม CSS classes อัตโนมัติเพื่อบอกสถานะปัจจุบันของคอมโพเนนต์ โดยใช้ prefix api- เพื่อหลีกเลี่ยงการชนกับชื่อ class ทั่วไป
State Classes
| Class | เมื่อไหร่ | คำอธิบาย |
|---|---|---|
api-component |
ตลอดเวลา | ถูกเพิ่มเมื่อคอมโพเนนต์ถูกเริ่มต้น |
api-loading |
ระหว่างดึงข้อมูล | กำลังโหลดข้อมูลจาก API |
api-content |
หลังโหลดสำเร็จ | โหลดและแสดงข้อมูลสำเร็จแล้ว |
api-error |
เมื่อเกิดข้อผิดพลาด | เกิดข้อผิดพลาดระหว่างโหลดข้อมูล |
api-empty |
เมื่อไม่มีข้อมูล | API ส่งข้อมูลว่างหรือ array ว่าง |
ตัวอย่างการใช้งาน
/* จัดสไตล์สถานะกำลังโหลด */
.api-component.api-loading {
opacity: 0.6;
pointer-events: none;
}
.api-component.api-loading::after {
content: 'กำลังโหลด...';
display: block;
text-align: center;
padding: 20px;
}
/* จัดสไตล์สถานะข้อผิดพลาด */
.api-component.api-error {
background-color: #fee;
border: 1px solid #fcc;
padding: 20px;
}
.api-component.api-error::before {
content: '⚠️ เกิดข้อผิดพลาดในการโหลดข้อมูล';
display: block;
color: #c00;
font-weight: bold;
margin-bottom: 10px;
}
/* จัดสไตล์สถานะไม่มีข้อมูล */
.api-component.api-empty {
text-align: center;
padding: 40px;
color: #999;
}
.api-component.api-empty::after {
content: 'ไม่มีข้อมูล';
display: block;
}
/* จัดสไตล์สถานะมีข้อมูล */
.api-component.api-content {
/* สไตล์สำหรับเนื้อหาของคุณ */
}ตัวอย่างแบบสมบูรณ์
<div data-component="api"
data-endpoint="/api/products"
class="product-list">
<template>
<div data-for="product in data" class="product-card">
<h3>{product.name}</h3>
<p>{product.price}</p>
</div>
</template>
</div>
<style>
/* สไตล์พื้นฐาน */
.product-list {
min-height: 200px;
transition: all 0.3s ease;
}
/* สถานะกำลังโหลด */
.product-list.api-loading {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: loading 1.5s ease-in-out infinite;
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* สถานะข้อผิดพลาด */
.product-list.api-error {
background-color: #fee2e2;
border: 2px solid #ef4444;
padding: 20px;
border-radius: 8px;
}
/* สถานะไม่มีข้อมูล */
.product-list.api-empty {
background-color: #f9fafb;
border: 2px dashed #d1d5db;
padding: 40px;
text-align: center;
color: #6b7280;
}
/* โหลดข้อมูลสำเร็จ */
.product-list.api-content {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
</style>ประโยชน์ของ prefix api-
การใช้ prefix api- ช่วยป้องกันการชนกับชื่อ class ที่ใช้บ่อย:
-
❌
.loading- อาจชนกับ loading class ของคุณเอง -
❌
.error- เป็นชื่อที่ใช้กันมากในหลายโปรเจค -
❌
.content- ใช้กันมากมายสำหรับ layout -
❌
.empty- ใช้กันบ่อยสำหรับสถานะว่าง -
✅
.api-loading- เฉพาะเจาะจงสำหรับ ApiComponent -
✅
.api-error- ชัดเจนว่ามาจาก ApiComponent -
✅
.api-content- ไม่ชนกับ layout classes -
✅
.api-empty- เฉพาะเจาะจงสำหรับสถานะข้อมูล API
API สำหรับ JavaScript
ApiComponent มี methods สำหรับควบคุมผ่าน JavaScript
สร้าง Instance
const instance = ApiComponent.create(element, options);อ่าน Instance
// จาก element โดยตรง
const instance = ApiComponent.getInstance(element);
// จาก selectors หรือ ID
const instance = ApiComponent.getInstance('#my-component');
// จาก property บน element
const instance = element.apiInstance;โหลดข้อมูล
// โหลดข้อมูลใหม่และรีเซ็ตหน้า (Load data & reset pagination)
await ApiComponent.loadData(instance);
// โหลดเพิ่มและต่อท้ายข้อมูลเดิม (Load more & append)
await ApiComponent.loadMore(instance);รีเฟรชข้อมูล
// รีเฟรชและกลับไปหน้าแรก (Refresh & reset to page 1)
ApiComponent.refresh(instance);
// หรือเรียกผ่านเมธอดของ instance
instance.refresh();ส่งข้อมูล
// ส่งข้อมูลด้วย POST
await ApiComponent.submit(instance, {
name: 'John Doe',
email: 'john@example.com'
});ควบคุม Polling
// เริ่มการ Polling
ApiComponent.startPolling(instance);
// หยุดการ Polling
ApiComponent.stopPolling(instance);การจัดการ Cache
// ล้าง cache ทั้งหมด
ApiComponent.clearCache();ลบ Instance
// ทำลาย instance และเคลียร์ทรัพยากร
ApiComponent.destroy(instance);ตัวอย่างการใช้งาน
1. แสดงรายการสินค้า
<div data-component="api"
data-endpoint="/api/products"
data-cache="true"
data-cache-time="300000">
<template>
<div class="product-grid">
<div data-for="product in data" class="product-card">
<img data-src="product.image" data-alt="product.name">
<h3>{product.name}</h3>
<p class="price">{product.price} บาท</p>
<button>เพิ่มในตะกร้า</button>
</div>
</div>
</template>
</div>2. โปรไฟล์ผู้ใช้ด้วย POST
<form id="profile-form">
<input type="text" name="name" placeholder="ชื่อ">
<input type="email" name="email" placeholder="อีเมล">
<button type="submit">บันทึก</button>
</form>
<div id="profile-result"
data-component="api"
data-endpoint="/api/profile"
data-method="POST"
data-autoload="false">
<template>
<div class="alert success">
โปรไฟล์ถูกอัปเดต: {data.name}
</div>
</template>
</div>
<script>
document.getElementById('profile-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
const instance = ApiComponent.getInstance('#profile-result');
await ApiComponent.submit(instance, data);
});
</script>3. เลื่อนหน้าแบบไม่สิ้นสุด
<div data-component="api"
data-endpoint="/api/posts"
data-page-size="20"
id="posts">
<template>
<div data-for="post in data" class="post">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</div>
</template>
</div>
<script>
// เลื่อนแบบไม่สิ้นสุด
window.addEventListener('scroll', () => {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
const instance = ApiComponent.getInstance('#posts');
if (!instance.loading && instance.page < instance.totalPages) {
ApiComponent.loadMore(instance);
}
}
});
</script>4. แจ้งเตือนแบบเรียลไทม์
<div data-component="api"
data-endpoint="/api/notifications"
data-polling="true"
data-polling-interval="10000"
data-cache="false">
<template>
<div class="notification-list">
<div data-for="notif in data" class="notification">
<span class="icon" data-if="notif.type === 'info'">ℹ️</span>
<span class="icon" data-if="notif.type === 'warning'">⚠️</span>
<span class="icon" data-if="notif.type === 'error'">❌</span>
<div class="content">
<h4>{notif.title}</h4>
<p>{notif.message}</p>
<time>{notif.created_at}</time>
</div>
</div>
</div>
</template>
</div>5. ค้นหาด้วย Debounce
<input type="text" id="search-input" placeholder="ค้นหาสินค้า...">
<div data-component="api"
data-endpoint="/api/products/search"
data-autoload="false"
id="search-results">
<template>
<div data-for="product in data">
<h4>{product.name}</h4>
<p>{product.description}</p>
</div>
</template>
</div>
<script>
let searchTimeout;
const searchInput = document.getElementById('search-input');
const instance = ApiComponent.getInstance('#search-results');
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
instance.options.params = { q: e.target.value };
ApiComponent.refresh(instance);
}, 300);
});
</script>6. กำหนดพารามิเตอร์แบบไดนามิก
<select id="category-select">
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="books">Books</option>
</select>
<div data-component="api"
data-endpoint="/api/products"
data-params='{"category": "electronics"}'
id="products">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
</div>
</template>
</div>
<script>
document.getElementById('category-select').addEventListener('change', (e) => {
const instance = ApiComponent.getInstance('#products');
instance.options.params.category = e.target.value;
ApiComponent.refresh(instance);
});
</script>7. รองรับข้อมูลซ้อนกัน
<div data-component="api"
data-endpoint="/api/categories">
<template>
<div data-for="category in data" class="category">
<h2>{category.name}</h2>
<div class="products">
<div data-for="product in category.products" class="product">
<h4>{product.name}</h4>
<p>{product.price} บาท</p>
</div>
</div>
</div>
</template>
</div>8. ✅ ใช้ data-path เมื่อโครงสร้าง response ซับซ้อน
<!-- โครงสร้าง Response: { "success": true, "result": { "items": [...] } } -->
<div data-component="api"
data-endpoint="/api/items"
data-data-path="result.items">
<template>
<div data-for="item in data">
{item.name}
</div>
</template>
</div>9. ✅ ใช้ events เพื่อทำ custom actions
<!-- ตะกร้าสินค้า -->
<div data-component="api"
data-endpoint="/api/cart"
data-refresh-event="cart:updated"
id="cart">
<template>
<div>
จำนวนสินค้า: {data.count} รายการ
ราคารวม: {data.total} บาท
</div>
</template>
</div>
<!-- ปุ่มเพิ่มในตะกร้า -->
<button onclick="addToCart(123)">เพิ่มในตะกร้า</button>
<script>
async function addToCart(productId) {
await fetch('/api/cart/add', {
method: 'POST',
body: JSON.stringify({ productId }),
headers: {'Content-Type': 'application/json'}
});
// ทริกเกอร์ให้รีเฟรช
EventManager.emit('cart:updated');
}
</script>10. ✅ การจัดการข้อผิดพลาดด้วย UI ที่กำหนดเอง
<div data-component="api"
data-endpoint="/api/products"
id="products">
<template>
<div data-for="product in data">
<h3>{product.name}</h3>
</div>
</template>
</div>
<script>
const element = document.getElementById('products');
element.addEventListener('api:error', (e) => {
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-error';
errorDiv.textContent = 'ไม่สามารถโหลดรายการสินค้าได้ ';
const retryBtn = document.createElement('button');
retryBtn.textContent = 'ลองอีกครั้ง';
retryBtn.onclick = () => {
ApiComponent.refresh(e.detail.instance);
errorDiv.remove();
};
errorDiv.appendChild(retryBtn);
element.insertBefore(errorDiv, element.firstChild);
});
</script>อ้างอิง API
เมธอดของ ApiComponent
// สร้างคอมโพเนนต์
create(element: HTMLElement|string, options?: object): Instance
// อ่าน instance ที่มีอยู่
getInstance(element: HTMLElement|string): Instance|null
// จัดการข้อมูล
loadData(instance: Instance, append?: boolean): Promise<void>
refresh(instance: Instance): void
loadMore(instance: Instance): void
submit(instance: Instance, data: object): Promise<void>
// ควบคุม Polling
startPolling(instance: Instance): void
stopPolling(instance: Instance): void
// จัดการ cache
clearCache(): void
// วงจรชีวิตของคอมโพเนนต์
destroy(instance: Instance): boolean
init(options?: object): Promise<ApiComponent>คุณสมบัติของ Instance
{
id: string,
element: HTMLElement,
options: object,
data: any,
loading: boolean,
error: string|null,
timestamp: number|null,
page: number,
totalPages: number,
pageSize: number,
totalItems: number,
polling: boolean,
timer: number|null,
abortController: AbortController|null,
templateContent: string|null
}แนวทางปฏิบัติที่ดี
1. ✅ ใช้ HTML attributes แทนการเขียน JavaScript
<!-- ❌ ไม่ดี: ใช้ JavaScript เมื่อไม่จำเป็น -->
<div id="products"></div>
<script>
ApiComponent.create('#products', {endpoint: '/api/products'});
</script>
<!-- ✅ ดี: ใช้ HTML attributes -->
<div data-component="api" data-endpoint="/api/products"></div>2. ✅ เพิ่มประสิทธิภาพด้วยการแคช
<!-- ✅ ดี: Cache data ที่ไม่เปลี่ยนแปลงบ่อย -->
<div data-component="api"
data-endpoint="/api/categories"
data-cache="true"
data-cache-time="300000">
</div>3. ✅ ใช้เทมเพลตภายนอกเมื่อใช้ซ้ำ
<!-- ✅ ดี: Template แยกออกมา ใช้ซ้ำได้ -->
<template id="product-card">
<div class="card">
<h3>{name}</h3>
<p>{price}</p>
</div>
</template>
<div data-component="api"
data-endpoint="/api/products"
data-template="product-card">
</div>
<div data-component="api"
data-endpoint="/api/featured-products"
data-template="product-card">
</div>4. ✅ ล้าง Instance เมื่อไม่ใช้แล้ว
// ✅ ดี: Destroy instance เมื่อไม่ใช้แล้ว
const instance = ApiComponent.getInstance('#my-component');
ApiComponent.destroy(instance);
// หรือเมื่อถอด element ออกจาก DOM
element.remove(); // ล้างอัตโนมัติ ถ้าใช้ ComponentManager5. ✅ ใช้ Pagination สำหรับข้อมูลจำนวนมาก
<!-- ✅ ดี: ใช้ pagination สำหรับข้อมูลจำนวนมาก -->
<div data-component="api"
data-endpoint="/api/posts"
data-page-size="20">
</div>6. ✅ ตรวจสอบข้อมูลว่างในเทมเพลต
<div data-component="api"
data-endpoint="/api/notifications">
<template>
<div data-if="data.length > 0">
<div data-for="notif in data">
{notif.message}
</div>
</div>
<div data-if="data.length === 0">
ไม่มีการแจ้งเตือน
</div>
</template>
</div>7. ✅ ใช้ Polling อย่างชาญฉลาด
<!-- ❌ ไม่ดี: Polling ทุก 1 วินาที -->
<div data-polling="true" data-polling-interval="1000"></div>
<!-- ✅ ดี: Polling ทุก 30 วินาที หรือใช้ WebSocket -->
<div data-polling="true" data-polling-interval="30000"></div>8. ✅ ปิด autoload เมื่อควบคุมการโหลดเอง
<!-- ✅ ดี: ปิด autoload เมื่อต้องการควบคุมเอง -->
<div data-component="api"
data-endpoint="/api/search"
data-autoload="false"
id="search">
</div>
<script>
// โหลดเมื่อผู้ใช้พิมพ์ค้นหา
searchInput.addEventListener('input', (e) => {
const instance = ApiComponent.getInstance('#search');
instance.options.params = {q: e.target.value};
ApiComponent.refresh(instance);
});
</script>9. ✅ ใช้ data-path เมื่อโครงสร้าง response ซับซ้อน
<!-- ✅ ดี: ระบุ path เมื่อ API response ไม่ใช่ {data: [...]} -->
<div data-component="api"
data-endpoint="/api/legacy"
data-data-path="result.items"
data-meta-path="result.pagination">
</div>10. ✅ ใช้ events เพื่อทำ custom actions
// ✅ ดี: ใช้ events เพื่อทำ custom actions
element.addEventListener('api:loaded', (e) => {
// อัปเดต UI elements อื่น ๆ
updateStats(e.detail.data);
// ติดตามการโหลดข้อมูล
analytics.track('data_loaded', {
endpoint: e.detail.instance.options.endpoint
});
});สรุป
เมื่อไหร่ควรใช้ ApiComponent
| กรณีการใช้งาน | ใช้ ApiComponent? |
|---|---|
| แสดงรายการข้อมูลจาก API | ✅ ใช่ - HTML-based |
| ต้องการ pagination | ✅ ใช่ - built-in support |
| ต้องการ infinite scroll | ✅ ใช่ - loadMore() |
| Auto-refresh ข้อมูล | ✅ ใช่ - polling support |
| ต้องการควบคุมละเอียด | ❌ ไม่ - ใช้ ApiService |
| Real-time ซับซ้อน | ❌ ไม่ - ใช้ WebSocket |
| Form submission | ✅ ใช้ได้ - submit() |
| Simple static content | ❌ ไม่จำเป็น |
ฟีเจอร์หลัก
| คุณสมบัติ | พร้อมใช้งาน | หมายเหตุ |
|---|---|---|
| HTML Attributes | ✅ | ไม่ต้องใช้ JavaScript |
| Template System | ✅ | รวมกับ TemplateManager |
| Auto-Loading | ✅ | กำหนดค่าได้ |
| Caching | ✅ | เก็บในหน่วยความจำ |
| Pagination | ✅ | ฝั่งเซิร์ฟเวอร์ |
| Polling | ✅ | รีเฟรชอัตโนมัติ |
| Events | ✅ | อีเวนต์ที่กำหนดเอง |
| Retry | ✅ | ลองใหม่อัตโนมัติเมื่อเกิดข้อผิดพลาด |
| Loading States | ✅ | สถานะ: กำลังโหลด, ข้อผิดพลาด, ไม่มีข้อมูล |