Skip to content

Instantly share code, notes, and snippets.

@version-control
Created February 14, 2022 15:48
Show Gist options
  • Save version-control/a9385c52d9c1d6b152e411d5209751e1 to your computer and use it in GitHub Desktop.
Save version-control/a9385c52d9c1d6b152e411d5209751e1 to your computer and use it in GitHub Desktop.
Journey - Date picker
<link href="https://fonts.googleapis.com/css?family=Coda|Open+Sans" rel="stylesheet">
<div id="date-picker-section" class="container">
<div id="date-picker-container">
<div id="date-picker-title">
<h4> Select your dates </h4>
</div>
<p> Click on a date to open the date picker. </p>
<div id="date-picker-dates">
<div id="date-picker-date-first" class="date-picker-date">
24/12/2017
</div>
<div class="date-picker-date">
28/12/2017
</div>
</div>
<p> Revel in how big the dates are now. </p>
<div id="date-picker-display-container">
<div class="date-picker-display-pair">
<div class="date-picker-display">
24 Dec
</div>
<div class="date-picker-display-label">
Date one
</div>
</div>
<div class="date-picker-display-pair">
<div class="date-picker-display">
28 Dec
</div>
<div class="date-picker-display-label">
Date two
</div>
</div>
</div>
<div id="date-picker-modal" class="hidden-2">
<div id="date-picker-top-bar">
<div id="date-picker-previous-month" class="date-picker-change-month">&lsaquo;</div>
<div id="date-picker-month">December 17</div>
<div id="date-picker-next-month" class="date-picker-change-month">&rsaquo;</div>
</div>
<div id="date-picker-exit">&times;</div>
<table id="date-picker">
<tr id="date-picker-weekdays">
<th>S</th>
<th>M</th>
<th>T</th>
<th>W</th>
<th>T</th>
<th>F</th>
<th>S</th>
</tr>
<!-- Actual calendar rows added dynamically -->
<!--<tr class="date-picker-calendar-row"></tr>-->
</table>
</div>
</div>
</div>
[[[https://codepen.io/samwooly/pen/OOKaEz/]]]
var monthFormatter = new Intl.DateTimeFormat("en-us", { month: "long" });
var weekdayFormatter = new Intl.DateTimeFormat("en-us", { weekday: "long" });
var dates = [];
dates[0] = new Date(); // defaults to today
dates[1] = addDays(dates[0], 31);
var currentDate = 0; // index into dates[]
var previousDate = 1;
var datesBoxes = $(".date-picker-date");
var displayBoxes = $(".date-picker-display");
// sensible default just in case jQuery doesn't kick in
// makes sure that the experience is still usable, and when $(window).width() returns then this variable is updated to the correct value
var windowWidth = 300;
var colourPickerWidth = 300;
// set up dates
$(document).ready(function() {
// will work the same the first time as every other
updateDatePicker();
// update dates shown to correct dates
$(datesBoxes[0]).text(getDateString(dates[0]));
$(datesBoxes[1]).text(getDateString(dates[1]));
$(displayBoxes[0]).text(dates[0].getDate() + " " + monthFormatter.format(dates[0]).slice(0,3));
$(displayBoxes[1]).text(dates[1].getDate() + " " + monthFormatter.format(dates[1]).slice(0,3));
});
// add event listeners
$(document).ready(function() {
// has to be applied each time, as it's removed when calendar is reset
applyDateEventListener();
$(".date-picker-date").click(function(e) {
// if active, toggle picker off and return
var currentlyActive = $(this).hasClass("active");
if (currentlyActive) {
$(this).removeClass("active");
hideDatePicker();
return;
}
$(".date-picker-date").removeClass("active");
$(this).addClass("active");
// update currentDate
previousDate = currentDate;
if ($(this)[0].id == "date-picker-date-first") {
currentDate = 0;
} else {
currentDate = 1;
}
// update calendar
showDatePicker(e);
updateDatePicker();
});
$("#date-picker-next-month").click(function() {
changeMonth("Next");
});
$("#date-picker-previous-month").click(function() {
changeMonth("Previous");
});
$("#date-picker-exit").click(function() {
hideDatePicker();
});
$(document).click(function(e) {
var target = $(e.target);
var clickedOnPicker = (target.closest("#date-picker-modal").length);
var clickedOnDate = (target.closest(".date-picker-date").length);
var isPreviousOrNext = target.hasClass("previous-month") || target.hasClass("next-month");
if (!(clickedOnPicker || clickedOnDate || isPreviousOrNext)) {
hideDatePicker();
}
});
});
// called on initialising (set to today) and then every time the month changes or on moving between dates
function updateDatePicker(changeMonth = false) {
var datePicker = $("#date-picker");
var curDate = dates[currentDate]; // shorthand
// check if it needs to update
// updates if changed month directly (changeMonth) or if switched to other .date-picker-date and month is different (differentMonth)
var differentMonth = checkChangedMonth();
if (changeMonth === false && differentMonth === false) { return; }
updatePickerMonth();
// clear out all tr instances other than the header row
// really just removing all rows and appending header row straight back in
var headerRow = `
<tr id="date-picker-weekdays">
<th>S</th>
<th>M</th>
<th>T</th>
<th>W</th>
<th>T</th>
<th>F</th>
<th>S</th>
</tr>`;
// clear all rows
datePicker.contents().remove();
datePicker.append(headerRow);
var todayDate = curDate.getDate();
var firstOfMonth = new Date(curDate.getFullYear(), curDate.getMonth(), 1);
var firstWeekday = firstOfMonth.getDay(); // 0-indexed; 0 is Sunday, 6 is Saturday
var lastMonthToInclude = firstWeekday; // happily, this just works as-is.
var firstOfNextMonth = addMonths(firstOfMonth, 1);
var lastOfMonth = addDays(firstOfNextMonth, -1).getDate();
var openRow = "<tr class='date-picker-calendar-row'>";
var closeRow = "</tr>";
var currentRow = openRow;
// Add in as many of last month as required
if (lastMonthToInclude > 0) {
var lastMonthLastDay = addDays(firstOfMonth, -1);
var lastMonthDays = lastMonthLastDay.getDate();
var lastMonthStartAdding = lastMonthDays - lastMonthToInclude + 1;
// add days from previous month
// takes arguments (start loop, end loop <=, counter, 'true' if current month OR class if another month (optional, default "") )
//addToCalendar(lastMonthStartAdding, lastMonthDays, 0, "previous-month");
//addToCalendar(lastMonthStartAdding, lastMonthDays, 0, "month-previous");
addToCalendar(lastMonthStartAdding, lastMonthDays, 0, "previous-month");
}
// fill out rest of row with current month
// doesn't matter how many of last month were included, all accounted for
addToCalendar(1, 7 - lastMonthToInclude, lastMonthToInclude, true);
// reset for current month generation
currentRow = openRow;
var counter = 7;
var addedFromCurrentMonth = 7 - firstWeekday + 1;
addToCalendar(addedFromCurrentMonth, lastOfMonth, counter, true);
// at this point, counter = all of this month + whatever was included from last month
counter = lastMonthToInclude + lastOfMonth;
var nextMonthToInclude = counter % 7 === 0 ? 0 : 7 - (counter % 7);
addToCalendar(1, nextMonthToInclude, counter, "next-month");
// add event listener again
applyDateEventListener();
// update current date box
updateDateShown();
// functions scoped to this outer function
//############################################################
function checkChangedMonth() {
// updates if changed month directly (changeMonth) or if switched to other .date-picker-date and month is different (differentMonth)
var differentMonth = false;
// checks if it's the same date again
if (currentDate !== previousDate) {
// if either month or year are different then month has changed
if (dates[0].getMonth() !== dates[1].getMonth() || dates[0].getYear() !== dates[1].getYear() ) {
differentMonth = true;
}
}
return differentMonth;
}
function addToCalendar(start, end, counter, cellClass) {
var currentMonth = cellClass === true ? true : false;
for (var i = start; i <= end; i++) {
counter += 1;
if (i === todayDate && currentMonth) {
currentRow += `<td class="active">${i}</td>`;
} else if (cellClass && !currentMonth) {
currentRow += `<td class="${cellClass}">${i}</td>`;
} else {
currentRow += `<td>${i}</td>`;
}
if (counter % 7 === 0) {
datePicker.append(currentRow + closeRow);
currentRow = openRow;
}
}
}
}
function updatePickerMonth() {
var monthName = monthFormatter.format(dates[currentDate]);
var year = dates[currentDate].getFullYear();
var dateText = monthName + " " + year;
$("#date-picker-month").text(dateText);
}
function dateSelected(currentDay) {
// update the active .date-picker-date with the current date
var activeDate = $( $(".date-picker-date.active")[0] );
// get current date and update
dates[currentDate].setDate(currentDay);
updateDateShown();
}
// 'direction' can be either "Next" or "Previous"
function changeMonth(direction) {
var increment = direction === "Next" ? 1 : -1;
// change month
dates[currentDate] = addMonths(dates[currentDate], increment);
// change month name in picker
updatePickerMonth();
// update calendar
// passes 'true' that month has changed
updateDatePicker(true);
}
function showDatePicker(e) {
var pxFromTop = $(".date-picker-date").offset().top;
var datePicker = $("#date-picker-modal");
datePicker.css("top", pxFromTop + 40);
// check if right edge of colourPicker will go off the edge of the screen, and if so then reduce left by that amount
var rightEdge = e.pageX + colourPickerWidth;
var overflowWidth = rightEdge - windowWidth;
if (overflowWidth > 0) {
datePicker.css("left", e.pageX - overflowWidth);
} else {
datePicker.css("left", e.pageX);
}
$("#date-picker-modal").removeClass("hidden-2");
}
function hideDatePicker() {
$(".date-picker-date").removeClass("active");
$("#date-picker-modal").addClass("hidden-2");
}
function applyDateEventListener() {
$("#date-picker td").click(function() {
// Calendar UI
$("#date-picker td").removeClass("active");
$(this).addClass("active");
// update variables
currentDay = $(this).text();
// update the current date
dateSelected(currentDay);
// change month based on calendar day class
if ($(this).hasClass("previous-month")) {
changeMonth("Previous");
} else if ($(this).hasClass("next-month")) {
changeMonth("Next");
} else {
// clicked in current month; made selection so hide picker again
hideDatePicker();
}
});
}
// Utilities
//################################################################
// set location for date picker
$(document).ready(function() {
updateWidths();
});
$(window).resize(function() {
updateWidths();
});
function updateWidths() {
windowWidth = $(window).width();
}
// courtesy of https://stackoverflow.com/questions/563406/add-days-to-javascript-date
function addDays(date, days) {
var result = new Date(date);
result.setDate(result.getDate() + days);
return result;
}
function addMonths(date, months) {
var result = new Date(date);
result.setMonth(result.getMonth() + months);
return result;
}
// courtesy of https://stackoverflow.com/a/15764763/7170445
function getDateString(date) {
var year = date.getFullYear();
var month = (1 + date.getMonth()).toString();
month = month.length > 1 ? month : '0' + month;
var day = date.getDate().toString();
day = day.length > 1 ? day : '0' + day;
return day + '/' + month + '/' + year;
}
function updateDateShown() {
var formattedDate = getDateString(dates[currentDate]);
var updateDateBox = $(datesBoxes[currentDate]);
var updateDisplayBox = $(displayBoxes[currentDate]);
var dayAndMonth = dates[currentDate].getDate() + " " + monthFormatter.format(dates[currentDate]).slice(0,3);
updateDateBox.text(formattedDate);
updateDisplayBox.text(dayAndMonth);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
$white: #fff;
$lightgray: #F5F5F5;
$midlightgray: #aaa;
$midgray: #999;
$middarkgray: #666;
$darkgray: #444;
$colour-primary: hsl(157,79%,49%);
$colour-primary-light: hsl(157,79%,90%);
$font-display: 'Coda', serif;
$font-body: 'Open Sans', sans-serif;
$drop-shadow-large: 0 0 100px 0 rgba(0,0,0,0.20), 30px 30px 50px 0 rgba(0,0,0,0.06);
$drop-shadow-modal: 0 0 40px 0 rgba(0,0,0,0.30), 30px 30px 40px 0 rgba(0,0,0,0.20);
$drop-shadow-light: 0 0 20px 0 rgba(0,0,0,0.15), 3px 3px 5px 0 rgba(0,0,0,0.06);
$drop-shadow-light-hover: 0 0 25px 0 rgba(0,0,0,0.2), 3px 3px 5px 0 rgba(0,0,0,0.15);
// no xs as this is the default styling
// these are all used as 'min-width' settings for media queries
$bootstrap-sm: 576px;
$bootstrap-md: 768px;
$bootstrap-lg: 992px;
$bootstrap-xl: 1200px;
*, *:before, *:after {
box-sizing: border-box;
transition: all 0.24s;
font-size: 16px;
font-family: $font-body;
border-radius: 5px;
}
html, body {
padding: 0; margin: 0;
background: white;
}
body {
background-color: $lightgray;
}
.hidden {
display: none !important;
}
.hidden-2 {
opacity: 0;
pointer-events: none;
transform: scale(0.92);
transform-origin: left center;
}
#date-picker-section {
padding-top: 80px; padding-bottom: 80px;
min-height: 80vh;
}
#date-picker-container {
margin: 0 auto;
padding: 40px;
background: white;
max-width: 500px;
box-shadow: $drop-shadow-large;
}
#date-picker-title {
font-family: $font-display;
margin-bottom: 25px;
}
#date-picker-dates {
background: $lightgray;
padding: 10px;
margin-bottom: 30px;
user-select: none;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
}
.date-picker-date {
position: relative; display: inline-block;
background: white;
padding: 10px 15px; padding-right: 50px;
margin: 10px;
cursor: pointer;
box-shadow: $drop-shadow-light;
transition: none;
&:hover, &.active {
box-shadow: $drop-shadow-light-hover;
}
&.active {
font-weight: bold;
}
&:after {
position: absolute; display: block;
height: 24px; width: 24px;
top: 10px; right: 10px;
content: "";
background: url("https://image.freepik.com/free-icon/calendar-icon-in-black_318-9776.jpg");
background-repeat: no-repeat;
background-size: 24px;
}
}
#date-picker-display-container {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.date-picker-display-pair {
margin: 10px;
}
.date-picker-display {
font-family: $font-display;
font-size: 40px;
}
.date-picker-display-label {
text-align: center;
font-size: 0.8rem;
position: relative;
top: -8px;
text-transform: uppercase;
}
#date-picker-modal {
width: 310px;
box-shadow: $drop-shadow-modal;
padding: 15px;
position: absolute;
overflow: hidden;
user-select: none;
transition: transform 0.1s;
background: white;
&:before {
position: absolute; display: block;
top: 0; left: -5px; right: -5px;
height: 96px;
background: $colour-primary;
content: '';
}
}
#date-picker-top-bar {
position: relative;
color: $middarkgray;
background: white;
width: 195px;
margin: 0 auto; margin-bottom: 3px;
padding: 5px 0;
display: flex;
flex-direction: row;
justify-content: center;
}
.date-picker-change-month {
font-size: 23px;
line-height: 23px;
padding: 0 10px; padding-bottom: 4px;
cursor: pointer;
position: absolute;
top: 4px;
&:hover {
background: $lightgray;
}
}
#date-picker-previous-month {
left: 5px;
}
#date-picker-next-month {
right: 5px;
}
#date-picker-month {
margin: 0 20px;
}
#date-picker-exit {
position: absolute;
top: 15px; right: 17px;
padding: 0 10px;
font-size: 23px;
cursor: pointer;
color: white;
&:hover {
background: hsla(0,0%,100%,0.3);
}
}
#date-picker {
width: 280px;
table-layout: fixed;
position: relative;
th, td {
text-align: center;
}
th {
cursor: default;
border-radius: 0;
height: 50px;
border-color: $colour-primary;
}
td {
cursor: pointer;
color: #666;
border-radius: 50%;
height: 38px;
&:hover {
background: #eee;
}
&.active {
background: $colour-primary;
color: white;
}
&.previous-month, &.next-month {
color: #bbb;
}
}
}
#date-picker-weekdays {
color: white;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css" rel="stylesheet" />
<link href="https://codepen.io/samwooly/pen/OOKaEz/" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment