Now.js Framework Documentation

Now.js Framework Documentation

Modal - Modal Dialog System

EN 06 May 2026 07:38

Modal - Modal Dialog System

Complete documentation for the Modal class - a feature-rich component for creating and managing modal dialogs with animation, accessibility, and data binding support.

📋 Table of Contents

  1. Overview
  2. Installation
  3. Basic Usage
  4. Configuration Options
  5. Data Binding
  6. Lifecycle Callbacks
  7. Accessibility
  8. JavaScript API
  9. Examples
  10. API Reference
  11. Best Practices

Overview

Modal is a class for creating feature-rich modal dialogs with overlay display, backdrop, animation, keyboard navigation, and data binding capabilities.

Key Features

  • Programmatic Creation: Create modals with JavaScript instantly
  • Animation Support: Built-in fade-in/fade-out animations
  • Backdrop Management: Automatic backdrop via BackdropManager
  • Keyboard Support: Close with ESC key
  • Focus Management: Automatic focus and focus trap
  • Accessibility: ARIA attributes and inert property support
  • Data Binding: Bind data to modal elements
  • Content Sanitization: XSS protection with automatic sanitization
  • Lifecycle Callbacks: Hooks for each lifecycle stage
  • Element Cleanup: Automatic cleanup when closing

When to Use Modal

Use Modal when:

  • Creating modals programmatically
  • Need animation and full accessibility
  • Need to bind data to modal content
  • Want JavaScript API control

Don't use Modal when:

  • Modal elements already exist in HTML (use ModalDataBinder instead)
  • Need simple modals without special features
  • Don't want backdrop

Installation

Modal is shipped in the Now.js core bundle. Load the core products first, then use the class through window.Modal.

<link rel="stylesheet" href="Now/dist/now.core.min.css">
<script src="Now/dist/now.core.min.js"></script>
// No import needed after now.core loads
console.log(window.Modal); // Modal class
console.log(window.Now?.getManager?.('modal')); // Registered manager when the registry is available

Implementation Files

  • Now/js/Modal.js
  • Now/css/modal.css

Dependencies

  • BackdropManager – Required for show() and hide()
  • FormManager – Optional cleanup for forms rendered inside the modal body
  • ElementManager – Optional cleanup and re-scan for enhanced elements inside the modal body
  • DOMPurify – Optional HTML sanitization helper used when available

Basic Usage

1. Simple Modal

// Create modal instance
const modal = new Modal({
  title: 'Welcome',
  content: '<p>This is your first modal!</p>'
});

// Show modal
modal.show();

2. Modal with Options

const modal = new Modal({
  id: 'my-modal',
  title: 'User Information',
  content: `
    <div class="user-info">
      <img data-modal-target="avatar" class="avatar">
      <h3 data-modal-target="name"></h3>
      <p data-modal-target="email"></p>
    </div>
  `,
  className: 'modal-user-card',
  closeButton: true,
  backdrop: true,
  keyboard: true,
  animation: true
});

modal.show();
.modal.modal-user-card .modal-dialog {
  max-width: 500px;
}

3. Close Modal

// Close modal
modal.hide();

// Or press ESC key
// Or click backdrop (if enabled)

Configuration Options

Modal supports these options:

Basic Options

Option Type Default Description
id string auto-generated Modal element ID
title string '' Modal title
content string|Node|DocumentFragment '' Modal content (HTML string, DOM node, or document fragment)
className string '' Additional CSS classes on the root .modal element. Use this for sizing or styling variants.
titleClass string '' Additional CSS classes on .modal-title

Display Options

Option Type Default Description
closeButton boolean true Show close button
animation boolean true Enable animations
backdrop boolean true Close on backdrop click
keyboard boolean true Close on ESC key
focus boolean true Auto focus management

Backdrop Options

Option Type Default Description
backdropOpacity number 0.5 Backdrop opacity
backdropColor string 'rgba(0,0,0,0.5)' Backdrop color

Note: The current implementation always creates a backdrop through BackdropManager. backdrop: false disables click-to-close behavior only; it does not remove the backdrop layer.

Sizing And Styling

Modal sizing is CSS-driven. Add a className to the root .modal element and target .modal-dialog in CSS.

.modal.modal-compact .modal-dialog {
  max-width: 420px;
}

.modal.modal-tall .modal-dialog {
  max-height: 95vh;
}

By default, .modal-dialog uses max-width: 600px and max-height: 90vh. On screens 640px wide or smaller, modal.css switches the dialog to a full-screen layout.

Lifecycle Callbacks

Option Type Description
onShow function Called before modal shows
onShown function Called after modal shown
onHide function Called before modal hides
onHidden function Called after modal hidden

Example of using Options

const modal = new Modal({
  id: 'confirm-modal',
  title: 'Confirm deletion',
  content: '<p>Are you sure to delete this item??</p>',
  className: 'modal-confirm modal-danger',
  closeButton: true,
  backdrop: true,
  keyboard: true,
  animation: true,
  backdropOpacity: 0.7,

  onShow: function() {
    console.log('Modal Showing...');
  },

  onShown: function() {
    console.log('Modal Show finished');
  },

  onHide: function() {
    console.log('Modal Closing...');
  },

  onHidden: function() {
    console.log('Modal Finished closing');
  }
});

Data Binding

Modal supports data binding to elements via data-modal-target attribute:

Basic Data Binding

// Create modal with data binding targets
const modal = new Modal({
  title: 'User Profile',
  content: `
    <div class="user-profile">
      <img data-modal-target="avatar" class="avatar">
      <h3 data-modal-target="name"></h3>
      <p data-modal-target="email"></p>
      <p data-modal-target="bio"></p>
    </div>
  `
});

// Bind data
modal.bindData({
  avatar: '/images/user.jpg',
  name: 'John Doe',
  email: 'john@example.com',
  bio: 'Software Developer'
});

// Show modal
modal.show();

Update Data

// Update partial data
modal.updateData({
  bio: 'Senior Software Developer'
});

// Or use method chaining
modal.updateData({
  name: 'John Doe (Admin)'
}).show();

Get Bound Data

// Get all bound data
const data = modal.getData();
console.log(data);
// { avatar: '/images/user.jpg', name: 'John Doe', ... }

Element Type Binding

Modal automatically binds data based on element type:

const modal = new Modal({
  content: `
    <!-- Image: sets src -->
    <img data-modal-target="productImage">

    <!-- Link: sets href -->
    <a data-modal-target="productLink">View Product</a>

    <!-- Input: sets value -->
    <input data-modal-target="productName">

    <!-- Text content (default) -->
    <h3 data-modal-target="title"></h3>
    <p data-modal-target="description"></p>

    <!-- HTML content (use data-modal-html) -->
    <div data-modal-target="richContent" data-modal-html></div>
  `
});

modal.bindData({
  productImage: '/images/product.jpg',
  productLink: '/products/123',
  productName: 'Sample Product',
  title: 'New Product',
  description: 'Product details',
  richContent: '<strong>HTML</strong> content'
});

Content Sanitization

Modal will automatically sanitize data to prevent XSS:

// Text content - escape HTML automatically
modal.bindData({
  name: '<script>alert("XSS")</script>' //will be displayed as plain text
});

// HTML content - Delete scripts and event handlers
modal.bindData({
  richContent: '<p onclick="alert()">Safe HTML</p>' // Remove onclick
});

// URL - validate protocols
modal.bindData({
  link: 'javascript:alert()' // will change to '#'
});

Lifecycle Callbacks

Modal has callbacks for each step of the lifecycle:

onShow - Before Modal Shows

const modal = new Modal({
  title: 'Loading...',
  content: '<div class="spinner"></div>',
  onShow: function() {
    console.log('Modal is showing');
    // Prepare data, start loading
  }
});

onShown - After Modal Shown

const modal = new Modal({
  title: 'User Data',
  content: '<div id="user-data"></div>',
  onShown: async function() {
    console.log('Modal shown');
    // Load data, focus input
    const data = await fetchUserData();
    this.bindData(data);
  }
});

onHide - Before Modal Hides

const modal = new Modal({
  title: 'Form',
  content: '<form>...</form>',
  onHide: function() {
    console.log('Modal is hiding');
    // Persist temporary state or cleanup here
    saveDraftState();
  }
});

Note: onHide is a notification hook in the current implementation. Its return value is ignored, so confirmation logic must happen before you call hide().

onHidden - After Modal Hidden

const modal = new Modal({
  title: 'Success',
  content: '<p>Data saved successfully</p>',
  onHidden: function() {
    console.log('Modal hidden');
    // Cleanup, redirect, refresh data
    window.location.reload();
  }
});

Accessibility

Modal includes comprehensive accessibility features:

ARIA Attributes

// Modal sets ARIA attributes automatically
const modal = new Modal({
  title: 'Information',
  content: '<p>Content</p>'
});

// Creates HTML like:
// <div role="dialog" aria-modal="true" aria-hidden="false">
//   ...
// </div>

Inert Property

// Modal uses inert property to prevent interaction when hidden
modal.hide(); // modal.inert = true
modal.show(); // modal.inert = false

Focus Management

const modal = new Modal({
  title: 'Form',
  content: `
    <input type="text" id="name">
    <button>Save</button>
  `,
  focus: true // Enable focus management (default: true)
});

modal.show();
// Automatically focuses first focusable element

Focus Trap

// Modal traps focus within modal
// Tab cycles through modal elements only
// Shift+Tab cycles backwards

Keyboard Navigation

const modal = new Modal({
  keyboard: true // Enable keyboard support (default: true)
});

// Press ESC to close modal

JavaScript API

Constructor

const modal = new Modal(options);

Parameters:

  • options (Object) - Modal configuration options

Returns:

  • Modal instance

show()

Show the modal

modal.show();

Returns: void

hide()

Hide the modal

modal.hide();

Returns: void

Note: After the close animation finishes, hide() clears the current modal body content and runs cleanup for forms and enhanced elements inside the body.

setContent(content)

Change modal content

// HTML string
modal.setContent('<p>New content</p>');

// DOM node
const element = document.createElement('div');
element.textContent = 'New content';
modal.setContent(element);

// DocumentFragment
const fragment = document.createDocumentFragment();
// ... add nodes to fragment
modal.setContent(fragment);

Parameters:

  • content (string|Node|DocumentFragment) - New content

Returns: void

setTitle(title)

Change modal title

modal.setTitle('New Title');

Parameters:

  • title (string) - New title

Returns: void

Note: setTitle() only updates an existing .modal-title. If the modal was created with an empty title, it does not create a new header block.

bindData(data)

Bind data to modal elements

modal.bindData({
  name: 'John Doe',
  email: 'john@example.com'
});

Parameters:

  • data (Object) - Data to bind

Returns: Modal instance (for method chaining)

updateData(data)

Update bound data

modal.updateData({
  email: 'newemail@example.com'
});

Parameters:

  • data (Object) - Data to update

Returns: Modal instance (for method chaining)

getData()

Get bound data

const data = modal.getData();
console.log(data);

Returns: Object - Bound data

destroy()

Remove modal from DOM

modal.destroy();

Returns: void

Note: destroy() removes the modal element and clears bound data, but it does not close the backdrop by itself. Prefer calling it from onHidden() after hide().

Examples

1. User Profile Modal

// Create modal
const userModal = new Modal({
  id: 'user-modal',
  title: 'User Profile',
  content: `
    <div class="user-profile">
      <img data-modal-target="avatar" class="avatar">
      <div class="user-info">
        <h3 data-modal-target="name"></h3>
        <p data-modal-target="email"></p>
        <p data-modal-target="role"></p>
      </div>
    </div>
  `,
  className: 'modal-user-profile'
});

// Bind data and show
userModal.bindData({
  avatar: '/images/users/john.jpg',
  name: 'John Doe',
  email: 'john@example.com',
  role: 'Administrator'
}).show();

2. Confirmation Modal

function confirmDelete(itemName, onConfirm) {
  const modal = new Modal({
    title: 'Confirm Delete',
    content: `
      <p>Are you sure you want to delete "<strong>${itemName}</strong>"?</p>
      <p class="text-muted">This action cannot be undone</p>
      <div class="modal-actions">
        <button class="btn btn-secondary" data-dismiss="modal">Cancel</button>
        <button class="btn btn-danger" id="confirm-delete">Delete</button>
      </div>
    `,
    className: 'modal-confirm',
    backdrop: true,
    keyboard: true,
    onShown: function() {
      // Bind confirm button
      document.getElementById('confirm-delete').addEventListener('click', () => {
        onConfirm();
        this.hide();
      });

      // Bind cancel button
      document.querySelector('[data-dismiss="modal"]').addEventListener('click', () => {
        this.hide();
      });
    }
  });

  modal.show();
}

// Usage
confirmDelete('Product #123', () => {
  console.log('Item deleted');
  // Perform actual deletion
});

3. Image Modal

function showImageModal(imageUrl, title, description) {
  const modal = new Modal({
    title: title,
    content: `
      <div class="image-modal">
        <img data-modal-target="image" class="modal-image">
        <p data-modal-target="description" class="image-description"></p>
      </div>
    `,
    className: 'modal-image-viewer image-modal-container'
  });

  modal.bindData({
    image: imageUrl,
    description: description
  }).show();
}

// Usage
showImageModal(
  '/images/product.jpg',
  'New Product',
  'Product details...'
);

4. API Data Modal

async function showUserDetails(userId) {
  // Create modal with loading state
  const modal = new Modal({
    title: 'Loading...',
    content: '<div class="spinner">Loading...</div>',
    className: 'modal-api-panel'
  });

  modal.show();

  try {
    // Load data
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();

    // Update content
    modal.setTitle('User Information');
    modal.setContent(`
      <div class="user-details">
        <img data-modal-target="avatar" class="avatar">
        <h3 data-modal-target="name"></h3>
        <p data-modal-target="email"></p>
        <p data-modal-target="bio"></p>
      </div>
    `);

    // Bind data
    modal.bindData(user.data);

  } catch (error) {
    modal.setTitle('Error');
    modal.setContent('<p class="error">Failed to load data</p>');
  }
}

// Usage
showUserDetails(123);

5. Form Modal

function showEditForm(userData) {
  const modal = new Modal({
    title: 'Edit Information',
    content: `
      <form id="edit-form">
        <div class="form-group">
          <label>Name:</label>
          <input type="text" name="name" data-modal-target="name" class="form-control">
        </div>
        <div class="form-group">
          <label>Email:</label>
          <input type="email" name="email" data-modal-target="email" class="form-control">
        </div>
        <div class="form-actions">
          <button type="submit" class="btn btn-primary">Save</button>
          <button type="button" class="btn btn-secondary" data-dismiss>Cancel</button>
        </div>
      </form>
    `,
    className: 'modal-form-panel',
    onShown: function() {
      const form = document.getElementById('edit-form');

      // Bind form submit
      form.addEventListener('submit', async (e) => {
        e.preventDefault();
        const formData = new FormData(form);
        const data = Object.fromEntries(formData);

        // Save data
        await saveUserData(data);
        this.hide();
      });

      // Bind cancel button
      form.querySelector('[data-dismiss]').addEventListener('click', () => {
        this.hide();
      });
    }
  });

  // Bind initial data
  modal.bindData(userData).show();
}

// Usage
showEditForm({
  name: 'John Doe',
  email: 'john@example.com'
});
.modal.modal-user-profile .modal-dialog {
  max-width: 500px;
}

.modal.modal-confirm .modal-dialog {
  max-width: 400px;
}

.modal.modal-image-viewer .modal-dialog {
  max-width: min(90vw, 800px);
}

.modal.modal-api-panel .modal-dialog {
  max-width: 600px;
}

.modal.modal-form-panel .modal-dialog {
  max-width: 500px;
}

API Reference

Properties

Property Type Description
id string Modal element ID
modal HTMLElement Modal element
dialog HTMLElement Modal dialog element
header HTMLElement|undefined Modal header element. Created only when title is not empty
body HTMLElement Modal body element
visible boolean Visibility state
boundData Object Bound data
backdropId string|null Backdrop ID

Methods

Constructor

new Modal(options)

Public Methods

  • show() - Show modal
  • hide() - Hide modal
  • setContent(content) - Change content
  • setTitle(title) - Change title
  • bindData(data) - Bind data
  • updateData(data) - Update data
  • getData() - Get data
  • destroy() - Remove modal

Private Methods

  • createModal() - Create modal DOM
  • bindEvents() - Bind event handlers
  • _cleanupModalElements() - Cleanup elements
  • _scanModalElements() - Re-scan modal content for managed elements and forms
  • _setElementContent(element, value, key) - Set element content
  • _sanitizeValue(value, context) - Sanitize value
  • _sanitizeText(text) - Sanitize text
  • _sanitizeHTML(html) - Sanitize HTML
  • _sanitizeURL(url) - Sanitize URL
  • _bindCloseButtons(container) - Bind close button handlers in a container

Best Practices

✅ Do

1. Use Data Binding Instead of Direct Manipulation

// ✅ Good - use data binding
modal.bindData({
  name: userName,
  email: userEmail
});

// ❌ Bad - direct manipulation
modal.body.querySelector('.name').textContent = userName;
modal.body.querySelector('.email').textContent = userEmail;

2. Use Lifecycle Callbacks

// ✅ Good - use callbacks
const modal = new Modal({
  content: '<div id="data"></div>',
  onShown: async function() {
    const data = await fetchData();
    this.bindData(data);
  }
});

// ❌ Bad - load before showing
const data = await fetchData();
modal.bindData(data);
modal.show();

3. Clean Up When Done

// ✅ Good - hide first, then destroy after closing
const modal = new Modal({
  onHidden() {
    this.destroy();
  }
});

modal.hide();

// ❌ Bad - destroy while the modal is still visible
modal.destroy();

4. Use Method Chaining

// ✅ Good - chain methods
modal.bindData(userData).show();

// ❌ Bad - separate lines
modal.bindData(userData);
modal.show();

❌ Don't

1. Don't Insert Unsafe HTML Directly

// ❌ Dangerous - XSS vulnerability
modal.setContent(`<p>${userInput}</p>`);

// ✅ Safe - use data binding
modal.setContent('<p data-modal-target="message"></p>');
modal.bindData({ message: userInput });

2. Don't Stack Multiple Modals

// ❌ Bad - stacked modals
modal1.show();
modal2.show(); // Will overlap

// ✅ Good - close before opening new
modal1.hide();
setTimeout(() => modal2.show(), 200);

3. Don't Forget Event Listener Cleanup

// ❌ Bad - memory leak
const modal = new Modal({
  onShown: function() {
    document.getElementById('btn').addEventListener('click', handler);
    // No listener removal
  }
});

// ✅ Good - cleanup listeners
const modal = new Modal({
  onShown: function() {
    const btn = document.getElementById('btn');
    btn.addEventListener('click', handler);
  },
  onHidden: function() {
    const btn = document.getElementById('btn');
    btn.removeEventListener('click', handler);
  }
});

💡 Tips

1. Use CSS Classes For Styling And Sizing

const modal = new Modal({
  className: 'modal-large modal-primary',
  content: '...'
});
.modal.modal-large .modal-dialog {
  max-width: 720px;
}

2. Use Template Literals for Complex Content

const modal = new Modal({
  content: `
    <div class="complex-content">
      <section>...</section>
      <section>...</section>
    </div>
  `
});

3. Rebuild Content Before Reusing An Instance

const detailsModal = new Modal({
  title: 'Details'
});

function openDetails(message) {
  detailsModal.setContent('<p data-modal-target="message"></p>');
  detailsModal.bindData({ message }).show();
}

Because hide() clears the current modal body, reusing an instance usually requires setContent() before the next show().

See Also