Showing posts with label AJAX. Show all posts
Showing posts with label AJAX. Show all posts

Sunday, 9 May 2021

Your AJAX Starter Kit, WordPress edition (Part 2/2)

I'm back!

Implementing AJAX, of course, requires a lot more than just JavaScript. We've done the front-end, now we need a back-end. AJAX in WordPress has its own implementation, via admin-ajax.php. To do this, we need to pass the URL of that location, like so.

plugins/tt_ajax_test/tt_ajax.php
<?php
/**
 * Plugin Name: AJAX Test
 * Plugin URI: http://www.teochewthunder.com
 * Description: This plugin implements and tests AJAX functionality
 * Version: 1.0.0
 * Author: TeochewThunder
 * Author URI: http://www.teochewthunder.com
 * License: GPL2
 */

function tt_ajax_listener() {
    wp_register_script( 'ajax_listener', plugin_dir_url( __FILE__ ) . '/js/tt_ajax_listener.js', ['jquery'] );
    wp_localize_script( 'ajax_listener', 'objAjax', ['ajaxurl' => admin_url( 'admin-ajax.php' )]);        

    wp_enqueue_script( 'ajax_listener' );
}

add_action( 'wp_enqueue_scripts', 'tt_ajax_listener' );


Then have some back-end code, defined in a function. In this case, it will take a string passed in via a POST, and use it as the format for a date() function. And most importantly, encode the output using json_encode(), then echo it.

plugins/tt_ajax_test/tt_ajax.php
<?php
/**
 * Plugin Name: AJAX Test
 * Plugin URI: http://www.teochewthunder.com
 * Description: This plugin implements and tests AJAX functionality
 * Version: 1.0.0
 * Author: TeochewThunder
 * Author URI: http://www.teochewthunder.com
 * License: GPL2
 */

function tt_ajax_listener() {
    wp_register_script( 'ajax_listener', plugin_dir_url( __FILE__ ) . '/js/tt_ajax_listener.js', ['jquery'] );
    wp_localize_script( 'ajax_listener', 'objAjax', ['ajaxurl' => admin_url( 'admin-ajax.php' )]);        

    wp_enqueue_script( 'ajax_listener' );
}

add_action( 'wp_enqueue_scripts', 'tt_ajax_listener' );

function tt_ajax_call() {
    $date = date($_POST['format'], strtotime('now'));
    echo json_encode(['date' => $date]);
    die();
}


After that,we use add_action() to add this function to the hooks wp_ajax_tt_ajax_call and wp_ajax_nopriv_tt_ajax_call, for logged in and anonymous access respectively. What happens is that every AJAX call is registered in a named variable at runtime, and we just did it for tt_ajax_call(). I'm aware that this isn't a great way of explaining it, but you get my drift...

plugins/tt_ajax_test/tt_ajax.php
<?php
/**
 * Plugin Name: AJAX Test
 * Plugin URI: http://www.teochewthunder.com
 * Description: This plugin implements and tests AJAX functionality
 * Version: 1.0.0
 * Author: TeochewThunder
 * Author URI: http://www.teochewthunder.com
 * License: GPL2
 */

function tt_ajax_listener() {
    wp_register_script( 'ajax_listener', plugin_dir_url( __FILE__ ) . '/js/tt_ajax_listener.js', ['jquery'] );
    wp_localize_script( 'ajax_listener', 'objAjax', ['ajaxurl' => admin_url( 'admin-ajax.php' )]);        

    wp_enqueue_script( 'ajax_listener' );
}

add_action( 'wp_enqueue_scripts', 'tt_ajax_listener' );

function tt_ajax_call() {
    $date = date($_POST['format'], strtotime('now'));
    echo json_encode(['date' => $date]);
    die();
}

add_action("wp_ajax_nopriv_tt_ajax_call", "tt_ajax_call");
add_action("wp_ajax_tt_ajax_call", "tt_ajax_call");


The AJAX call is pretty much what was presented in Your AJAX Start Kit, jQuery edition. For added security, we could implement a nonce, but I'm going with the bare minimum here. In here, we pass in a test string, to this URL. This references the function I just created in the backend. For data, I will pass in "j M Y H:i:s".

plugins/tt_ajax_test/js/ajax_listener.js
jQuery(document).ready(function(){
    if (window.location.href.indexOf("ajax-test") > -1) {
        jQuery('#btnAjax').click(()=>{
            jQuery.ajax({
                type : "post",
                dataType : "json",
                url : objAjax.ajaxurl,
                data : {action: "tt_ajax_call", format: "j M Y H:i:s"},
                success: function(data, textStatus, jqXHR) {

                },
                error: function(err) {
                    alert('Error');
                }
            });        
        });

    
        alert('test js');
    }
});


Once the data is returned, I populate the placeholder with the results. Oh yes, and I'll remove the earlier test command to remove the annoying pop-up.

plugins/tt_ajax_test/js/ajax_listener.js
jQuery(document).ready(function(){
    if (window.location.href.indexOf("ajax-test") > -1) {
        jQuery('#btnAjax').click(()=>{
            jQuery.ajax({
                type : "post",
                dataType : "json",
                url : objAjax.ajaxurl,
                data : {action: "tt_ajax_call", format: "j M Y H:i:s"},
                success: function(data, textStatus, jqXHR) {
                    jQuery('.tt_placeholder').html(data.date);
                },
                error: function(err) {
                    alert('Error');
                }
            });        
        });
    
        //alert('test js');
    }
});


Now click the button, and we should see the current date in the specified format. Every time you click, the format should change!


Finally!

That seemed simple enough.

AJAX isn't really that straightforward in WordPress. But now that I've documented which hoops to jump through, hopefully it's a lot easier from this point forward.

(Word)Press on,
T___T

Thursday, 6 May 2021

Your AJAX Starter Kit, WordPress edition (Part 1/2)

AJAX, or if you want to be unnecessarily verbose, Asynchronous JavaScript And XHTML, is simple enough to implement on its own. In the past, I've performed a few basic demos on how to implement AJAX, and even done a jQuery version. Today's post is analogous to the other two; except that there's a little complication.

Because we'll be doing it in WordPress.

For those of you who are blissfully unaware of what WordPress is, it's an open-source Content Management System used to develop websites and applications. However, the applications part would be pretty hard if a developer had no idea how to wrangle some AJAX out of WordPress. Having gone through the process myself, it's my opinion that this is not at all straightforward for the uninitiated. And what I would like to do today, is take you through the process of creating a plugin in WordPress which we can then use to implement AJAX.

This is going to have a few moving parts.

To begin, I will create a plugin in WordPress. This is really nothing more than creating a folder in the plugins folder of the wp-content directory, then creating a PHP file. I'm not going to elaborate on this; because this isn't a web tutorial and even if it were, that's not what it's about.

plugins/tt_ajax_test/tt_ajax.php
<?php
/**
 * Plugin Name: AJAX Test
 * Plugin URI: http://www.teochewthunder.com
 * Description: This plugin implements and tests AJAX functionality
 * Version: 1.0.0
 * Author: TeochewThunder
 * Author URI: http://www.teochewthunder.com
 * License: GPL2
 */


JavaScript

Now, before you can have AJAX, you first need to be able to run JavaScript. We will be creating a JavaScript file in the folder we just created. I'd prefer, as a matter of good practice, to put that file in a sub-folder, perhaps named js. Call it anything you like. We'll use jQuery for this. Use a ready() method to run our test command, which will simply pop up an alert message.

plugins/tt_ajax_test/js/ajax_listener.js
jQuery(document).ready(function(){
    alert('test js');
});


Then in the PHP file, we use add_action() to add this newly-created function tt_ajax_listener().
plugins/tt_ajax_test/tt_ajax.php
<?php
/**
 * Plugin Name: AJAX Test
 * Plugin URI: http://www.teochewthunder.com
 * Description: This plugin implements and tests AJAX functionality
 * Version: 1.0.0
 * Author: TeochewThunder
 * Author URI: http://www.teochewthunder.com
 * License: GPL2
 */

function tt_ajax_listener() {

}

add_action( 'wp_enqueue_scripts', 'tt_ajax_listener' );


This function basically calls wp_enqueue_script() to ensure that the JS file we just wrote, is included in the pages of the WordPress site. Before that, we also have to run wp_register_script() using the URL of the JavaScript file, and because we're going to use jQuery, we pass that in as an argument.

plugins/tt_ajax_test/tt_ajax.php
<?php
/**
 * Plugin Name: AJAX Test
 * Plugin URI: http://www.teochewthunder.com
 * Description: This plugin implements and tests AJAX functionality
 * Version: 1.0.0
 * Author: TeochewThunder
 * Author URI: http://www.teochewthunder.com
 * License: GPL2
 */

function tt_ajax_listener() {
    wp_register_script( 'ajax_listener', plugin_dir_url( __FILE__ ) . '/js/tt_ajax_listener.js', ['jquery'] );

    wp_enqueue_script( 'ajax_listener' );

}

add_action( 'wp_enqueue_scripts', 'tt_ajax_listener' );


Now when you refresh, this should come up. That's how you know it's working.

Of course, there's a possibility that we don't want this script to fire off just anywhere. So here's a really cheap way of making sure that it only fires off on specific pages. Basically, check for the URL and make sure it confirms to certain requirements. Now if you run the page again, you won't see anything.

Be warned that this is really not a great way to implement things, but this is just an example. A very clumsy example.

plugins/tt_ajax_test/js/ajax_listener.js
jQuery(document).ready(function(){
    if (window.location.href.indexOf("ajax-test") > -1) {    
        alert('test js');
    }
});


I'm creating a page here. This one is titled ajax-test, and in the screenshot, you can see the URL. And you can also see that the popup appears here because "ajax-test" is in the URL.

For this page, I added the following code via the built-in CMS.
<div>
    <div class="tt_placeholder">This is a placeholder</div>
    <input type="button" id="btnAjax" value="AJAX Call">
</div>



This ensures that now we have a button and a placeholder, with id and class attributes we can leverage on later.

All good? Moving on...

Now we have working JavaScript, and a test page from which to launch our AJAX call. All we're lacking now is more JavaScript to make that AJAX call, and back-end code to execute the command.

Next

Don't quit on me just yet! We will be implementing an AJAX call.

Wednesday, 18 September 2019

Spot The Bug: Internet Explorer Strikes Again!

Hey guys, the Spot The Bug wagon just rolled back into town, and it's time to kick some bug butt!

Die, bugs, die!

Cross-browser compatibility can be a nightmare. And every once in a while, I'm forcibly reminded of that fact.

So here I was, using a jQuery AJAX call to populate a table. The endpoint led to a PHP file (named, imaginatively, getdata.php) which was grabbing data from a MYSQL database.

index.html
        <script>
            $(document).ready
            (
                $.ajax
                (
                    {
                        url: "getdata.php?type=quotes",
                        type: "GET",
                        success: function(result)
                        {
                            var data = JSON.parse(result);

                            $(data.quotes).each
                            (
                                function(i, x)
                                {
                                    $("#tblQuotes").append("<tr><td style='text-align:right'><b>" + x.person + "</b></td><td><i>" + x.quote + "</i></td></tr>");                                                      
                                }
                            )
                        }
                    }
                )
            );
        </script>


All was fine and dandy, until I detected a typo around the last row, and fixed it in the database. See that I spelled "Linus" wrongly? It's an easy mistake to make, given that "s" and "x" are just about right nest next to each other and he did create Linux. Anyways...


I ran the code again in Chrome, everything seemed fine. But once I tried to do the same in Internet Explorer, the typo came back!

What went wrong

It certainly wasn't the PHP. I ran the file directly in the browser, and even in Internet Explorer, it produced the correct results. This was what it was sending back to the AJAX call. Or was it?

getdata.php
$result = array("quotes" => $techQuotes);
echo json_encode($result);




It seemed a little suspicious that this was only happening in Internet Explorer. And since the PHP wasn't at fault, the next moving part was the AJAX call. Which meant that it was a front-end problem, which in turn gelled with the fact that it was only happening in Internet Explorer.

Why it went wrong

Apparently, Internet Explorer caches all GET requests by default. Therefore, since the endpoint was the same, it simply recycled the previous data! Nice going, Microsoft!

How I fixed it

I just explicitly set caching to off, right there.

index.html
                $.ajax
                (
                    {
                        url: "getdata.php?type=quotes",
                        type: "GET",
                        cache: false,
                        success: function(result)
                        {
                            var data = JSON.parse(result);

                            $(data.quotes).each
                            (
                                function(i, x)
                                {
                                    $("#tblQuotes").append("<tr><td style='text-align:right'><b>" + x.person + "</b></td><td><i>" + x.quote + "</i></td></tr>");                                                      
                                }
                            )
                        }
                    }
                )


And presto! That was how it looked in both Chrome and Internet Explorer now.

Conclusion

You know in this day and age it's so easy to forget that web developers are at the mercy of the browsers of the end-users, and what an utter pain it is to ensure cross-browser compatibility. You think using jQuery will solve all your problems, and surprise, surprise, it really doesn't. Sometimes it introduces new ones.

Cache you later!
T___T

Tuesday, 19 June 2018

Web Tutorial: AngularJS Password Strength Validator (Part 2/2)

In the previous part, we implemented quite a few checks for password strength. Now, we're going to implement a Dictionary Check. This basically checks for any words in the password that can be found in the dictionary. This is important because most password-guessing attacks use a dictionary. So in order to be safe, you probably want to check the given password for such vulnerabilities.

There's a catch, though. Unless you're willing to host an entire dictionary database on your server, you'll have to use an API. The good news is, the Internet is full of these APIs. The one we're going to use today is at Oxford Dictionaries. We are going to use an AJAX call to this URL, passing in a series of strings that might be words, and the returned result will determine if any of the strings you sent, are actually dictionary words.

So, first, register an account and obtain a key.


Next, create a file, validate.php. In there, obtain the POST variables, url and words, and assign them to local variables. words will be a JSON array, so we need to run it through the json_decode() function.

Then declare two variables, wordsFound (which defaults to false) and wordToValidate. This file will print out an array, result, which contains wordsFound in JSON format.

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

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


Next, implement a For loop to iterate through the words array. For each iteration, set wordToValidate to the current element of the words array.

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

for ($i = 0; $i < sizeof($words); $i++)
{
    $wordToValidate = $words[$i];
}

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


We'll use cURL to access the URL. First, we have to initialize an object using the curl_init() function. Then we need to disable verification. Note that this is not recommended in a live environment - you need to verify stuff to ensure it's safe. But for the purposes of this exercise, sure, disable it.

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

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);
}

$result = array("wordsFound" =>$wordsFound, "word" => $wordToValidate);
echo json_encode($result);
?>


Now we use the curl_setopt_array() function, passing in the curl object and an array as arguments. And after that, we execute the curl_close() function on the curl object.

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

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()));

    curl_close($curl);
}

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


In the array, we include key-value pairs that will work as configuration. CURLOPT_URL will be set to the URL and the current word in the words array. The rest is pretty straightforward until you get to the last part, which is yet another array, CURLOPT_HTTPHEADER. This is where your id and key for the API are to be passed in. This is important. The API host is not going to let you use its services without some form of verification. No, my id and key aren't "xxxxx". Just get your own, already.

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

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: xxxxx",
    "app_key: xxxxx"
    ),
    ));

    curl_close($curl);
}

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


Now, run the function curl_exec() on the curl object, and return the result to the variable response.

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

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: xxxxx",
    "app_key: xxxxx"
    ),
    ));

    $response = curl_exec($curl);

    curl_close($curl);
}

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


The response will contain a "404" if no matches are found, and an array of matches otherwise. Now, we don't actually care how many matches there are - we just need to know that there is at least one match. So, if there's no "404" in the result, wordsFound is true. Not exactly ironclad logic, but it'll do for now.

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

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: xxxxx",
    "app_key: xxxxx"
    ),
    ));

    $response = curl_exec($curl);

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

    curl_close($curl);
}

$result = array("wordsFound" =>$wordsFound, "word" => $wordToValidate);
echo json_encode($result);
?>


Now, we'll want to work on the JavaScript some more. Let's start by dissecting the entered password and retrieving all the possible words of five letters and above from it. For this, we'll use the getWords() function which we'll create later. We'll pass in the scope variable enteredPassword, and the result will be the value of a new variable, possibleWords.

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

        var possibleWords = getWords($scope.enteredPassword)


Let's create the getWords() function. In this function, we declare the variable newArr as an empty array. At the end of the function, we will return newArr. We'll also create another array, arr, splitting the password by any non-alphanumeric characters. Thus, if you enter in a password like "teochew-thunder3pass@word", you'll get an array containing "teochew", "thunder", "pass" and "word".

js/main.js
    function getWords(password)
    {
        var arr = password.split(/[^A-Za-z]/);
        var newArr = [];

        return newArr;
    }

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


However, we only want words that are 5 characters and above. Because searching for words 4 letters and below would return too many positives. Thus, we iterate through the arr array, and push any result greater than 4 characters, into the newArr array. We also convert the pushed string to lowercase. Thus, if the array contained "teochew", "thunder", "pass" and "word", only "teochew" and "thunder" would get through.

js/main.js
    function getWords(password)
    {
        var arr = password.split(/[^A-Za-z]/);
        var newArr = [];

        for (var i = 0; i <arr.length; i++)
        {
            if (arr[i].length > 4)
            {
                newArr.push(arr[i].toLowerCase());
            }
        }

        return newArr;
    }


Next, we're going to get all possible words within the strings in newArr. Declare another variable, tempArr, as an empty array. Iterate through newArr with a For loop. The operation will only affect those strings that are longer than 5 characters, so put in a conditional for that.

js/main.js
    function getWords(password)
    {
        var arr = password.split(/[^A-Za-z]/);
        var newArr = [];

        for (var i = 0; i <arr.length; i++)
        {
            if (arr[i].length > 4)
            {
                newArr.push(arr[i].toLowerCase());
            }
        }

        var tempArr = [];

        for (var i = 0; i < newArr.length; i++)
        {
            if (newArr[i].length > 5)
            {

            }
        }

        return newArr;
    }


Now, this is how we process the strings that are greater than 5 characters. Say, for the string "teochew", we will derive the strings "teoch", "teoche", "teochew", "eoche", "eochew" and "ochew".

Next up is another nested For loop. The outer loop will start the search from strings beginning with the first three letters of the string "teochew", because starting the string with anything after those first three letters will net you a string of less than 5 characters.

The inner loop will process strings of 5 characters and above, to the maximum allowed by the length of the string "teochew".

js/main.js
    function getWords(password)
    {
        var arr = password.split(/[^A-Za-z]/);
        var newArr = [];

        for (var i = 0; i <arr.length; i++)
        {
            if (arr[i].length > 4)
            {
                newArr.push(arr[i].toLowerCase());
            }
        }

        var tempArr = [];

        for (var i = 0; i < newArr.length; i++)
        {
            if (newArr[i].length > 5)
            {
                for (var j = 0; j < newArr[i].length - 4; j++)
                {
                    for (var k = 5; k < newArr[i].length; k++)
                    {
               
                    }
                }
            }
        }

        return newArr;
    }


Thus, the sub-string is represented by newArr[i].substr(j, k). We then check if it's already in tempArr before pushing it in.

js/main.js
    function getWords(password)
    {
        var arr = password.split(/[^A-Za-z]/);
        var newArr = [];

        for (var i = 0; i <arr.length; i++)
        {
            if (arr[i].length > 4)
            {
                newArr.push(arr[i].toLowerCase());
            }
        }

        var tempArr = [];

        for (var i = 0; i < newArr.length; i++)
        {
            if (newArr[i].length > 5)
            {
                for (var j = 0; j < newArr[i].length - 4; j++)
                {
                    for (var k = 5; k < newArr[i].length; k++)
                    {
                        if (j + k <= newArr[i].length)
                        {
                            if (tempArr.indexOf(newArr[i].substr(j, k).toLowerCase()) == -1)
                            tempArr.push(newArr[i].substr(j, k).toLowerCase());   
                        }               
                    }
                }
            }
        }

        return newArr;
    }


After that, we use another For loop to iterate through tempArr. If newArr does not already contain the element in tempArr (this is possible because you may be processing multiple identical strings, for example, if the password was "teochew-teochew3pass@word"), then push that element into newArr.

js/main.js
    function getWords(password)
    {
        var arr = password.split(/[^A-Za-z]/);
        var newArr = [];

        for (var i = 0; i <arr.length; i++)
        {
            if (arr[i].length > 4)
            {
                newArr.push(arr[i].toLowerCase());
            }
        }

        var tempArr = [];

        for (var i = 0; i < newArr.length; i++)
        {
            if (newArr[i].length > 5)
            {
                for (var j = 0; j < newArr[i].length - 4; j++)
                {
                    for (var k = 5; k < newArr[i].length; k++)
                    {
                        if (j + k <= newArr[i].length)
                        {
                            if (tempArr.indexOf(newArr[i].substr(j, k).toLowerCase()) == -1)
                            tempArr.push(newArr[i].substr(j, k).toLowerCase());   
                        }               
                    }
                }
            }
        }

        for (var i = 0; i < tempArr.length; i++)
        {
            if (newArr.indexOf(tempArr[i].toLowerCase()) == -1)
            newArr.push(tempArr[i].toLowerCase());
        }

        return newArr;
    }


Now back to the processPassword() scope function!  Wrap the call to the getCode() function in an If statement. If the getWords() function had returned a non-empty array, we'll do more processing. If not, we end the function there with a return statement.

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

        if (possibleWords.length > 0)
        {

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


To process a non-empty array containing all the words you want to validate against the dictionary,  call some AJAX. Everything's pretty standard here. The file you are sending the POST to, is the validate.php file we wrote earlier. You pass in arguments such as a JSON string of the words, and the URL where your API resides.

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

        if (possibleWords.length > 0)
        {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.onreadystatechange = function()
            {
                if (this.readyState == 4 && this.status == 200)
                {

                }
            };

            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;
        }



Next, you use the parse() method of the JSON object on the returned response. If wordsFound is true, then decrement pts, set the strengthMessage and strengthCode variables, and return.

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

        if (possibleWords.length > 0)
        {
            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. (" + result.word + ")\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;
        }


Try it!


Something seems to have gone wrong... and I'm getting a warning from oxforddictionaries.com!




Yeah, that's to be expected. If you're a cheap bastard like me, you got the free version of the API, which means you can only make that many calls to the API within one minute. If you tested with an extra long password, that would have sent a gazillion requests to the API.

But the logic for this validation is sound... I think.

Till we meet again, stay STRONG.
T___T

Tuesday, 3 April 2018

Web Tutorial: Multilingual Easter Form (Part 2/3)

Now for the second part of this web tutorial - switching languages. We already learned how to do this last February, but this time it's going to be partially front-end. In fact, the AJAX portion of the code will still be used - we're just going to change the way content is being updated.

Go to the JavaScript file. Comment out the line that causes the page to reload, because we don't want to page to reload. Now test your code. Change to another language by using the drop-down list. Then refresh the page manually. Does it stay in that language? Yes? That's because we haven't removed the AJAX code that sets the cookie which determines what language the site is currently in.

assets\javascripts\application.js
function changeLang(lang)
{
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            //location.reload();
        }
    };
    xmlhttp.open("GET", "../../langs/index/" + lang, true);
    xmlhttp.send();
}


Instead of reloading the page, we'll set it to run the changeContent() function. And then let's define the changeContent() function. It will accept a parameter, lang.

assets\javascripts\application.js
function changeLang(lang)
{
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
            //location.reload();
            changeContent(lang);
        }
    };
    xmlhttp.open("GET", "../../langs/index/" + lang, true);
    xmlhttp.send();
}

function changeContent(lang)
{

}


Time for some JSON! First, declare an array labels.

assets\javascripts\application.js
function changeContent(lang)
{
    var labels = [];
}


Next, fill up the array with an object. The name property of that object will be the class name of the placeholder you are going to change content for.

assets\javascripts\application.js
    var labels =
    [
        {
            "name" : "lblTitle",           
        }
    ]


Next, set the content property of the object. It will be an array with two objects.

assets\javascripts\application.js
    var labels =
    [
        {
            "name" : "lblTitle",
            "content" :
            [
                {

                },   
                {

                }
            ]           
        }
    ]


Each object will have two properties - lang and val. lang specifies the language, and val is the translated content of that language. So in this case, the object with name lblTitle has English set to "Happy Easter 2018!" and Chinese set to "2018复活节快乐!"

assets\javascripts\application.js
    var labels =
    [
        {
            "name" : "lblTitle",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Happy Easter 2018!"
                },   
                {
                    "lang" : "cn",
                    "val" : "2018复活节快乐!"
                }
            ]           
        }
    ]


Now, we've set the language content of one label. Let's write the code to change the content. First, use a For loop to iterate through the labels array.

assets\javascripts\application.js
    var labels =
    [
        {
            "name" : "lblTitle",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Happy Easter 2018!"
                },   
                {
                    "lang" : "cn",
                    "val" : "2018复活节快乐!"
                }
            ]           
        }
    ]

    for (var i = 0; i < labels.length; i++)
    {

    }


Next, define an array, langLabels, by getting all elements with the same class name as the current element's name property.

assets\javascripts\application.js
    for (var i = 0; i < labels.length; i++)
    {
        var langLabels = document.getElementsByClassName(labels[i].name);
    }


The iterate through langLabels...

assets\javascripts\application.js
    for (var i = 0; i < labels.length; i++)
    {
        var langLabels = document.getElementsByClassName(labels[i].name);

        for (var j = 0; j < langLabels.length; j++)
        {

        }
    }


And use the filter() method on the content array in that object to get only the elements in the content array whose lang property corresponds with the value of the lang parameter. Set this to the array content.

assets\javascripts\application.js
    for (var i = 0; i < labels.length; i++)
    {
        var langLabels = document.getElementsByClassName(labels[i].name);

        for (var j = 0; j < langLabels.length; j++)
        {
            var content = labels[i].content.filter
            (
                function (x)
                {
                    return x.lang == lang;
                }
            )
        }
    }


Finally, set the innerHTML property of the current element to the val property of the appropriate content element! Since, after filtering, there should be only one element left in the content array, use the first element of content!

assets\javascripts\application.js
    for (var i = 0; i < labels.length; i++)
    {
        var langLabels = document.getElementsByClassName(labels[i].name);

        for (var j = 0; j < langLabels.length; j++)
        {
            var content = labels[i].content.filter
            (
                function (x)
                {
                    return x.lang == lang;
                }
            )

            langLabels[j].innerHTML = content[0].val;
        }
    }


It's time to put this to the test.


See how the page title changes as well? That's because both the h1 tag and the page title are styled using the class name lblTitle.


Now, to add more elements to the labels array.

assets\javascripts\application.js
    var labels =
    [
        {
            "name" : "lblTitle",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Happy Easter 2018!"
                },   
                {
                    "lang" : "cn",
                    "val" : "2018复活节快乐!"
                }
            ]           
        },
        {
            "name" : "lblName",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Name"
                },
                {
                    "lang" : "cn",
                    "val" : "名称"
                }
            ]
        },
        {
            "name" : "lblEmail",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Email"
                },
                {
                    "lang" : "cn",
                    "val" : "电子邮件"
                }
            ]
        },
        {
            "name" : "lblSendEmail",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Send mail"
                },
                {
                    "lang" : "cn",
                    "val" : "发送邮件"
                }
            ]
        }
    ]


More testing!




Let's add more content. Go to your about View and add a div with the class lblAbout, and a nice hr tag.

about\index.erb
<div class="lblAbout">

</div>

<hr />

<%= label_tag(:txtName, "Name", class:"lblName") %>
<%= text_field_tag(:txtName) %>
<br /><br />
<%= label_tag(:txtEmail, "Email", class:"lblEmail") %>
<%= text_field_tag(:txtEmail) %>
<br /><br />
<%= button_tag("Send mail", class:"lblSendEmail") %>


Then add the element for it. While we're at it, let's add one for lblFooter!

assets\javascripts\application.js
    var labels =
    [
        {
            "name" : "lblTitle",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Happy Easter 2018!"
                },   
                {
                    "lang" : "cn",
                    "val" : "2018复活节快乐!"
                }
            ]           
        },
        {
            "name" : "lblName",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Name"
                },
                {
                    "lang" : "cn",
                    "val" : "名称"
                }
            ]
        },
        {
            "name" : "lblEmail",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Email"
                },
                {
                    "lang" : "cn",
                    "val" : "电子邮件"
                }
            ]
        },
        {
            "name" : "lblSendEmail",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Send mail"
                },
                {
                    "lang" : "cn",
                    "val" : "发送邮件"
                }
            ]
        },
        {
            "name" : "lblAbout",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "Easter is the celebration of the resurrection of Jesus from the tomb on the third day after his cruxifixion. Learn more about the meaning of Easter including the history and holiday symbols like easter eggs, the bunny, and lilies."
                },
                {
                    "lang" : "cn",
                    "val" : "复活节是在耶稣受难之后第三天从坟墓复活耶稣的庆祝活动。了解更多关于复活节的含义,包括复活节彩蛋,兔子和百合花等历史和假日符号。"
                }
            ]
        },
        {
            "name" : "lblFooter",
            "content" :
            [
                {
                    "lang" : "en",
                    "val" : "&copy; 2018 Teochew Thunder "
                },
                {
                    "lang" : "cn",
                    "val" : "&copy; 潮州雷"
                }
            ]
        }
    ]


OK, this isn't the prettiest layout in the world, but the point isn't prettiness - the point is to be able to change languages on the fly without reloading the page. I think we're doing really well here!




You may have noticed that when you refresh the page, a lot of content is missing. That's because the changeContent() function isn't run on page load. Here's a simple remedy for that...


views\layouts\application.html.erb
    <body onload="changeLang(document.getElementById('ddlLang').value)">
        <div class="container">
            <div class="header">
                <div class="right">
                    <span class="lang">
                      <label class="lblLang"></label>
                      <select name="ddlLang" id="ddlLang" onchange="changeLang(this.value)">
                      <% Lang.allowedVals["languages"].each do |key, value|%>
                          <option value="<%= key %>" <%= selectedLang(key) %>><%= value %></option>
                      <% end %>
                      </select>
                    </span>
                </div>
                <h1 class="lblTitle">
                   
                </h1>
                <div class="clearfix"></div>
            </div>

            <div class="content">
                <%= yield %>
                <div class="clearfix"></div>
            </div>

            <div class="lblFooter">

            </div>
        </div>
    </body>


Next

This page was set up to send email. So while we're at it, let's do some email sending via Rails...