Saturday 31 July 2021

Five Dimensions of Competency, redux

I have written in the past about the Five Dimensions of Competency as defined by Workforce Skills Qualification (WSQ). At the time, I was learning about WSQ and was about to be tested on my knowledge, so writing about it seemed a good way to really drive home the knowledge.

This theory seemed sound, until I found a better teacher - a real, living example.

This guy was a colleague, a fellow software developer who lasted exactly one year in the organization. And when you're a contractor, serving your first contract with the company to completion isn't that big a deal. In fact, if it's a renewable contract which doesn't get renewed, it's almost akin to a termination of services.

Now, I'm not saying this guy was lousy. He was young, energetic and possibly technically better than me. But as mentioned, competency is measured in five dimensions. Today, we're going to take a look at dimensions of competency where he did and didn't make the grade.

I'd like to say that this guy wasn't a bad guy. He was easy to get along with. Not terribly mature, but at the age of thirty there's plenty of room for growth.

1. Task Skills

Individually, this is where he shone. His technical knowledge was solid enough. He was certainly able to code. But of course, it has to be said that if software devs were judged solely on their ability to code, a huge portion of us would pass quite easily. The other stuff matters just as much, if not more.

The ability to write code.

Unfortunately, while his code worked, he had this tendency to be slipshod where code formatting was concerned. He didn't indent or space out his code properly, and even remarked to me on more than one occasion that these things were not important. He left rubbish comments all over the place - comments that made sense to him alone.

You know the kind of guy who writes "fixbug" in commit remarks? He was that guy.

Not only did he do all this, he didn't seem to understand why it was such a big deal. Not that I don't make mistakes, but when they're pointed out to me, I tend to know why they matter.

Knowledge: Pass
Skill: Pass
Attitude: Fail


2. Task Management Skills

Task Management Skills are mostly about prioritization, and here there's not much to say because as far as I can tell, he was never assigned anything substantial and therefore never had the need to prioritize.

Working overtime.

A plus point: he had no issues working overtime to get shit done - what was suspect was the need to put in that much overtime in the first place. Diligence wasn't the issue; how much of that diligence was productive, was the issue. I recognize this because I've had the same problem in the past.

Knowledge: ?
Skill: ?
Attitude: Pass


3. Contingency Management Skills

Most of our work was implementing new features, and fixing bugs. This guy could code, but sometimes his bug fixes didn't solve the problem, and even resulted in new bugs. He seemed to have an aversion to testing his code.

Bad patches and fixes.

I mean, we're talking about the really simple stuff here. If the bug is in a formula which is supposed to produce a result, isn't it reasonable to expect that if you applied a fix, you would test for the correct output?

Nope, he didn't do it. One of my strongest memories was asking the QA to reassign a bug fix to this guy because I was being swamped with work, saying "it's so simple, there's no way he can screw this up". The QA didn't look so sure, and after the "fix", I realized just why. The output was still wrong.

Now, at this point, this wasn't a matter of aptitude. His heart just wasn't in it.

Knowledge: Pass
Skill: Pass
Attitude: Fail


4. Job Management Skills

Now, this is where he really tanked. Where writing code was concerned, we had to ensure that our work was maintainable, and as mentioned earlier, he just didn't think stuff like appropriate comments, proper indentation and spacing, was all that important.

But that's just one thing.

You see, as professionals, we have obligations other than writing and testing code. As contractors, we also had to fill in our timesheets and clock our hours. He received multiple warnings for failing to do so.

Locking your workstation.

The most important area where he dropped the ball, was security. Not coding security, but basic security such as locking his screen when he went for a smoke break, or even leaving his docking station unlocked the entire damn day. None of these had anything to do with programming; it was just good old-fashioned sloppiness.


Knowledge: Fail
Skill: Fail
Attitude: Fail


5. Transfer Skills

Just like me, his coding skills were applied to different contexts. There were various parts of our work that required us to pivot to vastly different tasks. One day we could be working on the back-end, and another day we could be writing Stored Procedures and generating reports.

Using different tools.

The tasks I was assigned to eventually turned out even more different - APIs and DevOps as opposed to just front and back end programming - but that wasn't really his fault because those were assigned after he had departed.

Knowledge: Pass
Skill: Pass
Attitude: Pass


Conclusion

This entire exercise isn't about denigrating an ex-colleague just to look good. No, I was actually thankful for his presence. If it wasn't for a negative example to take reference from, I might have made some of these mistakes as well.

I know I've said it before, but this bears repeating - being a dev is not just about coding, and competency is not just about knowing how to code.

I have a lot to learn, and hopefully with more people making mistakes for me to observe, I'll learn a lot faster.

Competent regards,
T___T

Tuesday 27 July 2021

Using Chinese in application user interfaces

Chinese can be a poetic and fantastically expressive language. And it's to my everlasting regret that I didn't take it more seriously when I was a schoolboy. That's because as my proficiency and exposure in my chosen trade increased and I eventually started learning how to make mobile apps, I discovered that UTF-8 was a great boon, because it allowed me to write Chinese characters in HTML.

What, you might ask, is the significance of Chinese characters in a mobile app? The answer, dear readers, is plenty.

You see, the one thing that a developer is always in need of, on a mobile app, is screen space. Due to small screen sizes, I'm always looking for creative ways to fit whatever functionality or information on that tiny screen. And where real estate is concerned, Chinese really saves the day.

Compactness

One Chinese character takes up one character space on screen. In fact, one Chinese character is one Chinese word! Therefore, text justification doesn't really apply to Chinese characters. Neither do word breaks.

Consider the example below from A Tale of Two Cities.
It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way – in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.


Now the Chinese version (just a Google translation). You can probably see how compact the entire paragraph is - no spaces wasted.
那是最美好的时光,那是最糟糕的时光,那是智慧的时代,那是愚昧的时代,那是信仰的时代,那是怀疑的时代,那是光明的时代, 是黑暗的季节,是希望的春天,是绝望的冬天,我们眼前的一切,眼前的一切,我们都直接去天堂,我们都直接去天堂– 简而言之,这一时期与目前的时期如此遥远,以至于它的一些喧闹的当局坚持以最高的比较程度来接受它是好是坏。


Chinese characters
are compact.

The side effect of this is that we can cram a lot more information into one paragraph than we could with English... or any other language that uses the English alphabet. That's no small thing when you consider how much screen space there really is on your typical mobile app.

Conciseness

Remember I said the Chinese language was expressive? In the previous examples, I showed how a lot more words could be crammed into one space. Now, I'm going do the opposite - I will show you how much visual space a piece of information takes in different languages.

Take the word "welcome".

In English, it takes 7 character spaces.
welcome


In Malay, it's worse. It takes 11 character spaces.
akan datang


In Chinese? It takes 2 character spaces. Two!
欢迎


Imagine if so much information could be represented in that little amount of horizontal spacing, how much space a mobile app developer could save by using Chinese, and how much more space there would be to play with.

The Chinese Conclusion

The advantages of using Chinese as a medium for mobile apps, similarly applies to other Asian languages like Japanese, Burmese and Korean. Of course, the fact that English is almost universally known, is a huge point in its favor. But Chinese is catching up. And the China market is nothing to sniff at.

Thanks and goodbye! 谢谢, 拜拜!
T___T

Saturday 24 July 2021

Actual Requirements For Tech Expertise

Companies say they need tech experts. And I have no doubt that this is what they truly think. They write all sorts of fancy job descriptions that sound deliciously complex, only for you to realize, during the interview - or worse, during the first week at work - that their needs are far more basic.

What causes this phenomenon? I submit that this is largely found outside of the tech sector. Companies dealing in food, logistics, healthcare and the like, that find themselves in the position of having to undergo some kind of digital transformation.

In other words, get with the times. Keep up with the Joneses. Move with the world before it moves on without you.

This is what I think is happening. Companies that are largely traditional (with very traditional salary ranges) are finding, to their consternation, that the average techie earns significantly more than their average senior executive - even those techies that don't get hired by banks or Big Tech. In order to hire your run-of-the-mill tech grunt in-house, they'd have to fork out amounts they're not accustomed to.

So what do they do? They raise the price a little more, and ask for a tech expert. After all, if they're going to pay that kind of money, they might as well make it worth the price, eh? That's consumer mentality for you. No, I'm not mocking these companies. Not exactly. That's a perfectly reasonable line of thought if your business is outside of the tech sector.

Hiring the cream of the crop.

But the uncomfortable truth is, these companies don't need experts. They don't need geniuses who can invert a binary tree on the spot, or wax lyrical about recursion. They don't need database specialists who can perform Relational Algebra, or extremely experienced rock-star devs who can name you all the best working practices in tech, in the last decade.

And even in the extremely unlikely scenario that they did need someone like that, chances are such a prodigy would prefer to be climbing the ranks within the tech sector itself, not outside of it.

You see, the main obstacle isn't money, or even the willingness to spend it. The obstacles are both cultural and structural.

Companies that are used to doing things the low-tech way are going to have to get used to doing things at a higher level. It's not just a matter of hiring the tech expert; the company, as a collective whole, needs to up their game. And that is the main sticking point. Techies within the tech industry are used to having their methods accepted. They're certainly rarely in the position where they actually have to justify the merits of technology to their bosses, or aggressively push new ways of doing things. Because in the tech sector, change is practically a way of life.

Outside of the tech sector? Not so much.

Case in point...

An ex-colleague was recently showing me the advances that the company had made in technology. Instead of using a physical punch-card system, now they use a biometric facial recognition system.

As outdated as this machine.

Oh, wow.

Using advanced technology to carry out the antiquated practice of clocking in and out of your workplace, as opposed to using an equally antiquated method of doing so? The technology had changed; the culture hadn't.

I mean, what's the difference here? Whether you're killing trees for paper or merely consuming bandwidth and electricity, you're still satisfying HR's need to have everybody conform to the same work timings. It's still a pig; you're merely putting lipstick on it.

What do these companies need, if not an expert?

Well, just to be clear, I'm not saying that experts aren't useful. I'm saying that were they to be hired by these companies, they would be criminally underused. And that's not the company's fault, exactly. After all, if you're not a tech company and therefore are not accustomed to the way tech companies do things, then a reckless wholesale overhaul of processes has the potential to be a disaster of horrendous proportions. Tech companies do things a certain way because it makes sense to do them a certain way. Other companies, within their own industries, have practices that are a requirement of industry-specific standards. Copying blindly helps absolutely nobody. Useful practices can and should be adopted; the trick is figuring which practices are useful.

But for a start, hiring experts when you have no real need to, and probably can't afford to give them the time and autonomy required for them to make a real difference, simply isn't ideal. Companies convince themselves that they need experts, but that's only because they don't fully grasp just what technology is capable of, and what those experts could accomplish as opposed to the relatively basic nature of the company's operations.

Really, do you need experts to set up a biometric facial recognition system for the purpose of making sure your employees clock in and out on time? That seems extraordinarily petty.

So what do companies actually need to bring up the next technological rung? Well, for starters, someone more well-versed in technology than the average employee would do. And if we're going to be honest, that might not actually mean an expert.

To a layperson, tech
is like witchcraft.

Simply put, some companies are at a technological level where the concept of MS Excel pivot tables is a cause for a Wow response. The tech expert, at this point, is the modern-day equivalent of Hank Morgan appearing to ignorant Englishmen as a powerful sorcerer. What techies do (even the experts) isn't magic, but if you're sufficiently backward in mindset and technology, it may as well be.

A Story

Last year, I was hired by an organization on the recommendation of one of my ex-bosses. He told the Director that what the organization needed at this point wasn't a specialist or an expert, but a generalist. I wasn't sure what this meant at the time, but it wouldn't take me long to find out.

Out of the blue, an issue came up. Apparently, the employees operating the end-of-day sales report module at all the outlets were having trouble opening the report, which was in MS Excel format. They did not want to use the Office 365 web-based interface to open the report, as this would require one extra step, and worse, would actually require them to learn something new. As MS Excel licenses were in short supply, the Infrastructure Manager proposed that I rewrite the report in PDF format (which would open in any browser) while he installed Adobe Acrobat Reader on all the machines, and trained the personnel to use it.

Just use tables.

I did him one better. They just needed the damn report to open up right away without any extra steps? All they needed were HTML tables. Even if they were using bloody Internet Explorer, it would still work. The Infrastructure Manager wouldn't have to go through the nightmarish task of installing software on all machines at all outlets across the island, and no extra training was required.

They thought it would take a week, or at least a few days. I took all of twenty minutes to write the code, and presented it in the morning. The outlet staff were satisfied, and the Infrastructure Manager was off the hook.

Duplicitous of me? Very. But at one fell swoop, I had scored a few goals. One; I'd met the objectives of the outlet staff in a way that inconvenienced them the least. Two; I had saved the Infrastructure Manager a shit-ton of labor.

But I digress; the point of this wasn't to tell you what a sneaky SOB I can be, but rather to illustrate that this would not have been possible had I been a tech expert. A tech expert would have found it unacceptable that staff weren't willing to pick up new technologies (and bear in mind that at this point, cloud-based software isn't exactly new) and refused to enable it. No, I was a run-of-the-mill techie with a healthy respect for best practices in tech, but also a willingness to disregard them when the situation called for it.

The fact that I had years of experience in Desktop Support and already learned (the hard way) about people who refuse to learn new ways of doing things, certainly didn't hurt.

In Summary

For companies outside of the tech sector, hiring tech experts is difficult, and in some cases, sub-optimal. Context is everything. Hiring has to be done in accordance to actual needs and not a wish-list.

Your non-expert,
T___T

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

Sunday 18 July 2021

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

Fun times! It's time to implement some click events.

Remember we could change the month display just by selecting the value of monthPicker? Well, let's expand on that a bit. We can work on the btnMonthDec and btnMonthInc elements in the initialize() method. Add a click event on both btnMonthDec and btnMonthInc. The content of the callback will be similar to monthPicker's change event... except that instead of passing in null for the month, you pass in the month number before and after the current month, for btnMonthDec and btnMonthInc respectively. Don't worry about showDate(); we'll be on that in due course.
var btnMonthDec = document.createElement("div");
btnMonthDec.classList.add("button");
btnMonthDec.classList.add("spaceleft");
btnMonthDec.innerHTML = "&#9664;";
btnMonthDec.onclick = function() { calendar.setCalendarDate(id, null, calendar.selectedDate.getMonth() + 1 - 1, null);calendar.showDate(); };
bottom.appendChild(btnMonthDec);

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(id, null, null, null);calendar.showDate(); };
bottom.appendChild(monthPicker);

var btnMonthInc = document.createElement("div");
btnMonthInc.classList.add("button");
btnMonthInc.classList.add("spaceleft");
btnMonthInc.innerHTML = "&#9654;";
btnMonthInc.onclick = function() { calendar.setCalendarDate(id, null, calendar.selectedDate.getMonth() + 1 + 1, null);calendar.showDate(); };
bottom.appendChild(btnMonthInc);


Now you can see, that when you click on these buttons, the month goes back and forth!


But if you get to December and click again, it goes blank! Let's fix that.


The setCalendarDate() method is where we fix this. Add an Else block after checking if month is null. If month is 13, reset monthVal to January (1) and increment yearVal. If month is 0, reset monthVal to December (12) and decrement yearVal.
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;
    }
    else
    {
        if (month == 13)
        {
            monthVal = 1;
            yearVal++;
        }

        if (month == 0)
        {
            monthVal = 12;
            yearVal--;
        }
    }


So now, if you're at December 2021 and you click to increment the month, it goes to January and the year changes to 2022.


If you're at January 2021 and you click to decrement the month, it goes to December and the year changes to 2020.


Next, we will write the code to handle changing the year. This part will be relatively easy because we're just leveraging off code we've already written. So in the initialize() method, add these lines. Note that in these cases, we pass in id and nulls, but the year portion of selectedDate as the argument for year. If you want to decrement by 10, you have to subtract 10 from the argument. If you want to increment by 1, you have to add 1 to the argument. And so on.
var btnYearDecTen = document.createElement("div");
btnYearDecTen.classList.add("button");
btnYearDecTen.classList.add("spaceleft_large");
btnYearDecTen.innerHTML = "&#9665;";
btnYearDecTen.onclick = function() { calendar.setCalendarDate(id, null, null, calendar.selectedDate.getFullYear() - 10);calendar.showDate(); };
bottom.appendChild(btnYearDecTen);

var btnYearDec = document.createElement("div");
btnYearDec.classList.add("button");
btnYearDec.classList.add("spaceleft");
btnYearDec.innerHTML = "&#9664;";
btnYearDec.onclick = function() { calendar.setCalendarDate(id, null, null, calendar.selectedDate.getFullYear() - 1);calendar.showDate(); };
bottom.appendChild(btnYearDec);

var lblYear = document.createElement("div");
lblYear.classList.add("lblYear");
lblYear.classList.add("spaceleft");
lblYear.id = "lblyear_" + id;
lblYear.innerHTML = this.selectedDate.getFullYear();
bottom.appendChild(lblYear);

var btnYearInc = document.createElement("div");
btnYearInc.classList.add("button");
btnYearInc.classList.add("spaceleft");
btnYearInc.innerHTML = "&#9654;";
btnYearInc.onclick = function() { calendar.setCalendarDate(id, null, null, calendar.selectedDate.getFullYear() + 1);calendar.showDate(); };
bottom.appendChild(btnYearInc);

var btnYearIncTen = document.createElement("div");
btnYearIncTen.classList.add("button");
btnYearIncTen.classList.add("spaceleft");
btnYearIncTen.innerHTML = "&#9655;";
btnYearIncTen.onclick = function() { calendar.setCalendarDate(id, null, null, calendar.selectedDate.getFullYear() + 10);calendar.showDate(); };
bottom.appendChild(btnYearIncTen);


Now try clicking on the various year buttons. You should be able to increment and decrement the year, by 1 or 10! And the dates in daysContainer should change accordingly!


Great, now it's time for date selection. To do this, we first need to add an element into the entire setup. This should be put near the end of the initialize() method. It's supposed to be a hidden element, but for now, we will make it a textbox so that we can see what's going on. This is the most pertinent part of the datepicker because it's where the value is actually stored.
    var daysContainer = document.createElement("div");
    daysContainer.classList.add("daysContainer");
    daysContainer.id = "dayscontainer_" + id;
    bottom.appendChild(daysContainer);

    var hiddenTextbox = document.createElement("input");
    hiddenTextbox.type = "text";
    hiddenTextbox.id = "hidtxt_" + id;
    hiddenTextbox.name = id;
    bottom.appendChild(hiddenTextbox)
;

    this.setProperties(id);
    this.setCalendarDate(id, this.selectedDate.getDate(), this.selectedDate.getMonth() + 1, this.selectedDate.getFullYear());
},


There you see the textbox we just added!


In setProperties(), we need to do the same for hiddenTextBox.
setProperties: function(id)
{
    this.container = document.getElementById(id);
    this.textbox = document.getElementById("txt_" + id);
    this.hiddenTextbox = document.getElementById("hidtxt_" + id);
    this.monthPicker = document.getElementById("monthpicker_" + id);
    this.lblYear = document.getElementById("lblyear_" + id);
    this.daysContainer = document.getElementById("dayscontainer_" + id);
},


At the end of setCalendarDate(), add this line to populate the textbox. Note that we format selectedDate to a value that can be sent to the server.
    this.lblYear.innerHTML = yearVal;
    this.hiddenTextbox.value = yearVal + "-" + (this.selectedDate.getMonth() + 1) + "-" + this.selectedDate.getDate();

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


Now if you refresh, you will see the date in the textbox. This is the value that is going to be sent to the server. If you change the value on monthPicker, the value in hiddenTextbox changes as well.


You'll notice that we have been calling showDate() in some places, and even have the method created. Well, now we are about to fill that in. This time, we fill in textbox, but with a user-friendly prettified date.
showDate: function()
{
    this.textbox.value = this.selectedDate.getDate() + " " + this.months[this.selectedDate.getMonth()] + " " + this.selectedDate.getFullYear();
},


Now when you change the value of monthPicker, you can see the date and the top change!


For date selection, go to the generateDateBox() method. Before returning dayBox, we need to add a click event. When the user clicks on any one of these dates, the setCalendarDate() method is called. Remember we set the data of day, month and year earlier in the element? Well, now we will use these as arguments in the callback.
    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();
    dayBox.onclick = function(e) { calendar.setCalendarDate(id, e.target.dataset["day"], e.target.dataset["month"], e.target.dataset["year"]); calendar.showDate();};

    return dayBox;
}


So now if you go to the July display and click on, say, 29, the dates in the textboxes change!


Now for one last thing. The widget controls have been visible up to now. We need to be able to hide and un-hide them. At the beginning of the initialize() method add this line so that container is styled using hide by default. So now if you refresh, the controls should no longer be visible.
initialize: function(id)
{
    this.selectedDate = new Date();
    this.container = document.getElementById(id);
    this.container.classList.add("calendarContainer");
    this.container.classList.add("hide");


And then, in the same method, further down the line to where btnDisplay is defined, add this line to call the display() method.
var btnDisplay = document.createElement("div");
btnDisplay.classList.add("button");
btnDisplay.classList.add("spaceleft");
btnDisplay.id = "btndisplay_" + id;
btnDisplay.innerHTML = "&#9660;";
btnDisplay.onclick = function() { calendar.display(id); };
top.appendChild(btnDisplay);


We'll define this method next.
setProperties: function(id)
{
    this.container = document.getElementById(id);
    this.textbox = document.getElementById("txt_" + id);
    this.hiddenTextbox = document.getElementById("hidtxt_" + id);
    this.monthPicker = document.getElementById("monthpicker_" + id);
    this.lblYear = document.getElementById("lblyear_" + id);
    this.daysContainer = document.getElementById("dayscontainer_" + id);
},
display: function(id)
{

},

setMonthDisplay: function(id, month, year)
{


So when display() is called, we call setProperties(). The variable show is used to check if container is styled using hide. So if container is invisible, show is true, false otherwise.
display: function(id)
{
    this.setProperties(id);
    var show = this.container.classList.contains("hide");

},


So if container is invisible, it means show is true, and we want the button click to show the controls. In that case, we want to remove the CSS class hide and add the CSS class show.
display: function(id)
{
    this.setProperties(id);
    var show = this.container.classList.contains("hide");

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

    }

},


If show is false, we remove show and add hide.
display: function(id)
{
    this.setProperties(id);
    var show = this.container.classList.contains("hide");

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

    }
},


Now refresh. When you click the btnDisplay button, it toggles the display on and off!

Next

Cleaning up. There's some more stuff we need to take care of before this is complete.

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 = "&#9655;";
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.