Showing posts with label errata. Show all posts
Showing posts with label errata. Show all posts

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

Monday, 28 January 2019

Web Tutorial: Nike Meme Generator Redux

Dear readers!

Remember that awesome little Nike Meme Generator I made late last year? Well, it's come to my attention that there's a teeny little flaw in its execution. This little sucker works fine with some pics... but when you use a picture that has a great deal of white in it, the contrast totally blows!

Take this lovely picture for example. I downloaded it off the net somewhere. It's just meant as an example, so please don't sue me.

Serena Williams in all
her athletic glory.

See what I mean? Some of the white text just doesn't show up well.


Today, we're going to rectify this issue. Because while I'm not a perfectionist, this strikes me as sloppy. The fix is pretty simple, actually. So here goes...

We start by adding a div tag around the paragraph tags, like so. This is meant to be an overlay of sorts.
<div id="memeContainer">
            <div>
                <p style="margin-top:50%"><?php echo $line1;?><br /><?php echo $line2;?></p>
                <p style="margin-top:30%"><img src="nikelogo.png"> <?php echo $slogan;?></p>
            </div>
</div>


Then we style this thing. It will fill the full width and height of the memecontainer class, with the position property set to absolute. Then we give it a translucent black background.
            #memeContainer
            {
                width: 500px;
                height: 500px;
                padding: 5px;
                margin: 5px;
                float: left;
                outline: 1px solid #DDDDDD;
                background: url(<?php echo "uploads/" . $filecode . "." . $filetype; ?>) center center no-repeat;
                background-size: cover;
                font-family: georgia;
                color: #FFFFFF;
                font-size: 25px;
                -webkit-filter: grayscale(100%);
                filter: grayscale(100%);
                text-align: center;
            }

            #memeContainer div
            {
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(0, 0, 0, 0.5);
            }


Refresh. This is how it should look like.


And now, with the same pic... wunderbar!


Print version!


Remember kids... life is not all black and white!
T___T

Thursday, 14 July 2016

Progress, Not Perfection

Recently, I was reminded of something called The Principle of Good Enough. Several reminders, to be exact.

This was when I got a popup notification from Microsoft asking me to upgrade to Windows 10.

Yes yes, we know.


Before that, it was in the form of an announcement that Microsoft support for Windows 8 would cease in 2018.

Shortly before that, it was in the form of a similar announcement for Windows 7.

And before that, around April 2014, yet another announcement for Windows XP.

What's all this got to do with The Principle of Good Enough, you may ask? Basically, that technology is all about progress. Progress, not perfection.

Hold on, I'm a perfectionist...

Whoa, buddy. Let me stop you right there. See, the problem I have with developers claiming to be "perfectionists" is that it's at best an extreme stance couched as a positive trait, and at worst, blatant self-praise.

Most of it misguided because, I say again, technology is all about progress, not perfection. Perfectionism is irrelevant, perhaps even poisonous, to the process.

Were any of the Windows Operating Systems "perfect"? From the ugly but serviceable Windows 3.11, to the monstrosity known as Windows Vista, to the latest iteration in the form of Windows 10? If they were "perfect", why the need for different versions? Why the need for patches?

And why, oh why, did Microsoft not wait till each version was at least bug-free before releasing to the public?

Because in technology, perfection is unprofitable.

Firstly, let's establish that there is no such thing as "perfect" unless all you're peddling is a "Hello World" kiddy script - perfect and utterly useless.

Perfectly useless.

There will always be bugs. There will always be security loopholes. But if Microsoft waited for all these to be ironed out before shipping each version, they wouldn't have sold a damn thing since 1985. Also, if you make something you consider "perfect" today, emerging technology will render it obsolete in a year.

This brings me to my second point...

Your company does not want perfection.

The duty of the developer is not to produce "perfect" software, but to produce software that will not exceed the pain threshold of their target market. Hence the Principle of Good Enough. If the user will pay for the product and not kick up too much of a stink when the inevitable bugs surface, it's "good enough". No one, not least the people paying your wages, have the time to wait for everything to be absolutely perfect before shipping.

But before this principle leads you to think you can get away with murder, think again. Just making a product shippable is a sizeable task on its own. If the product is so bug-ridden even at basic usability levels that no one will touch it with a ten-foot pole, then it is, quite simply, not good enough.

That is why, despite the Principle of Good Enough, testing is still relevant, and more important than ever.

So, perfectionists...

Remember that your first duty is to ship a viable product - whether it's an operating system, a web portal or a mobile game. Quality takes time - time that your superiors may not want to invest in. If an imperfect but serviceable system will pay the wages of your entire team for six months upon release whereas a perfect system will take another six months (while earning the company squat), which do you think upper management is likely to prefer?

Learn to look at the bigger picture. The business is far, far larger than you, your code or your principles.

Which brings to mind something I read in another blogpost, titled Don't Call Yourself A Programmer, And Other Career Advice. Now I don't always agree with McKenzie, but this hit home for me. This wasn't some airy-fairy idealism - this was real, hardcore pragmatism.
Producing beautiful software is not a goal.  Solving complex technical problems is not a goal.  Writing bug-free code is not a goal.  Using sexy programming languages is not a goal.  Add revenue.  Reduce costs.  Those are your only goals.
- Patrick McKenzie


Errata

In a previous blogpost, I provided a very shallow look at the difference between programmers and web developers.

Programmers often criticize web developers for not being thorough enough and being too willing to take shortcuts in order to get the job done, and lack of long-term vision. That's not wrong, but look at the context. Software is made to last, therefore programmers are allowed - encouraged, even - to do a proper, complete job.


Obviously, now that I consider this new evidence, I was mistaken.

Ah well, nobody's perfect.
T___T

Saturday, 23 April 2016

Web Tutorial: Wheel of Fortune, Safari Edition

It appears that my Wheel of Fortune web tutorial met some... misfortune.

Sometime after I had released the code, I happened to test it on Safari. And this is what I got.


Oh, the horror.

Despite the fact that I had conscientiously written WebKit equivalents of the CSS classes and specifications, Safari has this bug where the overflow property does not play nice with the border-radius property. And that sucks because there are some truly interesting effects to be enjoyed when these properties are combined.

That said, I can only blame myself for not checking that the code works on Safari and Opera. Most of the time, it's Firefox and Chrome. I don't test on Internet Explorer because, well, fuck IE.

What's the Safari fix, then?

Well, I'm sure given enough time and effort, one could find a way to break through this wall. But a good developer also entertains the notion of going around this wall. Which is precisely what we're going to do today.

First, add the CSS specification for safari_fix.
            #wheel_wrapper
            {
                width:500px;
                height:500px;
                position:relative;
                margin:0px auto 400px auto;
            }

            #safari_fix
            {
                width:500px;
                height:500px;
                position:absolute;
                z-index:100;
                margin-left:-120px;
                margin-top:-120px;
                border:120px solid #000000;
                border-radius:50%;
            }


Here are what the properties do...
- width:500px, height:500px, border-radius:50%; to create a circular div the same size as the wheel_wrapper div.
- position:absolute, z-index:100 because we want to overlap wheel_wrapper.
- border: 120px solid #00000 to create a very thick black border around the div. Why black? That's so you can see it better.
- margin-left and margin-top properties have been set to -120px in order to offset the positioning effects of having a 120 pixel-wide border.

Now create a div with the id safari_fix and put in inside wheel_wrapper.
        <div id="wheel_wrapper">
            <div id="safari_fix">
       
            </div>   
            <div class="semicircle_wrapper wheel2">
                <div class="segment_wrapper semicircle2">
                    <div class="segment7 segment">


Here's what you should have. A huge black God-awful ugly border around your wheel, hiding all the even uglier jagged corners. Try your code. Make sure everything works.



Now change the border color to match your background.
            #safari_fix
            {
                width:500px;
                height:500px;
                position:absolute;
                z-index:100;
                margin-left:-120px;
                margin-top:-120px;
                border:120px solid #FFFFFF;
                border-radius:50%;
            }


Voila! A cheap and dirty fix for Safari.



Not the best solution, but...

Yep, this is not the most elegant solution you could have. If you ever change the background color, you'll have to change the color of the border of your safari_fix div as well. But if it works, it works. Until I have a better solution, this stays.

Seeya around,
T___T