Monday, 28 September 2020

The Math Behind the Halloween-Christmas Nerd Joke

There's a famous programmer riddle that goes like this.
Why do programmers always mix up Halloween and Christmas?


This...

...is equivalent to this?!



The answer, as it was told, is...
Because Oct 31 is the same as Dec 25.


For years, I didn't get it, and honestly there were more fun things to do than obsess over this. I was told the answer was really geeky anyway.

But guess what - October's around the corner and maybe it's time to finally walk through the answer.

See, it's a pun. "Oct" in this case is a play on the word "Octal" and "Dec" is a play on the word "Decimal". Octal is actually a numbering system with 8 as a base, as opposed to Decimal, which has 10 as its base. If that didn't make sense, congratulations for not being a nerd.

Still, it's been so long since I tried to convert Octal to Decimal - almost two decades - that I should probably share the results of my labor here.

Converting Octal to Decimal

First, we take the Octal number, which is 31, and we perform an operation on every digit. Then we add it all up to get the Decimal equivalent. Simple enough, right?

The operation is - n starts from 0. Use 8 to the power of n, then multiply it by the current digit, starting from the rightmost. For every new digit, add 1 to n.

So for 31, we split this to become 3 and 1.

1 is the first digit, and we multiply 1 by 8n. Any number to the power of 0 is 0. So 8n is 0. Therefore, the result is (1 + 0 = 0).

The second number is 3, and we're supposed to multiply 3 by 8n. If we add 1 to n, n is now (0 + 1 = 1). Any number to the power of 1 is that number itself, so 8n is 8. If we multiply 3 by 8, we get (3 x 8 = 24).

We have 1, and we have 24. Adding it all up gets us (1 + 24 = 25). The Decimal equivalent is 25!

Ah, so Octal 31 is Decimal 25!

Explaining the joke in such explicit detail sure makes it a lot less funny, huh?

Math the force be with you, young Padawan!
T___T

Monday, 21 September 2020

Five Stupid Things Tech Bosses Say

Recently, there's been a big hoo-ha on Facebook, with the utterances of "thought leader" Delane Lim whining commenting about how unreasonable young interviewees are in light of the COVID-19 pandemic situation tanking Singapore's economy. He said something to the effect of youngsters not being "hungry enough". Now, much ink has already been spilled on behalf of this guy, so I won't add to it.

However, the term "hungry" did bring back some unpleasant memories. Back in 2013, I worked for a CEO whose favorite catchphrase was the admonishment "not hungry enough". And guess what - that very same boss got hauled to court in 2017 for not paying vendors and staff salaries. Couldn't have happened to a nicer guy.

Of all the stupid things to say, huh?

Bosses - can't live with 'em, definitely can't live without 'em. To be fair, not all of them have been utter douchebags, but even the best of them have produced some truly appalling, mind-boggling and cringe-worthy quotes. Today I have the dubious pleasure of sharing some of the most jaw-dropping words I've ever heard said by a superior at work.

Be warned that I might get a little vulgar here...

1. The Instigator

Sometimes Managers get cheeky. They say things that they think will provoke a reaction out of you, or sting your pride, in order to spur you on, get extra performance out of you. Y'know, mind games.

Whose code is this?


One such Manager probably thought he was being clever by urging me to take more ownership of the module I had been put in charge of.

"Do you really want someone else to touch your code?"

Sadly, that didn't work. You see, it isn't even my code. Legally, any code I write belongs to the company. Because I wrote it on company time, using company equipment. So as long as I deliver, I don't really have any fucks to give as to who handles the code after it's been checked into the repository and deployed. If it's me, great. If not, also great.

2. The Paternal

You know the old Chinese Towkays I've complained about in the past? Well, they don't literally have to be old Chinese Towkays, but they tend to share certain unsavory aspects of the Chinese Towkay Syndrome. That is, at some level, they see themselves as some kind of Godfather-figure.

"I like to think of my staff as family."

Oh hell, no. I'm your employee, not your goddamn relative and don't you ever forget it.

Granted, some bosses who say things like that, do that with the best of intentions. Maybe they just want a more open, less formal, work culture. More cynically, maybe people who reciprocate and see you as family are easier to manipulate into going the extra mile.

We are family!


Family do favors for each other. Family puts up with shit that other people won't. Fuck all that. End of the workday, I want to leave the office and go back to my actual family. You can fantasize about being my brother/father/son all you want. I will not be returning the compliment.

3. The Bravo

Sometimes, at the workplace, you're issued some highly classified equipment and the onus is on you to keep it safe. At one time, I was put to work on a module and that involved having to log in with an OTP which was supplied by an RSA token. I took to keeping it in my locked drawer because taking this damn thing around only increased the chances of me losing it.

A Manager saw me doing that, and this is what he had to say.

"You should take more care. I wear the security dongle around my neck so that if someone wants to take it, they'll have to literally chop my head off."

Oh, wow. You know what I would do if someone gave me a choice between giving them the security token, or getting my head chopped off? I'd give it to them. Hell, I'd even pay for their cab fare.

Don't get ahead
of yourself.

Then again, perhaps that Manager was just a better man than I am. Someone who's willing to die for this crummy job.

Um, no thanks. Why? Because, like any other normal, sane and rational human being, I kind of value my life more than this job or a security token that costs maybe 70 SGD.

4. The Redundant

Here's some context. It was 7PM, and the team was in a meeting trying to iron out a process to speed work up. We were all hungry, tired, and some of us had kids at home who needed attention. The Manager came up with this beauty.

"Do you want to know what I think?"

Gee, I dunno. What are we supposed to say to that? "No thanks, please keep your opinion to yourself"?

Shush, mate.


Honestly? FFS, nobody wants to know what you think. What is this, TCP/IP? If you've got something to say, spit it out, pronto or stop wasting our goddamn time. Jesus!

5. The Amnesiac

I saved the best for last, because this one definitely takes the prize. It came during a code review. My CTO was in the middle of telling me how disgusting my code was, and how it could be done better. OK, fair enough. If my code's not good, I can take criticism like a champ.

Except he did one better. Halfway through changing the code, he paused and said,
"There's no way this could ever work. Are you sure you tested it?"

He then went on to lose his cool, lecture me about integrity, and about not declaring something "done" until it was tested.

Why doesn't this shit work?

You see, the problem was that while screwing around with the code I'd written, he changed the function call to use an integer instead of an object as an argument. The function itself accepted an object as a parameter. So yes, of course, naturally the code wouldn't work. Because he'd just changed it himself five seconds ago.

What was he suggesting - that I somehow took the trouble to fake twelve motherfucking screenshots of the entire module working just so I could avoid actual testing? Look - being an asshole is practically a requirement for a boss in order to get shit done. Just don't be a stupid asshole.

Yeah that's pretty stupid...

I know, right? Maybe we should stop thinking of Managers, employers and the like as better than us. They're in positions of authority, yes. That doesn't mean they don't say stupid things. In fact, they're probably even more prone to it since some of them don't seem to think they're capable of it.

If nothing else, this will hopefully help any Managers reading this to understand how saying certain things can be counterproductive. Your subordinates are supposed to respect you. And it's really difficult if you say things that scream retard.

Got something to say? Just don't.
T___T

Wednesday, 16 September 2020

Reference Review: The Mythical Man-month: Essays on Software Engineering

Over the past few years, I've had the pleasure of finally reading the project management classic, The Mythical Man-Month: Essays on Software Engineering by Frederick P Brooks, Jr, also the man behind this infamous quote known as Brooks's Law.
"Adding manpower to a late software project makes it later."


In fact, this very quote is featured in one of the book's chapters, along with many other little gems. The particular version I read was The Anniversary Edition, which featured four new chapters, and new thoughts from the author.



Now, I've read this book several times, and each time it seems I unlock another piece of the great mystery of software development. I'm not shy to say that devouring it in one sitting is a virtual impossibility for me; value takes time to unpack. It took me years to write this review, and I'm not sure I'll ever truly complete it.

I first learned about The Mythical Man-Month when I read a source online that likened the software development process to childbirth. The person who made that assertion told me that he had obtained that nugget of wisdom from The Mythical Man-Month.

And here, I found it.
"When a task cannot be partitioned because of sequential constraints, the application of more effort has no effect on the schedule. The bearing of a child takes nine months, no matter how many women are assigned."

The Premise

The Mythical Man-Month is a collection of musings written back in 1995 or before, dealing with the nature of managing a software project. Brooks draws upon his experience in managing the hugely complex software system, OS/360.

He starts off with a description of what a Program is, and how it scales up into a Programming Product, a Programming System and finally a Programming Systems Product. That's elementary enough, and things get a lot deeper from there.

The Aesthetics

Not so many to speak of. There are some nice prints and photos of art exhibits preceding every chapter, though I'd venture to say those are merely fluff. The visuals are otherwise very much non-descript.

The Experience

Given that this was written way back then, the language used tends to feel a bit antiquated. Though the concepts aren't. Brooks has an elegant yet chummy way of writing that puts you at ease, or to sleep - pick one. He comes across as calm and logical, pretty much what any kind of Manager needs to be.

Brought me back to simpler times, where men wore ties and women wore skirts. It's a bit antiquated, yes, but the truths in there are ageless.

The Interface

While the book itself is a somewhat easy read, it's sometimes hard to decipher the accompanying graphs and charts. Which is a pity, because I suspect that understanding of those would have greatly deepened the insight imparted by Brooks's writing.

What I liked

In his essay, The Tar Pit, Brooks puts eloquently into words what I love about my industry, and why I've stayed in it so long: making stuff.

First is the sheer joy of making things. The child delights in his mudpie, so the adult enjoys building things, especially things of his own design.

Second is the pleasure of making things that are useful to other people. Deep within, we want others to use our work and to find it helpful.

Little essential truths that are only tangential to the topic. For example, in the chapter The Other Face, he discusses the importance of documentation, but also acknowledges, via the use of an anecdote, that while everybody understands the need for documentation, it's more important to know how to document. So instead of preaching its importance, he proceeds to explain how.

This quote, uttered in the context of Code Reuse. That's just zen, it is.
The best way to attack the essence of building software is not to build it at all.


Plan To Throw One Away is a fascinating chapter about trial and error. It's eerily reminiscent of my own efforts at writing mini-games at times.

Discussed in the chapter Hatching a Catastrophe, the author's words again strike a deep chord.
Conversely,when the manager knows his boss will accept status reports without panic or preemption, he comes to give honest appraisals.

Indeed, how many things are swept under the carpet simply because Management have a tendency to overreact? People need to know that their superiors are in control. When said superiors panic, it's not only the project that suffers; all future confidence in a leader is strained.

There's an outline in point form near the end of the book, encapsulating all relevant points made by Brooks, just in case the reader doesn't feel like wading through a sea of old-style English. That's pretty neat.

What I didn't

While Brooks's piece on a small surgical team being assembled to build software does sound very interesting, I'm not really sure how practical it is in this day and age. To be fair, it does read like a very early version of a cross-functional team in the Agile Manifesto.

Brooks has a valid point in Tower of Babel - communication is important. Problem is, he spends a really long time belaboring the point and I really struggled to keep up.

In the chapter The Other Face, Brooks speaks of the importance of documentation. I tend to agree, but I'm ambivalent about the statement.

In fact, flow charting is more preached than practiced. I have never seen an experienced programmer who routinely made detailed flowcharts before beginning to write programs.


Conclusion

This book is a joy to read and could very well be the Software Project Management equivalent of Sun Tzu's Art of War. As a reference on the human elements of software development, it's in a class of its own.

The man-month is a fallacious and dangerous myth, for it implies that men and months are interchangeable.

Sure, a lot of it comes across as common sense like the quote above. But it's common sense that is very well presented. Decades after it was first published, The Mythical Man-Month continues to be just as relevant, and that in itself is an anomaly in the fast-evolving world of software today. Part of this could be due to the fact that Brooks discusses human nature as much as he discusses software, and we all know that, unlike technology, human nature doesn't change.

My Rating

9 / 10

A book of mythical proportions,
T___T

Saturday, 12 September 2020

Web Tutorial: The Louver Carousel (Part 2/2)

Hi guys. I'm back for the JavaScript portion. We'll be making some changes to the carousel object.

Change contentContainer to louverContent because that makes more sense.
currentSlideIndex: 0,
louverContent: undefined,
c0: undefined,
c1: undefined,
canMove: true,


In the begin() method, modify this line. Obviously, now we have to change contentContainer to louverContent. We want to use the getElementsByClassName() method because louverContent is a CSS class.
this.louverContent = document.getElementsByClassName("louverContent");


Instead of the getElementById() method, use getElementsByClassName(). Again, because content0 and content1 are no longer ids, but CSS classes.
this.c0 = document.getElementsByClassName("content0");
this.c1 = document.getElementsByClassName("content1");


The rest of the logic for begin(), canMove(), slide() and sliderButtonClick() remains the same. For setTransitionSpeed() and setMargin(), however, it's time to change stuff. Basically, take all this away.
setTransitionSpeed: function(interval)
{
    //this.contentContainer.style.webKitTransition = interval + "s all";
    //this.contentContainer.style.transition = interval + "s all";
},
setMargin: function(margin)
{
    //this.contentContainer.style.marginLeft = margin + "%";
},


You want to set the transition and marginLeft property for all divs styled using louverContent, via For loops.
setTransitionSpeed: function(interval)
{
    for (var i = 0; i < this.louverContent.length; i++)
    {
        this.louverContent[i].style.webKitTransition = interval + "s all";
        this.louverContent[i].style.transition = interval + "s all";
    }
},
setMargin: function(margin)
{
    for (var i = 0; i < this.louverContent.length; i++)
    {
        this.louverContent[i].style.marginLeft = margin + "%";
    }
},


Next is the setContent() function. Take away everything to do with setting innerHTML because there's no longer any HTML content to set.
setContent: function(next, dir)
{
    if (dir == "left")
    {
        //this.c0.innerHTML = this.slides[this.currentSlideIndex].content;
        this.c0.style.backgroundImage = "url(img/" + this.slides[this.currentSlideIndex].bg + ")";

        //this.c1.innerHTML = this.slides[next].content;
        this.c1.style.backgroundImage = "url(img/" + this.slides[next].bg + ")";
    }

    if (dir == "right")
    {
        //this.c0.innerHTML = this.slides[next].content;
        this.c0.style.backgroundImage = "url(img/" + this.slides[next].bg + ")";

        //this.c1.innerHTML = this.slides[this.currentSlideIndex].content;
        this.c1.style.backgroundImage = "url(img/" + this.slides[this.currentSlideIndex].bg + ")";
    }
},


Encapsulate the whole thing in a For loop, iterating over each element of c0 (or c1, if that's your fancy.) Basically, we want to run this for as many louvers as we've got.
setContent: function(next, dir)
{
    for (var i = 0; i < this.c0.length; i++)
    {
        if (dir == "left")
        {
            //this.c0.innerHTML = this.slides[this.currentSlideIndex].content;
            this.c0.style.backgroundImage = "url(img/" + this.slides[this.currentSlideIndex].bg + ")";

            //this.c1.innerHTML = this.slides[next].content;
            this.c1.style.backgroundImage = "url(img/" + this.slides[next].bg + ")";
        }

        if (dir == "right")
        {
            //this.c0.innerHTML = this.slides[next].content;
            this.c0.style.backgroundImage = "url(img/" + this.slides[next].bg + ")";

            //this.c1.innerHTML = this.slides[this.currentSlideIndex].content;
            this.c1.style.backgroundImage = "url(img/" + this.slides[this.currentSlideIndex].bg + ")";
        }
    }
},


And of course, each instance of c0 and c1 must use i to point to the current element.
setContent: function(next, dir)
{
    for (var i = 0; i < this.c0.length; i++)
    {
        if (dir == "left")
        {
            //this.c0.innerHTML = this.slides[this.currentSlideIndex].content;
            this.c0[i].style.backgroundImage = "url(img/" + this.slides[this.currentSlideIndex].bg + ")";

            //this.c1.innerHTML = this.slides[next].content;
            this.c1[i].style.backgroundImage = "url(img/" + this.slides[next].bg + ")";
        }

        if (dir == "right")
        {
            //this.c0.innerHTML = this.slides[next].content;
            this.c0[i].style.backgroundImage = "url(img/" + this.slides[next].bg + ")";

            //this.c1.innerHTML = this.slides[this.currentSlideIndex].content;
            this.c1[i].style.backgroundImage = "url(img/" + this.slides[this.currentSlideIndex].bg + ")";
        }
    }
},


Oh crap, what happened?!


That's because each louver is showing the current image at 100% width and height. And since each louver is 10% of each picture's width, this results in a distorted picture. So make the following change...

.content
{
    width: 50%;
    height: 100%;
    float: left;
    background-color: #000044;
    background-size: 1100% 100%;
    background-repeat: no-repeat;
    padding-top: 1em;               
}


Much better.


Remove the red outlines.
div {outline: 0px solid #FF0000;}


Yay! See the transitions!






That's it!

Paul is six years old now, and these pictures aren't really relevant anymore. But they still bring me great joy. So much joy that I still use them in web tutorials.

With much louver,
T___T

Wednesday, 9 September 2020

Web Tutorial: The Louver Carousel (Part 1/2)

Hey guys, and welcome to yet another carousel tutorial!

I know I know, you're all probably thinking, enough already! This is the last one for the entire year, I promise. Just want to get this out of the way while the carousel code is still fresh in memory.

Today, we'll be modifying the Slide Carousel code to produce a Louver Carousel. And if you're wondering what in the loving heck a Louver Carousel is, well, read on!

The Effect

Instead of sliding from left to right, the carousel will transition each image in slices, like a louver.

Kind of like this!

This is really a variant on the Slide Carousel, so we'll be reusing the same code.

First, take the code and make some changes. We'll want to change the title, of course.
<!DOCTYPE html>
<html>
    <head>
        <title>Louver Carousel</title>


Change this because we want red outlines to show us boundaries.
div {outline: 1px solid #FF0000;}


Change this to 100%. We won't need that large a width because we won't be sliding in and out of the viewport.
#contentContainer
{
    width: 100%;
    height: 100%;
    background-color: #004400;
}


We won't need background-position for this. I'll explain why later. And instead of cover, let's set it to 100% for both height and width.
.content
{
    width: 50%;
    height: 100%;
    float: left;
    background-color: #000044;
    background-size: 100% 100%;
    /*background-position: 50% 50%;*/
    background-repeat: no-repeat;
    padding-top: 1em;               
}


We won't need this at all. The drawback of this special effect is that you can't really embed HTML content in there.
/*
.content div
{
    padding: 1em;
    background-color: rgba(255, 255, 255, 0.5);
    color: rgba(0, 0, 0, 1);
    font-weight: bold;
    width: 80%;
    border-radius: 5px;
    margin: 0 auto 0 auto;
}
*/


In the carousel object, there's a slides array. You won't need the content property.
slides:
[
    {
        bg: "00.jpg",
        /*
        content: "<div><h2>Phasellus ullamcorper</h2><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent fringilla molestie pellentesque. Aliquam auctor scelerisque mattis. Donec maximus ipsum non eros gravida suscipit. Sed vulputate massa orci, non placerat lectus suscipit mollis. Fusce faucibus lorem eu lectus mattis, et dictum urna semper. Maecenas massa magna, commodo at pulvinar ac, lacinia nec augue. Nullam pretium consectetur ultricies. In a mattis justo. Suspendisse potenti. Vivamus scelerisque, urna imperdiet faucibus facilisis, orci elit dictum ex, blandit rutrum metus diam eget mi. Phasellus ullamcorper eu purus eu venenatis. Nullam vestibulum dolor vel felis facilisis, ac rutrum tellus rutrum. Sed vehicula elementum erat, eget mattis tortor rhoncus a. Phasellus ut nisl volutpat, dictum lectus ut, dictum lectus.</p><h2>Quisque pharetra</h2><p>Lorem ipsum vel felis facilisis, ac rutrum tellus rutrum. Sed vehicula elementum erat, eget mattis tortor rhoncus a.</p><p>Vestibulum porta mattis quam a gravida. Aliquam quis quam nulla.</p></div>"
        */
    },
    {
        bg: "01.jpg",
        /*
        content: "<div><h2>Phasellus mattis</h2><p>Lorem ipsum vel felis facilisis, ac rutrum tellus rutrum. Sed vehicula elementum erat, eget mattis tortor rhoncus a.</p></div>"
        */
    },
    {
        bg: "02.jpg",
        /*
        content: "<div><h2>In diam nisi erat</h2><p>Vestibulum porta mattis quam a gravida. Aliquam quis quam nulla. Pellentesque nisi erat, elementum ut maximus sed, euismod vel dui. Curabitur vitae dui nec justo rhoncus imperdiet. In diam risus, egestas dictum viverra vel, pretium sed nisl.</p></div>"
        */
    },
    {
        bg: "03.jpg",
        /*
        content: "<div><h2>Nullam ullamcorper</h2><p>Lorem ipsum scelerisque, urna imperdiet faucibus facilisis, orci elit dictum ex, blandit rutrum metus diam eget mi. Phasellus ullamcorper eu purus eu venenatis. Nullam vestibulum dolor vel felis facilisis, ac rutrum tellus rutrum. Sed vehicula elementum erat, eget mattis tortor rhoncus a. Phasellus ut nisl volutpat, dictum lectus ut, dictum lectus.</p><h2>Quisque pharetra</h2><p><img src='img/03.jpg' height='150' align='right' />Vestibulum porta mattis quam a gravida. Aliquam quis quam nulla. Pellentesque nisi erat, elementum ut maximus sed, euismod vel dui. Curabitur vitae dui nec justo rhoncus imperdiet. In diam risus, egestas dictum viverra vel, pretium sed nisl. Nam mi risus, imperdiet sed purus id, posuere feugiat nibh. Curabitur dignissim ipsum eget est pretium, ut luctus leo dapibus. Praesent in vehicula felis. Nullam quis faucibus justo. Praesent ac turpis tempor elit lobortis sodales laoreet pulvinar velit. Nam vel leo arcu. Cras magna nulla, lacinia in turpis vel, efficitur sagittis sem. Interdum et malesuada fames ac ante ipsum primis in faucibus. In congue consectetur lacus, ac venenatis elit gravida at.</p></div>"
        */
    },
    {
        bg: "04.jpg",
        /*
        content: "<div><h2>Quisque pharetra</h2><p>Quisque eget aliquam urna. Integer tortor justo, fringilla ut diam in, ornare maximus sem. Nam dictum mauris nisl, id posuere sem sodales sit amet. Sed ac nulla ut nunc molestie vestibulum id a ipsum. Duis nec elementum libero. Etiam porttitor turpis eu venenatis porta. Quisque pharetra diam vel dui porta luctus. Duis sed odio viverra, molestie orci et, hendrerit erat. Duis ut tortor nec orci suscipit finibus nec non massa. </p></div>"
        */
    }
],


In the HTML, we'll add some stuff. First, embed a div into contentContainer and give it a class of louverContentContainer. Then embed another div within that div, and give it a class of louverContent. Then move the divs content0 and content1 inside it.
<div id="viewport">
    <div id="contentContainer">
        <div class="louverContentContainer">
            <div class="louverContent">
                <div id="content0" class="content"></div>
                <div id="content1" class="content"></div>
            </div>
        </div>
    </div>
</div>


Then remove the ids. Make the divs styled by content0 and content1 instead.
<div class="louverContentContainer">
    <div class="louverContent">
        <div class="content content0"></div>
        <div class="content content1"></div>
    </div>
</div>


Fill contentContainer with nine other copies of what we just did.
<div id="viewport">
    <div id="contentContainer">
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
        <div class="louverContentContainer">
            <div class="louverContent">
                <div class="content content0"></div>
                <div class="content content1"></div>
            </div>
        </div>
    </div>
</div>


Now we're all set! Well, kind of. We need to add styling for all these new elements. There are ten louvers, so each louver will take up 10% of the container's total width. Create louverContentContainer, and set the width to 10% and height to 100%. Make the background-color brown (or whatever color, it won't matter in the end) float the thing left, and set the overflow property to hidden.


#contentContainer
{
    width: 100%;
    height: 100%;
    background-color: #004400;
}

.louverContentContainer
{
    width: 10%;
    height: 100%;
    background-color: #440000;
    float: left;
    overflow: hidden;
}

.content
{
    width: 50%;
    height: 100%;
    float: left;
    background-size: 100% 100%;
    background-repeat: no-repeat;
    padding-top: 1em;               
}


Then create louverContent. Width will be twice the size of its parent, so set it to 200%. Height is 100%. Why, you may ask? Because we're prepping each div styled using louverContent to slide from left to right simultaneously. Instead of one big div as in the Slide Carousel, we want these guys to slide in louvers.
#contentContainer
{
    width: 100%;
    height: 100%;
    background-color: #004400;
}

.louverContentContainer
{
    width: 10%;
    height: 100%;
    background-color: #440000;
    float: left;
    overflow: hidden;
}

.louverContent
{
    width: 200%;
    height: 100%;
}

.content
{
    width: 50%;
    height: 100%;
    float: left;
    background-color: #000044;
    background-size: 100% 100%;
    background-repeat: no-repeat;
    padding-top: 1em;               
}


Take a look at what's going on here. You'll see that it's deep blue beause that's the background color we set for content. But if we remove the background color of content, then it should default to brown, which is its parent's background color.


And here's why you don't need background-position for the content CSS class. Because we're going to use the nth-of-type pseudoselector on louverContentContainer, cascading the property down to louverContent and content.
#contentContainer
{
    width: 100%;
    height: 100%;
    background-color: #004400;
}

.louverContentContainer
{
    width: 10%;
    height: 100%;
    float: left;
    overflow: hidden;
}

.louverContent
{
    width: 200%;
    height: 100%;
}

.content
{
    width: 50%;
    height: 100%;
    float: left;
    background-color: #000044;
    background-size: 100% 100%;
    background-repeat: no-repeat;
    padding-top: 1em;               
}

.louverContentContainer:nth-of-type(1) .louverContent .content
{
        background-position: 0% 50%;
}

.louverContentContainer:nth-of-type(2) .louverContent .content
{
        background-position: 10% 50%;
}

.louverContentContainer:nth-of-type(3) .louverContent .content
{
        background-position: 20% 50%;
}

.louverContentContainer:nth-of-type(4) .louverContent .content
{
        background-position: 30% 50%;
}

.louverContentContainer:nth-of-type(5) .louverContent .content
{
        background-position: 40% 50%;
}

.louverContentContainer:nth-of-type(6) .louverContent .content
{
        background-position: 50% 50%;
}

.louverContentContainer:nth-of-type(7) .louverContent .content
{
        background-position: 60% 50%;
}

.louverContentContainer:nth-of-type(8) .louverContent .content
{
        background-position: 70% 50%;
}

.louverContentContainer:nth-of-type(9) .louverContent .content
{
        background-position: 80% 50%;
}

.louverContentContainer:nth-of-type(10) .louverContent .content
{
        background-position: 90% 50%;
}   


Got all that? Great!

We've pretty much fxed up the HTML and CSS. You won't see much now, but hopefully when we get to the JavaScript, things will make more sense.

Next

JavaScript for the Louver Carousel, coming right up!

Thursday, 3 September 2020

Web Tutorial: AngularJS Password Strength Validator, Redux

Two years have passed since I last walked you through the AngularJS Password Strength Validator, and quite a few things have changed. For one, AngularJS isn't quite as hot as it used to be, and this particular version that was in use, is severely outdated. For another, the API that we were using for this was upgraded and no longer works quite the same way.

So today we will be revisiting this piece of code, and making some improvements!

The API

Remember how we set the code to make a dictionary check every time text was changed? Well, as it turns out, this was clumsy as hell due to being an asynchronous operation. Since I've upgraded my account over at Oxford Dictionaries and now need to pay whenever somebody uses my account to make that API call, it only makes sense not to be quite so generous with these calls.

For starters, let's remove the entire AJAX call from the processPassword() function. Leave the call to the getWords() function; that'll come in useful later.

js/main.js
$scope.processPassword=
function()
{
    if ($scope.enteredPassword.length == 0) return;

    if ($scope.enteredPassword.length < 8)
    {
        $scope.strengthCode = "weak";
        $scope.strengthMessage = "Password is too short. 8 characters or above recommended.";
        return;
    }

    var pts = 1;
    $scope.strengthMessage = "";

    if (/[~`!#$%\^&@*+=\-\[\]\\';,/{}|\\":<>\?]/g.test($scope.enteredPassword))
    {
        pts ++;
    }
    else
    {
        $scope.strengthMessage += "Try using special characters in your password.\n";
    }

    if (/[A-Z]/g.test($scope.enteredPassword))
    {
        pts ++;
    }
    else
    {
        $scope.strengthMessage += "Try using a mix of uppercase letter and lowercase letters.\n";
    }

    if (/[0-9]/g.test($scope.enteredPassword))
    {
        pts ++;
    }
    else
    {
        $scope.strengthMessage += "Try including numbers in your password.\n";
    }

  
    var possibleWords = getWords($scope.enteredPassword);
    /*
    if (possibleWords.length > 0)
    {
        var xmlhttp = new XMLHttpRequest();
                xmlhttp.onreadystatechange = function()
        {
                        if (this.readyState == 4 && this.status == 200)
            {console.log(this.responseText);
                var result = JSON.parse(this.responseText);
                if (result.wordsFound)
                {
                    pts --;
                    $scope.strengthMessage += "Avoid using dictionary words.\n";   
                    $scope.strengthCode = getCode(pts);
                    return;
                }
                        }
                };

        xmlhttp.open("POST", "validate.php", true);
        xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xmlhttp.send("words=" +  JSON.stringify(possibleWords) + "&url=https://od-api.oxforddictionaries.com/api/v1/entries/en/");
    }
    else
    {
        $scope.strengthCode = getCode(pts);
        return;
    }
    */

    $scope.strengthCode = getCode(pts);
    return;
};


And then move the entire AJAX call to application scope. We'll call this new function checkDictionary(). Note that we'll still call getWords() here.

js/main.js
function getCode(pts)
{
    if (pts <= 0) return "weak";   
    if (pts == 1) return "moderate";   
    if (pts == 2) return "strong";   
    if (pts >= 3) return "excellent";
}

$scope.checkDictionary =
function()
{
    var possibleWords = getWords($scope.enteredPassword);

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function()
    {
        if (this.readyState == 4 && this.status == 200)
        {
            var result = JSON.parse(this.responseText);
            if (result.wordsFound)
            {
                pts --;
                $scope.strengthMessage += "Avoid using dictionary words.\n";   
                $scope.strengthCode = getCode(pts);
                return;
            }
        }
    };

    xmlhttp.open("POST", "validate.php", true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.send("words=" +  JSON.stringify(possibleWords) + "&url=https://od-api.oxforddictionaries.com/api/v1/entries/en/");
}

$scope.processPassword =
function()
{


Remove these lines for now, won't need them. Also, change the API url.

js/main.js
$scope.checkDictionary =
function()
{
    var possibleWords = getWords($scope.enteredPassword);

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function()
    {
        if (this.readyState == 4 && this.status == 200)
        {
            var result = JSON.parse(this.responseText);
            if (result.wordsFound)
            {
                //pts --;
                //$scope.strengthMessage += "Avoid using dictionary words.\n";   
                //$scope.strengthCode = getCode(pts);
                //return;
            }
        }
    };

    xmlhttp.open("POST", "validate.php", true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.send("words=" +  JSON.stringify(possibleWords) + "&url=https://od-api.oxforddictionaries.com:443/api/v2/entries/en-us/");
}


Now add a new scope variable, showDictionaryButton. By default, its value is false.

js/main.js
$scope.strengthCode = "";
$scope.strengthMessage = "";
$scope.enteredPassword = "";
$scope.showDictionaryButton = false;


Remember in processPassword() we removed the AJAX call but left the call to getWords()? Well, after that call, check if the length of possibleWords is more than 0; and set showDictionaryButton accordingly.

js/main.js
    var possibleWords = getWords($scope.enteredPassword);

    $scope.showDictionaryButton = (possibleWords.length > 0);

    $scope.strengthCode = getCode(pts);
    return;
};

$scope.strengthCode = "";
$scope.strengthMessage = "";
$scope.enteredPassword = "";
$scope.showDictionaryButton = false;


Now in the front-end, add a button. When clicked. it calls checkDictionary().
index.html
<input type="text" id="txtPassword" ng-change="processPassword()" ng-model="enteredPassword">
<button ng-click="checkDictionary()">Dictionary Check</button>


But wait! Unless showDictionaryButton is true, we want to disable it. Because showDictionaryButton is true only if there are possible words, remember? So we want to enable this button only if there are words to check.
<button ng-disabled="!showDictionaryButton" ng-click="checkDictionary()">Dictionary Check</button>


So now we have a disabled button. Yay. But the code should still work.


Let's test this with input!


If you enter a series of five letters or more, the button should be enabled.


Back to the JavaScript, let's correct this Regular Expression. I somehow forgot to cater for underscores. Imagine that.
js/main.js
if (/[~`!#$%\_\^&@*+=\-\[\]\\';,/{}|\\":<>\?]/g.test($scope.enteredPassword))
{
    pts ++;
}
else
{
    $scope.strengthMessage += "Try using special characters in your password.\n";
}


Now let's go to the back-end code. This thing was hideously inefficient because it insisted on going through the entire list of words provided even though we only needed to check that at least one of these words was found via the API. We'll also improve it by returning the word found.

Add the variables response and word.

validate.php
$url = $_POST["url"];
$words = json_decode($_POST["words"]);
$wordsFound = false;
$wordToValidate = "";
$response = "";
$word = "";

for ($i = 0; $i < sizeof($words); $i++)
{
    $wordToValidate = $words[$i];
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);

    curl_setopt_array($curl, array(
    CURLOPT_URL => $url . $wordToValidate,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_HTTPHEADER => array(
    "app_id: 958ab193",
    "app_key: 60d5994148ce1699defd2194dd9c25b9"
    ),
    ));

    $response = curl_exec($curl);

    if (strpos($response, "404") === false) $wordsFound = true;

    curl_close($curl);
}

$result = array("wordsFound" =>$wordsFound);
echo json_encode($result);


Remove this entire If block. Because it's simplistic and just not very good.

validate.php
$response = curl_exec($curl);

//if (strpos($response, "404") === false) $wordsFound = true;

curl_close($curl);


Instead, run response through the json_decode() function and then attach the result to res.

validate.php
$response = curl_exec($curl);

//if (strpos($response, "404") === false) $wordsFound = true;

$res = json_decode($response);

curl_close($curl);


Check if res has the "id" key using the array_key_exists() function.

validate.php
$response = curl_exec($curl);

//if (strpos($response, "404") === false) $wordsFound = true;

$res = json_decode($response);

if (array_key_exists("id", $res))
{

}

curl_close($curl);


If so, set wordsFound to true and word to the value of the id key in res.

validate.php
$response = curl_exec($curl);

//if (strpos($response, "404") === false) $wordsFound = true;

$res = json_decode($response);

if (array_key_exists("id", $res))
{
    $wordsFound = true;
    $word = $res->id;
}

curl_close($curl);


And finally, use the break statement to end the For loop processing. You've already found one word and that was all we really wanted, right?

validate.php
if (array_key_exists("id", $res))
{
    $wordsFound = true;
    $word = $res->id;
    break;
}


At the end of it, we want to return the word as well.

validate.php
$result = array("wordsFound" => $wordsFound, "word" => $word);
echo json_encode($result);


Now back to the AJAX call, we're expecting both wordsFound and word. So if wordsFound is true, use the alert() function to display the word. And if not, just say no words were found.

js/main.js
$scope.checkDictionary =
function()
{
    var possibleWords = getWords($scope.enteredPassword);

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function()
    {
        if (this.readyState == 4 && this.status == 200)
        {
            var result = JSON.parse(this.responseText);
            if (result.wordsFound)
            {
                //pts --;
                //$scope.strengthMessage += "Avoid using dictionary words.\n";   
                //$scope.strengthCode = getCode(pts);
                //return;

                if (result.wordsFound)
                {
                    alert("'" + result.word + "' found in dictionary!");
                }
                else
                {
                    alert("No word found!");
                }
            }
        }
    };

    xmlhttp.open("POST", "validate.php", true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.send("words=" +  JSON.stringify(possibleWords) + "&url=https://od-api.oxforddictionaries.com:443/api/v2/entries/en-us/");
}


Don't forget to set showDictionaryButton here. We want to disable the button as the API is being called, so that the user can't keep clicking and making a flurry of API calls (and costing me money for no good reason).

js/main.js
$scope.checkDictionary =
function()
{
    var possibleWords = getWords($scope.enteredPassword);

    $scope.showDictionaryButton = false;

    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function()
    {
        if (this.readyState == 4 && this.status == 200)
        {
            var result = JSON.parse(this.responseText);
            if (result.wordsFound)
            {
                //pts --;
                //$scope.strengthMessage += "Avoid using dictionary words.\n";   
                //$scope.strengthCode = getCode(pts);
                //return;

                if (result.wordsFound)
                {
                    alert("'" + result.word + "' found in dictionary!");
                }
                else
                {
                    alert("No word found!");
                }
            }
        }
    };

    xmlhttp.open("POST", "validate.php", true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.send("words=" +  JSON.stringify(possibleWords) + "&url=https://od-api.oxforddictionaries.com:443/api/v2/entries/en-us/");
}


Enter a series of five letters or more. The button should be enabled. Then click the button.


Search was successful!


And if we do this...


Search was not successful!


Thanks for reading!

I don't recommend doing back to this version of AngularJS, of course. But the point today was to update the API and improve the code. The framework used is really secondary.

May your search not be in vain,
T___T