Now.js Framework Documentation

Now.js Framework Documentation

data-script

EN 11 Dec 2025 11:59

data-script

Overview

data-script specifies a global function to call when a template is loaded and ready. It enables JavaScript initialization for template components.

When to use:

  • Initialize JavaScript when template loads
  • Set up event listeners or third-party libraries
  • Create cleanup functions for navigation

Why use it:

  • ✅ Clean separation between HTML and JS
  • ✅ Receives data from API automatically
  • ✅ Supports cleanup functions
  • ✅ Works with RouterManager

Basic Usage

Template with Script

profile.html:

<main data-script="initProfile">
  <h1 data-text="user.name"></h1>
  <form data-form="editProfile">
    <input type="text" name="bio" data-attr="value:user.bio">
    <button type="submit">Save</button>
  </form>
</main>

main.js:

function initProfile(element, data) {
  console.log('Profile loaded!', element);
  console.log('Data from API:', data);

  // Initialize components
  const form = element.querySelector('form');

  // Return cleanup function (optional)
  return () => {
    console.log('Profile cleanup');
  };
}

Syntax

<element data-script="functionName">
Part Description
functionName Name of global function to call

Function Signature

function functionName(element, data) {
  // element: DOM element with data-script attribute
  // data: Data from API (if route has data-load-api)

  // Your initialization code

  // Return cleanup function (optional)
  return () => {
    // Cleanup code runs when navigating away
  };
}

Parameters

Parameter Type Description
element HTMLElement The DOM element with data-script
data Object Data from API (if data-load-api is used)

Return Value

Return Effect
undefined No cleanup
Function Called when template is removed

Features

1. Basic Initialization

<div class="chart-container" data-script="initChart">
  <canvas id="myChart"></canvas>
</div>
function initChart(element, data) {
  const canvas = element.querySelector('#myChart');
  const chart = new Chart(canvas, {
    type: 'bar',
    data: data.chartData
  });

  return () => {
    chart.destroy();
  };
}

2. With API Data

Router config:

routes: {
  '/dashboard': {
    template: 'dashboard.html',
    title: 'Dashboard',
    'data-load-api': 'api/dashboard/stats'
  }
}

dashboard.html:

<main data-script="initDashboard">
  <div id="stats"></div>
</main>
function initDashboard(element, data) {
  // data = { totalUsers: 150, revenue: 50000, ... }
  console.log('Stats:', data);

  renderStats(element.querySelector('#stats'), data);
}

3. Multiple Scripts

<div class="page">
  <header data-script="initHeader">
    <nav>...</nav>
  </header>

  <main data-script="initMainContent">
    <div class="content">...</div>
  </main>

  <aside data-script="initSidebar">
    <div class="widgets">...</div>
  </aside>
</div>

Functions are called in DOM order (top to bottom).

4. Cleanup Functions

function initTimer(element, data) {
  const timerEl = element.querySelector('#timer');

  // Start interval
  const intervalId = setInterval(() => {
    timerEl.textContent = new Date().toLocaleTimeString();
  }, 1000);

  // Return cleanup - called when navigating away
  return () => {
    clearInterval(intervalId);
  };
}

Advanced Examples

Interactive Map

<div class="map-page" data-script="initMap">
  <div id="map" style="height: 400px;"></div>
  <button id="center-btn">Center Map</button>
</div>
function initMap(element, data) {
  const mapEl = element.querySelector('#map');
  const centerBtn = element.querySelector('#center-btn');

  // Initialize Leaflet map
  const map = L.map(mapEl).setView([13.7, 100.5], 13);
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);

  // Add markers from data
  data.locations.forEach(loc => {
    L.marker([loc.lat, loc.lng])
      .addTo(map)
      .bindPopup(loc.name);
  });

  // Event listeners
  const handleCenter = () => {
    map.setView([13.7, 100.5], 13);
  };
  centerBtn.addEventListener('click', handleCenter);

  // Cleanup
  return () => {
    centerBtn.removeEventListener('click', handleCenter);
    map.remove();
  };
}

Form with Validation

<form data-form="register" data-script="initRegisterForm">
  <input type="email" name="email" id="email">
  <span class="error" id="email-error"></span>

  <input type="password" name="password" id="password">
  <span class="error" id="password-error"></span>

  <button type="submit">Register</button>
</form>
function initRegisterForm(element, data) {
  const emailInput = element.querySelector('#email');
  const passwordInput = element.querySelector('#password');

  let debounceTimer;

  const validateEmail = async () => {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(async () => {
      const email = emailInput.value;
      // Validation logic
    }, 500);
  };

  emailInput.addEventListener('input', validateEmail);

  return () => {
    clearTimeout(debounceTimer);
    emailInput.removeEventListener('input', validateEmail);
  };
}

Live Chat

function initLiveChat(element, data) {
  const messagesEl = element.querySelector('#messages');
  const inputEl = element.querySelector('#message-input');
  const sendBtn = element.querySelector('#send-btn');

  // Connect WebSocket
  const ws = new WebSocket('wss://chat.example.com');

  ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);
    appendMessage(messagesEl, msg);
  };

  const sendMessage = () => {
    const text = inputEl.value.trim();
    if (text) {
      ws.send(JSON.stringify({ type: 'message', text }));
      inputEl.value = '';
    }
  };

  sendBtn.addEventListener('click', sendMessage);
  inputEl.addEventListener('keydown', (e) => {
    if (e.key === 'Enter') sendMessage();
  });

  // Cleanup - close WebSocket
  return () => {
    ws.close();
    sendBtn.removeEventListener('click', sendMessage);
  };
}

Third-Party Library

<div data-script="initEditor">
  <textarea id="content"></textarea>
</div>
function initEditor(element, data) {
  const textarea = element.querySelector('#content');

  // Initialize TinyMCE or similar
  const editor = tinymce.init({
    target: textarea,
    // ... config
  });

  if (data.content) {
    editor.then(ed => ed[0].setContent(data.content));
  }

  return () => {
    tinymce.remove(textarea);
  };
}

API Reference

When Scripts Execute

Event Behavior
Template load Script called after DOM is ready
Route navigation Previous cleanup called, new script called
Manual cleanup Call RouterManager.cleanup()

Execution Order

  1. Template HTML rendered
  2. data-script functions called (DOM order)
  3. Cleanup functions stored
  4. On navigation away: cleanup functions called

Common Pitfalls

⚠️ 1. Function Must Be Global

// ❌ Not accessible - scoped to DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
  function initPage(element, data) { }
});

// ✅ Global function
function initPage(element, data) { }

// ✅ Explicit global assignment
window.initPage = function(element, data) { };

⚠️ 2. Memory Leaks

// ❌ Bad - no cleanup for timer
function initClock(element, data) {
  setInterval(() => {
    element.querySelector('#time').textContent = new Date().toLocaleTimeString();
  }, 1000);
}

// ✅ Good - with cleanup
function initClock(element, data) {
  const timer = setInterval(() => {
    element.querySelector('#time').textContent = new Date().toLocaleTimeString();
  }, 1000);

  return () => clearInterval(timer);
}

⚠️ 3. Async Functions

// ✅ Async works - cleanup still supported
async function initDashboard(element, data) {
  const stats = await fetchStats();
  renderStats(stats);

  return () => {
    // Cleanup
  };
}

⚠️ 4. Check Element Existence

// ❌ May error if element missing
function initForm(element, data) {
  element.querySelector('#submit').addEventListener('click', submit);
}

// ✅ Check first
function initForm(element, data) {
  const submitBtn = element.querySelector('#submit');
  if (!submitBtn) {
    console.warn('Submit button not found');
    return;
  }
  submitBtn.addEventListener('click', submit);
}

⚠️ 5. Unique Function Names

// ❌ Same name - second overwrites first
function initForm(element, data) { console.log('Form 1'); }
function initForm(element, data) { console.log('Form 2'); }

// ✅ Unique names
function initLoginForm(element, data) { }
function initRegisterForm(element, data) { }

Best Practices

1. Descriptive Names

// ❌ Unclear
function init(element, data) { }

// ✅ Clear
function initProductGallery(element, data) { }
function initCheckoutForm(element, data) { }

2. Error Handling

function initChart(element, data) {
  try {
    const canvas = element.querySelector('#chart');
    if (!canvas) throw new Error('Canvas not found');

    // Initialize
  } catch (error) {
    console.error('Chart init failed:', error);
    element.innerHTML = '<p class="error">Failed to load chart</p>';
  }
}

3. Break Into Helpers

function initDashboard(element, data) {
  const cleanup1 = initStats(element, data.stats);
  const cleanup2 = initChart(element, data.chart);
  const cleanup3 = initActivityFeed(element, data.activity);

  return () => {
    cleanup1?.();
    cleanup2?.();
    cleanup3?.();
  };
}

function initStats(element, stats) { /* ... */ }
function initChart(element, chartData) { /* ... */ }