From 90c488ba50fc7dd5377522ce3cb0e094e33f2c54 Mon Sep 17 00:00:00 2001 From: Surya Prakash Kahar Date: Mon, 6 Oct 2025 18:22:27 +0530 Subject: [PATCH] Add example solution for Interactive Calendar (#321) --- examples/interactive-calendar/README.md | 133 +++++++++ examples/interactive-calendar/index.html | 72 +++++ examples/interactive-calendar/script.js | 274 +++++++++++++++++++ examples/interactive-calendar/style.css | 328 +++++++++++++++++++++++ 4 files changed, 807 insertions(+) create mode 100644 examples/interactive-calendar/README.md create mode 100644 examples/interactive-calendar/index.html create mode 100644 examples/interactive-calendar/script.js create mode 100644 examples/interactive-calendar/style.css diff --git a/examples/interactive-calendar/README.md b/examples/interactive-calendar/README.md new file mode 100644 index 00000000..e286cd74 --- /dev/null +++ b/examples/interactive-calendar/README.md @@ -0,0 +1,133 @@ +# Interactive Calendar App + +A dynamic, interactive calendar application that demonstrates date manipulation, DOM updates, and event-driven programming in JavaScript. The calendar allows users to navigate through months and years, highlights the current date, and includes event management functionality. + +## Features + +- **Dynamic Calendar Display**: Shows all days of the selected month and year +- **Current Date Highlighting**: Today's date is visually distinguished +- **Month/Year Navigation**: Intuitive buttons for browsing through time +- **Quick Navigation**: Dropdown for months and input for years +- **Responsive Design**: Adapts to different screen sizes +- **Event Management**: Add, view, and remove events for specific dates +- **Interactive Tooltips**: Hover to see event previews +- **Modal Interface**: Detailed event management for selected dates + +## How It Works + +### HTML Structure (`index.html`) +- **Header Section**: Navigation buttons and month/year display +- **Calendar Grid**: Weekday headers and dynamic day cells +- **Controls**: Today button and quick month/year selectors +- **Modal**: Event management interface with form and event list +- **Tooltip**: Hover preview for dates with events + +### CSS Styling (`style.css`) +- Modern, responsive design with gradient backgrounds +- Smooth animations and transitions +- Mobile-first approach with media queries +- Visual feedback for interactions (hover, active states) +- Modal and tooltip styling with fade-in animations + +### JavaScript Implementation (`script.js`) + +#### Core Calendar Logic + +**Date State Management** +```javascript +let currentDate = new Date(); // Currently displayed month/year +let selectedDate = null; // Date selected for event management +let events = {}; // Event storage: {'YYYY-MM-DD': ['event1', 'event2']} +``` + +**Calendar Rendering** +The `renderCalendar()` function dynamically generates the calendar grid: +1. Calculate first and last days of the month +2. Determine start/end dates for full week display +3. Create day elements with appropriate styling +4. Add event indicators and click handlers + +**Date Manipulation** +```javascript +// Navigate months +function navigateMonth(delta) { + currentDate.setMonth(currentDate.getMonth() + delta); + renderCalendar(); +} + +// Get date key for event storage +function getDateKey(date) { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; +} +``` + +#### Event Handling + +**Navigation Events** +- Month/Year buttons: `navigateMonth()`, `navigateYear()` +- Today button: `goToToday()` - resets to current date +- Quick selectors: `goToSpecificMonthYear()` - direct month/year input + +**Day Interactions** +- Click: Opens event modal for selected date +- Hover: Shows tooltip with event preview (if events exist) + +**Modal Management** +- Add events: `addEvent()` - validates input and updates storage +- Remove events: `removeEvent()` - deletes specific events +- Display events: `displayEvents()` - renders event list in modal + +#### Key Concepts Demonstrated + +**Date Object Manipulation** +- Creating and modifying Date objects +- Extracting date components (year, month, day) +- Calculating date ranges and day-of-week +- Formatting dates for display and storage + +**DOM Manipulation** +- Dynamic element creation and removal +- Class manipulation for styling +- Event listener attachment and management +- Real-time UI updates + +**Event-Driven Programming** +- User interaction handling (clicks, hovers, form submission) +- State management and updates +- Callback functions and closures + +**Data Structures** +- Object-based event storage +- Array manipulation for event lists +- Date string keys for efficient lookups + +## Usage + +1. **Navigation**: Use arrow buttons or dropdown/input to change months/years +2. **Today Button**: Quickly return to the current date +3. **Add Events**: Click any date to open the event modal and add events +4. **View Events**: Hover over dates with events to see tooltips, or click to edit +5. **Remove Events**: Use the × button in the modal to delete events + +## Sample Events + +The calendar includes sample events for demonstration: +- Today's date: "Meeting with team" +- Tomorrow: "Doctor appointment", "Project deadline" +- Next week: "Conference call" + +## Browser Compatibility + +Works in all modern browsers supporting ES6 features including: +- Date object methods +- Template literals +- Arrow functions +- Array methods (forEach, splice) + +## Responsive Features + +- **Desktop**: Full navigation and layout +- **Tablet**: Adjusted button sizes and spacing +- **Mobile**: Stacked controls, smaller text, touch-friendly interface + +This implementation provides a solid foundation for calendar-based applications and demonstrates essential JavaScript concepts in an interactive, practical way. diff --git a/examples/interactive-calendar/index.html b/examples/interactive-calendar/index.html new file mode 100644 index 00000000..6a1c54bd --- /dev/null +++ b/examples/interactive-calendar/index.html @@ -0,0 +1,72 @@ + + + + + + Interactive Calendar + + + +
+
+ + +

October 2025

+ + +
+ +
+
+
Sun
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+
+
+
+ +
+ +
+ + +
+
+
+ + + + + +
+ + + + diff --git a/examples/interactive-calendar/script.js b/examples/interactive-calendar/script.js new file mode 100644 index 00000000..e97b18e5 --- /dev/null +++ b/examples/interactive-calendar/script.js @@ -0,0 +1,274 @@ +// Calendar state +let currentDate = new Date(); +let selectedDate = null; +let events = {}; // Store events by date string 'YYYY-MM-DD' + +// DOM elements +const monthYearElement = document.getElementById('month-year'); +const calendarDaysElement = document.getElementById('calendar-days'); +const prevMonthBtn = document.getElementById('prev-month'); +const nextMonthBtn = document.getElementById('next-month'); +const prevYearBtn = document.getElementById('prev-year'); +const nextYearBtn = document.getElementById('next-year'); +const todayBtn = document.getElementById('today-btn'); +const monthSelect = document.getElementById('month-select'); +const yearInput = document.getElementById('year-input'); +const modal = document.getElementById('event-modal'); +const modalDateElement = document.getElementById('modal-date'); +const eventListElement = document.getElementById('event-list'); +const eventTitleInput = document.getElementById('event-title'); +const addEventBtn = document.getElementById('add-event-btn'); +const closeModalBtn = document.querySelector('.close'); +const tooltip = document.getElementById('tooltip'); + +// Initialize calendar +function initCalendar() { + renderCalendar(); + setupEventListeners(); + updateMonthYearDisplay(); + updateControls(); + + // Set initial month/year in controls + monthSelect.value = currentDate.getMonth(); + yearInput.value = currentDate.getFullYear(); +} + +// Render the calendar grid +function renderCalendar() { + calendarDaysElement.innerHTML = ''; + + const year = currentDate.getFullYear(); + const month = currentDate.getMonth(); + + // Get first day of month and last day of month + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const startDate = new Date(firstDay); + startDate.setDate(startDate.getDate() - firstDay.getDay()); // Start from Sunday + + const endDate = new Date(lastDay); + endDate.setDate(endDate.getDate() + (6 - lastDay.getDay())); // End on Saturday + + // Generate calendar days + const current = new Date(startDate); + while (current <= endDate) { + const dayElement = createDayElement(current, month); + calendarDaysElement.appendChild(dayElement); + current.setDate(current.getDate() + 1); + } +} + +// Create a day element +function createDayElement(date, currentMonth) { + const dayElement = document.createElement('div'); + dayElement.className = 'day'; + dayElement.textContent = date.getDate(); + + // Check if day is in current month + if (date.getMonth() !== currentMonth) { + dayElement.classList.add('other-month'); + } + + // Highlight today + const today = new Date(); + if (date.toDateString() === today.toDateString()) { + dayElement.classList.add('today'); + } + + // Check for events + const dateKey = getDateKey(date); + if (events[dateKey] && events[dateKey].length > 0) { + dayElement.classList.add('has-event'); + } + + // Add click handler + dayElement.addEventListener('click', () => handleDayClick(date, dayElement)); + dayElement.addEventListener('mouseenter', (e) => showTooltip(e, date)); + dayElement.addEventListener('mouseleave', () => hideTooltip()); + + return dayElement; +} + +// Handle day click - show modal with events +function handleDayClick(date, dayElement) { + selectedDate = date; + const dateKey = getDateKey(date); + + modalDateElement.textContent = date.toLocaleDateString('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + // Display existing events + displayEvents(dateKey); + + // Show modal + modal.style.display = 'block'; +} + +// Display events for a date +function displayEvents(dateKey) { + eventListElement.innerHTML = ''; + + if (events[dateKey] && events[dateKey].length > 0) { + events[dateKey].forEach((event, index) => { + const eventElement = document.createElement('div'); + eventElement.className = 'event-item'; + eventElement.innerHTML = ` + ${event} + + `; + eventListElement.appendChild(eventElement); + }); + } else { + eventListElement.innerHTML = '

No events for this date

'; + } +} + +// Add new event +function addEvent() { + const title = eventTitleInput.value.trim(); + if (!title) return; + + const dateKey = getDateKey(selectedDate); + if (!events[dateKey]) { + events[dateKey] = []; + } + + events[dateKey].push(title); + eventTitleInput.value = ''; + displayEvents(dateKey); + renderCalendar(); // Re-render to show event indicator +} + +// Remove event +function removeEvent(dateKey, index) { + events[dateKey].splice(index, 1); + if (events[dateKey].length === 0) { + delete events[dateKey]; + } + displayEvents(dateKey); + renderCalendar(); +} + +// Tooltip functionality +function showTooltip(event, date) { + const dateKey = getDateKey(date); + if (events[dateKey] && events[dateKey].length > 0) { + tooltip.textContent = events[dateKey].join(', '); + tooltip.classList.add('show'); + + // Position tooltip + const rect = event.target.getBoundingClientRect(); + tooltip.style.left = rect.left + (rect.width / 2) - (tooltip.offsetWidth / 2) + 'px'; + tooltip.style.top = rect.top - tooltip.offsetHeight - 10 + 'px'; + } +} + +function hideTooltip() { + tooltip.classList.remove('show'); +} + +// Navigation functions +function navigateMonth(delta) { + currentDate.setMonth(currentDate.getMonth() + delta); + updateMonthYearDisplay(); + updateControls(); + renderCalendar(); +} + +function navigateYear(delta) { + currentDate.setFullYear(currentDate.getFullYear() + delta); + updateMonthYearDisplay(); + updateControls(); + renderCalendar(); +} + +function goToToday() { + currentDate = new Date(); + updateMonthYearDisplay(); + updateControls(); + renderCalendar(); +} + +function goToSpecificMonthYear() { + const month = parseInt(monthSelect.value); + const year = parseInt(yearInput.value); + + if (year >= 1900 && year <= 2100) { + currentDate.setFullYear(year); + currentDate.setMonth(month); + updateMonthYearDisplay(); + renderCalendar(); + } +} + +// Update display functions +function updateMonthYearDisplay() { + const options = { year: 'numeric', month: 'long' }; + monthYearElement.textContent = currentDate.toLocaleDateString('en-US', options); +} + +function updateControls() { + monthSelect.value = currentDate.getMonth(); + yearInput.value = currentDate.getFullYear(); +} + +// Utility functions +function getDateKey(date) { + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; +} + +// Event listeners setup +function setupEventListeners() { + // Navigation buttons + prevMonthBtn.addEventListener('click', () => navigateMonth(-1)); + nextMonthBtn.addEventListener('click', () => navigateMonth(1)); + prevYearBtn.addEventListener('click', () => navigateYear(-1)); + nextYearBtn.addEventListener('click', () => navigateYear(1)); + + // Today button + todayBtn.addEventListener('click', goToToday); + + // Month/Year controls + monthSelect.addEventListener('change', goToSpecificMonthYear); + yearInput.addEventListener('change', goToSpecificMonthYear); + + // Modal controls + closeModalBtn.addEventListener('click', () => modal.style.display = 'none'); + window.addEventListener('click', (e) => { + if (e.target === modal) { + modal.style.display = 'none'; + } + }); + + // Add event button + addEventBtn.addEventListener('click', addEvent); + eventTitleInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') { + addEvent(); + } + }); + + // Sample events for demonstration + addSampleEvents(); +} + +// Add some sample events +function addSampleEvents() { + const today = new Date(); + const tomorrow = new Date(today); + tomorrow.setDate(today.getDate() + 1); + + const nextWeek = new Date(today); + nextWeek.setDate(today.getDate() + 7); + + events[getDateKey(today)] = ['Meeting with team']; + events[getDateKey(tomorrow)] = ['Doctor appointment', 'Project deadline']; + events[getDateKey(nextWeek)] = ['Conference call']; +} + +// Initialize the calendar when DOM is loaded +document.addEventListener('DOMContentLoaded', initCalendar); diff --git a/examples/interactive-calendar/style.css b/examples/interactive-calendar/style.css new file mode 100644 index 00000000..6daf6126 --- /dev/null +++ b/examples/interactive-calendar/style.css @@ -0,0 +1,328 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.calendar-container { + background: white; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); + overflow: hidden; + width: 100%; + max-width: 800px; + animation: fadeIn 0.5s ease-in-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(-20px); } + to { opacity: 1; transform: translateY(0); } +} + +.calendar-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 20px; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 10px; +} + +.nav-btn { + background: rgba(255, 255, 255, 0.2); + border: none; + color: white; + padding: 10px 15px; + border-radius: 8px; + cursor: pointer; + font-size: 18px; + transition: all 0.3s ease; +} + +.nav-btn:hover { + background: rgba(255, 255, 255, 0.3); + transform: scale(1.05); +} + +#month-year { + font-size: 2rem; + font-weight: 300; + margin: 0 20px; + flex-grow: 1; + text-align: center; +} + +.calendar-grid { + padding: 20px; +} + +.weekdays { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 5px; + margin-bottom: 10px; +} + +.weekdays div { + text-align: center; + font-weight: bold; + color: #666; + padding: 10px; + font-size: 14px; +} + +.days { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 5px; +} + +.day { + aspect-ratio: 1; + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + font-weight: 500; +} + +.day:hover { + background: #f0f0f0; + transform: scale(1.05); +} + +.day.today { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + font-weight: bold; +} + +.day.other-month { + color: #ccc; + cursor: default; +} + +.day.other-month:hover { + background: transparent; + transform: none; +} + +.day.has-event::after { + content: ''; + position: absolute; + bottom: 5px; + width: 6px; + height: 6px; + background: #e74c3c; + border-radius: 50%; +} + +.calendar-controls { + padding: 20px; + background: #f8f9fa; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 15px; +} + +.control-btn { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 12px 20px; + border-radius: 8px; + cursor: pointer; + font-weight: bold; + transition: all 0.3s ease; +} + +.control-btn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4); +} + +.quick-nav { + display: flex; + gap: 10px; + align-items: center; +} + +#month-select, #year-input { + padding: 8px 12px; + border: 2px solid #ddd; + border-radius: 6px; + font-size: 16px; +} + +/* Modal Styles */ +.modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + animation: modalFadeIn 0.3s ease; +} + +@keyframes modalFadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +.modal-content { + background-color: white; + margin: 15% auto; + padding: 20px; + border-radius: 10px; + width: 90%; + max-width: 500px; + position: relative; + animation: modalSlideIn 0.3s ease; +} + +@keyframes modalSlideIn { + from { transform: translateY(-50px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +.close { + color: #aaa; + float: right; + font-size: 28px; + font-weight: bold; + cursor: pointer; +} + +.close:hover { + color: #000; +} + +#modal-date { + margin-bottom: 15px; + color: #333; +} + +#event-list { + margin-bottom: 20px; +} + +.event-item { + background: #f8f9fa; + padding: 10px; + margin-bottom: 8px; + border-radius: 6px; + border-left: 4px solid #667eea; +} + +.event-form { + display: flex; + gap: 10px; + margin-top: 15px; +} + +#event-title { + flex-grow: 1; + padding: 8px 12px; + border: 2px solid #ddd; + border-radius: 6px; +} + +#add-event-btn { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 8px 16px; + border-radius: 6px; + cursor: pointer; + font-weight: bold; + transition: all 0.3s ease; +} + +#add-event-btn:hover { + transform: translateY(-1px); + box-shadow: 0 3px 10px rgba(102, 126, 234, 0.3); +} + +/* Tooltip */ +.tooltip { + position: absolute; + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 8px 12px; + border-radius: 6px; + font-size: 14px; + pointer-events: none; + z-index: 1001; + opacity: 0; + transition: opacity 0.3s ease; +} + +.tooltip.show { + opacity: 1; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .calendar-header { + flex-direction: column; + text-align: center; + } + + #month-year { + font-size: 1.5rem; + margin: 10px 0; + } + + .nav-btn { + padding: 8px 12px; + font-size: 16px; + } + + .calendar-grid { + padding: 15px; + } + + .calendar-controls { + flex-direction: column; + align-items: stretch; + } + + .quick-nav { + justify-content: center; + } +} + +@media (max-width: 480px) { + body { + padding: 10px; + } + + .weekdays div { + font-size: 12px; + padding: 8px; + } + + .day { + font-size: 14px; + } +}