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 @@ + + +
+ + +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; + } +}