Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Custom Calendar Component with Vanilla JavaScript

Notes May 11 6

Custom Calendar Component with Vanilla JavaScript

HTML Structure

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Custom Calendar</title>
    <link rel="stylesheet" href="../css/calendar.css">
</head>
<body>
    <div class="calendar-container">
        <div class="calendar-header">
            <input type="date" class="date-input">
        </div>
        <div class="calendar-body">
            <button class="nav-button prev">
                &lt;
            </button>
            <div class="calendar-main">
                <table class="calendar-table">
                </table>
            </div>
            <button class="nav-button next">
                &gt;
            </button>
        </div>
    </div>
</body>
</html>
<script src="../js/calendar.js"></script>

CSS Styling

* {
    margin: 0;
    padding: 0;
}

ul, li, ol {
    list-style-type: none;
}

button {
    cursor: pointer;
    border: none;
    background: rgba(255, 255, 255, 1);
}

.calendar-container {
    width: 500px;
    height: 430px;
    margin: 100px auto;
    border: 1px #ccc solid;
}

.calendar-header {
    width: 100%;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-bottom: 1px #ccc solid;
}

.calendar-body {
    width: 100%;
    height: 400px;
    display: flex;
}

.nav-button {
    width: 50px;
    height: 100%;
    line-height: 400px;
    text-align: center;
    font-size: 30px;
    border-bottom: 1px #ccc solid;
}

.prev {
    border-right: 1px #ccc solid;
}

.next {
    border-left: 1px #ccc solid;
}

.calendar-main {
    width: 400px;
    height: 400px;
    border-left: 1px #ccc solid;
    border-right: 1px #ccc solid;
}

.calendar-table {
    width: 100%;
    height: 100%;
}

th, td {
    text-align: center;
    width: 50px;
    height: 50px;
    border: 1px #ccc solid;
}

.selected-date {
    background: #04beff;
    color: #fff;
}

.empty-date {
    background: rgba(0, 0, 0, 0.1);
    color: #ccc;
}

JavaScript Functionality

// DOM elements
const calendarTable = document.querySelector('.calendar-table');
const dateInput = document.querySelector('.date-input');
const prevButton = document.querySelector('.prev');
const nextButton = document.querySelector('.next');
let dateCells;

// Current date state
let currentDate = new Date();
let currentYear = currentDate.getFullYear();
let currentMonth = Number(currentDate.getMonth()) + 1;
let currentDay = currentDate.getDate();

// Initialize date input field
function initializeDateInput() {
    const formattedMonth = currentMonth < 10 ? `0${currentMonth}` : currentMonth;
    const formattedDay = currentDay < 10 ? `0${currentDay}` : currentDay;
    dateInput.value = `${currentYear}-${formattedMonth}-${formattedDay}`;
}

// Get number of days in a month
function getDaysInMonth(year, month) {
    const date = new Date(year, month - 1, 1);
    date.setDate(date.getDate() - 1);
    return date.getDate();
}

// Get last days of previous month
function getPreviousMonthDays(count) {
    const prevMonthDate = new Date(currentYear, currentMonth - 1, 1);
    const days = [];
    for (let i = 1; i <= count; i++) {
        const date = new Date(prevMonthDate.getFullYear(), prevMonthDate.getMonth(), prevMonthDate.getDate() - i);
        days.push(date.getDate());
    }
    return days.reverse();
}

// Get first days of next month
function getNextMonthDays(count) {
    const nextMonthDate = new Date(currentYear, currentMonth, 1);
    const days = [];
    for (let i = 1; i <= count; i++) {
        const date = new Date(nextMonthDate.getFullYear(), nextMonthDate.getMonth(), i);
        days.push(date.getDate());
    }
    return days;
}

// Array to store calendar data
let calendarData = [];

// Build calendar data array
function buildCalendarData(prevDaysCount, currentMonthDays) {
    calendarData = [];
    const prevMonthDays = getPreviousMonthDays(prevDaysCount);
    const nextMonthDays = getNextMonthDays(42 - prevDaysCount - currentMonthDays);
    
    calendarData.push(...prevMonthDays);
    calendarData.push(...Array.from({length: currentMonthDays}, (_, i) => i + 1));
    calendarData.push(...nextMonthDays);
}

// Calendar header HTML
const tableHeader = `
    <tr>
        <th>Mon</th>
        <th>Tue</th>
        <th>Wed</th>
        <th>Thu</th>
        <th>Fri</th>
        <th>Sat</th>
        <th>Sun</th>
    </tr>
`;

// Render calendar
function renderCalendar() {
    // Get first day of month and number of days
    const firstDay = new Date(currentYear, currentMonth - 1, 1);
    const dayOfWeek = firstDay.getDay() || 7;
    const daysInMonth = getDaysInMonth(currentYear, currentMonth);
    
    // Build calendar data
    buildCalendarData(dayOfWeek - 1, daysInMonth);
    
    // Generate calendar rows
    const tableRows = [tableHeader];
    for (let i = 0; i < calendarData.length; i += 7) {
        const rowCells = Array.from({length: 7}, (_, j) => 
            `<td class="date-cell">${calendarData[i + j]}</td>`
        ).join('');
        tableRows.push(`<tr>${rowCells}</tr>`);
    }
    
    // Update table content
    calendarTable.innerHTML = tableRows.join('');
    
    // Apply styling to date cells
    dateCells = document.querySelectorAll('.date-cell');
    dateCells.forEach((cell, index) => {
        if (cell.textContent == currentDay && index >= dayOfWeek - 1 && index < daysInMonth + dayOfWeek - 1) {
            cell.className = 'date-cell selected-date';
        } else if (index < dayOfWeek - 1 || index > daysInMonth + dayOfWeek - 2) {
            cell.className = 'date-cell empty-date';
        }
    });
}

// Reset calendar data
function resetCalendarData() {
    calendarData = [];
}

// Initialize date input
initializeDateInput();

// Initial calendar render
renderCalendar();

// Handle date input change
dateInput.addEventListener('change', function() {
    const [year, month, day] = dateInput.value.split('-').map(Number);
    currentYear = year;
    currentMonth = month;
    currentDay = day;
    resetCalendarData();
    renderCalendar();
});

// Handle date cell click
calendarTable.addEventListener('click', function(e) {
    if (e.target.classList.contains('date-cell')) {
        const clickedDay = Number(e.target.textContent);
        
        // Handle previous month navigation
        if (e.target.classList.contains('empty-date') && clickedDay > 20) {
            currentMonth--;
            if (currentMonth <= 0) {
                currentMonth = 12;
                currentYear--;
            }
        }
        
        // Handle next month navigation
        if (e.target.classList.contains('empty-date') && clickedDay < 10) {
            currentMonth++;
            if (currentMonth > 12) {
                currentMonth = 1;
                currentYear++;
            }
        }
        
        currentDay = clickedDay;
        resetCalendarData();
        renderCalendar();
        initializeDateInput();
    }
});

// Handle previous month button click
prevButton.addEventListener('click', function() {
    currentMonth--;
    if (currentMonth <= 0) {
        currentMonth = 12;
        currentYear--;
    }
    currentDay = 1;
    resetCalendarData();
    renderCalendar();
    initializeDateInput();
});

// Handle next month button click
nextButton.addEventListener('click', function() {
    currentMonth++;
    if (currentMonth > 12) {
        currentMonth = 1;
        currentYear++;
    }
    currentDay = 1;
    resetCalendarData();
    renderCalendar();
    initializeDateInput();
});
Tags: Calendar

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.