javascript

exercises

exercises.js
/**
 * Custom Elements - Exercises
 *
 * Practice creating and registering custom elements
 */

// ============================================
// EXERCISE 1: Progress Bar Element
// ============================================

/**
 * Create a progress bar custom element that:
 * - Accepts 'value' and 'max' attributes
 * - Shows percentage text
 * - Has configurable colors via 'color' attribute
 * - Animates when value changes
 * - Supports indeterminate state
 */

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

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

  get value() {
    // TODO: Return value as number
  }

  set value(val) {
    // TODO: Set value, constrain to 0-max range
    // TODO: Update attribute
  }

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

  set max(val) {
    // TODO: Set max value
  }

  get percentage() {
    // TODO: Calculate and return percentage
  }

  connectedCallback() {
    // TODO: Initial render
  }

  attributeChangedCallback(name, oldVal, newVal) {
    // TODO: Handle attribute changes
    // TODO: Re-render if connected
  }

  render() {
    // TODO: Render progress bar with:
    // - Background track
    // - Fill bar (width based on percentage)
    // - Percentage text
    // - Animation on value change
    // - Indeterminate animation if value not set
  }
}

// customElements.define('progress-bar', ProgressBar);

/**
 * Expected Usage:
 * <progress-bar value="75" max="100" color="#22c55e"></progress-bar>
 */

// ============================================
// EXERCISE 2: Toggle Switch Element
// ============================================

/**
 * Create a toggle switch element that:
 * - Works like a checkbox but looks like a switch
 * - Syncs with 'checked' attribute
 * - Dispatches 'change' event when toggled
 * - Is keyboard accessible (Space/Enter to toggle)
 * - Supports 'disabled' state
 * - Can have labels for on/off states
 */

class ToggleSwitch extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return ['checked', 'disabled', 'on-label', 'off-label']
  }

  constructor() {
    super();
    // TODO: Initialize
  }

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

  set checked(value) {
    // TODO: Set checked state
    // TODO: Reflect to attribute
  }

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

  set disabled(value) {
    // TODO: Set disabled state
  }

  connectedCallback() {
    // TODO: Render
    // TODO: Add click listener
    // TODO: Add keyboard listener
    // TODO: Set tabindex for accessibility
  }

  toggle() {
    // TODO: Toggle checked state if not disabled
    // TODO: Dispatch 'change' event
  }

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

  render() {
    // TODO: Render switch with:
    // - Track background
    // - Sliding thumb
    // - Optional on/off labels
    // - Disabled styling
    // - Focus styling for accessibility
  }
}

// customElements.define('toggle-switch', ToggleSwitch);

/**
 * Expected Usage:
 * <toggle-switch checked on-label="ON" off-label="OFF"></toggle-switch>
 *
 * const toggle = document.querySelector('toggle-switch');
 * toggle.addEventListener('change', (e) => {
 *     console.log('Checked:', e.target.checked);
 * });
 */

// ============================================
// EXERCISE 3: Star Rating Element
// ============================================

/**
 * Create a star rating element that:
 * - Shows 5 stars (configurable via 'max' attribute)
 * - Allows clicking to set rating
 * - Shows hover preview
 * - Supports half-star ratings (optional)
 * - Is readonly when 'readonly' attribute is present
 * - Dispatches 'rate' event with rating value
 */

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

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

  get value() {
    // TODO: Return current rating
  }

  set value(val) {
    // TODO: Set rating within valid range
  }

  get max() {
    // TODO: Return max stars (default 5)
  }

  connectedCallback() {
    // TODO: Render stars
    // TODO: Add event listeners if not readonly
  }

  _handleClick(index) {
    // TODO: Set value to clicked star index
    // TODO: Dispatch 'rate' event
  }

  _handleMouseEnter(index) {
    // TODO: Show hover preview
  }

  _handleMouseLeave() {
    // TODO: Remove hover preview
  }

  render() {
    // TODO: Render stars with:
    // - Filled stars up to current value
    // - Empty stars for remaining
    // - Hover styling
    // - Readonly styling (dimmed, no pointer)
  }
}

// customElements.define('star-rating', StarRating);

/**
 * Expected Usage:
 * <star-rating value="3.5" max="5"></star-rating>
 * <star-rating value="4" readonly></star-rating>
 */

// ============================================
// EXERCISE 4: Collapsible Section
// ============================================

/**
 * Create a collapsible section element that:
 * - Has a clickable header
 * - Shows/hides content when clicked
 * - Animates open/close
 * - Syncs with 'open' attribute
 * - Shows expand/collapse icon
 * - Dispatches 'toggle' event
 */

class CollapsibleSection extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return ['open', 'header']
  }

  constructor() {
    super();
    // TODO: Initialize
  }

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

  set open(value) {
    // TODO: Set open state with attribute reflection
  }

  toggle() {
    // TODO: Toggle open state
    // TODO: Dispatch 'toggle' event
  }

  connectedCallback() {
    // TODO: Render with header and content
    // TODO: Add click listener to header
  }

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

  render() {
    // TODO: Render with:
    // - Clickable header with icon
    // - Content area (hidden when collapsed)
    // - Smooth height animation
    // - Icon rotation animation
  }
}

// customElements.define('collapsible-section', CollapsibleSection);

/**
 * Expected Usage:
 * <collapsible-section header="Click to expand" open>
 *     <p>This content is collapsible</p>
 * </collapsible-section>
 */

// ============================================
// EXERCISE 5: Tag Input Element
// ============================================

/**
 * Create a tag input element that:
 * - Allows entering multiple tags
 * - Creates tag chips when Enter is pressed
 * - Allows removing tags by clicking X or Backspace
 * - Prevents duplicate tags
 * - Has a 'value' property returning array of tags
 * - Supports initial tags via attribute
 */

class TagInput extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return ['tags', 'placeholder']
  }

  constructor() {
    super();
    // TODO: Initialize tags array
  }

  get value() {
    // TODO: Return tags array
  }

  set value(tags) {
    // TODO: Set tags array
  }

  addTag(tag) {
    // TODO: Add tag if not duplicate
    // TODO: Dispatch 'change' event
  }

  removeTag(index) {
    // TODO: Remove tag at index
    // TODO: Dispatch 'change' event
  }

  connectedCallback() {
    // TODO: Parse initial tags from attribute
    // TODO: Render input and tags
    // TODO: Setup event listeners
  }

  _handleKeyDown(event) {
    // TODO: Handle Enter to add tag
    // TODO: Handle Backspace to remove last tag
  }

  render() {
    // TODO: Render with:
    // - Container styled like input
    // - Tag chips with X button
    // - Text input for new tags
    // - Focus styling
  }
}

// customElements.define('tag-input', TagInput);

/**
 * Expected Usage:
 * <tag-input tags="javascript,html,css" placeholder="Add tag..."></tag-input>
 *
 * const input = document.querySelector('tag-input');
 * console.log(input.value); // ['javascript', 'html', 'css']
 */

// ============================================
// EXERCISE 6: Countdown Timer
// ============================================

/**
 * Create a countdown timer element that:
 * - Counts down from a specified duration
 * - Displays time in MM:SS or HH:MM:SS format
 * - Has start, pause, and reset methods
 * - Dispatches 'tick' and 'complete' events
 * - Shows visual feedback when time is low
 */

class CountdownTimer extends HTMLElement {
  static get observedAttributes() {
    // TODO: Return ['duration', 'warn-at', 'auto-start']
  }

  constructor() {
    super();
    // TODO: Initialize timer state
  }

  get remaining() {
    // TODO: Return remaining seconds
  }

  start() {
    // TODO: Start or resume countdown
  }

  pause() {
    // TODO: Pause countdown
  }

  reset() {
    // TODO: Reset to initial duration
  }

  connectedCallback() {
    // TODO: Parse duration
    // TODO: Render timer
    // TODO: Auto-start if attribute present
  }

  disconnectedCallback() {
    // TODO: Clear interval
  }

  _tick() {
    // TODO: Decrement time
    // TODO: Update display
    // TODO: Dispatch 'tick' event
    // TODO: Check if complete
  }

  _formatTime(seconds) {
    // TODO: Format as MM:SS or HH:MM:SS
  }

  render() {
    // TODO: Render with:
    // - Time display
    // - Control buttons
    // - Warning styling when low
    // - Progress indicator
  }
}

// customElements.define('countdown-timer', CountdownTimer);

/**
 * Expected Usage:
 * <countdown-timer duration="300" warn-at="60" auto-start></countdown-timer>
 *
 * timer.addEventListener('complete', () => {
 *     alert('Time is up!');
 * });
 */

// ============================================
// BONUS: Extended Built-in Element
// ============================================

/**
 * Create an extended textarea that:
 * - Shows character count
 * - Has a max character limit
 * - Shows warning when approaching limit
 * - Auto-resizes to fit content
 */

class CharCountTextarea extends HTMLTextAreaElement {
  constructor() {
    super();
    // TODO: Initialize
  }

  connectedCallback() {
    // TODO: Create wrapper for counter display
    // TODO: Setup input listener
    // TODO: Setup auto-resize
  }

  disconnectedCallback() {
    // TODO: Cleanup
  }

  _updateCount() {
    // TODO: Update character count display
    // TODO: Show warning styling near limit
  }

  _autoResize() {
    // TODO: Adjust height to fit content
  }
}

// customElements.define('charcount-textarea', CharCountTextarea, { extends: 'textarea' });

/**
 * Expected Usage:
 * <textarea is="charcount-textarea" maxlength="280"></textarea>
 */

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

function testCustomElements() {
  console.log('=== Custom Elements Exercise Tests ===\n');

  // Test progress bar
  console.log('1. ProgressBar');
  console.log('   - Should show visual progress');
  console.log('   - Should display percentage');
  console.log('   - Should animate on change');

  // Test toggle switch
  console.log('\n2. ToggleSwitch');
  console.log('   - Should toggle on click');
  console.log('   - Should dispatch change event');
  console.log('   - Should be keyboard accessible');

  // Test star rating
  console.log('\n3. StarRating');
  console.log('   - Should show correct rating');
  console.log('   - Should allow clicking to rate');
  console.log('   - Should show hover preview');

  // Test collapsible
  console.log('\n4. CollapsibleSection');
  console.log('   - Should toggle content visibility');
  console.log('   - Should animate smoothly');
  console.log('   - Should dispatch toggle event');

  // Test tag input
  console.log('\n5. TagInput');
  console.log('   - Should add tags on Enter');
  console.log('   - Should remove tags on X click');
  console.log('   - Should prevent duplicates');

  // Test countdown
  console.log('\n6. CountdownTimer');
  console.log('   - Should count down correctly');
  console.log('   - Should dispatch events');
  console.log('   - Should show warning state');
}

// Export for module usage
if (typeof module !== 'undefined' && module.exports) {
  module.exports = {
    ProgressBar,
    ToggleSwitch,
    StarRating,
    CollapsibleSection,
    TagInput,
    CountdownTimer,
    CharCountTextarea,
    testCustomElements,
  };
}
Exercises - JavaScript Tutorial | DeepML