Skip to content

Commit

Permalink
Add Alpine.js calendar component.
Browse files Browse the repository at this point in the history
  • Loading branch information
bennadel committed Mar 23, 2024
1 parent 8c620c6 commit b577a8f
Show file tree
Hide file tree
Showing 4 changed files with 555 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ with.

## My JavaScript Demos - I Love JavaScript!

* [Calendar Component In Alpine.js](https://bennadel.github.io/JavaScript-Demos/demos/calendar-alpinejs)
* [HTML Templates Can Be Mutated Just Like Any Other DOM](https://bennadel.github.io/JavaScript-Demos/demos/mutate-template)
* [CSS Enter Animations Follow The 80/20 Rule](https://bennadel.github.io/JavaScript-Demos/demos/enter-animations-80-20)
* [Reading Element Attributes In JavaScript](https://bennadel.github.io/JavaScript-Demos/demos/attribute-parts)
Expand Down
282 changes: 282 additions & 0 deletions demos/calendar-alpinejs/alpine.calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
document.addEventListener(
"alpine:init",
function setupAlpineBindings() {

Alpine.data( "calendar", CalendarController );

}
);

var CALENDAR_WEEKDAYS = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
];
var CALENDAR_WEEKDAYS_ABBREVIATED = [
"Sun",
"Mon",
"Tue",
"Wed",
"Thr",
"Fri",
"Sat"
];
var CALENDAR_MONTHS = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
var CURRENT_MONTH = true;
var OTHER_MONTH = false;

/**
* I control the calendar component.
*/
function CalendarController( initialYear, initialMonth ) {

var timestamp = new Date();
var year = ( initialYear ?? timestamp.getFullYear() );
var month = ( initialMonth ?? timestamp.getMonth() );
var monthName = CALENDAR_MONTHS[ month ];
// Entries contains the entries for this month only.
var entries = buildEntries( year, month );
// Grid contains the headers and the entries for the rendered month (which may extend
// into both the previous month and the next month in order to create a full grid).
var grid = buildGrid( entries );

return {
// Properties.
year: year,
month: month,
monthName: monthName,
entries: entries,
grid: grid,

// Methods.
gotoDate: gotoDate,
gotoNextMonth: gotoNextMonth,
gotoNow: gotoNow,
gotoPrevMonth: gotoPrevMonth,
gotoYear: gotoYear,
}

// ---
// PUBLIC METHODS.
// ---

/**
* I update the calendar to represent the month that contains the given date.
*/
function gotoDate( target ) {

this.year = target.getFullYear();
this.month = target.getMonth();
this.monthName = CALENDAR_MONTHS[ this.month ];
this.entries = buildEntries( this.year, this.month );
this.grid = buildGrid( this.entries );

}

/**
* I update the calendar to represent the next month.
*/
function gotoNextMonth() {

this.gotoDate( new Date( this.year, ( this.month + 1 ), 1 ) );

}

/**
* I update the calendar to represent the current month.
*/
function gotoNow() {

this.gotoDate( new Date() );

}

/**
* I update the calendar to represent the previous month.
*/
function gotoPrevMonth() {

this.gotoDate( new Date( this.year, ( this.month - 1 ), 1 ) );

}

/**
* I update the calendar to represent the given year (and optional month).
*/
function gotoYear( year, month ) {

this.gotoDate( new Date( year, ( month || 0 ), 1 ) );

}

// ---
// PRIVATE METHODS.
// ---

/**
* I build the entries for the given year/month.
*/
function buildEntries( year, month ) {

var daysInMonth = getDaysInMonth( year, month );
var entries = [];

for ( var i = 1 ; i <= daysInMonth ; i++ ) {

entries.push( buildEntry( year, month, i, CURRENT_MONTH ) );

}

return entries;

}

/**
* I build the entry for the given year/month/date.
*/
function buildEntry( year, month, date, isCurrentMonth ) {

var timestamp = new Date( year, month, date );

return {
id: timestamp.getTime(),
year: timestamp.getFullYear(),
month: timestamp.getMonth(),
monthName: CALENDAR_MONTHS[ timestamp.getMonth() ],
date: timestamp.getDate(),
day: timestamp.getDay(),
dayName: CALENDAR_WEEKDAYS[ timestamp.getDay() ],
isToday: getIsToday( timestamp ),
isCurrentMonth: isCurrentMonth,
isOtherMonth: ! isCurrentMonth,
isWeekday: getIsWeekday( timestamp.getDay() ),
isWeekend: getIsWeekend( timestamp.getDay() )
};

}

/**
* I guild the grid based on the given entries.
*/
function buildGrid( entries ) {

var grid = {
headers: CALENDAR_WEEKDAYS.slice(),
headersAbbreviated: CALENDAR_WEEKDAYS_ABBREVIATED.slice(),
entries: entries.slice(),
weeks: []
};
var temp;

// Extend the grid entries into the PREVIOUS month if necessary.
while ( ! getIsFirstEntryOfWeek( temp = grid.entries.at( 0 ) ) ) {

grid.entries.unshift(
buildEntry( temp.year, temp.month, ( temp.date - 1 ), OTHER_MONTH )
);

}

// Extend the grid entries into the NEXT month if necessary.
while ( ! getIsLastEntryOfWeek( temp = grid.entries.at( -1 ) ) ) {

grid.entries.push(
buildEntry( temp.year, temp.month, ( temp.date + 1 ), OTHER_MONTH )
);

}

// Slice the full list of entries into weeks (for easier rendering).
for ( var i = 0 ; i < grid.entries.length ; i += 7 ) {

grid.weeks.push(
grid.entries.slice( i, ( i + 7 ) )
);

}

return grid;

}

/**
* I get the number of days in the given year/month.
*/
function getDaysInMonth( year, month ) {

// I freaking love the Date object - makes working with dates so easy!
var lastDayOfMonth = new Date( year, ( month + 1 ) , 0 );

return lastDayOfMonth.getDate();

}

/**
* I determine if the given entry represents the first day of the week.
*/
function getIsFirstEntryOfWeek( entry ) {

return ( entry.day === 0 );

}

/**
* I determine if the given entry represents the last day of the week.
*/
function getIsLastEntryOfWeek( entry ) {

return ( entry.day === 6 );

}

/**
* I determine if the given date represents Today.
*/
function getIsToday( date ) {

var timestamp = new Date();

return (
( date.getFullYear() === timestamp.getFullYear() ) &&
( date.getMonth() === timestamp.getMonth() ) &&
( date.getDate() === timestamp.getDate() )
);

}

/**
* I determine if the given day is a weekday.
*/
function getIsWeekday( day ) {

return ! getIsWeekend( day );

}

/**
* I determine if the given day is a weekend.
*/
function getIsWeekend( day ) {

return ( ( day === 0 ) || ( day === 6 ) );

}

}
Loading

0 comments on commit b577a8f

Please sign in to comment.