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