javascript
examples
examples.js⚡javascript
/**
* HTML Templates - Examples
*
* Comprehensive examples of using HTML templates with Web Components
*/
// ============================================
// EXAMPLE 1: Basic Template Usage
// ============================================
// Create a template programmatically
const basicTemplate = document.createElement('template');
basicTemplate.innerHTML = `
<style>
.greeting {
padding: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px;
font-family: system-ui, sans-serif;
}
.greeting h2 {
margin: 0 0 8px 0;
}
.greeting p {
margin: 0;
opacity: 0.9;
}
</style>
<div class="greeting">
<h2>Hello, World!</h2>
<p>This content comes from a template</p>
</div>
`;
class BasicTemplateExample extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(basicTemplate.content.cloneNode(true));
}
}
customElements.define('basic-template-example', BasicTemplateExample);
// ============================================
// EXAMPLE 2: Template with Data Binding
// ============================================
const cardTemplate = document.createElement('template');
cardTemplate.innerHTML = `
<style>
:host {
display: block;
}
.card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow: hidden;
font-family: system-ui, sans-serif;
}
.card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.card-body {
padding: 16px;
}
.card-title {
margin: 0 0 8px 0;
font-size: 18px;
color: #1e293b;
}
.card-text {
margin: 0;
color: #64748b;
line-height: 1.6;
}
</style>
<div class="card">
<img class="card-image" src="" alt="">
<div class="card-body">
<h3 class="card-title"></h3>
<p class="card-text"></p>
</div>
</div>
`;
class DataCard extends HTMLElement {
static get observedAttributes() {
return ['image', 'title', 'description'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(cardTemplate.content.cloneNode(true));
}
connectedCallback() {
this.updateContent();
}
attributeChangedCallback() {
this.updateContent();
}
updateContent() {
const image = this.shadowRoot.querySelector('.card-image');
const title = this.shadowRoot.querySelector('.card-title');
const text = this.shadowRoot.querySelector('.card-text');
image.src = this.getAttribute('image') || '';
image.alt = this.getAttribute('title') || '';
title.textContent = this.getAttribute('title') || 'Untitled';
text.textContent = this.getAttribute('description') || '';
}
}
customElements.define('data-card', DataCard);
// ============================================
// EXAMPLE 3: Template with Slots
// ============================================
const slotTemplate = document.createElement('template');
slotTemplate.innerHTML = `
<style>
:host {
display: block;
font-family: system-ui, sans-serif;
}
.panel {
border: 1px solid #e5e7eb;
border-radius: 8px;
overflow: hidden;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
background: #f8fafc;
border-bottom: 1px solid #e5e7eb;
}
.panel-title {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.panel-actions {
display: flex;
gap: 8px;
}
.panel-body {
padding: 16px;
}
.panel-footer {
padding: 12px 16px;
background: #f8fafc;
border-top: 1px solid #e5e7eb;
}
/* Style slotted content */
::slotted(h2) {
color: #1e293b;
}
::slotted(button) {
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
}
</style>
<div class="panel">
<div class="panel-header">
<h3 class="panel-title">
<slot name="title">Panel Title</slot>
</h3>
<div class="panel-actions">
<slot name="actions"></slot>
</div>
</div>
<div class="panel-body">
<slot>Panel content goes here</slot>
</div>
<div class="panel-footer">
<slot name="footer"></slot>
</div>
</div>
`;
class SlotPanel extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(slotTemplate.content.cloneNode(true));
}
connectedCallback() {
// Hide footer if no content slotted
const footerSlot = this.shadowRoot.querySelector('slot[name="footer"]');
footerSlot.addEventListener('slotchange', () => {
const footer = this.shadowRoot.querySelector('.panel-footer');
footer.style.display = footerSlot.assignedElements().length ? '' : 'none';
});
}
}
customElements.define('slot-panel', SlotPanel);
// ============================================
// EXAMPLE 4: Template Factory
// ============================================
class TemplateFactory {
static _templates = new Map();
/**
* Register a template by name
*/
static register(name, html) {
const template = document.createElement('template');
template.innerHTML = html.trim();
this._templates.set(name, template);
return template;
}
/**
* Get a registered template
*/
static get(name) {
return this._templates.get(name);
}
/**
* Clone a template
*/
static clone(name) {
const template = this.get(name);
if (!template) {
throw new Error(`Template "${name}" not found`);
}
return template.content.cloneNode(true);
}
/**
* Clone and populate with data
*/
static createWithData(name, data) {
const fragment = this.clone(name);
// Find all elements with data-field attribute
fragment.querySelectorAll('[data-field]').forEach((el) => {
const field = el.getAttribute('data-field');
if (data[field] !== undefined) {
if (el.tagName === 'IMG') {
el.src = data[field];
} else if (el.tagName === 'INPUT') {
el.value = data[field];
} else {
el.textContent = data[field];
}
}
});
return fragment;
}
/**
* List all registered templates
*/
static list() {
return Array.from(this._templates.keys());
}
}
// Register some templates
TemplateFactory.register(
'user-item',
`
<div class="user-item" style="
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-bottom: 1px solid #e5e7eb;
">
<img data-field="avatar" style="
width: 40px;
height: 40px;
border-radius: 50%;
" alt="">
<div>
<div data-field="name" style="font-weight: 500;"></div>
<div data-field="email" style="font-size: 12px; color: #64748b;"></div>
</div>
</div>
`
);
TemplateFactory.register(
'alert',
`
<div class="alert" style="
padding: 12px 16px;
border-radius: 6px;
display: flex;
align-items: center;
gap: 10px;
">
<span data-field="icon">ℹ️</span>
<span data-field="message"></span>
</div>
`
);
// Example usage
function createUserList(users) {
const container = document.createElement('div');
users.forEach((user) => {
const userElement = TemplateFactory.createWithData('user-item', {
avatar: user.avatar,
name: user.name,
email: user.email,
});
container.appendChild(userElement);
});
return container;
}
// ============================================
// EXAMPLE 5: Conditional Templates
// ============================================
const conditionalTemplates = {
loading: document.createElement('template'),
error: document.createElement('template'),
empty: document.createElement('template'),
content: document.createElement('template'),
};
conditionalTemplates.loading.innerHTML = `
<div class="state loading" style="
text-align: center;
padding: 40px;
color: #64748b;
">
<div style="font-size: 24px; margin-bottom: 8px;">⏳</div>
<p>Loading...</p>
</div>
`;
conditionalTemplates.error.innerHTML = `
<div class="state error" style="
text-align: center;
padding: 40px;
color: #ef4444;
background: #fef2f2;
border-radius: 8px;
">
<div style="font-size: 24px; margin-bottom: 8px;">❌</div>
<p class="message">An error occurred</p>
<button class="retry-btn" style="margin-top: 12px;">Retry</button>
</div>
`;
conditionalTemplates.empty.innerHTML = `
<div class="state empty" style="
text-align: center;
padding: 40px;
color: #64748b;
">
<div style="font-size: 24px; margin-bottom: 8px;">📭</div>
<p>No items found</p>
</div>
`;
conditionalTemplates.content.innerHTML = `
<div class="content">
<slot></slot>
</div>
`;
class ConditionalContent extends HTMLElement {
static get observedAttributes() {
return ['state', 'message'];
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
}
attributeChangedCallback() {
this.render();
}
render() {
const state = this.getAttribute('state') || 'content';
const template = conditionalTemplates[state];
if (!template) {
console.warn(`Unknown state: ${state}`);
return;
}
this.shadowRoot.innerHTML = '';
const content = template.content.cloneNode(true);
// Update message if present
if (state === 'error') {
const message = content.querySelector('.message');
if (message && this.getAttribute('message')) {
message.textContent = this.getAttribute('message');
}
// Add retry handler
const retryBtn = content.querySelector('.retry-btn');
if (retryBtn) {
retryBtn.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('retry', { bubbles: true }));
});
}
}
this.shadowRoot.appendChild(content);
}
}
customElements.define('conditional-content', ConditionalContent);
// ============================================
// EXAMPLE 6: List Rendering with Templates
// ============================================
const listItemTemplate = document.createElement('template');
listItemTemplate.innerHTML = `
<li class="list-item" style="
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #e5e7eb;
">
<span class="item-text"></span>
<button class="delete-btn" style="
background: none;
border: none;
color: #ef4444;
cursor: pointer;
font-size: 18px;
">×</button>
</li>
`;
class TemplateList extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._items = [];
}
get items() {
return this._items;
}
set items(value) {
this._items = Array.isArray(value) ? value : [];
this.render();
}
connectedCallback() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
font-family: system-ui, sans-serif;
}
ul {
list-style: none;
margin: 0;
padding: 0;
border: 1px solid #e5e7eb;
border-radius: 8px;
overflow: hidden;
}
.list-item:last-child {
border-bottom: none;
}
.list-item:hover {
background: #f8fafc;
}
</style>
<ul></ul>
`;
this.render();
}
addItem(text) {
this._items.push({ id: Date.now(), text });
this.render();
}
removeItem(id) {
this._items = this._items.filter((item) => item.id !== id);
this.render();
}
render() {
const ul = this.shadowRoot.querySelector('ul');
if (!ul) return;
ul.innerHTML = '';
this._items.forEach((item) => {
const li = listItemTemplate.content.cloneNode(true);
const listItem = li.querySelector('.list-item');
listItem.dataset.id = item.id;
li.querySelector('.item-text').textContent = item.text;
li.querySelector('.delete-btn').addEventListener('click', () => {
this.removeItem(item.id);
});
ul.appendChild(li);
});
}
}
customElements.define('template-list', TemplateList);
// ============================================
// EXAMPLE 7: Template Inheritance Pattern
// ============================================
class BaseComponent extends HTMLElement {
static template = null;
constructor() {
super();
this.attachShadow({ mode: 'open' });
const template = this.constructor.template;
if (template) {
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
$(selector) {
return this.shadowRoot.querySelector(selector);
}
$$(selector) {
return this.shadowRoot.querySelectorAll(selector);
}
}
// Create derived component with its own template
const alertButtonTemplate = document.createElement('template');
alertButtonTemplate.innerHTML = `
<style>
:host {
display: inline-block;
}
button {
padding: 10px 20px;
border: none;
border-radius: 6px;
background: #3b82f6;
color: white;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
}
button:hover {
background: #2563eb;
}
</style>
<button>
<slot>Click Me</slot>
</button>
`;
class AlertButton extends BaseComponent {
static template = alertButtonTemplate;
connectedCallback() {
this.$('button').addEventListener('click', () => {
const message = this.getAttribute('message') || 'Button clicked!';
alert(message);
});
}
}
customElements.define('alert-button', AlertButton);
// ============================================
// EXAMPLE 8: Dynamic Template Generation
// ============================================
class DynamicTemplate {
/**
* Generate template from configuration
*/
static generate(config) {
const {
tag = 'div',
className = '',
styles = {},
children = [],
slots = [],
} = config;
let html = '<style>';
// Generate CSS
for (const [selector, rules] of Object.entries(styles)) {
html += `${selector} { ${this._rulesToCSS(rules)} }`;
}
html += '</style>';
// Generate HTML
html += this._generateElement(config);
const template = document.createElement('template');
template.innerHTML = html;
return template;
}
static _rulesToCSS(rules) {
return Object.entries(rules)
.map(([prop, value]) => `${this._camelToKebab(prop)}: ${value};`)
.join(' ');
}
static _camelToKebab(str) {
return str.replace(/([A-Z])/g, '-$1').toLowerCase();
}
static _generateElement(config) {
const {
tag = 'div',
className = '',
attributes = {},
children = [],
text = '',
slot = null,
} = config;
let attrs = className ? `class="${className}"` : '';
for (const [key, value] of Object.entries(attributes)) {
attrs += ` ${key}="${value}"`;
}
let content = text;
if (slot) {
content = `<slot name="${slot}"></slot>`;
}
if (Array.isArray(children)) {
content += children
.map((child) => {
if (typeof child === 'string') {
return child === 'slot' ? '<slot></slot>' : child;
}
return this._generateElement(child);
})
.join('');
}
return `<${tag}${attrs ? ' ' + attrs : ''}>${content}</${tag}>`;
}
}
// Usage example
const generatedTemplate = DynamicTemplate.generate({
tag: 'div',
className: 'container',
styles: {
':host': { display: 'block', padding: '16px' },
'.container': { background: '#f5f5f5', borderRadius: '8px' },
'.header': { fontWeight: 'bold', marginBottom: '8px' },
},
children: [
{ tag: 'div', className: 'header', slot: 'header' },
{ tag: 'div', className: 'content', children: ['slot'] },
],
});
// ============================================
// EXAMPLE 9: Template with Event Delegation
// ============================================
const todoTemplate = document.createElement('template');
todoTemplate.innerHTML = `
<style>
:host {
display: block;
font-family: system-ui, sans-serif;
max-width: 400px;
}
.todo-input {
display: flex;
gap: 8px;
margin-bottom: 16px;
}
input {
flex: 1;
padding: 10px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
}
button {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
}
.add-btn {
background: #3b82f6;
color: white;
}
.todo-list {
list-style: none;
padding: 0;
margin: 0;
}
.todo-item {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
border-bottom: 1px solid #e5e7eb;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
opacity: 0.5;
}
.todo-text {
flex: 1;
}
.delete-btn {
background: #fef2f2;
color: #ef4444;
}
</style>
<div class="todo-input">
<input type="text" placeholder="Add new todo..." />
<button class="add-btn">Add</button>
</div>
<ul class="todo-list"></ul>
`;
const todoItemTemplate = document.createElement('template');
todoItemTemplate.innerHTML = `
<li class="todo-item">
<input type="checkbox" class="todo-checkbox" />
<span class="todo-text"></span>
<button class="delete-btn">Delete</button>
</li>
`;
class TodoList extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(todoTemplate.content.cloneNode(true));
this._todos = [];
}
connectedCallback() {
const input = this.shadowRoot.querySelector('input[type="text"]');
const addBtn = this.shadowRoot.querySelector('.add-btn');
const list = this.shadowRoot.querySelector('.todo-list');
addBtn.addEventListener('click', () => {
if (input.value.trim()) {
this.addTodo(input.value.trim());
input.value = '';
}
});
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && input.value.trim()) {
this.addTodo(input.value.trim());
input.value = '';
}
});
// Event delegation for todo items
list.addEventListener('click', (e) => {
const item = e.target.closest('.todo-item');
if (!item) return;
const id = parseInt(item.dataset.id);
if (e.target.classList.contains('delete-btn')) {
this.removeTodo(id);
} else if (e.target.classList.contains('todo-checkbox')) {
this.toggleTodo(id);
}
});
}
addTodo(text) {
const todo = { id: Date.now(), text, completed: false };
this._todos.push(todo);
this.renderTodo(todo);
}
removeTodo(id) {
this._todos = this._todos.filter((t) => t.id !== id);
const item = this.shadowRoot.querySelector(`[data-id="${id}"]`);
if (item) item.remove();
}
toggleTodo(id) {
const todo = this._todos.find((t) => t.id === id);
if (todo) {
todo.completed = !todo.completed;
const item = this.shadowRoot.querySelector(`[data-id="${id}"]`);
if (item) {
item.classList.toggle('completed', todo.completed);
item.querySelector('.todo-checkbox').checked = todo.completed;
}
}
}
renderTodo(todo) {
const list = this.shadowRoot.querySelector('.todo-list');
const item = todoItemTemplate.content.cloneNode(true);
const li = item.querySelector('.todo-item');
li.dataset.id = todo.id;
li.querySelector('.todo-text').textContent = todo.text;
li.querySelector('.todo-checkbox').checked = todo.completed;
if (todo.completed) li.classList.add('completed');
list.appendChild(item);
}
}
customElements.define('todo-list', TodoList);
// ============================================
// EXAMPLE 10: Template Caching
// ============================================
class TemplateCache {
static _cache = new Map();
static _inlineTemplates = new Map();
/**
* Get template from cache or create new one
*/
static getOrCreate(key, html) {
if (!this._cache.has(key)) {
const template = document.createElement('template');
template.innerHTML = html;
this._cache.set(key, template);
}
return this._cache.get(key);
}
/**
* Get template from inline <template> element
*/
static getInline(id) {
if (!this._inlineTemplates.has(id)) {
const template = document.getElementById(id);
if (template && template instanceof HTMLTemplateElement) {
this._inlineTemplates.set(id, template);
}
}
return this._inlineTemplates.get(id);
}
/**
* Clone a cached template
*/
static clone(key) {
const template = this._cache.get(key) || this._inlineTemplates.get(key);
if (!template) {
throw new Error(`Template "${key}" not found`);
}
return template.content.cloneNode(true);
}
/**
* Preload all inline templates from document
*/
static preloadInline() {
document.querySelectorAll('template[id]').forEach((template) => {
this._inlineTemplates.set(template.id, template);
});
console.log(`Preloaded ${this._inlineTemplates.size} inline templates`);
}
/**
* Clear cache
*/
static clear() {
this._cache.clear();
this._inlineTemplates.clear();
}
/**
* Get cache stats
*/
static stats() {
return {
dynamic: this._cache.size,
inline: this._inlineTemplates.size,
total: this._cache.size + this._inlineTemplates.size,
};
}
}
// ============================================
// USAGE DEMONSTRATION
// ============================================
function demonstrateTemplates() {
console.log('=== HTML Templates Demonstration ===\n');
// Check template support
const supportsTemplate = 'content' in document.createElement('template');
console.log('Template element supported:', supportsTemplate);
// List registered templates
console.log('\nRegistered templates in TemplateFactory:');
TemplateFactory.list().forEach((name) => {
console.log(` - ${name}`);
});
// Template cache stats
console.log('\nTemplate cache stats:', TemplateCache.stats());
// Components using templates
console.log('\nComponents using templates:');
const components = [
'basic-template-example',
'data-card',
'slot-panel',
'conditional-content',
'template-list',
'alert-button',
'todo-list',
];
components.forEach((name) => {
console.log(` <${name}>`);
});
}
// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
BasicTemplateExample,
DataCard,
SlotPanel,
TemplateFactory,
ConditionalContent,
TemplateList,
BaseComponent,
AlertButton,
DynamicTemplate,
TodoList,
TemplateCache,
demonstrateTemplates,
};
}