Now.js Framework Documentation

Now.js Framework Documentation

Special Elements

EN 09 Nov 2026 03:28

Special Elements

Documentation for specialized form elements: ColorElementFactory, RangeElementFactory, TagsElementFactory, MaskElementFactory, and TextareaElementFactory.

📋 Table of Contents

  1. Overview
  2. ColorElementFactory
  3. RangeElementFactory
  4. TagsElementFactory
  5. MaskElementFactory
  6. TextareaElementFactory
  7. Best Practices

Overview

Special elements provide enhanced UI for specific input types that need more than basic text or number handling.

Element data-element Description
ColorElementFactory color Color picker with palette
RangeElementFactory range Single/dual range slider
TagsElementFactory tags Tags/chips input with autocomplete
MaskElementFactory mask Input masking (phone, date, etc.)
TextareaElementFactory textarea Auto-resize textarea

ColorElementFactory

ColorElementFactory creates a custom color picker with predefined palette and hex input.

Basic Usage

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

<!-- Or with text input -->
<input type="text"
       data-element="color"
       name="bg_color"
       value="#ffffff">

HTML Attributes

Attribute Type Default Description
data-element string - Set to color
value string #000000 Initial color (hex)
disabled boolean false Disable picker
readonly boolean false Read-only mode

Default Color Palette

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);

// Get current color
const color = instance.getColor();  // '#007bff'

// Set color
instance.setColor('#ff5722');

// Open/close picker
instance.open();
instance.close();
instance.toggle();

// Disable/enable
instance.setDisabled(true);
instance.setReadonly(true);

Features

  • Color Palette: Click predefined colors
  • Hex Input: Type custom hex values
  • Preview: Color preview in button
  • Copy/Paste: Ctrl+C to copy, Ctrl+V to paste colors
  • Contrast Text: Button text auto-adjusts for visibility

Events

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

RangeElementFactory

RangeElementFactory creates enhanced range sliders with better UX than native range inputs. Supports single and dual range modes.

Basic Usage

<!-- 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 Type Default Description
data-element string - Set to range
min number 0 Minimum value
max number 100 Maximum value
step number 1 Step increment
value string - Single value or "min-max" for dual
data-dual boolean false Enable dual handles
data-show-value boolean true Show value label
data-format string - Value format (e.g., {value}%)
disabled boolean false Disable slider
readonly boolean false Read-only mode

Single Range

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

Dual Range (Min-Max)

<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);

// Get value
const value = instance.getValue();
// Single: 75
// Dual: {min: 2000, max: 8000}

// Set value
instance.setValue(80);           // Single
instance.setValue({min: 1000, max: 5000});  // Dual

// Update constraints
instance.setMin(10);
instance.setMax(200);
instance.setStep(10);

// Enable/disable
instance.setDisabled(true);
instance.setReadonly(true);

Keyboard Navigation

Key Action
← / ↓ Decrease by step
→ / ↑ Increase by step
Home Go to min
End Go to max
Page Up Increase by 10 steps
Page Down Decrease by 10 steps

Events

// 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 transforms text input into a tags/chips input with visual tags, keyboard navigation, and optional autocomplete.

Basic Usage

<!-- Simple tags input -->
<input type="text"
       data-element="tags"
       name="keywords"
       placeholder="Add tags...">

<!-- Suggestions from the options of the form's load API -->
<input type="text"
       data-element="tags"
       data-options-key="tags"
       name="categories">

<!-- Suggestions searched from an endpoint, only picked values become tags -->
<input type="text"
       data-element="tags"
       data-source="api/example/members"
       data-allow-new="false"
       name="members">

<!-- Tags with 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 Type Default Description
data-element string - Set to tags
data-source string - A URL (api/…, /…, http…) is searched as you type, see below. Anything else names a global variable or holds a JSON list
data-options-key string - Key of the suggestion list in options of the form's load API
list string - Datalist ID for options
data-allow-new boolean true false = only a picked suggestion becomes a tag. Enter on typed text and a pasted list add nothing
data-separator string , Separator for pasted values
data-max-tags number none Maximum tags allowed
data-duplicates boolean false Allow duplicate tags
data-min-length number 2 for a URL Characters typed before an endpoint is asked. A list filters from the first character and shows every option on focus
data-depends string - Other fields of the form sent with the search, or used to filter a list — the same attribute text inputs read, see text-elements.md "Filtering by Other Fields"
data-depends-clear boolean false true = remove every tag when a data-depends field changes

Searching an Endpoint

When data-source is a URL the input asks the endpoint instead of filtering a list.
The request and response are the ones a text input with data-autocomplete uses, so one endpoint serves both.

  • Typing at least data-min-length characters sends GET <data-source>?q=<typed> (300 ms after the last key)
  • The endpoint answers with the standard envelope, data is a list of {value, text}
  • Rows are shown as returned — the endpoint has already matched them, possibly on a column text does not show
  • An answer to an older query that arrives after a newer one is dropped
  • Picking a row closes the suggestions. Focus asks again only when enough text is still typed
// GET api/example/members?q=som
$search = $request->get('q')->topic();
return $this->successResponse([
    ['value' => '12', 'text' => 'Somchai Jaidee (somchai@example.com)']
], 'Search completed');

An endpoint cannot turn a saved value back into its label, so a load API binds saved
values as {key, value} pairs — data-attr="value:members" with members: [{key: '12', value: 'Somchai Jaidee'}].

Only Picked Values

data-allow-new="false" is for values that must exist on the server, such as member IDs.
Typed text stays in the input to be corrected and is never submitted, so the server only receives keys the endpoint offered.

Configuration

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

Key Action
Enter Pick the highlighted suggestion, or add the typed text when new tags are allowed
Backspace Remove last tag (when input empty)
↓ / ↑ Navigate autocomplete
Escape Close autocomplete

JavaScript API

const instance = ElementManager.getInstance(tagsInput);

// Get all tags
const tags = instance.getTags();
// [{key: '1', value: 'JavaScript'}, {key: '2', value: 'Python'}]

// Add tag
instance.addTag('3', 'React');

// Remove tag
instance.removeTag('2');

// Set all tags
instance.setTags([
  {key: '1', value: 'JavaScript'},
  {key: '4', value: 'Vue'}
]);

// Clear all
instance.clear();

// Update autocomplete options
instance.updateOptions([
  {value: '1', text: 'JavaScript'},
  {value: '2', text: 'Python'}
]);

Form Submission

Tags are submitted as hidden inputs:

<!-- 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">

Events

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

TagsElementFactory emits no tags:* events — adding or removing a tag fires the
native change event only. Compare instance.getTags() against the previous
list if you need to know which tag moved.

MaskElementFactory

MaskElementFactory applies input masks for structured data like phone numbers, dates, credit cards, etc.

Basic Usage

<!-- Phone number -->
<input type="tel"
       data-element="mask"
       data-mask="(###) ###-####"
       name="phone">

<!-- Thai phone -->
<input type="tel"
       data-element="tel"
       name="mobile">

<!-- Credit card -->
<input type="text"
       data-element="mask"
       data-mask="#### #### #### ####"
       name="card">

<!-- Date -->
<input type="text"
       data-element="mask"
       data-mask="##/##/####"
       name="date">

HTML Attributes

Attribute Type Default Description
data-element string - mask or tel
data-mask string - Mask pattern
data-placeholder-char string _ Character for unfilled positions
data-mask-on-init boolean true Apply mask on init
data-mask-on-change boolean true Apply mask on change
data-mask-on-blur boolean true Apply mask on blur
data-unmask-on-submit boolean false Remove mask on form submit

Mask Pattern Characters

Character Accepts
# Digit (0-9)
A Letter (A-Z, a-z)
* Alphanumeric
Other Literal (shown as-is)

Common Patterns

Use Case Pattern Example
US Phone (###) ###-#### (123) 456-7890
Thai Phone 99-9999-9999 08-1234-5678
Date ##/##/#### 31/12/2025
Time ##:## 14:30
Credit Card #### #### #### #### 1234 5678 9012 3456
SSN ###-##-#### 123-45-6789
Zip+4 #####-#### 12345-6789

TelElementFactory

Mask for Thai phone numbers, applied to every <input type="tel">:

<input type="tel" name="phone" maxlength="10">
<!-- Typing 0812345678 shows 08-1234-5678 -->

The mask is 99-9999-9999; a value must match ^[0-9]{2}-[0-9]{4}-[0-9]{3,4}$.

maxlength and masks: maxlength may be written for the raw value (10
digits). A fixed mask adds its own separators, so a maxlength shorter than the
mask grows to the mask length (12 here) — otherwise typing would stop at
08-1234-56 and validation would reject the complete number. A longer
maxlength, and masks containing # (variable length), stay as written.

The server receives the masked text (08-1234-5678) and strips the separators
(Kotchasan: $request->post('phone')->number()).

JavaScript API

// Assigning the value formats it
maskedInput.value = '0898765432';                  // shows 08-9876-5432

// The raw digits
const raw = maskedInput.value.replace(/\D/g, '');  // '0898765432'

Events

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

TextareaElementFactory

TextareaElementFactory enhances textarea with auto-resize functionality.

Basic Usage

<textarea data-element="textarea"
          name="description"
          rows="3"
          placeholder="Enter description..."></textarea>

HTML Attributes

Attribute Type Default Description
data-element string - Set to textarea
data-auto-resize boolean true Enable auto-resize
data-min-rows number 3 Minimum rows
data-max-rows number 10 Maximum rows before scroll
maxlength number - Character limit
data-show-counter boolean false Show character counter

Auto-Resize Behavior

The textarea automatically adjusts height based on content:

/* 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 */
}

Character Counter

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

JavaScript API

const instance = ElementManager.getInstance(textareaEl);

// Force resize recalculation
instance.resize();

// Get current height
const height = instance.getHeight();

Best Practices

1. ✅ Use Appropriate Element for Data Type

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

<!-- ✅ Price ranges -->
<input data-element="range" data-dual="true" name="budget">

<!-- ✅ Multiple categories -->
<input data-element="tags" name="categories">

<!-- ✅ Structured input -->
<input data-element="mask" data-mask="##/##/####" name="date">

2. ✅ Set Sensible Limits

<!-- ✅ Good: Limit tags -->
<input data-element="tags" data-max-tags="5" name="keywords">

<!-- ✅ Good: Limit textarea -->
<textarea data-element="textarea" maxlength="1000" data-max-rows="15"></textarea>

3. ✅ Provide Autocomplete for Tags

<!-- ✅ Good: User can discover existing tags -->
<input data-element="tags"
       data-source="/api/tags"
       name="skills">

<!-- ❌ Bad: User must guess tag names -->
<input data-element="tags" name="skills">

4. ✅ Use tel Type for Phone Numbers

<!-- ✅ Good: Mobile keyboard shows numbers -->
<input type="tel"
       data-element="tel"
       name="phone">

<!-- ❌ Less ideal: Text keyboard on mobile -->
<input type="text"
       data-element="mask"
       data-mask="###-###-####"
       name="phone">

5. ✅ Consider Unmask on Submit

<!-- ✅ Good: Server receives clean number -->
<input data-element="mask"
       data-mask="(###) ###-####"
       data-unmask-on-submit="true"
       name="phone">
<!-- Form submits: 1234567890 instead of (123) 456-7890 -->

6. ✅ Use Dual Range for Filters

<!-- ✅ Good: Price filter with min-max -->
<input data-element="range"
       data-dual="true"
       min="0"
       max="10000"
       step="100"
       data-format="฿{value}"
       name="price">