Thursday 15 July 2021

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

Welcome back. In this part, we will be displaying the dates of the current month.

Let's create daysContainer. It's the element where we will place all the dates. It will be styled using the CSS class daysContainer, and have an id based on "cal1". Append this to bottom.
var btnYearIncTen = document.createElement("div");
btnYearIncTen.classList.add("button");
btnYearIncTen.classList.add("spaceleft");
btnYearIncTen.innerHTML = "▷";
bottom.appendChild(btnYearIncTen);

var daysContainer = document.createElement("div");
daysContainer.classList.add("daysContainer");
daysContainer.id = "dayscontainer_" + id;
bottom.appendChild(daysContainer);


This is the styling for daysContainer. There's nothing visible about it, but it will keep all the dates in line when we start adding them.
.calendarContainer .lblYear
{
    width: 80px;
    height: 24px;
    text-align: center;
    float: left;
    font-size: 1.6em;
}

.daysContainer
{
    width: 100%;
    height: 150px;
    margin-top: 10px;
    text-align: center;
    float: left;
}


.spaceleft
{
    margin-left: 2px;
}


Now, in the initialize() method, call the setProperties() method, passing in id as an argument.
var daysContainer = document.createElement("div");
daysContainer.classList.add("daysContainer");
daysContainer.id = "dayscontainer_" + id;
bottom.appendChild(daysContainer);

this.setProperties(id);


Then add the setProperties() method.
    this.setProperties(id);
},
setProperties: function(id)
{

}
,


In here, we will set all the properties of the calendar object. Remember we created ids for all these elements, based on "cal1"? Well, the getElementById() method here comes in handy as we assign all these elements to properties within the calendar object, for easy reference.
    this.setProperties(id);
},
setProperties: function(id)
{
    this.container = document.getElementById(id);
    this.textbox = document.getElementById("txt_" + id);
    this.monthPicker = document.getElementById("monthpicker_" + id);
    this.lblYear = document.getElementById("lblyear_" + id);
    this.daysContainer = document.getElementById("dayscontainer_" + id);

},


Now, in the initialize() method where we first created monthPicker, add an event handler. Whenever the value in there is changed, the setCalendarDate() and showDate() methods are called.
var monthPicker = document.createElement("select");
monthPicker.classList.add("spaceleft");
monthPicker.id = "monthpicker_" + id;

for (var i = 0; i < this.months.length; i++)
{
    var option = document.createElement("option");
    option.value = i + 1;
    option.innerHTML = this.months[i];
    monthPicker.appendChild(option);
}

monthPicker.onchange = function() { calendar.setCalendarDate();calendar.showDate(); };
bottom.appendChild(monthPicker);


setCalendarDates() require four arguments to be passed in. Pass in id, then null for the others.
monthPicker.onchange = function() { calendar.setCalendarDate(id, null, null, null);calendar.showDate(); };


Add the setCalendarDate() and showDate() methods. We will be working on setCalendarDate() only, and leaving showDate() alone for now.
setProperties: function(id)
{
    this.container = document.getElementById(id);
    this.textbox = document.getElementById("txt_" + id);
    this.monthPicker = document.getElementById("monthpicker_" + id);
    this.lblYear = document.getElementById("lblyear_" + id);
    this.daysContainer = document.getElementById("dayscontainer_" + id);
},
setCalendarDate: function(id, day, month, year)
{

},
showDate: function()
{

},


In setCalendarDate(), we begin by declaring the variables monthVal, yearVal and dayVal. Then we set them to the values of month, year and day respectively.
setCalendarDate: function(id, day, month, year)
{
    var monthVal = month;
    var yearVal = year;
    var dayVal = day;

},


Since we passed in null values, we are going to handle that. If day is null, dayVal will be the date of the selectedDate property, which is set to the current date, by default. Same for year.
setCalendarDate: function(id, day, month, year)
{
    var monthVal = month;
    var yearVal = year;
    var dayVal = day;

    if (day == null)
    {
        dayVal = this.selectedDate.getDate();
    }

    if (year == null)
    {
        yearVal = this.selectedDate.getFullYear();
    }

},


But for month, monthVal will be set to the current value of monthPicker.
setCalendarDate: function(id, day, month, year)
{
    var monthVal = month;
    var yearVal = year;
    var dayVal = day;

    if (day == null)
    {
        dayVal = this.selectedDate.getDate();
    }

    if (year == null)
    {
        yearVal = this.selectedDate.getFullYear();
    }

    if (month == null)
    {
        monthVal = this.monthPicker.value;
    }

},


At this point, selectedDate should be reset using yearVal, monthVal (deduct 1 because in JavaScript, months start from 0) and dayVal. And lblYear should display the value of yearVal. This may seem really superfluous now, but trust me, it will be useful later.
if (month == null)
{
    monthVal = this.monthPicker.value;
}

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


After all this, call setMonthDisplay(), passing in id, monthVal and yearVal. This is supposed to display all the dates for the month denoted by monthVal.
    if (month == null)
    {
        monthVal = this.monthPicker.value;
    }

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

    this.setMonthDisplay(id, monthVal, yearVal);
},


Now let's add setMonthDisplay() as a method. It accepts three parameters - id, month and year.
setProperties: function(id)
{
    this.container = document.getElementById(id);
    this.textbox = document.getElementById("txt_" + id);
    this.monthPicker = document.getElementById("monthpicker_" + id);
    this.lblYear = document.getElementById("lblyear_" + id);
    this.daysContainer = document.getElementById("dayscontainer_" + id);
},
setMonthDisplay: function(id, month, year)
{

},

setCalendarDate: function(id, day, month, year)
{


We start by setting monthPicker's value to month. This will seem counter-intuitive considering we arrived here by setting monthPicker's value in the first place. But here, we are actually closing a loop by ensuring that this method and monthPicker will have the same month value even if setMonthDisplay() was called through some way other than by activating monthPicker.
setMonthDisplay: function(id, month, year)
{
    this.monthPicker.value = month;
},


And then we clear daysContainer of content.
setMonthDisplay: function(id, month, year)
{
    this.monthPicker.value = month;
    this.daysContainer.innerHTML = "";
},


Now, iterate through the days array. We're going to create headers for the table.
setMonthDisplay: function(id, month, year)
{
    this.monthPicker.value = month;
    this.daysContainer.innerHTML = "";

    for (var i = 0; i < this.days.length; i++)
    {
                        
    }

},


In the For loop, create div elements and ensure that each one is styled using day, header and spaceleft. And append to daysContainer.
setMonthDisplay: function(id, month, year)
{
    this.monthPicker.value = month;
    this.daysContainer.innerHTML = "";

    for (var i = 0; i < this.days.length; i++)
    {
        var dayHeader = document.createElement("div");
        dayHeader.classList.add("day");
        dayHeader.classList.add("header");
        dayHeader.classList.add("spaceleft");    

        this.daysContainer.append(dayHeader); 
                   
    }
},


But before that step, check if the current value is a Saturday (6) or Sunday (0). If so, make sure the div is styled using weekend as well.
setMonthDisplay: function(id, month, year)
{
    this.monthPicker.value = month;
    this.daysContainer.innerHTML = "";

    for (var i = 0; i < this.days.length; i++)
    {
        var dayHeader = document.createElement("div");
        dayHeader.classList.add("day");
        dayHeader.classList.add("header");
        dayHeader.classList.add("spaceleft");

        if (i == 0 || i == 6) dayHeader.classList.add("weekend");

        this.daysContainer.append(dayHeader);                    
    }
},


Fill in the div using the value of the current element of days.
setMonthDisplay: function(id, month, year)
{
    this.monthPicker.value = month;
    this.daysContainer.innerHTML = "";

    for (var i = 0; i < this.days.length; i++)
    {
        var dayHeader = document.createElement("div");
        dayHeader.classList.add("day");
        dayHeader.classList.add("header");
        dayHeader.classList.add("spaceleft");

        if (i == 0 || i == 6) dayHeader.classList.add("weekend");

        dayHeader.innerHTML = this.days[i];

        this.daysContainer.append(dayHeader);                        
    }
},


It's a good start, but we will need to write some CSS.


Here's the styling for day, header and weekend. We already have styling for spaceleft. The styling for day pertains to width and height, text alignment and most importantly, set the float property to left. header merely sets the text to bold. And weekend sets the font color to red.
.daysContainer
{
    width: 100%;
    height: 150px;
    margin-top: 10px;
    text-align: center;
    float: left;
}

.daysContainer .day
{
    width: 47px;
    height: 20px;
    text-align: center;
    float: left;
}

.daysContainer .header
{
    font-weight:bold;
}

.daysContainer .weekend
{
    color: rgba(255, 0, 0, 1);
}

.spaceleft
{
    margin-left: 2px;
}


Now as you can see, it all appears in a straight line (due to us setting the float property to left), the text is boldened, and the weekends appear in red.


Now we start filling in dates. We define firstDate as the first date of the current month defined by month (minus 1, for the same reasons mentioned earlier). firstDay is the first weekday that firstDate is on. (Monday, Tuesday, etc)
for (var i = 0; i < this.days.length; i++)
{
    var dayHeader = document.createElement("div");
    dayHeader.classList.add("day");
    dayHeader.classList.add("header");
    dayHeader.classList.add("spaceleft");

    if (i == 0 || i == 6) dayHeader.classList.add("weekend");

    dayHeader.innerHTML = this.days[i];

    this.daysContainer.append(dayHeader);                        
}

var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();


Then we define tempDate. We set this to the same value as firstDate.
var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();

var tempDate = new Date(year, month - 1, 1);


Now we define a While loop, which will run as long as tempDate has the same month as firstDate. Within the loop, increase tempDate by one day. So this will run until tempDate reaches the end of the current month defined by month!
var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();

var tempDate = new Date(year, month - 1, 1);

while (tempDate.getMonth() == firstDate.getMonth())
{
    tempDate.setDate(tempDate.getDate() + 1);
}


And we use this While loop to append something to daysContainer...
var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();

var tempDate = new Date(year, month - 1, 1);

while (tempDate.getMonth() == firstDate.getMonth())
{
    this.daysContainer.appendChild();

    tempDate.setDate(tempDate.getDate() + 1);
}


And what this thing is, is the element returned by the generateDayBox() method. In this method, we pass in id, tempDate, and true as arguments.
var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();

var tempDate = new Date(year, month - 1, 1);

while (tempDate.getMonth() == firstDate.getMonth())
{
    this.daysContainer.appendChild(calendar.generateDayBox(id, tempDate, true));

    tempDate.setDate(tempDate.getDate() + 1);
}


Now, let's define the generateDayBox() method. In here, we create a new div element, dayBox, and eventually return it.
showDate: function()
{

},
generateDayBox: function(id, date, isCurrentMonth)
{
    var dayBox = document.createElement("div");

    return dayBox;

}


And we fill in dayBox with the value of date.
generateDayBox: function(id, date, isCurrentMonth)
{
    var dayBox = document.createElement("div");

    dayBox.innerHTML = date.getDate();

    return dayBox;
}


If day is a weekend, add the CSS class weekend.
generateDayBox: function(id, date, isCurrentMonth)
{
    var dayBox = document.createElement("div");

    if (date.getDay() == 0 || date.getDay() == 6) dayBox.classList.add("weekend");

    dayBox.innerHTML = date.getDate();

    return dayBox;
}


And add in more CSS classes - date and spaceleft.
generateDayBox: function(id, date, isCurrentMonth)
{
    var dayBox = document.createElement("div");
    dayBox.classList.add("date");
    dayBox.classList.add("spaceleft");


    if (date.getDay() == 0 || date.getDay() == 6) dayBox.classList.add("weekend");

    dayBox.innerHTML = date.getDate();

    return dayBox;
}


And here, we set the data of the element - day, month and year. This will be important in the next part of this tutorial. isCurrentMonth is going to be important later on in this part.
generateDayBox: function(id, date, isCurrentMonth)
{
    var dayBox = document.createElement("div");
    dayBox.classList.add("date");
    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");

    dayBox.innerHTML = date.getDate();

    return dayBox;
}


You see when we select, for example, February, the numbers go from 1 to 28, but it's all in a mess. Time to do some styling!


If we do this, date will take on whatever day is styled by!
.daysContainer .day, .daysContainer .date
{
    width: 47px;
    height: 20px;
    text-align: center;
    float: left;
}


Since weekend has already been styled as well, the colors and layout look better now. But 1st February 2021 is not on a Sunday - it's on a Monday. We will need to fix that.


In the setMonthDisplay() method, before displaying the current month's days, we first want to display the tail end of the previous month. Set an If block and check if the first day does not fall on a Sunday.
var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();

if (firstDay > 0)
{

}


var tempDate = new Date(year, month - 1, 1);


Within that block, declare tempDate and set it to the same value as firstDate. Then set it one day earlier.
var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();

if (firstDay > 0)
{
    var tempDate = new Date(year, month - 1, 1);
    tempDate.setDate(firstDate.getDate() - firstDay - 1);

}

var tempDate = new Date(year, month - 1, 1);


Now, we just want to fill in all those days from Sunday to the day preceding the first day. So if the first day of the month falls on, say, Wednesday, we want to fill in the days for Sunday, Monday and Tuesday. For that, create a For loop that will go from Sunday (0) to the day preceding the first day.
var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();

if (firstDay > 0)
{
    var tempDate = new Date(year, month - 1, 1);
    tempDate.setDate(firstDate.getDate() - firstDay - 1);

    for (var i = 0; i < firstDay; i++)
    {

    }

}

var tempDate = new Date(year, month - 1, 1);


And in this loop, we keep increasing tempDate by one day. And append to daysContainer the same way we did for the days of the current month! But note that the last argument is false, because it's not the current month.
var firstDate = new Date(year, month - 1, 1);
var firstDay = firstDate.getDay();

if (firstDay > 0)
{
    var tempDate = new Date(year, month - 1, 1);
    tempDate.setDate(firstDate.getDate() - firstDay - 1);

    for (var i = 0; i < firstDay; i++)
    {
        tempDate.setDate(tempDate.getDate() + 1);

        this.daysContainer.appendChild(calendar.generateDayBox(id, tempDate, false));

    }
}

var tempDate = new Date(year, month - 1, 1);


Now when you select, for example, April, you will see the days of the week from Sunday to the first day of April (which is Thursday) have been filled in!


And the other side of the coin is, we need to fill in the rest of the dates from the end of the current month, until Saturday! So let's do this in the setMonthDisplay() method. After displaying the days of the current month, check if the day after the last day of the current month (which is actually the value of tempDate at this point) falls on any day other than Sunday.
    while (tempDate.getMonth() == firstDate.getMonth())
    {
        this.daysContainer.appendChild(calendar.generateDayBox(id, tempDate, true));

        tempDate.setDate(tempDate.getDate() + 1);
    }

    if (tempDate.getDay() != 0)
    {

    }

},


When that happens, we want to append at least one more day to daysContainer. Again, we have to run generateDayBox(), and the last argument has to be false.
if (tempDate.getDay() != 0)
{
    this.daysContainer.appendChild(calendar.generateDayBox(id, tempDate, false));
}


And the next piece of code runs for as long as tempDate is earlier than Saturday (i.e, we want to fill in the rest of the days until Saturday.) and keep adding one day to tempDate and appending a day to daysContainer.
if (tempDate.getDay() != 0)
{
    this.daysContainer.appendChild(calendar.generateDayBox(id, tempDate, false));

    while (tempDate.getDay() < 6) {
        tempDate.setDate(tempDate.getDate() + 1);

        this.daysContainer.appendChild(calendar.generateDayBox(id, tempDate, false));
    }

}


So now you see the rest of the dates for the next month are filled in!


OK, so this is where isCurrentMonth comes in. If it's false, add the CSS class notCurrentMonth.
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();


The CSS we write for this is first, for date. Remember we previously already styled date? Well, this is an override, or an add-on. We add a little padding on top, and change the color slightly. Most importantly, we ensure that the text is bold, and that when moused over, the cursor is a pointer. Because we'll be clicking on these suckers. And then notCurrentMonth un-bolds the text.
.daysContainer .header
{
    font-weight:bold;
}

.daysContainer .date
{
    padding-top: 3px;
    color: rgba(100, 100, 100, 1);
    font-weight: bold;
    cursor: pointer;
}

.daysContainer .notCurrentMonth
{
    font-weight: normal;
}


.daysContainer .weekend
{
    color: rgba(255, 0, 0, 1);
}


And now you can see all the displayed dates of the currently selected month are in bold, and those for the previous and next month are not.


Now, at the end of initalize(), let's make a call to setCalendatDate().
    this.setProperties(id);
    this.setCalendarDate(id, this.selectedDate.getDate(), this.selectedDate.getMonth() + 1, this.selectedDate.getFullYear());
},


It's currently June as I write this, so once you refresh the page, as soon as initialize() is run, the displayed date shows the current month!


Still here?

You hung in there! I'm impressed. So far this is all display. Fortunately, most of the calculation is done.

Next

Click events and date selection.

No comments:

Post a Comment