Now.js Framework Documentation

Now.js Framework Documentation

Text-based Elements

EN 04 Sep 2026 01:40

Text-based Elements

Documentation for TextElementFactory, PasswordElementFactory, and SearchElementFactory - text input components with autocomplete, validation, and specialized features.

📋 Table of Contents

  1. Overview
  2. TextElementFactory
  3. PasswordElementFactory
  4. SearchElementFactory
  5. Events
  6. Best Practices

Overview

Text-based elements handle text input with features like autocomplete, validation, and formatting. They share the TextElementFactory base and register different data-element types.

Element data-element Description
TextElementFactory text Text input with autocomplete, hidden input support
PasswordElementFactory password Password with strength meter, toggle visibility
SearchElementFactory search Search input with debounce, results container

TextElementFactory

TextElementFactory manages text inputs with autocomplete and hidden input support. When autocomplete is enabled, it creates a hidden input to store the selected value (key) while displaying the text (label) to the user.

Basic Usage

<!-- Simple text input -->
<input type="text"
       data-element="text"
       name="username"
       placeholder="Enter username">

<!-- Text with validation -->
<input type="text"
       data-element="text"
       data-validate="email"
       name="email"
       placeholder="Enter email">

Autocomplete Feature

Static Source (Array)

<input type="text"
       data-element="text"
       data-autocomplete="true"
       data-source='[{"value":"1","text":"Bangkok"},{"value":"2","text":"Chiang Mai"}]'
       name="city"
       placeholder="Select city">

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>

💡 Note: When using datalist, the <datalist> element is automatically removed and converted to an autocomplete dropdown.

Ajax Source

<input type="text"
       data-element="text"
       data-autocomplete="true"
       data-source="/api/cities"
       data-min-length="2"
       name="city"
       placeholder="Search city...">

Expected API Response:

{
  "success": true,
  "data": [
    {"value": "1", "text": "Bangkok"},
    {"value": "2", "text": "Chiang Mai"}
  ]
}

Filling Other Fields From the Selected Item

Whatever else the API returns with a suggestion is written into the fields of the
same name, so one selection can fill a whole block of the form. No markup is
needed: the API decides what a selection fills.

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

<!-- Filled by the item's `topic` and `price` when a suggestion is chosen -->
<input type="text" id="topic" name="topic" readonly>
<input type="text" id="price" name="price">

A target is looked up by id first, then by name, and only inside the same
form — two forms on one page never fill each other. Every element type is
supported: text inputs and textareas take the value, a checkbox is ticked by the
truthiness of the value, a radio group selects the member whose value matches, a
multiple select selects each value of a comma-separated list or array, and an
element that is not a form control (a <span>, <div>, or table cell) receives
the text. A target that is itself an autocomplete is written through its own
instance, so its visible text and hidden value stay in step.

What is never filled: value, text, label, key and options describe
the suggestion itself, and id almost always means the record the API returned,
not the record the form is editing — filling it would overwrite the form's own
id. The autocomplete's own input is skipped too. Map any of them explicitly
with data-fill when you do want them.

Keeping the form honest: a property the item does not carry empties its
field, and typing again after a selection clears everything that selection
filled. A field never keeps showing data belonging to an item that is no longer
selected.

Mapping different names, or turning it off — data-fill takes a list of
field:property pairs (a bare name means the field and the property share it),
or JSON. When data-fill is present, only what it lists is filled:

<!-- The item's `topic` fills the field named `equipment`; nothing else is filled -->
<input type="text" name="product_no" data-autocomplete="true" data-source="/api/products"
       data-fill="equipment:topic">

<!-- Same thing in JSON -->
<input type="text" name="product_no" data-autocomplete="true" data-source="/api/products"
       data-fill='{"equipment":"topic","price":"unit_price"}'>

<!-- Fill nothing -->
<input type="text" name="product_no" data-autocomplete="true" data-source="/api/products"
       data-fill="false">

For address fields (province → district → subdistrict → zipcode):

<input type="text"
       data-element="text"
       data-autocomplete="true"
       data-search-api="/api/address/search"
       data-search-field="subdistrict"
       name="subdistrict"
       placeholder="Search subdistrict...">

When user selects an item, all related address fields are populated automatically.

Hidden Input Support

When autocomplete is enabled, TextElementFactory creates a hidden input to store the key value:

<!-- 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 Type Default Description
data-element string - Set to text
data-autocomplete boolean false Enable autocomplete
data-source string/JSON - Autocomplete source (URL, array, or global variable name)
data-min-length number 2 Minimum characters before search
data-max-results number 10 Maximum results to display
data-delay number 300 Debounce delay (ms)
data-hierarchy string - Hierarchical level (province/district/subdistrict)
data-dependent string - ID of parent field for dependent dropdowns
data-callback string - Custom callback function name for rendering items
data-fill string/JSON - Fields the selected item fills: field:property list, JSON map, or false to turn off. Omit it to fill by matching id/name
data-search-api string - API endpoint for hierarchical search
data-search-field string - Field name for hierarchical search
data-validate string - Validation rules (email, url, alpha, etc.)
data-formatter string - Formatter function name

Built-in Validators

Validator Pattern Description
email ^[^\s@]+@[^\s@]+\.[^\s@]+$ Valid email format
url HTTP/HTTPS URL pattern Valid URL
number ^-?\d*\.?\d+$ Numeric value
integer ^-?\d+$ Whole number
alpha ^[a-zA-Z]+$ Letters only
alphanumeric ^[a-zA-Z0-9]+$ Letters and numbers
usernameOrEmail Combined pattern Username or email

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);       // The whole item that was selected, including its extra properties

// 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 extends TextElementFactory with password-specific features: strength meter, toggle visibility, criteria list, and match validation.

Basic Usage

<!-- Password with toggle visibility -->
<input type="password"
       data-element="password"
       name="password"
       minlength="8"
       required>

<!-- Password with 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">At least 8 characters</span>
<!-- Strength bar will be inserted after comment -->

CSS Classes on Strength Bar:

  • .weak - Red (< 40% criteria met)
  • .medium - Yellow (40-80% criteria met)
  • .strong - Green (> 80% criteria met)

Password Criteria List

<input type="password"
       data-element="password"
       data-password-criteria-list="true"
       name="password">

Default Criteria:

  • ✅ At least 8 characters
  • ✅ At least one uppercase letter
  • ✅ At least one lowercase letter
  • ⬜ At least one number (disabled by default)
  • ⬜ At least one special character (disabled by default)

Password Match Validation

<!-- Main password field -->
<input type="password"
       data-element="password"
       id="password"
       name="password"
       required>

<!-- Confirm password field -->
<input type="password"
       data-element="password"
       data-target-password="password"
       name="password_confirm"
       required>

The confirm field will show valid/invalid state based on whether passwords match.

HTML Attributes

Attribute Type Default Description
data-element string - Set to password
data-password-strength boolean false Show strength meter
data-password-criteria-list boolean false Show criteria checklist
data-target-password string - ID of main password field (for confirm field)
minlength number 8 Minimum password length
maxlength number 50 Maximum password length

Password Toggle Visibility

Password toggle is enabled by default. Users can:

  • Click the eye icon to show/hide password
  • Press Alt + S keyboard shortcut

CSS Classes:

  • .icon-published0 - Password hidden (eye icon)
  • .icon-published1 - Password visible (eye-slash icon)

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

Customizing Criteria

// 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 extends TextElementFactory with search-specific features: debounced input, clear button, results container, and FormManager integration.

Basic Usage

<!-- Simple search input -->
<input type="search"
       data-element="search"
       name="search"
       placeholder="Search...">

<!-- Search with onSearch callback -->
<input type="search"
       data-element="search"
       id="product-search"
       name="search"
       placeholder="Search products...">

<script>
const searchInput = document.getElementById('product-search');
searchInput.addEventListener('input', async (e) => {
  // Handle search with debounce built-in
});
</script>

Search with Results

// 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 Type Default Description
data-element string - Set to search
data-search-delay number 300 Debounce delay (ms)
data-min-length number 2 Minimum characters before search
data-max-length number 100 Maximum input length
data-max-results number 10 Maximum results to show
data-highlight-matches boolean true Highlight matching text

Configuration Options

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']
  }
};

Clear Button

A clear button is automatically added and shown when the input has a value:

.search-clear {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
}

Loading State

.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

Events

All text-based elements emit these events:

Common Events

Event When Emitted Detail
element:change Value changed {value, valid}
element:valid Validation passed {value}
element:invalid Validation failed {value, error}
input User types Native event
change Value confirmed Native event
focus Element focused Native event
blur Element blurred Native event

TextElementFactory Events

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 Events

// 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 Events

// Now.js event system
Now.on('search:performed', ({elementId, query}) => {
  console.log(`Search performed on ${elementId}: ${query}`);
});

Best Practices

1. ✅ Use Hidden Input for Key-Value Pairs

<!-- ✅ Good: Autocomplete stores key in hidden input -->
<input type="text"
       data-element="text"
       data-autocomplete="true"
       data-source="/api/cities"
       name="city">

<!-- Form submits: city=123 (hidden input value, not display text) -->

2. ✅ Set Appropriate Min Length for Autocomplete

<!-- ✅ Good: Wait for at least 2 characters -->
<input type="text"
       data-element="text"
       data-autocomplete="true"
       data-source="/api/products"
       data-min-length="2">

<!-- ❌ Bad: Triggers on every keystroke -->
<input type="text"
       data-autocomplete="true"
       data-source="/api/products"
       data-min-length="0">

3. ✅ Use Password Strength for Registration

<!-- ✅ Good: Show strength meter during registration -->
<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 with data-target-password

<!-- ✅ Good: Use built-in match validation -->
<input type="password" id="password" name="password">
<input type="password"
       data-target-password="password"
       name="password_confirm">

<!-- ❌ Bad: Custom JavaScript for matching -->
<script>
  confirmInput.addEventListener('input', () => {
    if (passwordInput.value !== confirmInput.value) { ... }
  });
</script>

5. ✅ Debounce Search Input

<!-- ✅ Good: Built-in debounce with SearchElementFactory -->
<input type="search"
       data-element="search"
       data-search-delay="300">

<!-- ❌ Bad: No debounce on every keystroke -->
<input type="text" oninput="search(this.value)">

6. ✅ Provide Clear Error Messages

<input type="text"
       data-element="text"
       data-validate="email"
       data-error-email="Please enter a valid email address like example@domain.com"
       name="email">