javascript
exercises
exercises.js⚡javascript
/**
* 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,
};
}