javascript

examples

examples.js
/**
 * 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,
  };
}
Examples - JavaScript Tutorial | DeepML