Tuesday 20 July 2021

Web Tutorial: The JS Datepicker (Part 4/4)

Almost everything is in place. We have the displays, the button events, a place to put the selected values in. But this being about dates, things can get a little complicated.

For instance, if you go forward by one month from 30th June, you get 30th July, right?

Well, what if you were at 30th January, and went forward by one month? February has (usually) only twenty-eight days! You'll see the date goes to 2nd March. That's not wrong, but it's not very user-friendly. It's better if the current date lands within the targeted month instead.


So in setCalendarDate(), insert some code. Create a new variable, tryDay, and set it to the same value as dayVal. Then create a new variable, tryDate, and set it to a date value using the yearVal, monthVal, and tryDay.
    if (month == 0)
    {
        monthVal = 12;
        yearVal--;
    }
}

var tryDay = dayVal;
var tryDate = new Date(yearVal, monthVal - 1, tryDay);

this.selectedDate = new Date(yearVal, monthVal - 1, dayVal);


Now use a While loop. It should run until the month of tryDate is no longer equal to monthVal (minus 1). In it, decrement tryDay and regenerate tryDate.
    if (month == 0)
    {
        monthVal = 12;
        yearVal--;
    }
}

var tryDay = dayVal;
var tryDate = new Date(yearVal, monthVal - 1, tryDay);

while(tryDate.getMonth() != monthVal - 1)
{
    tryDay = tryDay - 1;
    tryDate = new Date(yearVal, monthVal - 1, tryDay);
}


this.selectedDate = new Date(yearVal, monthVal - 1, dayVal);


And then we change selectedDate to use tryDay.
    if (month == 0)
    {
        monthVal = 12;
        yearVal--;
    }
}

var tryDay = dayVal;
var tryDate = new Date(yearVal, monthVal - 1, tryDay);

while(tryDate.getMonth() != monthVal - 1)
{
    tryDay = tryDay - 1;
    tryDate = new Date(yearVal, monthVal - 1, tryDay);
}

this.selectedDate = new Date(yearVal, monthVal - 1, tryDay);


Now, you see, in these cases, it goes to the last day of the current month instead!


Now, we have enabled date selection via clicking on the calendar, but let us make this a lot more obvious. In the CSS add a hover psedoselector to the date CSS class. The styling here is a light grey outline.
.daysContainer .weekend
{
    color: rgba(255, 0, 0, 1);
}

.daysContainer .date:hover
{
    outline: 1px solid rgba(100, 100, 100, 1);
}


.spaceleft
{
    margin-left: 2px;
}

See the outline when you mouse over a date?



Now add the selected CSS class. This will be a faint black outline.
.daysContainer .date:hover
{
    outline: 1px solid rgba(100, 100, 100, 1);
}

.daysContainer .selected
{
    outline: 1px solid  rgba(0, 0, 0, 1);
}


.spaceleft
{
    margin-left: 2px;
}


In the generateDayBox(), after the line where we add the CSS class weekend, add an If block to check if the date and month values of selectedDate are equal to the date and month values of date, add the CSS class selected.
generateDayBox: function(id, date, isCurrentMonth)
{
    var dayBox = document.createElement("div");
    dayBox.classList.add("date");
    if (!isCurrentMonth) dayBox.classList.add("notCurrentMonth");
    dayBox.classList.add("spaceleft");
    dayBox.dataset.day = date.getDate();
    dayBox.dataset.month = date.getMonth() + 1;
    dayBox.dataset.year = date.getFullYear();

    if (date.getDay() == 0 || date.getDay() == 6) dayBox.classList.add("weekend");
    if (date.getDate() == this.selectedDate.getDate() && date.getMonth() == this.selectedDate.getMonth()) dayBox.classList.add("selected");

    dayBox.innerHTML = date.getDate();
    dayBox.onclick = function(e) { calendar.setCalendarDate(id, e.target.dataset["day"], e.target.dataset["month"], e.target.dataset["year"]); calendar.showDate();};

    return dayBox;
}


Now let's try selecting 1st June, then mouse over 16th June. See the different stylings in play?


Multiple Datepickers

Now, this part is major. We want to make sure that this code works with multiple datepickers. Add a second span element with an id of "cal2".
<body>
    <span id="cal1"></span>
    <br />
    <span id="cal2"></span>


    <script>
        calendar.initialize("cal1");
    </script>
</body>


And initialize it.
<body>
    <span id="cal1"></span>
    <br />
    <span id="cal2"></span>

    <script>
        calendar.initialize("cal1");
        calendar.initialize("cal2");
    </script>
</body>


This is what we get now.

Now try opening the first one. See a problem? The top datepicker's controls are partially obscured by the bottom one.


We can fix this by adding this styling. This is basically using the display and z-index property. The one on top, naturally, gets a higher value for z-index. We can do this better with more organized CSS, but this is just to give you an idea of what is required.
<body>
    <span id="cal1" style="position:relative;z-index:1000"></span>
    <br />
    <span id="cal2" style="position:relative;z-index:500"></span>

    <script>
        calendar.initialize("cal1");
        calendar.initialize("cal2");
    </script>
</body>


Now at this code to display(). This gets all elements that are styled using calendarContainer and styles them all using the CSS class hidden. Right before we display the appropriate datepicker. So after this, you should no longer have an issue of multiple datepicker controls being open.
if (show)
{
    var containers = document.getElementsByClassName("calendarContainer");
    for (var i = 0; i < containers.length; i++)
    {
        if (containers[i].classList.contains("show"))
        {
            containers[i].classList.remove("show");
            containers[i].classList.add("hide");
        }
    }


    this.container.classList.remove("hide");
    this.container.classList.add("show");
}


Last few bits of cleanup. Remove the blue outline.
.calendarContainer
{
    width: 350px;
    height: 30px;
    font-size: 12px;
    font-family: arial;
    display: inline-block;
    outline: 0px solid blue;
}


In initialize(), make this change to ensure that the value holder is invisible.
var hiddenTextbox = document.createElement("input");
hiddenTextbox.type = "hidden";
hiddenTextbox.id = "hidtxt_" + id;
hiddenTextbox.name = id;
bottom.appendChild(hiddenTextbox);


It's done!


Now that was fun...

...if you're a geek.

It's hardly necessary to go to this much trouble in order to implement a calendar widget, of course. Libraries are available, with far more functionality in their datepickers than there were in what I just showed you. And they probably look nicer, too.

Let's do this again sometime. Pick a date!
T___T

No comments:

Post a Comment