Now.js Framework Documentation

Now.js Framework Documentation

CascadingSelectManager

EN 15 Dec 2025 07:08

CascadingSelectManager

Overview

CascadingSelectManager is the cascading/dependent select box management system in Now.js Framework. It loads options dynamically based on previous selections.

When to use:

  • Province → District → Subdistrict selects
  • Category → Subcategory → Product selects
  • Country → State → City selects
  • Any dependent dropdown chains

Why use it:

  • ✅ AJAX loading with debounce
  • ✅ Auto-disable empty selects
  • ✅ Supports form integration
  • ✅ Default option customization
  • ✅ Auto-initialization from HTML

Basic Usage

HTML Declarative

<form id="address-form">
  <!-- Parent select -->
  <select name="province"
          data-cascade-url="/api/provinces"
          data-cascade-children="district"
          data-cascade-placeholder="Select province">
    <option value="">Please select...</option>
  </select>

  <!-- Child select (depends on province) -->
  <select name="district"
          data-cascade-url="/api/districts"
          data-cascade-parent="province"
          data-cascade-children="subdistrict"
          data-cascade-placeholder="Select district"
          disabled>
    <option value="">Please select...</option>
  </select>

  <!-- Grandchild select (depends on district) -->
  <select name="subdistrict"
          data-cascade-url="/api/subdistricts"
          data-cascade-parent="district"
          data-cascade-placeholder="Select subdistrict"
          disabled>
    <option value="">Please select...</option>
  </select>
</form>

JavaScript API

// Initialize in container
CascadingSelectManager.initInContainer(document.getElementById('address-form'));

// Or create manually
CascadingSelectManager.create([
  document.querySelector('[name="province"]'),
  document.querySelector('[name="district"]'),
  document.querySelector('[name="subdistrict"]')
], {
  autoLoad: true,
  autoDisable: true
});

Data Attributes

Attribute Description
data-cascade-url API URL for loading options
data-cascade-parent name of parent select
data-cascade-children name of child select(s) (comma-separated)
data-cascade-placeholder Placeholder text
data-cascade-params Additional query params (JSON)
data-cascade-value-field Field name for value (default: 'id')
data-cascade-label-field Field name for label (default: 'name')

API Response Format

// Array of objects
[
  { id: 1, name: 'New York' },
  { id: 2, name: 'Los Angeles' },
  { id: 3, name: 'Chicago' }
]

// Or custom fields
[
  { code: 'NY', title: 'New York' },
  { code: 'LA', title: 'Los Angeles' }
]
// Use: data-cascade-value-field="code" data-cascade-label-field="title"

Query parameters sent:

  • Parent field name = parent value
  • Additional params from data-cascade-params
GET /api/districts?province=1
GET /api/subdistricts?district=101

Configuration

CascadingSelectManager.init({
  // Loading indicator class
  loadingClass: 'loading',

  // Debounce delay (ms)
  ajaxDelay: 300,

  // Add default "Please select" option
  defaultOption: true,
  defaultOptionText: 'Please select...',

  // Auto-load first select on init
  autoLoad: true,

  // Auto-disable empty selects
  autoDisable: true,

  // Custom placeholders per select
  placeholders: {
    province: 'Select province',
    district: 'Select district'
  },

  debug: false
});

API Reference

CascadingSelectManager.initInContainer(container)

Initialize cascading selects in container

Parameter Type Description
container HTMLElement Form or container element

CascadingSelectManager.create(selects, options)

Create cascading select chain

Parameter Type Description
selects Array Array of select elements
options object Configuration options

Returns: Object - Instance

CascadingSelectManager.reset(instance)

Reset all selects to initial state

Parameter Type Description
instance Object Instance from create()

CascadingSelectManager.destroy(instance)

Destroy instance

Parameter Type Description
instance Object Instance

CascadingSelectManager.getInstance(id)

Get instance by ID

CascadingSelectManager.getInstanceForElement(element)

Get instance from select element

Events

Event When Triggered Detail
cascade:loading Start loading options {select, parent}
cascade:loaded Loading complete {select, options, count}
cascade:error Loading error {select, error}
cascade:change Select value changed {select, value, children}
cascade:reset All reset {instance}
EventManager.on('cascade:loaded', (data) => {
  console.log(`Loaded ${data.count} options for ${data.select.name}`);
});

Real-World Examples

Address Form

<div class="address-form" id="address-section">
  <div class="form-group">
    <label>Country</label>
    <select name="country_id"
            data-cascade-url="/api/address/countries"
            data-cascade-children="state_id"
            data-cascade-value-field="id"
            data-cascade-label-field="name">
      <option value="">Select country</option>
    </select>
  </div>

  <div class="form-group">
    <label>State</label>
    <select name="state_id"
            data-cascade-url="/api/address/states"
            data-cascade-parent="country_id"
            data-cascade-children="city_id"
            disabled>
      <option value="">Select state</option>
    </select>
  </div>

  <div class="form-group">
    <label>City</label>
    <select name="city_id"
            data-cascade-url="/api/address/cities"
            data-cascade-parent="state_id"
            disabled>
      <option value="">Select city</option>
    </select>
  </div>
</div>

<script>
document.addEventListener('DOMContentLoaded', () => {
  CascadingSelectManager.initInContainer(document.getElementById('address-section'));
});
</script>

Category Filter

// Product category → Subcategory → Brand
const instance = CascadingSelectManager.create([
  categorySelect,
  subcategorySelect,
  brandSelect
], {
  autoLoad: true,
  defaultOptionText: 'All'
});

// Listen for final selection
brandSelect.addEventListener('change', (e) => {
  filterProducts({
    category: categorySelect.value,
    subcategory: subcategorySelect.value,
    brand: e.target.value
  });
});

Pre-populated Values

<!-- For edit form with existing values -->
<select name="country" data-cascade-url="/api/countries">
  <option value="">Select</option>
  <option value="1" selected>United States</option>
</select>

<select name="state"
        data-cascade-url="/api/states"
        data-cascade-parent="country"
        data-cascade-initial-value="5">
  <option value="">Select</option>
</select>

Common Pitfalls

⚠️ 1. Parent Name Must Match

<!-- ❌ Parent doesn't match -->
<select name="province_id">...</select>
<select data-cascade-parent="province">...</select>

<!-- ✅ Must match exactly -->
<select name="province">...</select>
<select data-cascade-parent="province">...</select>

⚠️ 2. API Response Format

// ❌ Incorrect response format
{ success: true, data: [...] }

// ✅ Return array directly
[{ id: 1, name: 'New York' }, ...]

// Or configure fields
data-cascade-value-field="id"
data-cascade-label-field="name"

⚠️ 3. Initialize After DOM Ready

// ❌ Before DOM ready
CascadingSelectManager.initInContainer(document.getElementById('form'));

// ✅ After DOM ready
document.addEventListener('DOMContentLoaded', () => {
  CascadingSelectManager.initInContainer(document.getElementById('form'));
});