Thursday, 30 December 2021

How I Learned Various JavaScript Frameworks

JavaScript has been a staple of my professional life since the early 2000s. Today, JavaScript frameworks dot the tech landscape. And I have even dabbled in some of them. In recent years, I have picked up AngularJS, ReactJS and VueJS, in that order. Today, I would like to recount my learning process.

Most of my learning, at least with JavaScript frameworks, was done less out of immediate necessity, and more out of the curiosity that seems almost to be professional obligation for a web developer. And one of my most valuable takeaways from all this learning is that I've discovered it is extremely possible for me to learn. I have the requisite motivation, and the numerous resources on the web are very helpful, especially if one keeps at it.

Encountering the framework

All these frameworks were first brought to my attention either via the process of sourcing for new job opportunities, or actually working in a new job opportunity.

AngularJS and ReactJS were encountered in the same way - via job ads in LinkedIn. It's my practice to periodically monitor the local software industry and look out for better opportunities. In doing so, I noticed that these two terms just kept coming up in job ad skillset descriptions. AngularJS. ReactJS.

JavaScript frameworks.

I started with AngularJS first, even though it was on its way to becoming obsolete, because there were just so many eminently readable examples on the internet.

Later on, I delved into ReactJS. Part of the reason I did it was because I was challenged by an interviewer to pick it up. It had already been on my radar; now I had even greater reason to explore.

And even later on, I picked up VueJS en route to trying to pick up Laravel. It was something I was trying to learn for a new job; VueJS was peripheral to the goal. However, I had already learned AngularJS and ReactJS by then. Consequently, learning VueJS didn't feel like it would be such a huge deal.

Learning

With AngularJS and VueJS, it started from books. Learning AngularJS by Brad Dayley and Full-Stack Vue.js 2 and Laravel 5: Bring the frontend and backend together with Vue, Vuex, and Laravel by Anthony Core respectively. As you can see with VueJS, it was more about Laravel. For AnguarJS, I learned the concept of front-end brackets. This would be one of the foundational concepts of picking up ReactJS and VueJS.

Learning from books.

When it came to learning ReactJS, however, I fell back to my second go-to method: YouTube tutorials. Specifically, Level Up Tuts.


From there, I learned the concept of State Management and how it was used to effect changes to the front-end. It was markedly different from what I had covered with AngularJS.

However, later on when I tried to pick up VueJS, I found the going much easier. The front-end binding from AngularJS and the State Management from ReactJS I had previously learned, flattened the learning curve for me immensely. VueJS was pretty much a combination of all my favorite parts of ReactJS and AngularJS, with less of the annoying bits.

Projects

Once I'd started learning the ins and outs of these various frameworks, it was time to actually put that knowledge to use, no matter how minor.

For AngularJS, it began with an exceedingly simple BMI Calculator, where I used template strings and front-end data binding. Later on, I graduated to a Password Strength Validator, using AJAX. The final project I did with this was the Liverpool Quiz, where I proceeded to perform more fancy variations of front-end data binding.

With ReactJS, it was just a teeny bit more complicated. I did three projects the "non-professional" way - by remote-linking the ReactJS libraries in the code and just writing the components in the same page. These were very simple applications such as Lee Bee Wah Lau! and the British Insult Generator, Ada Lovelace Day Generator and later on the Easter Memory Game.

Various ReactJS projects.

However, after a short stint with a prospective employer, I graduated to using NodeJS to generate ReactJS code and then running tests on the applications I wrote, attempting to do things in a more professional way. The end result was The 12 Days of Christmas and the rather more useful Hangman game.

On to VueJS. The one and only application I have written so far is the VueJS Easter Puzzle, and all this really amounted to a nice distraction because it just wasn't complicated at all. Whatever I had already done in AngularJS and ReactJS just combined nicely to give me the end result.

Editor's Note: At the time of publication of this blogpost, the VueJS Tile Slider Game is up as well.

Final Thoughts

No one is ever done learning - not in life, and most certainly not in tech.

My current method seems to be - read up on the subject, get some hands-on practice, then cement the knowledge with coding projects. It's not perfect, but if you're stuck as to how to proceed, you could probably do worse!

{goodbyeMessage};
T___T

Sunday, 26 December 2021

Spot The Bug: The Case of the Unstyleable Text

Hello, bug-hunters!

This is yet another PHP-related bug we will be exploring today, though in another sense, it's a lot more general than that.
Good hunting, guys!

It all started when I was working on yet another PHP report, that was an open-source plugin to export to PDF format. One of the users had requested me to make the font of the italicized descriptions (outlined in red) bigger, and I thought, how difficult could this be?



Oh, poor naive fool. It could get plenty more difficult.

What went wrong

I added this code around the code block. Easy fix, right?
<tr>
    <td>
        <?php
            echo get_name($item);
        ?>
        <span style="font-size:14px">
        <?php
            echo get_desc($item);
        ?>
        </span>
    </td>
    <td class="spacer"></td>
    <td class="figure" valign="top">
        <?php
            echo get_qty($item);
        ?>
    </td>
    <td class="spacer"></td>
    <td class="figure" valign="top">
        <?php
            echo get_price($item);
        ?>
    </td>
    <td class="spacer"></td>
    <td class="figure" valign="top">
        <?php
            echo get_subtotal($item);
        ?>
    </td>
</tr>


Nope! The PDF font remained stubbornly small. So what now?!


Why it went wrong

After several hours of tearing my hair out, I noticed that the text was in italics, though there was nothing in the code that suggested that it would be italicized. With that in mind, I made changes to the code to see what was in the HTML.
<?php
    echo htmlspecialchars(get_desc($item));
?>


And viola! Apparently it didn't return just text! There was a span tag containing all that text. That was why the code I wrote had no effect!


How I fixed it

So now this was a problem for the attached CSS file. What I needed to do, was fix the CSS class info_box.
.info_box
{
    font-size: 14px;
    font-style: italic;
}


And now the font was larger!


Moral of the story

There's usually more than meets the eye, especially when it comes to HTML. Developers who find themselves in situations where they can't just view the source, need to adopt other measures to have some visibility as to the HTML that they are trying to fix.

Think out of the info_box!
T___T

Wednesday, 22 December 2021

Web Tutorial: VueJS Tile Slider Game (Part 2/2)

Welcome back to the Christmas-themed web tutorial!

We will be making things work, and for that to happen, we need to write more methods and beef up existing ones. We'll start with the isComplete() method. Basically, it checks if all the pieces are in their correct positions. By default, it returns true. And sets the button text to "RESTART".

getStyle: function(id)
{
    if (id == null) return "";

    var obj = this.pieces[id];
    var image = "background-image: url(xmas" + this.imageNo + ".jpg);";
    var offset = "background-position:" + obj.offsetX + "% " + obj.offsetY + "%";

    return image + offset;
},
isComplete: function()
{
    this.btnText = "RESTART";

    return true;
}


Now, we will handle the cases where it returns false. Iterate through the arrangement array using a For loop, and in each iteration, check if the current index is equal to blankIndex.
isComplete: function()
{
    for (var i = 0; i < this.arrangement.length; i++)
    {
        if (i != this.blankIndex)
        {

        }
    }


    this.btnText = "RESTART";

    return true;
}


If not, then check if the current element's piece property matches its id property. If any of them do not match, return false.
isComplete: function()
{
    for (var i = 0; i < this.arrangement.length; i++)
    {
        if (i != this.blankIndex)
        {
            if (this.arrangement[i].piece != this.arrangement[i].id) return false;
        }
    }

    this.btnText = "RESTART";

    return true;
}


In the HTML, add this code to the puzzleContainer div. Basically, we bind the class attribute to the result of isComplete(). If it is true, then we set the class to win.
<div id="puzzleContainer" v-bind:class="isComplete() ? 'win' : ''">


Here's the styling for win. For this, we just give the entire div a nice thick orange border.
.blank
{
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 1);
}

.win
{
    outline: 5px solid rgba(255, 100, 0, 1);
}


Let's make some changes to the getStyle() method. We add the variable clickable, and then run the isClickable() method (we'll be building that later, never fear) and set the class to clickable or unclickable based on the result.
getClass: function(id)
{
    var baseClass = (id == this.blankIndex ? "blank" : "piece");
    var clickable = (this.isClickable(id) == false ? "unclickable" : "clickable");

    return baseClass;
},


Then we return the concatenated string of baseClass and clickable, with a space in between.
getClass: function(id)
{
    var baseClass = (id == this.blankIndex ? "blank" : "piece");
    var clickable = (this.isClickable(id) == false ? "unclickable" : "clickable");

    return baseClass + " " + clickable;
},


Let's take a look at the styling for clickable and unclickable. clickable has the cursor property changed. In addition, on hover, we make the image brighter. unclickable, on the other hand, will show no response.
.blank
{
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 1);
}

.clickable
{
    cursor: pointer;
}

.clickable:hover
{
    filter: brightness(150%);
}

.unclickable
{
    cursor: default;
}


.win
{
    outline: 5px solid rgba(255, 100, 0, 1);
}


Time to create the isClickable() method. By default, it returns false. The purpose of this method is to determine if any of the tiles are clickable, and what direction they can be moved towards ("n", "s", "e" or "w"). They are clickable if they are next to the black tile.
getStyle: function(id)
{
    if (id == null) return "";

    var obj = this.pieces[id];
    var image = "background-image: url(xmas" + this.imageNo + ".jpg);";
    var offset = "background-position:" + obj.offsetX + "% " + obj.offsetY + "%";

    return image + offset;
},
isClickable: function(index)
{
    return false;
},

isComplete: function()
{
    for (var i = 0; i < this.arrangement.length; i++)
    {
        if (i != this.blankIndex)
        {
            if (this.arrangement[i].piece != this.arrangement[i].id) return false;
        }
    }

    this.btnText = "RESTART";

    return true;
}


There are two more cases for a false result. If null is passed in as the index, this means that it is the black tile and naturally cannot be clicked. Also, if the result of isComplete() is true, then the game is over and nothing can be clicked.
isClickable: function(index)
{
    if (index == null) return false;
    if (this.isComplete()) return false;


    return false;
},


Now the next part is tricky. Think of your grid as the one shown below. The numbers are the ids.
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


So the tile can be clicked on and move right, or "e", if it's not tile id 3, 7 or 11 (the rightmost column) and blankIndex is the square on the right of the tile being examined.
isClickable: function(index)
{
    if (index == null) return false;
    if (this.isComplete()) return false;

    if (index + 1 == this.blankIndex && [3, 7, 11].indexOf(index) == -1) return "e";

    return false;
},


Similarly, the tile can be clicked on and move left, or "w", if it's not tile id 4, 8 or 12 (the leftmost column) and blankIndex is the square on the left of the tile being examined.
isClickable: function(index)
{
    if (index == null) return false;
    if (this.isComplete()) return false;

    if (index + 1 == this.blankIndex && [3, 7, 11].indexOf(index) == -1) return "e";
    if (index - 1 == this.blankIndex && [4, 8, 12].indexOf(index) == -1) return "w";

    return false;
},


The logic is similar for "n" and "s" directions, except that we add and subtract 4 instead of 1, because being one square above or below, due to the arrangement of the grid, means that the tile is actually 4 tiles ahead or behind.
isClickable: function(index)
{
    if (index == null) return false;
    if (this.isComplete()) return false;

    if (index + 1 == this.blankIndex && [3, 7, 11].indexOf(index) == -1) return "e";
    if (index - 1 == this.blankIndex && [4, 8, 12].indexOf(index) == -1) return "w";
    if (index + 4 == this.blankIndex) return "s";
    if (index - 4 == this.blankIndex) return "n";


    return false;
},


Got all that? Cool. Now if you refresh, and you run your mouse cursor over tile id 1 (top row, third column), you will see that it grows brighter.




Same for tile 7 (second row, rightmost column). That's because right now, blankIndex is 3 (as per the default) and the tiles next to tile id 3 are tile ids 4 and 7.




Moving the tiles

We've already created isClickable(), which determines what direction each tile can move when clicked. So now let's implement a method to move those tiles.

First, add this to the HTML. It's a binding to each tile for a click event. When clicked, the moveTile() method is called. Make sure to pass in the id of the tile as an argument.
<div v-bind:class="getClass(holder.id)"  v-bind:id="'piece'+holder.id" v-bind:style="getStyle(holder.piece)" v-on:click="moveTile(holder.id)">

</div>


Then we define the moveTile() method.
        isComplete: function()
        {
            for (var i = 0; i < this.arrangement.length; i++)
            {
                if (i != this.blankIndex)
                {
                    if (this.arrangement[i].piece != this.arrangement[i].id) return false;
                }
            }

            this.btnText = "RESTART";

            return true;
        },
        moveTile: function(index)
        {

        }

    },
    created: function()
    {
        this.reset();
    }                    
}


In here, we declare dir. Run the isClickable() method, passing in index as an argument. Set dir to the value of the result. If dir is falsy, that means that tile is not clickable and therefore we should exit early.
moveTile: function(index)
{
    var dir = this.isClickable(index);
    if (!dir) return;

}


Otherwise, declare piece and set it to the appropriate element by using index to determine the id. Now create four If blocks for each possible direction.
moveTile: function(index)
{
    var dir = this.isClickable(index);
    if (!dir) return;

    var piece = document.getElementById("piece" + index);

    if (dir == "e")
    {

    }

    if (dir == "w")
    {

    }

    if (dir == "s")
    {

    }

    if (dir == "n")
    {

    }

}


Declare v and set it to the value of this. That's because we are going to use a callback soon, and want to avoid scope errors.
moveTile: function(index)
{
    var dir = this.isClickable(index);
    if (!dir) return;

    var piece = document.getElementById("piece" + index);
    var v = this;

    if (dir == "e")
    {

    }

    if (dir == "w")
    {

    }

    if (dir == "s")
    {

    }

    if (dir == "n")
    {

    }
}


Now let's start with moving pieces eastwards. Set the class of piece to piece and clickable and move_e. Then use a setTimeout() function with a callback and 300 as the timeout value.
moveTile: function(index)
{
    var dir = this.isClickable(index);
    if (!dir) return;

    var piece = document.getElementById("piece" + index);
    var v = this;

    if (dir == "e")
    {
        piece.className = "piece clickable move_" + dir;

        setTimeout
        (
            function()
            {

            },
            300
        );

    }

    if (dir == "w")
    {

    }

    if (dir == "s")
    {

    }

    if (dir == "n")
    {

    }
}


Now you'll see why v needed to be declared. We are going to perform operations on the arrangement array and the property blankIndex. If we move the tile eastwards, the blank tile that was there will now take the place of the moved tile. And at the end of it, we set the class back to piece.
moveTile: function(index)
{
    var dir = this.isClickable(index);
    if (!dir) return;

    var piece = document.getElementById("piece" + index);
    var v = this;

    if (dir == "e")
    {
        piece.className = "piece clickable move_" + dir;

        setTimeout
        (
            function()
            {
                v.arrangement[index + 1].piece = v.arrangement[index].piece;
                v.arrangement[index].piece = null;
                v.blankIndex = index;
                piece.className = "piece";

            },
            300
        );
    }

    if (dir == "w")
    {

    }

    if (dir == "s")
    {

    }

    if (dir == "n")
    {

    }
}


We apply the same logic to the other directions.
moveTile: function(index)
{
    var dir = this.isClickable(index);
    if (!dir) return;

    var piece = document.getElementById("piece" + index);
    var v = this;

    if (dir == "e")
    {
        piece.className = "piece clickable move_" + dir;

        setTimeout
        (
            function()
            {
                v.arrangement[index + 1].piece = v.arrangement[index].piece;
                v.arrangement[index].piece = null;
                v.blankIndex = index;
                piece.className = "piece";
            },
            300
        );

    }

    if (dir == "w")
    {
        piece.className = "piece clickable move_" + dir;

        setTimeout
        (
            function()
            {
                v.arrangement[index - 1].piece = v.arrangement[index].piece;
                v.arrangement[index].piece = null;
                v.blankIndex = index;
                piece.className = "piece";
            },
            300
        );

    }

    if (dir == "s")
    {
        piece.className = "piece clickable move_" + dir;

        setTimeout
        (
            function()
            {
                v.arrangement[index + 4].piece = v.arrangement[index].piece;
                v.arrangement[index].piece = null;
                v.blankIndex = index;
                piece.className = "piece";
            },
            300
        );

    }

    if (dir == "n")
    {
        piece.className = "piece clickable move_" + dir;

        setTimeout
        (
            function()
            {
                v.arrangement[index - 4].piece = v.arrangement[index].piece;
                v.arrangement[index].piece = null;
                v.blankIndex = index;
                piece.className = "piece";
            },
            300
        );

    }
}


Here's the CSS for each of these classes. For move_e, we are moving the tile right. So the margin-left property will be set to 100%. For move_w, it's -100% because we're moving in the opposite direction. Same logic for the others. For all of these, transition speed has been set to 0.3 seconds, which is the same for the timeout values within the moveTile() method.
.win
{
    outline: 5px solid rgba(255, 100, 0, 1);
}

.move_e
{
    margin-left: 100%;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}

.move_w
{
    margin-left: -100%;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}

.move_n
{
    margin-top: -100%;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}

.move_s
{
    margin-top: 100%;
    -webkit-transition: all 0.3s;
    transition: all 0.3s;
}


Now we will need to try this. Click on tile id 2. What happens? Does the tile move right?




Now click on tile id 6. Does it move up?




Keep clicking and shifting the tiles till you get the full picture. This could take a while, though! You should see the thick orange border appear.




If you like, add these lines to make it fade in.
#puzzleContainer
{
    width: 100%;
    height: 500px;
    -webkit-transition: all 1s;
    transition: all 1s;

}


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


Nice and clean!




Adding more images

We are going to add some more images to bring some variety to our game.

xmas1.jpg

xmas2.jpg

xmas3.jpg

xmas4.jpg



Just modify the data property, like so. We now have five images, so change the value of the images property to 5.
data:
{
    btnText: "RESET",
    pieces: [],
    arrangement: [],
    blankIndex: 3,
    images: 5,
    imageNo: 0
},


Now you'll get a random puzzle each time you click the button.




Merry Christmas!

I'm really happy to share this web tutorial with you today. I wish you all happy and fun-filled holidays!

piece on Earth, goodwill to men!
T___T

Monday, 20 December 2021

Web Tutorial: VueJS Tile Slider Game (Part 1/2)

Christmas is coming!

As with the yearly tradition, TeochewThunder is proud to present the Christmas-themed web tutorial. This year, we will be making a game using VueJS.

Remember those puzzles where the pieces were scrambled and you had to shift those pieces around till you assembled the picture? Yep, that's what we are doing today. For starters, we will use this image. Note that this image is 500 by 500 pixels.

xmas0.jpg

We will then cut it up into 16 pieces, scramble the locations of the pieces within a 4 by 4 grid, and let the user click on these pieces to move them into the correct locations.

Let's begin with some HTML. Note that we are including a remote link to the VueJS script.
<!DOCTYPE html>
<html>
    <head>
        <title>Christmas Puzzle</title>

        <style>

        </style>
    </head>

    <body>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>

        <script>

        </script>
    </body>
</html>


Add a div with id xmasApp. This is what VueJS will use to perform binding.
<body>
    <div id="xmasApp">

    </div>


    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>

    <script>

    </script>
</body>


Style this. Give all divs a red outline so we can see what we are doing. xmasApp will be 500 pixels wide, and centered in the middle of the screen via the margin property. Font size has been set here as well, but that is optional.
<style>
    div { outline: 1px solid #FF0000;}

    #xmasApp
    {
        width: 500px;
        margin: 0 auto 0 auto;
        font-family: verdana;
        font-size: 12px;
    }
</style>


In here, add another div with id of labelContainer. Inside this div, add some text and controls for the user. The button will be useful later.
<div id="xmasApp">
    <div id="labelContainer">
        <h1>MERRY CHRISTMAS!</h1>

        <p>
            This picture has been scrambled. Click on the tiles to move them and unscramble the picture. Tip: The top right hand tile is always the blank tile.
        </p>

        <button></button>
    </div>
</div>


Not nearly pretty enough. Let's clean this up.


Here's some styling for labelContainer. We set the height and width, and center the text. The button is orange, and pretty much everything in the styling is optional.
#xmasApp
{
    width: 500px;
    margin: 0 auto 0 auto;
    font-family: verdana;
    font-size: 12px;
}

#labelContainer
{
    width: 100%;
    height: 150px;
    text-align: center;
}

#labelContainer button
{
    width: 8em;
    height: 2em;
    text-align: center;
    background-color: rgba(255, 100, 0, 1);
    color: rgba(255, 255, 255, 1);
    font-weight: bold;
    border-radius: 10px;
    border: 0px solid black;
}

#labelContainer button:hover
{
    background-color: rgba(255, 100, 0, 0.5);
    color: rgba(255, 0, 0, 1);
}


Much, much better.




Now let's add another div, puzzleContainer.
<div id="xmasApp">
    <div id="labelContainer">
        <h1>MERRY CHRISTMAS!</h1>

        <p>
            This picture has been scrambled. Click on the tiles to move them and unscramble the picture. Tip: The top right hand tile is always the blank tile.
        </p>

        <button></button>
    </div>

    <div id="puzzleContainer">

    </div>

</div>


Here with the styling for puzzleContainer, you see that the width is the whole of its parent, which is 500 pixels, and its height is also 500 pixels. Yes, it's a square.

#labelContainer button:hover
{
    background-color: rgba(255, 100, 0, 0.5);
    color: rgba(255, 0, 0, 1);
}

#puzzleContainer
{
    width: 100%;
    height: 500px;
}


And here's the square. We will turn it into a grid later on.



Let's do some scripting!

We first instantiate a new Vue object. And we pass in an object.
<script>
    var app = new Vue
    (
        {

        }
    );
</script>


The first property we put in, is el, the element. This is xmasApp. The next properties are data and methods. Both of these are, in turn, objects. created is another property, and it's a method that calls the reset() method, which we will define soon.
var app = new Vue
(
    {
        el: "#xmasApp",
        data:
        {

        },
        methods:
        {

        },
        created: function()
        {
            this.reset();
        }                 
       
    }
);


For data, we have the following properties.

btnText: this is a string which will be used as the button label.
pieces: an array that holds information about each piece of the picture.
arrangement: an array that tells us which piece is currently in which slot.
blankIndex: this defines the tile that is blank.
images: this is a number that defines the number of images we will be using.
imageNo: this is a number which defines which image is in use at the moment.

Below, pieces and arrangement are empty arrays by default. For blankIndex, I personally prefer the top right corner (3) but honestly, any number from 0 to 15 works. Currently we are only using one image, so we set images to 1 and we always use the first image by default, so imageNo is 0.
data:
{
    btnText: "RESET",
    pieces: [],
    arrangement: [],
    blankIndex: 3,
    images: 1,
    imageNo: 0

},


In methods, we declare reset(). We begin by setting the properties of data to their default values. For imageNo, we set it to a random number from 0 to (images - 1). In this case, since images is 1, the result is always 0.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);
    }
}


Then we create a nested For loop because this is where we define the grid. We have 5 rows and 5 columns.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);

        for (var row = 0; row < 4; row++)
        {
            for (var col = 0; col < 4; col++)
            {

            }
        }

    },
}


Here, we declare offsetX and offsetY so that the browser knows what to offset the image in each piece by. I've done some trial and error, and the optimum number is 33.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);

        for (var row = 0; row < 4; row++)
        {
            for (var col = 0; col < 4; col++)
            {
                var offsetX = col * 33;
                var offsetY = row * 33;

            }
        }
    },
}


Then we declare an object, piece. The id is a function of row and col, and we'll have the offsetX and offsetY properties. And then we will push each piece object into the pieces array.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);
        var tempArr = [];

        for (var row = 0; row < 4; row++)
        {
            for (var col = 0; col < 4; col++)
            {
                var offsetX = col * 33;
                var offsetY = row * 33;

                var piece =
                {
                    id: (row * 4) + col,
                    offsetX: offsetX,
                    offsetY: offsetY,
                }

                this.pieces.push(piece);                                       

            }
        }
    },
}


Now declare holder as an object. The id is also a function of row and col. If the current piece id is equal to blankIndex, then the value of piece is null. Otherwise, it is undefined.

And then push the piece into arrangement.
methods:
{
    reset: function()
    {
        this.btnText = "RESET";
        this.pieces = [];
        this.arrangement = [];
        this.blankIndex = 3;
        this.imageNo = Math.floor(Math.random() * this.images);
        var tempArr = [];

        for (var row = 0; row < 4; row++)
        {
            for (var col = 0; col < 4; col++)
            {
                var offsetX = col * 33;
                var offsetY = row * 33;

                var piece =
                {
                    id: (row * 4) + col,
                    offsetX: offsetX,
                    offsetY: offsetY,
                }

                this.pieces.push(piece);                                        

                var holder =
                {
                    id: (row * 4) + col,
                    piece: ((row * 4) + col == this.blankIndex ? null : undefined)
                }

                this.arrangement.push(holder);

            }
        }
    },
}


After that, we iterate through the arrangement array. If the index of the current element is not the same as blankIndex, set the piece property to the corresponding element in the pieces array.
reset: function()
{
    this.btnText = "RESET";
    this.pieces = [];
    this.arrangement = [];
    this.blankIndex = 3;
    this.imageNo = Math.floor(Math.random() * this.images);
    var tempArr = [];

    for (var row = 0; row < 4; row++)
    {
        for (var col = 0; col < 4; col++)
        {
            var offsetX = col * 33;
            var offsetY = row * 33;

            var piece =
            {
                id: (row * 4) + col,
                offsetX: offsetX,
                offsetY: offsetY,
            }

            this.pieces.push(piece);                                    

            var holder =
            {
                id: (row * 4) + col,
                piece: ((row * 4) + col == this.blankIndex ? null : undefined)
            }

            this.arrangement.push(holder);
        }
    }

    for (var i = 0; i < this.arrangement.length; i++)
    {
        if (i != this.blankIndex)
        {
            this.arrangement[i].piece = this.pieces[i];
        }
    }

}


Now let's put this code into the HTML. We have the pieces array and the arrangement array. So for every element in arrangement, we will have a div styled using pieceContainer. Within that div will be another div, and its class and style will be bound to the getClass() and getStyle() methods, respectively.
<div id="puzzleContainer">
    <div class="pieceContainer" v-for="holder in arrangement">
        <div v-bind:class="getClass(holder.id)" v-bind:style="getStyle(holder.piece)">

        </div>
    </div>

</div>


We'll also bind the id attribute to the id attribute of the current element. Not really necessary for anything to work, but it's a good practice.
<div id="puzzleContainer">
    <div class="pieceContainer" v-for="holder in arrangement">
        <div v-bind:class="getClass(holder.id)" v-bind:id="'piece'+holder.id" v-bind:style="getStyle(holder.piece)">

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


Here's the styling for pieceContainer. Since there are 4 rows and 4 columns of such "pieces", this stands to reason that each of these would take up 25% width and height of the parent. We set the float property to left so that they fall in line, and set the background color to black.
#puzzleContainer
{
    width: 100%;
    height: 500px;
}

.pieceContainer
{
    width: 25%;
    height: 25%;
    float: left;
    background-color: rgba(0, 0, 0, 1);
}


Now let's define the getClass() and getStyle() methods. Both of these have a parameter, id.
    for (var i = 0; i < this.arrangement.length; i++)
    {
        if (i != this.blankIndex)
        {
            this.arrangement[i].piece = i;
        }
    }
},
getClass: function(id)
{

},
getStyle: function(id)
{

}


For getClass(), we first declare baseClass. If the id passed in is equal to blankIndex, the class is blank. But otherwise, it is piece. Return the value of baseClass, and we're all set for this method, for now.
getClass: function(id)
{
    var baseClass = (id == this.blankIndex ? "blank" : "piece");

    return baseClass;

},


Here's the styling for piece and blank. Both piece and blank take up full height and width of the parent, which is pieceContainer. But while piece has background-repeat and margin properties (the latter will be relevant later), blank only has a black background.
.pieceContainer
{
    width: 25%;
    height: 25%;
    float: left;
    background-color: rgba(0, 0, 0, 1);
}

.piece
{
    width: 100%;
    height: 100%;
    background-repeat: no-repeat;
    margin: 0px;
}

.blank
{
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 1);
}


For getStyle(), if id is null, just return an empty string. Because the piece will be styled using blank and thus will not require any styling for the background image.
getStyle: function(id)
{
    if (id == null) return "";
}


But if there's a non-null value being passed in, we declare obj and set it to the element in pieces pointed to by id.
getStyle: function(id)
{
    if (id == null) return "";

    var obj = this.pieces[id];
}


Then we declare image and create a styling string value for it, using imageNo to set the background image. Remember that right now, the value of imageNo is always 0.
getStyle: function(id)
{
    if (id == null) return "";

    var obj = this.pieces[id];
    var image = "background-image: url(xmas" + this.imageNo + ".jpg);";
}


Then declare offset. For this, we use the offsetX and offsetY properties of obj. Each piece will thus use the same image background but with a different offset!
getStyle: function(id)
{
    if (id == null) return "";

    var obj = this.pieces[id];
    var image = "background-image: url(xmas" + this.imageNo + ".jpg);";
    var offset = "background-position:" + obj.offsetX + "% " + obj.offsetY + "%";
}


Finally, return both image and offset in concatenated form.
getStyle: function(id)
{
    if (id == null) return "";

    var obj = this.pieces[id];
    var image = "background-image: url(xmas" + this.imageNo + ".jpg);";
    var offset = "background-position:" + obj.offsetX + "% " + obj.offsetY + "%";

    return image + offset;
}


Also, let's fix this button so that the text appears, and when it gets clicked, it should run the reset() method.
<button v-on:click="reset">{{btnText}}</button>


Now, take a look at what we have. It's the picture, divided into 16 pieces, but the top right hand corner piece is blank beause blank has a black background!




Scrambling the tiles

The tiles are currently in the correct position. We want them to be randomly located. So let's begin by declaring tempArr, an empty array.
this.btnText = "RESET";
this.pieces = [];
this.arrangement = [];
this.blankIndex = 3;
this.imageNo = Math.floor(Math.random() * this.images);
var tempArr = [];


We need this array because right after we push the element into pieces, we need to push the id into tempArr.
var tempArr = [];

for (var row = 0; row < 4; row++)
{
    for (var col = 0; col < 4; col++)
    {
        var offsetX = col * 33;
        var offsetY = row * 33;

        var piece =
        {
            id: (row * 4) + col,
            offsetX: offsetX,
            offsetY: offsetY,
        }

        this.pieces.push(piece);
        tempArr.push((row * 4) + col);                                        

        var holder =
        {
            id: (row * 4) + col,
            piece: ((row * 4) + col == this.blankIndex ? null : undefined)
        }

        this.arrangement.push(holder);
    }
}


We're going to remove this line right here.
for (var i = 0; i < this.arrangement.length; i++)
{
    if (i != this.blankIndex)
    {
        //this.arrangement[i].piece = i;
    }
}


Here, we check if tempArr has only one element. That can happen - you'll see why in a minute.
for (var i = 0; i < this.arrangement.length; i++)
{
    if (i != this.blankIndex)
    {
        //this.arrangement[i].piece = i;
        if (tempArr.length == 1)
        {

        }
        else
        {
    
        }

    }
}


If there's only one element left in tempArr, we just assign the value of that element to the current element of arrangement's piece property.
for (var i = 0; i < this.arrangement.length; i++)
{
    if (i != this.blankIndex)
    {
        //this.arrangement[i].piece = i;
        if (tempArr.length == 1)
        {
            this.arrangement[i].piece = tempArr[0];
        }
        else
        {
    
        }
    }
}


If there's more than one element, then we need to randomly pick one and assign it to the current element of arrangement's piece property. For this we use a Do-while loop that will keep running if the index of the assigned piece is actually blankIndex. Eventually, all tiles except blankIndex will be randomly assigned.
for (var i = 0; i < this.arrangement.length; i++)
{
    if (i != this.blankIndex)
    {
        //this.arrangement[i].piece = i;
        if (tempArr.length == 1)
        {
            this.arrangement[i].piece = tempArr[0];
        }
        else
        {
            do
            {
                                        
            }
            while(this.arrangement[i].piece == this.blankIndex);  
 
        }
    }
}


Within the loop, declare index and set it to a random number between 0 and the length of tempArr. Set the piece property of the current element of arrangement to the element in tempArr pointed to by index. And then remove that element from tempArr using the splice() method.

Here's some info on the splice() method.
do
{
    var index = Math.floor(Math.random() * tempArr.length);
    this.arrangement[i].piece = tempArr[index];
    tempArr.splice(index, 1);                                            

}
while(this.arrangement[i].piece == this.blankIndex);


Now refresh your page. The tiles should be randomly arranged, though the top right corner should still be black. If you click the button, it should run the reset() method and give you a different random arrangement.




We don't want to go into too much at one go, so we'll stop right here.

Next

Making the tiles movable, and setting the win condition.

Monday, 13 December 2021

Google moves to 2FA!

Around the beginning of this month, I received an email from Google services. There was a big header that said Soon you’ll sign in with 2-Step Verification, and a message.

After you enter your password, you'll complete a second step on your phone. Keep your phone nearby when you sign in.

2-Step Verification will be turned on automatically on December 8. You can turn this on sooner if you want - your account is all set.


What this basically means

Google implemented Two-factor Authentication for its users. For those who don't know what that is, I wrote an informational piece about it back in 2018. Instead of just having to key in a password to access your services, there is an extra step - the second authentication method - of having to key in a response when a notification is sent to your mobile phone.

And sure enough, on the 8th of this month, when I tried to access my GMail account, after keying in the password, this appeared on screen.

Additional
authentication step.



And this appeared on my phone.

Mobile
confirmation.

Google is by no means alone in this. Microsoft already has support for 2FA for its services such as Outlook and Teams, though that feature is, for now, largely optional.

How I feel about this

Damn, this is a pain in the ass. But oh, so necessary. Security has become a pressing concern (more so than usual, anyway) with people losing control of their accounts due to hacker intrusions and theft of passwords. Adding to the concern is a general lack of paranoia in the user base.

What Google has done, is balance necessity against convenience. Of course it would be a lot nicer to be able to access our accounts without that hassle. But things have arrived at a point where this is no longer a viable option. This ramping up of security protocol is one-way - there is no going back from this, and as intruders up their game in future as they undoubtedly will, even more stringent measures will be required.

This moves also excludes a small percentage of users - namely, those who have a Google account but not a mobile phone. Unthinkable in this day and age? Yes, very. But not entirely out of the question. Still, Google seems to have decided that it is a risk worth taking.

What's in store?

Who's next? Google has taken that step. It isn't at all far-fetched to think that the likes of Twitter and Instagram are far behind.

Yes. This is really me.
T___T

Wednesday, 8 December 2021

Software Review: Tibco Spotfire

Data visualization seems to be getting some traction in the tech industry, or it could just seem that way due to my sudden exposure. Data visualization software takes care of some of the tedious data wrangling tasks that come with the job of analysis, without the user having to write swathes of code for the visualization itself.



Enter Spotfire by Tibco. It is a data visualization tool that connects to the cloud for user authentication and provides a suite of features for the arrangement and cleanup of data before generating charts and analysis.

The Premise

Spotfire takes care of your data the moment it is imported - from diverse sources such as CSV files, databases and spreadsheets. From there, you can slice or join different tables to form a coherent dataset, perhaps creating new columns from existing data.


Once done, the data may be used in several different visualization types.

The Aesthetics

Basic color scheme is white, with various shades of blue. The round corners and abundance of white space can make this software seem overly simplistic.

The Experience

Using Spotfire was a bit of a pain in the beginning. Some of the icons were a complete mystery as to their usage and it took a fair bit of clicking around and seeing what worked (or didn't work). Sometimes controls were a bit slow to respond, but this could depend on your internet connection.

The Interface

The interface feels clean and simple for the most part. The visualization flow can be as basic as - select a data field, bring up recommended visualizations, and adjust. It can be very straightforward.










The number of different visualizations available is adequate. There does not appear to be much that is super-advanced.


There are more analytics tools available if required. I did not explore that portion of the software.

What I liked

For visualization, controls are relatively straightforward. If nothing else is needed, it can be a very simple process.



The color scheme grew on me the longer I used it. It's not at all pretty, but at the same time, it's not jarring and that earns huge points from me.

What I didn't

No support for MacOS. All these features only apply if you're using a Windows machine. You could use the cloud version, but that seems to be a watered-down version of Spotfire.




Spotfire is kind of slow. There is a certain amount of lag that occurs when working with data outside of visualization, and sometimes even with visualization. This could be a result of my beat-up Windows machine, however.

Sometimes, the interface is not very intuitive and feels a bit clumsy for working with data.

Conclusion

Spotfire isn't unique in its abilities. There are other software packages that do the job just as well as Spotfire, if not better. If all you want is a fuss-free visualization tool, Spotfire is more than adequate. Anything more is perhaps pushing your luck.

My Rating

6 / 10

Try it if you like the (g)raph stuff!
T___T