javascript

exercises

exercises.js
/**
 * Web Components - Exercises
 *
 * Practice building reusable custom elements
 */

// ============================================
// EXERCISE 1: Tooltip Component
// ============================================

/**
 * Create a tooltip custom element that:
 * - Shows text when hovering over slotted content
 * - Supports 'position' attribute (top, bottom, left, right)
 * - Has smooth fade-in/fade-out animation
 * - Uses Shadow DOM for encapsulation
 */

class TooltipElement extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return observed attributes
  }

  constructor() {
    super();
    // TODO: Attach shadow root
    // TODO: Initialize properties
  }

  connectedCallback() {
    // TODO: Render the component
    // TODO: Add hover event listeners
  }

  disconnectedCallback() {
    // TODO: Cleanup event listeners
  }

  attributeChangedCallback(name, oldValue, newValue) {
    // TODO: Handle attribute changes
  }

  show() {
    // TODO: Show the tooltip
  }

  hide() {
    // TODO: Hide the tooltip
  }

  render() {
    // TODO: Render tooltip with:
    // - Styles for positioning (top, bottom, left, right)
    // - Arrow pointing to content
    // - Fade animation
    // - Slot for trigger content
    // - Tooltip text from 'text' attribute
  }
}

// customElements.define('tooltip-element', TooltipElement);

/**
 * Expected Usage:
 * <tooltip-element text="This is a tooltip" position="top">
 *     <button>Hover me</button>
 * </tooltip-element>
 */

// ============================================
// EXERCISE 2: Accordion Component
// ============================================

/**
 * Create an accordion custom element that:
 * - Supports multiple collapsible sections
 * - Has 'single' mode (only one open) or 'multiple' mode
 * - Animates expansion/collapse
 * - Dispatches events when sections open/close
 * - Uses slots for header and content
 */

class AccordionContainer extends HTMLElement {
  constructor() {
    super();
    // TODO: Attach shadow root
    // TODO: Track open sections
  }

  connectedCallback() {
    // TODO: Render container
    // TODO: Listen for item events
  }

  get mode() {
    // TODO: Return 'single' or 'multiple' based on attribute
  }

  openSection(index) {
    // TODO: Open section by index
    // TODO: In 'single' mode, close other sections
  }

  closeSection(index) {
    // TODO: Close section by index
  }

  toggleSection(index) {
    // TODO: Toggle section open/close
  }
}

class AccordionItem extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return ['open'] for tracking state
  }

  constructor() {
    super();
    // TODO: Attach shadow root
  }

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

  set open(value) {
    // TODO: Set open state
    // TODO: Dispatch 'toggle' event
  }

  connectedCallback() {
    // TODO: Render item
    // TODO: Add header click listener
  }

  attributeChangedCallback(name, oldVal, newVal) {
    // TODO: Handle 'open' attribute changes
  }

  render() {
    // TODO: Render with:
    // - Header slot with expand/collapse icon
    // - Content slot (hidden when collapsed)
    // - Smooth height animation
  }
}

// customElements.define('accordion-container', AccordionContainer);
// customElements.define('accordion-item', AccordionItem);

/**
 * Expected Usage:
 * <accordion-container mode="single">
 *     <accordion-item>
 *         <span slot="header">Section 1</span>
 *         <div slot="content">Content 1</div>
 *     </accordion-item>
 *     <accordion-item open>
 *         <span slot="header">Section 2</span>
 *         <div slot="content">Content 2</div>
 *     </accordion-item>
 * </accordion-container>
 */

// ============================================
// EXERCISE 3: Data Table Component
// ============================================

/**
 * Create a data table custom element that:
 * - Accepts data via property or JSON attribute
 * - Supports column definitions with labels and formatters
 * - Has sortable columns
 * - Supports row selection (single or multiple)
 * - Dispatches events for sort and selection changes
 */

class DataTable extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return observed attributes
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });

    // TODO: Initialize:
    // - _data array
    // - _columns array
    // - _sortColumn and _sortDirection
    // - _selectedRows Set
  }

  // Data property
  get data() {
    // TODO: Return data array
  }

  set data(value) {
    // TODO: Set data and re-render
  }

  // Columns property
  get columns() {
    // TODO: Return columns array
  }

  set columns(value) {
    // TODO: Set columns and re-render
  }

  connectedCallback() {
    // TODO: Parse data from attribute if present
    // TODO: Initial render
  }

  sortBy(column) {
    // TODO: Sort data by column
    // TODO: Toggle direction if same column
    // TODO: Dispatch 'sort' event
  }

  selectRow(index, multi = false) {
    // TODO: Select row (toggle if already selected)
    // TODO: Clear others if not multi-select
    // TODO: Dispatch 'selection-change' event
  }

  getSelectedRows() {
    // TODO: Return array of selected row data
  }

  render() {
    // TODO: Render table with:
    // - <thead> with sortable column headers
    // - <tbody> with data rows
    // - Sort indicators (↑/↓)
    // - Selected row styling
    // - Responsive design
  }
}

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

/**
 * Expected Usage:
 * const table = document.querySelector('data-table');
 * table.columns = [
 *     { key: 'name', label: 'Name', sortable: true },
 *     { key: 'age', label: 'Age', sortable: true },
 *     { key: 'email', label: 'Email' }
 * ];
 * table.data = [
 *     { name: 'John', age: 30, email: 'john@example.com' },
 *     { name: 'Jane', age: 25, email: 'jane@example.com' }
 * ];
 */

// ============================================
// EXERCISE 4: Form Validation Component
// ============================================

/**
 * Create a form-associated custom input that:
 * - Integrates with native forms
 * - Supports validation rules (required, minlength, pattern, etc.)
 * - Shows inline error messages
 * - Has visual feedback for valid/invalid states
 * - Supports custom validation functions
 */

class ValidatedInput extends HTMLElement {
  static formAssociated = true;

  static get observedAttributes() {
    // TODO: Return validation-related attributes
  }

  constructor() {
    super();
    // TODO: Attach internals and shadow root
    // TODO: Initialize validation state
  }

  get value() {
    // TODO: Return input value
  }

  set value(val) {
    // TODO: Set value and validate
  }

  get validity() {
    // TODO: Return validity state object
  }

  connectedCallback() {
    // TODO: Render input with error message area
    // TODO: Setup input event listeners
  }

  validate() {
    // TODO: Run all validation rules
    // TODO: Update visual state
    // TODO: Set form validity via internals
    // TODO: Return validation result
  }

  setCustomValidity(message) {
    // TODO: Set custom error message
  }

  // Form callbacks
  formResetCallback() {
    // TODO: Reset to default value
  }

  formStateRestoreCallback(state) {
    // TODO: Restore value from state
  }

  render() {
    // TODO: Render with:
    // - Styled input element
    // - Error message display
    // - Valid/invalid visual states
    // - Required indicator
  }
}

// customElements.define('validated-input', ValidatedInput);

/**
 * Expected Usage:
 * <form>
 *     <validated-input
 *         name="email"
 *         type="email"
 *         required
 *         pattern="[a-z]+@[a-z]+\.[a-z]+"
 *         error-message="Please enter a valid email"
 *     ></validated-input>
 *     <button type="submit">Submit</button>
 * </form>
 */

// ============================================
// EXERCISE 5: Notification Toast Component
// ============================================

/**
 * Create a notification toast system with:
 * - A container component that manages toasts
 * - Individual toast components
 * - Auto-dismiss with configurable duration
 * - Types: success, error, warning, info
 * - Position options (top-right, bottom-right, etc.)
 * - Stack management
 */

class ToastContainer extends HTMLElement {
  constructor() {
    super();
    // TODO: Attach shadow root
    // TODO: Initialize toast queue
  }

  static get instance() {
    // TODO: Singleton pattern - return or create instance
  }

  connectedCallback() {
    // TODO: Render container
    // TODO: Set position from attribute
  }

  show(message, options = {}) {
    // TODO: Create and add new toast
    // TODO: Return toast element for chaining
  }

  success(message, options = {}) {
    // TODO: Show success toast
  }

  error(message, options = {}) {
    // TODO: Show error toast
  }

  warning(message, options = {}) {
    // TODO: Show warning toast
  }

  info(message, options = {}) {
    // TODO: Show info toast
  }

  dismissAll() {
    // TODO: Remove all toasts
  }
}

class ToastNotification extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return ['type', 'duration', 'dismissible']
  }

  constructor() {
    super();
    // TODO: Attach shadow root
    // TODO: Initialize timer
  }

  connectedCallback() {
    // TODO: Render toast
    // TODO: Start auto-dismiss timer
    // TODO: Add entrance animation
  }

  disconnectedCallback() {
    // TODO: Clear timer
  }

  dismiss() {
    // TODO: Play exit animation
    // TODO: Remove from DOM
    // TODO: Dispatch 'dismiss' event
  }

  render() {
    // TODO: Render with:
    // - Type-specific styling and icons
    // - Message slot or text
    // - Dismiss button (if dismissible)
    // - Progress bar showing time remaining
  }
}

// customElements.define('toast-container', ToastContainer);
// customElements.define('toast-notification', ToastNotification);

/**
 * Expected Usage:
 * // Get or create singleton container
 * const toast = ToastContainer.instance;
 *
 * // Show different types
 * toast.success('Data saved successfully!');
 * toast.error('Failed to load data', { duration: 5000 });
 * toast.warning('Your session will expire soon');
 * toast.info('New version available');
 */

// ============================================
// EXERCISE 6: Draggable List Component
// ============================================

/**
 * Create a draggable list component that:
 * - Allows reordering items via drag and drop
 * - Provides visual feedback during drag
 * - Dispatches events when order changes
 * - Works with slotted content
 * - Supports keyboard navigation
 */

class DraggableList extends HTMLElement {
  constructor() {
    super();
    // TODO: Attach shadow root
    // TODO: Track dragging state
  }

  connectedCallback() {
    // TODO: Render list container
    // TODO: Setup drag event listeners
    // TODO: Setup keyboard event listeners
  }

  disconnectedCallback() {
    // TODO: Cleanup event listeners
  }

  getOrder() {
    // TODO: Return current order of items
  }

  setOrder(order) {
    // TODO: Reorder items based on array
  }

  _handleDragStart(e, item) {
    // TODO: Start drag operation
    // TODO: Set drag data
    // TODO: Add dragging class
  }

  _handleDragOver(e) {
    // TODO: Determine drop position
    // TODO: Show drop indicator
  }

  _handleDrop(e, targetItem) {
    // TODO: Complete reorder
    // TODO: Dispatch 'reorder' event
  }

  _handleKeyDown(e, item) {
    // TODO: Handle arrow keys for keyboard reordering
  }

  render() {
    // TODO: Render with:
    // - Slot for list items
    // - Drag handle styling
    // - Drop indicator
    // - Accessible ARIA attributes
  }
}

// customElements.define('draggable-list', DraggableList);

/**
 * Expected Usage:
 * <draggable-list>
 *     <div class="item" data-id="1">Item 1</div>
 *     <div class="item" data-id="2">Item 2</div>
 *     <div class="item" data-id="3">Item 3</div>
 * </draggable-list>
 *
 * list.addEventListener('reorder', (e) => {
 *     console.log('New order:', e.detail.order);
 * });
 */

// ============================================
// BONUS CHALLENGE: Component Library Framework
// ============================================

/**
 * Build a mini component framework that:
 * - Provides a base class with common functionality
 * - Includes reactive state management
 * - Supports computed properties
 * - Has event delegation helpers
 * - Provides template rendering utilities
 */

class ComponentFramework {
  // TODO: Implement createComponent factory
  static createComponent(config) {
    // config = {
    //     name: 'my-component',
    //     template: '<div>...</div>',
    //     styles: '...',
    //     props: { count: { type: Number, default: 0 } },
    //     state: { ... },
    //     computed: { ... },
    //     methods: { ... },
    //     lifecycle: { mounted, unmounted, ... }
    // }
  }

  // TODO: Implement reactive state system
  static createReactiveState(initialState, onChange) {
    // Return Proxy that triggers onChange on mutations
  }

  // TODO: Implement template engine
  static parseTemplate(template, data) {
    // Handle {{ expressions }}
    // Handle @event bindings
    // Handle :attribute bindings
  }
}

/**
 * Expected Usage:
 * ComponentFramework.createComponent({
 *     name: 'counter-component',
 *     template: `
 *         <button @click="decrement">-</button>
 *         <span>{{ count }}</span>
 *         <button @click="increment">+</button>
 *     `,
 *     styles: `
 *         button { padding: 10px; }
 *     `,
 *     props: {
 *         initial: { type: Number, default: 0 }
 *     },
 *     state() {
 *         return { count: this.initial };
 *     },
 *     methods: {
 *         increment() { this.state.count++; },
 *         decrement() { this.state.count--; }
 *     }
 * });
 */

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

function testWebComponents() {
  console.log('=== Web Components Exercise Tests ===\n');

  // Test tooltip
  console.log('1. TooltipElement');
  console.log('   - Should show on hover');
  console.log('   - Should position correctly');
  console.log('   - Should animate smoothly');

  // Test accordion
  console.log('\n2. Accordion');
  console.log('   - Should expand/collapse sections');
  console.log('   - Single mode should close others');
  console.log('   - Should dispatch toggle events');

  // Test data table
  console.log('\n3. DataTable');
  console.log('   - Should render columns and data');
  console.log('   - Should sort on header click');
  console.log('   - Should handle row selection');

  // Test validated input
  console.log('\n4. ValidatedInput');
  console.log('   - Should validate on input');
  console.log('   - Should integrate with form');
  console.log('   - Should show error messages');

  // Test toast
  console.log('\n5. Toast System');
  console.log('   - Should show notifications');
  console.log('   - Should auto-dismiss');
  console.log('   - Should stack properly');

  // Test draggable list
  console.log('\n6. DraggableList');
  console.log('   - Should reorder on drag');
  console.log('   - Should support keyboard');
  console.log('   - Should dispatch events');
}

// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    TooltipElement,
    AccordionContainer,
    AccordionItem,
    DataTable,
    ValidatedInput,
    ToastContainer,
    ToastNotification,
    DraggableList,
    ComponentFramework,
    testWebComponents,
  };
}
Exercises - JavaScript Tutorial | DeepML