Now.js Framework Documentation

Now.js Framework Documentation

Special Elements (อิลิเมนต์พิเศษ)

TH 09 Nov 2026 03:28

Special Elements (อิลิเมนต์พิเศษ)

เอกสารสำหรับ form elements พิเศษ: ColorElementFactory, RangeElementFactory, TagsElementFactory, MaskElementFactory และ TextareaElementFactory

📋 สารบัญ

  1. ภาพรวม
  2. ColorElementFactory
  3. RangeElementFactory
  4. TagsElementFactory
  5. MaskElementFactory
  6. TextareaElementFactory
  7. แนวทางปฏิบัติที่ดี

ภาพรวม

Special elements มอบ UI ที่ปรับปรุงสำหรับ input types เฉพาะที่ต้องการมากกว่าการจัดการ text หรือ number พื้นฐาน

Element data-element คำอธิบาย
ColorElementFactory color Color picker พร้อม palette
RangeElementFactory range Single/dual range slider
TagsElementFactory tags Tags/chips input พร้อม autocomplete
MaskElementFactory mask Input masking (โทรศัพท์, วันที่, ฯลฯ)
TextareaElementFactory textarea Auto-resize textarea

ColorElementFactory

ColorElementFactory สร้าง custom color picker พร้อม predefined palette และ hex input

การใช้งานพื้นฐาน

<input type="color"
       data-element="color"
       name="theme_color"
       value="#007bff">

HTML Attributes

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-element string - ตั้งค่าเป็น color
value string #000000 สีเริ่มต้น (hex)
disabled boolean false ปิดการใช้งาน picker
readonly boolean false โหมดอ่านอย่างเดียว

จานสีเริ่มต้น

const COLOR_PALETTE = [
  '#FF5722', '#F44336', '#E91E63', '#9C27B0',
  '#673AB7', '#3F51B5', '#2196F3', '#03A9F4',
  '#00BCD4', '#009688', '#4CAF50', '#8BC34A',
  '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800',
  // ... and more standard colors
  '#FFFFFF', '#000000'
];

JavaScript API

const instance = ElementManager.getInstance(colorInput);

// รับสีปัจจุบัน
const color = instance.getColor();  // '#007bff'

// ตั้งค่าสี
instance.setColor('#ff5722');

// เปิด/ปิด picker
instance.open();
instance.close();
instance.toggle();

// ปิด/เปิดการใช้งาน
instance.setDisabled(true);
instance.setReadonly(true);

ฟีเจอร์

  • Color Palette: คลิกสีที่กำหนดไว้
  • Hex Input: พิมพ์ค่า hex กำหนดเอง
  • Preview: แสดงตัวอย่างสีในปุ่ม
  • Copy/Paste: Ctrl+C คัดลอก, Ctrl+V วาง
  • Contrast Text: สีตัวอักษรบนปุ่มปรับเองให้อ่านออกเสมอ

เหตุการณ์

colorInput.addEventListener('change', (e) => {
  console.log('Selected color:', e.target.value);
});

RangeElementFactory

RangeElementFactory สร้าง range sliders พร้อม UX ที่ดีกว่า native range inputs รองรับ single และ dual range modes

การใช้งานพื้นฐาน

<!-- Single range slider -->
<input type="range"
       data-element="range"
       min="0"
       max="100"
       value="50"
       name="volume">

<!-- Dual range slider (min-max) -->
<input type="range"
       data-element="range"
       min="0"
       max="1000"
       data-value="200-800"
       data-dual="true"
       name="price_range">

HTML Attributes

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-element string - ตั้งค่าเป็น range
min number 0 ค่าขั้นต่ำ
max number 100 ค่าสูงสุด
step number 1 ค่าเพิ่ม/ลด
data-dual boolean false เปิด dual handles
data-show-value boolean true แสดง value label
data-format string - รูปแบบค่า (เช่น {value}%)
value string - ค่าเดี่ยว หรือ "min-max" สำหรับ dual
disabled boolean false ปิดการใช้งาน slider
readonly boolean false โหมดอ่านอย่างเดียว

Range เดี่ยว

<input type="range"
       data-element="range"
       min="0"
       max="100"
       step="5"
       value="75"
       data-format="{value}%"
       name="opacity">

Range คู่ (ต่ำสุด-สูงสุด)

<input type="range"
       data-element="range"
       min="0"
       max="10000"
       step="100"
       data-value="2000-8000"
       data-dual="true"
       data-format="฿{value}"
       name="budget">

JavaScript API

const instance = ElementManager.getInstance(rangeInput);

// รับค่า
const value = instance.getValue();
// Single: 75
// Dual: {min: 2000, max: 8000}

// ตั้งค่า
instance.setValue(80);                       // Single
instance.setValue({min: 1000, max: 5000});   // Dual

// อัพเดท constraints
instance.setMin(10);
instance.setMax(200);
instance.setStep(10);

// ปิด/เปิดการใช้งาน
instance.setDisabled(true);
instance.setReadonly(true);

การใช้คีย์บอร์ด

ปุ่ม การทำงาน
← / ↓ ลดลงทีละ step
→ / ↑ เพิ่มขึ้นทีละ step
Home ไปค่าต่ำสุด
End ไปค่าสูงสุด
Page Up เพิ่มขึ้น 10 step
Page Down ลดลง 10 step

เหตุการณ์

// Input event (while dragging)
rangeInput.addEventListener('input', (e) => {
  console.log('Current value:', e.detail.value);
});

// Change event (after release)
rangeInput.addEventListener('change', (e) => {
  console.log('Final value:', e.detail.value);
});

TagsElementFactory

TagsElementFactory แปลง text input เป็น tags/chips input พร้อม visual tags, keyboard navigation และ optional autocomplete

การใช้งานพื้นฐาน

<!-- Simple tags input -->
<input type="text"
       data-element="tags"
       name="keywords"
       placeholder="เพิ่ม tags...">

<!-- ตัวเลือกจาก options ของ API ที่โหลดฟอร์ม -->
<input type="text"
       data-element="tags"
       data-options-key="tags"
       name="categories">

<!-- ค้นตัวเลือกจาก endpoint และรับเฉพาะค่าที่เลือกจากรายการ -->
<input type="text"
       data-element="tags"
       data-source="api/example/members"
       data-allow-new="false"
       name="members">

<!-- Tags พร้อม datalist -->
<input type="text"
       data-element="tags"
       list="available-tags"
       name="skills">
<datalist id="available-tags">
  <option value="1" label="JavaScript">
  <option value="2" label="Python">
</datalist>

HTML Attributes

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-element string - ตั้งค่าเป็น tags
data-source string - URL (api/…, /…, http…) = ค้นจาก endpoint ระหว่างพิมพ์ ดูด้านล่าง ค่าอื่นเป็นชื่อตัวแปร global หรือรายการ JSON
data-options-key string - คีย์ของรายการตัวเลือกใน options ของ API ที่โหลดฟอร์ม
list string - id ของ datalist ที่ใช้เป็น options
data-allow-new boolean true false = เป็น tag ได้เฉพาะรายการที่เลือกจากตัวเลือก กด Enter บนข้อความที่พิมพ์หรือวางรายการจะไม่เพิ่มอะไร
data-separator string , ตัวคั่นสำหรับค่าที่วาง
data-max-tags number ไม่จำกัด จำนวน tags สูงสุด
data-duplicates boolean false อนุญาต tags ซ้ำ
data-min-length number 2 เมื่อเป็น URL จำนวนตัวอักษรที่ต้องพิมพ์ก่อนถาม endpoint ตัวเลือกแบบรายการกรองตั้งแต่ตัวแรกและแสดงทุกตัวเลือกเมื่อ focus
data-depends string - ช่องอื่นในฟอร์มที่ส่งไปกับการค้นหา หรือใช้กรองรายการ — attribute เดียวกับของ text input ดู text-elements.md "กรองด้วยช่องอื่น"
data-depends-clear boolean false true = ลบ tag ทั้งหมดเมื่อช่องใน data-depends เปลี่ยน

ค้นจาก Endpoint

เมื่อ data-source เป็น URL ช่องนี้จะถาม endpoint แทนการกรองรายการ
คำขอและคำตอบเป็นแบบเดียวกับ text input ที่มี data-autocomplete endpoint เดียวจึงใช้ได้กับทั้งสองแบบ

  • พิมพ์อย่างน้อย data-min-length ตัว จะส่ง GET <data-source>?q=<ข้อความที่พิมพ์> (หลังกดแป้นครั้งสุดท้าย 300 ms)
  • endpoint ตอบด้วยซองคำตอบมาตรฐาน data เป็นรายการ {value, text}
  • แสดงแถวตามที่ได้รับ — endpoint จับคู่มาแล้ว ซึ่งอาจจับจากคอลัมน์ที่ text ไม่ได้แสดง
  • คำตอบของคำค้นเก่าที่มาถึงหลังคำค้นใหม่จะถูกทิ้ง
  • เลือกแถวแล้วรายการปิด focus ใหม่จะถามอีกครั้งเฉพาะเมื่อยังมีข้อความพิมพ์อยู่พอ
// GET api/example/members?q=som
$search = $request->get('q')->topic();
return $this->successResponse([
    ['value' => '12', 'text' => 'Somchai Jaidee (somchai@example.com)']
], 'Search completed');

endpoint แปลงค่าที่บันทึกไว้กลับเป็นชื่อที่แสดงไม่ได้ API ที่โหลดฟอร์มจึงผูกค่าที่บันทึกไว้
เป็นคู่ {key, value} — data-attr="value:members" กับ members: [{key: '12', value: 'Somchai Jaidee'}]

รับเฉพาะค่าที่เลือก

data-allow-new="false" ใช้กับค่าที่ต้องมีอยู่จริงที่ server เช่น id ของสมาชิก
ข้อความที่พิมพ์ค้างอยู่ในช่องให้แก้ และไม่ถูกส่งไปกับฟอร์ม server จึงได้รับเฉพาะคีย์ที่ endpoint เสนอ

การตั้งค่า

TagsElementFactory.config = {
  type: 'text',
  placeholder: 'Add tags...',
  separator: ',',
  maxTags: null,
  duplicates: false,
  allowNew: true,
  autocomplete: {enabled: false, source: null, minLength: 2, maxResults: 10, delay: 300}
};

Keyboard Interaction

ปุ่ม การทำงาน
Enter เลือกตัวเลือกที่ไฮไลต์อยู่ หรือเพิ่มข้อความที่พิมพ์เมื่ออนุญาต tag ใหม่
Backspace ลบ tag สุดท้าย (เมื่อ input ว่าง)
↓ / ↑ Navigate autocomplete
Escape ปิด autocomplete

JavaScript API

const instance = ElementManager.getInstance(tagsInput);

// รับ tags ทั้งหมด
const tags = instance.getTags();
// [{key: '1', value: 'JavaScript'}, {key: '2', value: 'Python'}]

// เพิ่ม tag
instance.addTag('3', 'React');

// ลบ tag
instance.removeTag('2');

// กำหนด tags ทั้งหมด
instance.setTags([
  {key: '1', value: 'JavaScript'},
  {key: '4', value: 'Vue'}
]);

// ล้างทั้งหมด
instance.clear();

// เปลี่ยนรายการตัวเลือกของ autocomplete
instance.updateOptions([
  {value: '1', text: 'JavaScript'},
  {value: '2', text: 'Python'}
]);

การส่งค่าไปกับฟอร์ม

tags ถูกส่งไปเป็น hidden input

<!-- Original input -->
<input data-element="tags" name="skills">

<!-- Generated hidden inputs -->
<input type="hidden" name="skills[]" value="1">
<input type="hidden" name="skills[]" value="2">
<input type="hidden" name="skills[]" value="3">

เหตุการณ์

tagsInput.addEventListener('change', (e) => {
  const instance = ElementManager.getInstance(e.target);
  console.log('Tags:', instance.getTags());
});

TagsElementFactory ไม่ได้ส่งเหตุการณ์ tags:* เลย การเพิ่มหรือลบแท็กยิงแค่
change แบบ native เท่านั้น ถ้าต้องการรู้ว่าแท็กไหนเปลี่ยน ให้เทียบ
instance.getTags() กับรายการก่อนหน้าเอง

MaskElementFactory

MaskElementFactory ใช้ input masks สำหรับข้อมูลที่มีโครงสร้าง เช่น หมายเลขโทรศัพท์ วันที่ หมายเลขบัตร ฯลฯ

การใช้งานพื้นฐาน

<!-- หมายเลขโทรศัพท์ไทย -->
<input type="tel"
       data-element="tel"
       name="mobile">

<!-- บัตรเครดิต -->
<input type="text"
       data-element="mask"
       data-mask="#### #### #### ####"
       name="card">

<!-- วันที่ -->
<input type="text"
       data-element="mask"
       data-mask="##/##/####"
       name="date">

HTML Attributes

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-element string - mask หรือ tel
data-mask string - รูปแบบ mask
data-placeholder-char string _ ตัวอักษรแทนตำแหน่งที่ยังไม่กรอก
data-mask-on-init boolean true ใส่ mask ตอนเริ่มต้น
data-mask-on-change boolean true ใส่ mask เมื่อค่าเปลี่ยน
data-mask-on-blur boolean true ใส่ mask เมื่อออกจากช่อง
data-unmask-on-submit boolean false ถอด mask ออกตอนส่งฟอร์ม

Mask Pattern Characters

ตัวอักษร รับ
# ตัวเลข (0-9)
A ตัวอักษร (A-Z, a-z)
* ตัวอักษรหรือตัวเลข
อื่นๆ Literal (แสดงตามที่เป็น)

Common Patterns

กรณีใช้งาน Pattern ตัวอย่าง
โทรศัพท์สหรัฐ (###) ###-#### (123) 456-7890
โทรศัพท์ไทย 99-9999-9999 08-1234-5678
วันที่ ##/##/#### 31/12/2025
เวลา ##:## 14:30
บัตรเครดิต #### #### #### #### 1234 5678 9012 3456
SSN ###-##-#### 123-45-6789
Zip+4 #####-#### 12345-6789

TelElementFactory

mask สำหรับหมายเลขโทรศัพท์ไทย ใช้กับ <input type="tel"> ทุกช่องโดยอัตโนมัติ

<input type="tel" name="phone" maxlength="10">
<!-- พิมพ์ 0812345678 จะแสดงเป็น 08-1234-5678 -->

mask คือ 99-9999-9999 ค่าต้องตรงกับ ^[0-9]{2}-[0-9]{4}-[0-9]{3,4}$

maxlength กับ mask: เขียน maxlength ตามจำนวนตัวเลขจริง (10 หลัก) ได้ mask แบบตายตัว
เติมขีดคั่นเอง maxlength ที่สั้นกว่า mask จึงถูกขยายเป็นความยาวของ mask (ที่นี่ 12)
ไม่งั้นจะพิมพ์ได้แค่ 08-1234-56 และการตรวจสอบจะไม่ยอมรับเบอร์ที่ครบ maxlength
ที่ยาวกว่า mask และ mask ที่มี # (ความยาวไม่คงที่) คงตามที่เขียนไว้

ฝั่งเซิร์ฟเวอร์ได้รับข้อความที่มีขีด (08-1234-5678) และตัดขีดออกเอง
(Kotchasan: $request->post('phone')->number())

JavaScript API

// กำหนดค่า = จัดรูปแบบให้เอง
maskedInput.value = '0898765432';                  // แสดง 08-9876-5432

// ตัวเลขล้วน
const raw = maskedInput.value.replace(/\D/g, '');  // '0898765432'

เหตุการณ์

maskedInput.addEventListener('change', (e) => {
  console.log('Masked:', e.target.value);
  console.log('Raw:', e.target.value.replace(/\D/g, ''));
});

TextareaElementFactory

TextareaElementFactory ปรับปรุง textarea ด้วยฟังก์ชัน auto-resize

การใช้งานพื้นฐาน

<textarea data-element="textarea"
          name="description"
          rows="3"
          placeholder="กรอกคำอธิบาย..."></textarea>

HTML Attributes

Attribute ประเภท ค่าเริ่มต้น คำอธิบาย
data-element string - ตั้งค่าเป็น textarea
data-auto-resize boolean true เปิด auto-resize
data-min-rows number 3 จำนวนแถวขั้นต่ำ
data-max-rows number 10 จำนวนแถวสูงสุดก่อน scroll
maxlength number - จำกัดตัวอักษร
data-show-counter boolean false แสดงตัวนับตัวอักษร

พฤติกรรมการขยายอัตโนมัติ

textarea ปรับความสูงตามเนื้อหาให้เอง

/* Textarea expands with content */
[data-element="textarea"] {
  min-height: calc(3 * 1.5em);  /* 3 rows minimum */
  max-height: calc(10 * 1.5em); /* 10 rows maximum */
  overflow-y: auto;             /* Scroll when max reached */
  resize: vertical;             /* Allow manual resize */
}

ตัวนับตัวอักษร

<textarea data-element="textarea"
          data-show-counter="true"
          maxlength="500"
          name="bio"></textarea>
<!-- Shows: 0/500 characters -->

JavaScript API

const instance = ElementManager.getInstance(textareaEl);

// สั่งคำนวณความสูงใหม่
instance.resize();

// อ่านความสูงปัจจุบัน
const height = instance.getHeight();

แนวทางปฏิบัติที่ดี

1. ✅ ใช้ Element ที่เหมาะกับประเภทข้อมูล

<!-- ✅ สี -->
<input data-element="color" name="theme">

<!-- ✅ ช่วงราคา -->
<input data-element="range" data-dual="true" name="budget">

<!-- ✅ หลายหมวดหมู่ -->
<input data-element="tags" name="categories">

<!-- ✅ Input มีโครงสร้าง -->
<input data-element="mask" data-mask="##/##/####" name="date">

2. ✅ กำหนดขอบเขตที่สมเหตุสมผล

<!-- ✅ ดี: จำกัด tags -->
<input data-element="tags" data-max-tags="5" name="keywords">

<!-- ✅ ดี: จำกัด textarea -->
<textarea data-element="textarea" maxlength="1000" data-max-rows="15"></textarea>

3. ✅ ใส่ Autocomplete ให้ Tags

<!-- ✅ ดี: ผู้ใช้เห็น tag ที่มีอยู่แล้ว -->
<input data-element="tags"
       data-source="/api/tags"
       name="skills">

<!-- ❌ ไม่ดี: ผู้ใช้ต้องเดาชื่อ tag เอง -->
<input data-element="tags" name="skills">

4. ✅ ใช้ tel Type สำหรับหมายเลขโทรศัพท์

<!-- ✅ ดี: Keyboard มือถือแสดงตัวเลข -->
<input type="tel"
       data-element="tel"
       name="phone">

5. ✅ พิจารณาถอด mask ก่อนส่ง

<!-- ✅ ดี: เซิร์ฟเวอร์ได้ตัวเลขล้วน -->
<input data-element="mask"
       data-mask="(###) ###-####"
       data-unmask-on-submit="true"
       name="phone">
<!-- Form submits: 1234567890 instead of (123) 456-7890 -->

6. ✅ ใช้ Dual Range สำหรับ Filters

<!-- ✅ ดี: Price filter พร้อม min-max -->
<input data-element="range"
       data-dual="true"
       min="0"
       max="10000"
       step="100"
       data-format="฿{value}"
       name="price">

เอกสารที่เกี่ยวข้อง