Now.js Framework Documentation

Now.js Framework Documentation

Text-based Elements

EN 09 Nov 2026 03:28

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"}
  ]
}

Filtering by Other Fields

data-depends sends the values of other fields of the same form along with the search.
It works on every autocomplete — text inputs (and the search, email, url and username inputs built on them) and tags inputs.

<select name="department">...</select>
<input type="checkbox" name="status[]" value="1">
<input type="text"
       data-autocomplete="true"
       data-source="api/example/members"
       data-depends="department,status"
       name="member">
<!-- GET api/example/members?q=som&department=2&status[]=1 -->
  • Entries are comma separated: a field name, or param:field to send the field under another name (dept:department → &dept=2)
  • A field is found in the form by name (field or field[]), then by id
  • Values are read the way the form submits them: the checked boxes, the checked radio, every selected option, the tags of a tags field, the picked value of another autocomplete
  • A name ending in [] or a multiple select is sent as an array (status[]=1&status[]=2), anything else as one value
  • Disabled fields and fields without a value send nothing, so an empty filter does not narrow the search
  • A list source (array, datalist, data-options-key) is filtered on the row property named like the parameter: {value: '5', text: 'Somchai', department: '2'} is shown while the department field holds 2. Rows without that property are always shown, and an array property matches any of its values
  • Changing a filter field keeps what was already picked. data-depends-clear="true" empties the input (or removes every tag) when a filter field changes

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.

Rows may share a value. The row the user picks — by mouse or by
Arrow + Enter — is the row whose properties are filled, even when several rows
carry the same value (a sub-district name that exists in several provinces).
Still, give each row a distinct value when the API can: it is what the hidden
input submits.

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

What the Input Shows After a Choice

An item's text is the line shown in the dropdown while searching. Once an item is
chosen, the input shows the item's property named like the field — its name — so a
code field shows the code, not the whole search line. The hidden input still submits
value.

{"value": "C0001", "text": "C0001 Alpha Co. (Head office)", "customer_no": "C0001"}
<!-- Shows "C0001" once chosen; the dropdown still lists the full text -->
<input type="text" name="customer_no" data-autocomplete="true"
       data-source="api/inventory/customers/autocomplete?by=customer_no">
  • A property holding a {value, text} pair shows its text.
  • A data-fill entry that targets the field itself names the property to show,
    for an API whose key differs from the field name:
    data-fill="course_code:code,course_name" shows the item's code.
  • An item without such a property shows its text, as before.
  • Text the user types without choosing anything stays exactly as typed, and is what
    the hidden input submits.

To fill another autocomplete from a choice, send a {value, text} pair under that
field's name — a bare id would show the number in the visible input:

{"value": "C0001", "text": "C0001 Alpha Co.", "customer_id": {"value": "7", "text": "Alpha Co."}}

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-depends string - Other fields of the form sent with the search, or used to filter a list: field or param:field, comma separated. See "Filtering by Other Fields"
data-depends-clear boolean false true = empty the input when a data-depends field changes
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. An entry naming the field itself picks the property the field shows
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}, ...]
  }
});

The search runs data-search-delay ms after the last keystroke, so a burst of typing makes one request, and an answer
to an older query that arrives late is ignored. Result text is shown as text (escaped), with the matching part in
<em>. ArrowDown and ArrowUp move through the results, Enter picks one, and Escape closes the list. The results and the
clear button go into the field's wrapper (or its parent), so the input must already be in the page, or be created with
wrapper; a field created with wrapper gets the search class on it.

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