diff --git a/README.md b/README.md index 312ba56a0..40cb6ab2f 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/demos/calendar-alpinejs/alpine.calendar.js b/demos/calendar-alpinejs/alpine.calendar.js new file mode 100644 index 000000000..b3113168f --- /dev/null +++ b/demos/calendar-alpinejs/alpine.calendar.js @@ -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 ) ); + + } + +} diff --git a/demos/calendar-alpinejs/index.htm b/demos/calendar-alpinejs/index.htm new file mode 100644 index 000000000..a97644e1c --- /dev/null +++ b/demos/calendar-alpinejs/index.htm @@ -0,0 +1,190 @@ + + +
++ Please selected your date. +
+ + ++ You selected + + + , + + + + ( that's today! ) + + +
+ + + +
+
+
+
+
+
+
+
+
+
+
+ |
+ ||||||
---|---|---|---|---|---|---|
+ | + +||||||
+ + | + +
+ Jump to: + + + + + + +
+ + + + + + + + \ No newline at end of file diff --git a/demos/calendar-alpinejs/main.css b/demos/calendar-alpinejs/main.css new file mode 100644 index 000000000..c8790f0d2 --- /dev/null +++ b/demos/calendar-alpinejs/main.css @@ -0,0 +1,82 @@ +html { + font-size: 140% ; +} + +body, +button { + font-family: monospace ; + font-size: inherit ; + line-height: inherit ; +} + +.calendar { + background-color: #ffffff ; + border: 1px solid #333333 ; + border-collapse: collapse ; + margin: 20px 0 ; +} +.calendar th { + border: 1px solid #333333 ; + padding: 10px 15px ; +} +.calendar td { + border: 1px solid #333333 ; + padding: 0 ; +} +.calendar tbody td button { + border: none ; + background-color: #bdebff ; + cursor: pointer ; + display: block ; + padding: 10px 15px ; + width: 100% ; +} +.calendar tbody td button.other { + background-color: #f8fdff ; +} +.calendar tbody td button.today { + background-color: #0095d1 ; + color: #ffffff ; + font-weight: bold ; +} +.calendar tbody td button.selected, +.calendar tbody td button:hover { + outline: 3px solid #009fff ; + outline-offset: -3px ; +} + +.calendar .tools { + align-items: center ; + display: flex ; + justify-content: center ; + gap: 20px ; +} +.calendar .tools button { + background-color: transparent ; + border: 1px solid #0095d1 ; + border-radius: 3px ; + color: #0095d1 ; + cursor: pointer ; + padding: 5px 9px ; +} +.calendar .tools button:first-of-type { + margin-right: auto ; +} +.calendar .tools button:last-of-type { + margin-left: auto ; +} + +.jumper { + align-items: center ; + display: flex ; + gap: 10px ; +} +.jumper button { + background-color: transparent ; + border: 1px solid #0095d1 ; + border-radius: 3px ; + color: #0095d1 ; + cursor: pointer ; + padding: 2px 5px ; + margin: 0 ; +} \ No newline at end of file