Now.js Framework Documentation
Text-based Elements (อิลิเมนต์ข้อความ)
Text-based Elements (อิลิเมนต์ข้อความ)
เอกสารสำหรับ TextElementFactory, PasswordElementFactory และ SearchElementFactory - components สำหรับ text input พร้อม autocomplete, validation และฟีเจอร์พิเศษ
📋 สารบัญ
ภาพรวม
Text-based elements จัดการ text input พร้อมฟีเจอร์ต่างๆ เช่น autocomplete, validation และ formatting
| Element | data-element | คำอธิบาย |
|---|---|---|
| TextElementFactory | text |
Text input พร้อม autocomplete, hidden input |
| PasswordElementFactory | password |
Password พร้อม strength meter, toggle visibility |
| SearchElementFactory | search |
Search input พร้อม debounce, results container |
TextElementFactory
TextElementFactory จัดการ text inputs พร้อม autocomplete และรองรับ hidden input เมื่อเปิด autocomplete จะสร้าง hidden input เพื่อเก็บค่า (key) ขณะที่แสดงข้อความ (label) ให้ผู้ใช้
การใช้งานพื้นฐาน
<!-- Simple text input -->
<input type="text"
data-element="text"
name="username"
placeholder="กรอก username">
<!-- Text พร้อม validation -->
<input type="text"
data-element="text"
data-validate="email"
name="email"
placeholder="กรอก email">ฟีเจอร์ Autocomplete
Static Source (Array)
<input type="text"
data-element="text"
data-autocomplete="true"
data-source='[{"value":"1","text":"กรุงเทพ"},{"value":"2","text":"เชียงใหม่"}]'
name="city"
placeholder="เลือกจังหวัด">Datalist Source
<input type="text"
data-element="text"
list="cities"
name="city">
<datalist id="cities">
<option value="1" label="Bangkok">
<option value="2" label="Chiang Mai">
<option value="3" label="Phuket">
</datalist>💡 หมายเหตุ: เมื่อใช้ datalist อีลิเมนต์
<datalist>จะถูกถอดออกและแปลงเป็น autocomplete dropdown ให้เอง
Ajax Source
<input type="text"
data-element="text"
data-autocomplete="true"
data-source="/api/cities"
data-min-length="2"
name="city"
placeholder="ค้นหาจังหวัด...">Expected API Response:
{
"success": true,
"data": [
{"value": "1", "text": "กรุงเทพ"},
{"value": "2", "text": "เชียงใหม่"}
]
}เติมช่องอื่นจากรายการที่เลือก
ข้อมูลอื่นที่ API ส่งกลับมาพร้อมรายการ จะถูกเติมลงช่องที่ชื่อตรงกันให้เอง
การเลือกครั้งเดียวจึงเติมข้อมูลได้ทั้งชุด ไม่ต้องประกาศอะไรเพิ่มใน HTML
เพราะฝั่ง API เป็นผู้กำหนดว่าการเลือกหนึ่งครั้งจะเติมอะไรบ้าง
{
"success": true,
"data": [
{"value": "SN-001", "text": "SN-001 : Notebook", "topic": "Notebook", "price": "25000"}
]
}<input type="text" id="product_no" name="product_no"
data-autocomplete="true" data-source="api/repair/autocomplete/find">
<!-- ได้ค่าจาก topic และ price ของรายการที่เลือก -->
<input type="text" id="topic" name="topic" readonly>
<input type="text" id="price" name="price">การหาช่องปลายทางจะดูจาก id ก่อน แล้วจึงดูจาก name และหาเฉพาะภายในฟอร์มเดียวกัน
ฟอร์มคนละใบบนหน้าเดียวกันจึงไม่เติมข้ามกัน รองรับ element ทุกชนิด: input ปกติและ
textarea รับค่าโดยตรง, checkbox ติ๊กตามค่าที่ส่งมา, radio เลือกตัวที่ value ตรงกัน,
select แบบ multiple เลือกได้หลายค่าจาก array หรือข้อความคั่นด้วยจุลภาค และ element
ที่ไม่ใช่ช่องกรอก (<span>, <div>, ช่องในตาราง) จะได้ข้อความแทน ส่วนช่องปลายทาง
ที่เป็น autocomplete เองจะถูกเขียนผ่าน instance ของมัน ข้อความที่แสดงกับค่าจริง
ใน hidden input จึงตรงกันเสมอ
ค่าที่ไม่ถูกเติมให้: value text label key และ options เป็นคีย์ของตัวรายการเอง
ส่วน id เกือบทุกครั้งหมายถึง id ของข้อมูลที่ API ส่งมา ไม่ใช่ id ของข้อมูลที่ฟอร์มกำลังแก้ไข
การเติมจึงจะไปทับ id ของฟอร์ม และช่อง autocomplete ตัวมันเองก็ถูกข้ามเช่นกัน
หากต้องการใช้คีย์เหล่านี้จริงๆ ให้ระบุด้วย data-fill
กันข้อมูลค้าง: ถ้ารายการที่เลือกใหม่ไม่มีค่านั้น ช่องที่เคยถูกเติมจะถูกล้าง
และเมื่อผู้ใช้พิมพ์ต่อหลังเลือกไปแล้ว ทุกช่องที่การเลือกครั้งนั้นเติมไว้จะถูกล้างทันที
ไม่มีช่องไหนแสดงข้อมูลของรายการที่ไม่ได้เลือกแล้ว
แมปชื่อที่ไม่ตรงกัน หรือปิดการเติม — data-fill รับรายการคู่ ช่อง:คีย์
(ถ้าใส่ชื่อเดียวหมายถึงช่องกับคีย์ชื่อเดียวกัน) หรือรับเป็น JSON ก็ได้
เมื่อระบุ data-fill แล้ว จะเติมเฉพาะที่ระบุไว้เท่านั้น
<!-- คีย์ topic ของรายการ เติมลงช่องชื่อ equipment และไม่เติมช่องอื่น -->
<input type="text" name="product_no" data-autocomplete="true" data-source="/api/products"
data-fill="equipment:topic">
<!-- แบบ JSON -->
<input type="text" name="product_no" data-autocomplete="true" data-source="/api/products"
data-fill='{"equipment":"topic","price":"unit_price"}'>
<!-- ไม่เติมช่องใดเลย -->
<input type="text" name="product_no" data-autocomplete="true" data-source="/api/products"
data-fill="false">ค้นหาแบบลำดับชั้น
สำหรับช่องที่อยู่ (จังหวัด → อำเภอ → ตำบล → รหัสไปรษณีย์)
<input type="text"
data-element="text"
data-autocomplete="true"
data-search-api="/api/address/search"
data-search-field="subdistrict"
name="subdistrict"
placeholder="Search subdistrict...">เมื่อผู้ใช้เลือกรายการ ช่องที่อยู่ที่เกี่ยวข้องทั้งหมดจะถูกเติมให้เอง
รองรับ Hidden Input
เมื่อเปิด autocomplete TextElementFactory จะสร้าง hidden input ไว้เก็บค่าคีย์
<!-- Before enhancement -->
<input type="text" name="city" data-element="text" data-autocomplete="true" ...>
<!-- After enhancement -->
<input type="text" name="city_text" ...> <!-- Shows display text -->
<input type="hidden" name="city"> <!-- Stores key value -->HTML Attributes
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-element |
string | - | ตั้งค่าเป็น text |
data-autocomplete |
boolean | false |
เปิด autocomplete |
data-source |
string/JSON | - | แหล่งข้อมูล autocomplete (URL, array) |
data-min-length |
number | 2 |
ตัวอักษรขั้นต่ำก่อนค้นหา |
data-max-results |
number | 10 |
จำนวนผลลัพธ์สูงสุด |
data-delay |
number | 300 |
Debounce delay (ms) |
data-hierarchy |
string | - | ระดับของลำดับชั้น (province/district/subdistrict) |
data-dependent |
string | - | id ของช่องแม่สำหรับ dropdown ที่ขึ้นต่อกัน |
data-callback |
string | - | ชื่อฟังก์ชันที่ใช้ render รายการเอง |
data-fill |
string/JSON | - | ช่องที่รายการที่เลือกจะเติมให้: รายการ ช่อง:คีย์, JSON หรือ false เพื่อปิด ถ้าไม่ระบุจะเติมตาม id/name ที่ตรงกัน |
data-search-api |
string | - | endpoint สำหรับค้นหาแบบลำดับชั้น |
data-search-field |
string | - | ชื่อฟิลด์สำหรับค้นหาแบบลำดับชั้น |
data-validate |
string | - | Validation rules (email, url, alpha, ฯลฯ) |
data-formatter |
string | - | ชื่อฟังก์ชัน formatter |
Built-in Validators
| Validator | Pattern | คำอธิบาย |
|---|---|---|
email |
^[^\s@]+@[^\s@]+\.[^\s@]+$ |
รูปแบบ email |
url |
HTTP/HTTPS URL pattern | URL ที่ถูกต้อง |
number |
^-?\d*\.?\d+$ |
ค่าตัวเลข |
integer |
^-?\d+$ |
จำนวนเต็ม |
alpha |
^[a-zA-Z]+$ |
ตัวอักษรเท่านั้น |
alphanumeric |
^[a-zA-Z0-9]+$ |
ตัวอักษรและตัวเลข |
usernameOrEmail |
Combined pattern | ชื่อผู้ใช้หรืออีเมล |
JavaScript API
// Get instance
const cityInput = document.querySelector('[name="city"]');
const instance = ElementManager.getInstance(cityInput);
// Access values
console.log(instance.element.value); // Display text
console.log(instance.hiddenInput.value); // Key value
console.log(instance.selectedValue); // Currently selected key
console.log(instance.selectedItem); // ข้อมูลทั้งรายการที่เลือก รวมคีย์อื่นๆ ที่ API ส่งมาด้วย
// Programmatically update autocomplete options
instance.populate([
{value: '1', text: 'Option 1'},
{value: '2', text: 'Option 2'}
]);
// Show/hide dropdown
instance.show();
instance.hide();Custom Render Callback
// Define callback function
window.renderCityItem = function({key, value, search, level}) {
const div = document.createElement('div');
div.className = 'city-item';
div.innerHTML = `
<strong>${value}</strong>
<span class="code">${key}</span>
`;
return div;
};<input type="text"
data-element="text"
data-autocomplete="true"
data-source="/api/cities"
data-callback="renderCityItem"
name="city">PasswordElementFactory
PasswordElementFactory ขยาย TextElementFactory พร้อมฟีเจอร์เฉพาะ password: strength meter, toggle visibility, criteria list และ match validation
การใช้งานพื้นฐาน
<!-- Password พร้อม toggle visibility -->
<input type="password"
data-element="password"
name="password"
minlength="8"
required>
<!-- Password พร้อม strength meter -->
<input type="password"
data-element="password"
data-password-strength="true"
name="password"
required>Password Strength Meter
<input type="password"
data-element="password"
data-password-strength="true"
id="password"
name="password">
<span id="result_password" class="comment">อย่างน้อย 8 ตัวอักษร</span>
<!-- Strength bar จะแทรกหลัง comment -->CSS Classes บน Strength Bar:
.weak- สีแดง (< 40% เกณฑ์ผ่าน).medium- สีเหลือง (40-80% เกณฑ์ผ่าน).strong- สีเขียว (> 80% เกณฑ์ผ่าน)
รายการเงื่อนไขรหัสผ่าน
<input type="password"
data-element="password"
data-password-criteria-list="true"
name="password">เงื่อนไขเริ่มต้น:
- ✅ อย่างน้อย 8 ตัวอักษร
- ✅ มีตัวพิมพ์ใหญ่อย่างน้อยหนึ่งตัว
- ✅ มีตัวพิมพ์เล็กอย่างน้อยหนึ่งตัว
- ⬜ มีตัวเลขอย่างน้อยหนึ่งตัว (ปิดไว้เป็นค่าเริ่มต้น)
- ⬜ มีอักขระพิเศษอย่างน้อยหนึ่งตัว (ปิดไว้เป็นค่าเริ่มต้น)
Password Match Validation
<!-- Password หลัก -->
<input type="password"
data-element="password"
id="password"
name="password"
required>
<!-- ยืนยัน password -->
<input type="password"
data-element="password"
data-target-password="password"
name="password_confirm"
required>HTML Attributes
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-element |
string | - | ตั้งค่าเป็น password |
data-password-strength |
boolean | false |
แสดง strength meter |
data-password-criteria-list |
boolean | false |
แสดง checklist เกณฑ์ |
data-target-password |
string | - | ID ของช่อง password หลัก |
minlength |
number | 8 |
ความยาว password ขั้นต่ำ |
maxlength |
number | 50 |
ความยาว password สูงสุด |
Toggle Visibility
เปิดใช้งานโดยค่าเริ่มต้น ผู้ใช้สามารถ:
- คลิกไอคอนตาเพื่อแสดง/ซ่อน password
- กด
Alt + Skeyboard shortcut
JavaScript API
const instance = ElementManager.getInstance(passwordInput);
// Check if passwords match
const confirmInput = document.getElementById('password_confirm');
const matches = instance.checkMatch(confirmInput);
// Check specific criterion
instance.passesCriterion('minLength', 'myPassword123'); // true/false
instance.passesCriterion('uppercase', 'myPassword123'); // true
instance.passesCriterion('numbers', 'myPassword123'); // true
// Get all failed criteria
const failed = instance.getFailedCriteria('weak');
// ['At least 8 characters', 'At least one uppercase letter']
// Update UI manually
instance.updateStrengthBar();
instance.updateCriteriaList();
instance.updateMatchStatus();ปรับแต่งเงื่อนไขเอง
// Override default criteria in create/enhance
PasswordElementFactory.enhance(element, {
passwordCriteria: {
minLength: {enabled: true, value: 12, label: 'At least {value} characters'},
uppercase: {enabled: true, label: 'At least one uppercase letter'},
lowercase: {enabled: true, label: 'At least one lowercase letter'},
numbers: {enabled: true, label: 'At least one number'},
special: {enabled: true, label: 'At least one special character (!@#$%^&*)'}
}
});SearchElementFactory
SearchElementFactory ขยาย TextElementFactory พร้อมฟีเจอร์เฉพาะการค้นหา: debounced input, clear button และ results container
การใช้งานพื้นฐาน
<!-- Simple search input -->
<input type="search"
data-element="search"
name="search"
placeholder="ค้นหา...">ค้นหาพร้อมแสดงผลลัพธ์
// Create search with custom onSearch handler
SearchElementFactory.enhance(searchInput, {
onSearch: async (query) => {
const response = await fetch(`/api/products?q=${query}`);
const data = await response.json();
return data.results; // [{value, text}, ...]
}
});HTML Attributes
| Attribute | ประเภท | ค่าเริ่มต้น | คำอธิบาย |
|---|---|---|---|
data-element |
string | - | ตั้งค่าเป็น search |
data-search-delay |
number | 300 |
Debounce delay (ms) |
data-min-length |
number | 2 |
ตัวอักษรขั้นต่ำก่อนค้นหา |
data-max-length |
number | 100 |
ความยาวข้อความสูงสุด |
data-max-results |
number | 10 |
จำนวนผลลัพธ์สูงสุด |
data-highlight-matches |
boolean | true |
ไฮไลท์ข้อความที่ตรงกัน |
ตัวเลือกการตั้งค่า
SearchElementFactory.config = {
type: 'search',
inputMode: 'search',
searchDelay: 300,
minLength: 2,
maxLength: 100,
placeholder: 'Search',
wrapperClass: 'search',
inputClass: 'search-input',
clearClass: 'search-clear',
loadingClass: 'search-loading',
resultsClass: 'search-results',
activeClass: 'active',
maxResults: 10,
highlightMatches: true,
formIntegration: true,
keyboard: {
selectKeys: ['Enter', ' '],
closeKeys: ['Escape'],
navigateUpKeys: ['ArrowUp'],
navigateDownKeys: ['ArrowDown']
}
};ปุ่มล้างค่า
ปุ่มล้างค่าถูกเพิ่มให้เองและแสดงเมื่อช่องมีค่าอยู่
.search-clear {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
}สถานะกำลังโหลด
.search-loading {
position: relative;
}
.search-loading::after {
content: '';
position: absolute;
right: 40px;
top: 50%;
width: 16px;
height: 16px;
border: 2px solid #ccc;
border-top-color: #333;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}JavaScript API
const instance = ElementManager.getInstance(searchInput);
// Show results programmatically
instance.createSuggestionList([
{value: '1', text: 'Result 1'},
{value: '2', text: 'Result 2'}
]);
// Access clear button
searchInput.clearButton.click(); // Clear inputเหตุการณ์
Common Events
| Event | เมื่อเกิด | Detail |
|---|---|---|
element:change |
ค่าเปลี่ยน | {value, valid} |
element:valid |
Validation ผ่าน | {value} |
element:invalid |
Validation ไม่ผ่าน | {value, error} |
input |
ผู้ใช้พิมพ์ | Native event |
change |
ค่าถูกยืนยัน | Native event |
focus |
element ได้โฟกัส | Native event |
blur |
element เสียโฟกัส | Native event |
เหตุการณ์ของ TextElementFactory
element.addEventListener('change', (e) => {
const instance = ElementManager.getInstance(e.target);
console.log('Display value:', e.target.value);
console.log('Selected key:', instance.hiddenInput?.value);
});เหตุการณ์ของ PasswordElementFactory
// Listen to password changes with strength info
passwordInput.addEventListener('input', (e) => {
const instance = ElementManager.getInstance(e.target);
const failed = instance.getFailedCriteria(e.target.value);
console.log('Failed criteria:', failed);
});เหตุการณ์ของ SearchElementFactory
// Now.js event system
Now.on('search:performed', ({elementId, query}) => {
console.log(`Search performed on ${elementId}: ${query}`);
});แนวทางปฏิบัติที่ดี
1. ✅ ใช้ Hidden Input สำหรับ Key-Value Pairs
<!-- ✅ ดี: Autocomplete เก็บ key ใน hidden input -->
<input type="text"
data-element="text"
data-autocomplete="true"
data-source="/api/cities"
name="city">
<!-- Form ส่ง: city=123 (hidden input value ไม่ใช่ display text) -->2. ✅ ตั้ง Min Length ของ Autocomplete ให้เหมาะสม
<!-- ✅ ดี: รออย่างน้อย 2 ตัวอักษร -->
<input type="text"
data-element="text"
data-autocomplete="true"
data-source="/api/products"
data-min-length="2">
<!-- ❌ ไม่ดี: ยิงทุกครั้งที่กดปุ่ม -->
<input type="text"
data-autocomplete="true"
data-source="/api/products"
data-min-length="0">3. ✅ ใช้ Password Strength สำหรับการลงทะเบียน
<!-- ✅ ดี: แสดง strength meter ระหว่างลงทะเบียน -->
<form data-form="register">
<input type="password"
data-element="password"
data-password-strength="true"
data-password-criteria-list="true"
name="password">
</form>4. ✅ Match Passwords ด้วย data-target-password
<!-- ✅ ดี: ใช้ built-in match validation -->
<input type="password" id="password" name="password">
<input type="password"
data-target-password="password"
name="password_confirm">5. ✅ หน่วงการค้นหา
<!-- ✅ ดี: SearchElementFactory มี debounce ในตัว -->
<input type="search"
data-element="search"
data-search-delay="300">
<!-- ❌ ไม่ดี: ยิงทุกครั้งที่กดปุ่ม -->
<input type="text" oninput="search(this.value)">6. ✅ ให้ข้อความผิดพลาดที่ชัดเจน
<input type="text"
data-element="text"
data-validate="email"
data-error-email="Please enter a valid email address like example@domain.com"
name="email">เอกสารที่เกี่ยวข้อง
- Form Elements ภาพรวม
- ElementFactory - Base class
- FormManager - Form-level validation
- Number Elements - Number inputs