javascript

exercises

exercises.js
/**
 * HTML Templates - Exercises
 *
 * Practice using HTML templates for reusable content
 */

// ============================================
// EXERCISE 1: Product Card Template
// ============================================

/**
 * Create a product card component using templates that:
 * - Shows product image, name, price, and description
 * - Has an "Add to Cart" button
 * - Supports sale price display (crossed out original + sale price)
 * - Uses a single template for all product instances
 */

const productCardTemplate = document.createElement('template');
// TODO: Define product card HTML template with:
// - Product image
// - Product name
// - Price (with optional sale price styling)
// - Description
// - Add to Cart button

class ProductCard extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return relevant attributes
  }

  constructor() {
    super();
    // TODO: Attach shadow and clone template
  }

  connectedCallback() {
    // TODO: Populate content from attributes
    // TODO: Setup event listeners
  }

  attributeChangedCallback(name, oldVal, newVal) {
    // TODO: Update content on attribute change
  }

  _updateContent() {
    // TODO: Update all fields from attributes
    // TODO: Handle sale price display
  }

  _handleAddToCart() {
    // TODO: Dispatch custom event with product details
  }
}

// customElements.define('product-card', ProductCard);

/**
 * Expected Usage:
 * <product-card
 *     image="/products/item.jpg"
 *     name="Wireless Headphones"
 *     price="99.99"
 *     sale-price="79.99"
 *     description="High-quality wireless audio">
 * </product-card>
 */

// ============================================
// EXERCISE 2: Multi-Slot Layout Template
// ============================================

/**
 * Create a flexible layout component with:
 * - Header slot with icon and title
 * - Main content slot
 * - Sidebar slot (optional)
 * - Footer slot with actions
 * - Collapsible sidebar on small screens
 */

const layoutTemplate = document.createElement('template');
// TODO: Define layout with multiple named slots

class FlexibleLayout extends HTMLElement {
  constructor() {
    super();
    // TODO: Setup shadow DOM with template
  }

  connectedCallback() {
    // TODO: Handle optional slots (hide if empty)
    // TODO: Setup responsive behavior
  }

  toggleSidebar() {
    // TODO: Toggle sidebar visibility
  }

  render() {
    // TODO: Render layout with:
    // - Header area with icon slot and title slot
    // - Main content area
    // - Optional sidebar
    // - Footer with action slots
  }
}

// customElements.define('flexible-layout', FlexibleLayout);

/**
 * Expected Usage:
 * <flexible-layout>
 *     <span slot="icon">🏠</span>
 *     <span slot="title">Dashboard</span>
 *
 *     <div slot="sidebar">
 *         <nav>Navigation here</nav>
 *     </div>
 *
 *     <main>Main content</main>
 *
 *     <button slot="footer-actions">Save</button>
 *     <button slot="footer-actions">Cancel</button>
 * </flexible-layout>
 */

// ============================================
// EXERCISE 3: Template-Based Table
// ============================================

/**
 * Create a data table component that:
 * - Uses templates for rows
 * - Supports column definitions
 * - Has sortable headers
 * - Supports custom cell templates per column
 */

class DataTable extends HTMLElement {
  constructor() {
    super();
    // TODO: Setup shadow DOM
    // TODO: Initialize data and columns
  }

  set columns(cols) {
    // TODO: Store column definitions
    // TODO: Re-render headers
  }

  set data(rows) {
    // TODO: Store data
    // TODO: Re-render body
  }

  registerCellTemplate(columnKey, template) {
    // TODO: Store custom template for column
  }

  connectedCallback() {
    // TODO: Initial render
  }

  _renderHeaders() {
    // TODO: Generate header row with sort indicators
  }

  _renderBody() {
    // TODO: Generate body rows using templates
    // TODO: Use custom cell templates if registered
  }

  _sortBy(columnKey) {
    // TODO: Sort data and re-render
  }
}

// customElements.define('data-table', DataTable);

/**
 * Expected Usage:
 * const table = document.querySelector('data-table');
 *
 * // Define columns
 * table.columns = [
 *     { key: 'name', label: 'Name', sortable: true },
 *     { key: 'email', label: 'Email' },
 *     { key: 'status', label: 'Status' }
 * ];
 *
 * // Register custom cell template
 * const statusTemplate = document.createElement('template');
 * statusTemplate.innerHTML = '<span class="badge"></span>';
 * table.registerCellTemplate('status', statusTemplate);
 *
 * // Set data
 * table.data = [
 *     { name: 'John', email: 'john@example.com', status: 'active' },
 *     { name: 'Jane', email: 'jane@example.com', status: 'inactive' }
 * ];
 */

// ============================================
// EXERCISE 4: Template Registry System
// ============================================

/**
 * Build a template registry that:
 * - Allows registering templates by name
 * - Supports template inheritance (extend another template)
 * - Provides compile-time variable substitution
 * - Caches compiled templates
 */

class TemplateRegistry {
  constructor() {
    // TODO: Initialize templates map
    // TODO: Initialize compiled cache
  }

  register(name, html, options = {}) {
    // TODO: Create template element
    // TODO: Store with options (extends, variables)
  }

  extend(name, parentName, overrides) {
    // TODO: Get parent template
    // TODO: Apply overrides to create new template
  }

  compile(name, data) {
    // TODO: Clone template
    // TODO: Substitute variables ({{variable}})
    // TODO: Cache compiled result
  }

  render(name, data, container) {
    // TODO: Compile template
    // TODO: Append to container
  }

  has(name) {
    // TODO: Check if template exists
  }

  list() {
    // TODO: Return all template names
  }
}

// const registry = new TemplateRegistry();

/**
 * Expected Usage:
 * // Register base template
 * registry.register('base-card', `
 *     <div class="card">
 *         <div class="card-header">{{title}}</div>
 *         <div class="card-body">{{content}}</div>
 *     </div>
 * `);
 *
 * // Extend base template
 * registry.extend('image-card', 'base-card', {
 *     before: '<img src="{{image}}" />'
 * });
 *
 * // Compile with data
 * const fragment = registry.compile('image-card', {
 *     title: 'My Card',
 *     content: 'Card content',
 *     image: '/path/to/image.jpg'
 * });
 */

// ============================================
// EXERCISE 5: Conditional Templates
// ============================================

/**
 * Create a component that:
 * - Supports if/else template selection
 * - Shows loading, error, empty, or content templates
 * - Transitions between states smoothly
 */

class StateView extends HTMLElement {
  static states = ['loading', 'error', 'empty', 'content'];

  constructor() {
    super();
    // TODO: Attach shadow
    // TODO: Create templates for each state
  }

  static get observedAttributes() {
    // TODO: Return ['state', 'error-message']
  }

  get state() {
    // TODO: Return current state
  }

  set state(value) {
    // TODO: Validate and set state
    // TODO: Transition to new state
  }

  connectedCallback() {
    // TODO: Create base structure with transition container
    // TODO: Render initial state
  }

  attributeChangedCallback(name, oldVal, newVal) {
    // TODO: Handle state changes
  }

  _createStateTemplates() {
    // TODO: Create templates for:
    // - Loading: spinner + text
    // - Error: icon + message + retry button
    // - Empty: icon + message
    // - Content: slot for actual content
  }

  _transitionTo(state) {
    // TODO: Animate out current content
    // TODO: Clone and insert new template
    // TODO: Animate in new content
  }
}

// customElements.define('state-view', StateView);

/**
 * Expected Usage:
 * <state-view state="loading"></state-view>
 *
 * // Change state
 * view.state = 'content';
 *
 * // Error state with message
 * view.setAttribute('state', 'error');
 * view.setAttribute('error-message', 'Failed to load data');
 */

// ============================================
// EXERCISE 6: Template-Based Form Builder
// ============================================

/**
 * Create a form builder that:
 * - Uses templates for each field type
 * - Supports validation rules
 * - Generates complete forms from config
 * - Handles form submission
 */

class FormBuilder extends HTMLElement {
  constructor() {
    super();
    // TODO: Attach shadow
    // TODO: Create field templates
  }

  set config(formConfig) {
    // TODO: Store config
    // TODO: Build form from config
  }

  get values() {
    // TODO: Collect all field values
  }

  validate() {
    // TODO: Run validation on all fields
    // TODO: Return validation result
  }

  _createFieldTemplates() {
    // TODO: Create templates for:
    // - text input
    // - email input
    // - password input
    // - textarea
    // - select
    // - checkbox
    // - radio group
  }

  _buildField(fieldConfig) {
    // TODO: Get appropriate template
    // TODO: Clone and configure
    // TODO: Add validation
  }

  _buildForm() {
    // TODO: Clear existing form
    // TODO: Build each field
    // TODO: Add submit button
  }

  _handleSubmit(e) {
    // TODO: Prevent default
    // TODO: Validate
    // TODO: Dispatch submit event with values
  }
}

// customElements.define('form-builder', FormBuilder);

/**
 * Expected Usage:
 * const form = document.querySelector('form-builder');
 *
 * form.config = {
 *     fields: [
 *         { name: 'email', type: 'email', label: 'Email', required: true },
 *         { name: 'password', type: 'password', label: 'Password', minLength: 8 },
 *         { name: 'role', type: 'select', label: 'Role', options: ['Admin', 'User'] }
 *     ],
 *     submitText: 'Sign Up'
 * };
 *
 * form.addEventListener('form-submit', (e) => {
 *     console.log('Form values:', e.detail);
 * });
 */

// ============================================
// BONUS: Template Compiler
// ============================================

/**
 * Build a simple template compiler that:
 * - Parses {{variable}} expressions
 * - Supports {{#if condition}}...{{/if}}
 * - Supports {{#each items}}...{{/each}}
 * - Caches compiled templates
 */

class TemplateCompiler {
  constructor() {
    // TODO: Initialize cache
  }

  compile(templateString) {
    // TODO: Parse template string
    // TODO: Return function that takes data
  }

  _parseVariables(str, data) {
    // TODO: Replace {{variable}} with values
  }

  _parseConditionals(str, data) {
    // TODO: Handle {{#if}}...{{/if}}
  }

  _parseLoops(str, data) {
    // TODO: Handle {{#each}}...{{/each}}
  }
}

// const compiler = new TemplateCompiler();

/**
 * Expected Usage:
 * const template = compiler.compile(`
 *     <div class="user">
 *         <h2>{{name}}</h2>
 *         {{#if isAdmin}}
 *             <span class="badge">Admin</span>
 *         {{/if}}
 *         <ul>
 *             {{#each roles}}
 *                 <li>{{this}}</li>
 *             {{/each}}
 *         </ul>
 *     </div>
 * `);
 *
 * const html = template({
 *     name: 'John',
 *     isAdmin: true,
 *     roles: ['Editor', 'Reviewer']
 * });
 */

// ============================================
// TEST HELPERS
// ============================================

function testTemplates() {
  console.log('=== HTML Templates Exercise Tests ===\n');

  // Test product card
  console.log('1. ProductCard');
  console.log('   - Should render product info');
  console.log('   - Should handle sale prices');
  console.log('   - Should dispatch add-to-cart event');

  // Test flexible layout
  console.log('\n2. FlexibleLayout');
  console.log('   - Should handle multiple slots');
  console.log('   - Should hide empty optional slots');
  console.log('   - Should be responsive');

  // Test data table
  console.log('\n3. DataTable');
  console.log('   - Should render from template');
  console.log('   - Should support custom cell templates');
  console.log('   - Should handle sorting');

  // Test template registry
  console.log('\n4. TemplateRegistry');
  console.log('   - Should register templates');
  console.log('   - Should support inheritance');
  console.log('   - Should compile with data');

  // Test state view
  console.log('\n5. StateView');
  console.log('   - Should show correct state template');
  console.log('   - Should transition smoothly');
  console.log('   - Should handle error messages');

  // Test form builder
  console.log('\n6. FormBuilder');
  console.log('   - Should build forms from config');
  console.log('   - Should use field templates');
  console.log('   - Should validate and submit');
}

// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    ProductCard,
    FlexibleLayout,
    DataTable,
    TemplateRegistry,
    StateView,
    FormBuilder,
    TemplateCompiler,
    testTemplates,
  };
}
Exercises - JavaScript Tutorial | DeepML