Now.js Framework Documentation
data-script
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
- Template HTML rendered
data-scriptfunctions called (DOM order)- Cleanup functions stored
- 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) { /* ... */ }Related
- RouterManager - Route configuration
- FormManager - Form handling
- ComponentManager - Component system