Sunday 30 August 2020

How I Learned Ruby

It was a fine weekend in 2017 when I started learning Ruby. Three years have passed and I wouldn't exactly call myself an expert... but I have reached a certain level of proficiency in it. What's remarkable is that the way I learned it didn't really follow the way I learned ASP back in the 2000s or PHP in 2010. Back then, I picked up a book, started reading, and then started doing. How I learned Ruby was a little less straightforward than simply reading a book.

Why did I pick up Ruby?

You know what, that's an excellent question. I don't know exactly. I was jobless, low on confidence, and needed to distract myself by learning something. Something or other brought me to this site, Try Ruby. I went through their online tutorial and the Ruby syntax was pretty straightforward. It just kind of flowed. It was only a while later that I read a description of Ruby as a programmer's best friend, and by that time I thoroughly agreed.

What the hell, right? It wasn't like I had anything else to do between attending interviews and waiting for replies to my job applications. So I rolled up my metaphorical sleeves and got cracking.

The process

First was the installation of the Ruby server on my aging laptop. It turned out to be a breeze, especially when you consider all the pain I experienced while setting up the WAMP stack for PHP back then. And then in the Command Line Interface, I learned a bit by typing a few commands here and there.

Then I started typing in entire block codes into text files and running them on the CLI. Now, this was something I could get into. After all, I'm doing pretty much the same thing whenever I use QBasic.

That was when I started doing katas. It was easy stuff like the FizzBuzz algorithm, simple calculators, and so on. Stuff that used basic code blocks like inputs, outputs, If-Else, For... you know, the usual suspects. I even picked up this book, Exercises for programmers: 57 challenges to develop your coding skills by Brian P. Hogan, and did quite a few exercises in them using Ruby. One of them resulted in the Ruby Website Template Generator.


And speaking of books, I did eventually get around to reading them, just to see if it could help reinforce everything I thought I'd learned so far. Ruby recipes: a problem-solution approach by Malay Mandal was one of them.


What I did learn was that there was a lot more detail to the Ruby commands and functions that I had been using - stuff I never knew about simply because my use cases never touched them. Interesting. Perhaps not absolutely necessary, but good to know.

I was starting to write Ruby programs that were still rudimentary, but had more moving parts. By this time, I had landed a job at a major tech company that used a lot of C#. Ruby was a non-essential part of my career at this point, but I kept using it now and then in my off-time because it was just such a joy to code in.

Ruby on Rails

But learning Ruby on its own just doesn't cut it these days. If I wanted to get some real value out of that experience, I was going to have to graduate to using its most famous framework - Ruby On Rails. This was done by exploration of the Rails for Zombies portal, which gave me a fun little kick start. I love programming, and I love zombies.


Turns out Ruby On Rails wasn't such a big revelation as I thought it would be. I picked up some more reading from the local library. All things considered, it was pretty dry.


The Rails view: creating a beautiful and maintainable user experience by John Athayde and Bruce Williams


Rails crash course: a no-nonsense guide to Rails development by Anthony Lewis

Editor's Note: I am neither recommending nor not recommending these books. They were well-edited; I just didn't get a whole lot of value out of them and would have been fine not having read them. But that doesn't mean you're going to have the same experience.

After starting up a Rails server and going through all that stuff about Models, Controllers, Views and Helpers, I quickly realized that this was pretty much the same shit I'd gone through with ASP.NET 4.x or even CodeIgniter. I mean, there just doesn't seem to be that many ways to implement MVC, does it?

To test out this theory, I had to build a website. There was this thing my ex-boss implemented - a multi-lingual site that allowed the user to switch languages at will. I had an itch to try it for myself. To make the exercise even more interesting, I implemented the idea in Ruby On Rails.

And it really wasn't that hard. Personally, I feel that if you're having a problem with Ruby On Rails, your problem might be Ruby itself, or the fact that you need to understand MVC. If you've done both, this is a piece of cake.

Epilogue

Years after the fact, I'm still using Ruby. It's great for recreational programming and I'm getting as much use out of it as QBasic, which is a pity because it can be used for so much more. I got into it because I was idle at the time and needed to engage in a nominally productive pursuit. Perhaps that's why I never delved into it the same way I did for PHP - because my professional survival wasn't on the line.

Also, I discovered something strange. While the Ruby syntax was really a delight to use - terse, uncomplicated and smooth as silk - I had trouble memorizing it the way I did for JavaScript and QBasic back then. Maybe my memory's just not what it used to be, or maybe I've just evolved as a programmer. I'm no longer interested in memorizing commands - I want to get stuff done, and I have no compunction against copying and pasting from the internet, and modifying stuff to make it work. And perhaps, psychologically, since there's no longer any real motivation to memorize anything, I simply don't.

But really, if you're looking to pick up an extra language, I couldn't recommend Ruby enough.

Try it. It's a real gem!
T___T

Sunday 23 August 2020

JavaScript array operations, the hard way (Part 2/2)

Adding elements to an array, even manually, wasn't all that hard in the final analysis. But deleting... now that was a whole new kettle of fish.

Deleting elements

Again, it could have all been accomplished by use of the splice() method. Say, I wanted to delete "mamba" from the new array.
var snakes = ["cobra", "viper", "mamba", "adder", "python", "aconada"];

snakes.splice(2, 1);


Making the required array element blank would be easy. But the trick here was ensuring that the total number of elements was reduced by one! I won't go through everything I tried in order to produce the delFromArray() function, but here's what I finally did.
var snakes = ["cobra", "viper", "mamba", "adder", "python", "aconada"];

snakes = delFromArray(snakes, 2);

function delFromArray(arr, pos)
{

}


First, I declared temp as an empty array, and returned it.
function delFromArray(arr, pos)
{
    var temp = [];

    return temp;
}


Then I checked for an edge case. If pos wasn't a valid position within the array, I simply made sure the original array was returned.
function delFromArray(arr, pos)
{
    var temp = [];

    if (pos < arr.length && pos > arr.length)
    {
        temp = arr;
    }

    return temp;
}


And then there was an Else block to handle the other cases. First, I declared j and set it to -1. Then I created a For loop to iterate through the contents of arr.
function delFromArray(arr, pos)
{
    var temp = [];

    if (pos < arr.length && pos > arr.length)
    {
        temp = arr;
    }
    else
    {
        var j = -1;

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

        }  
    }

    return temp;
}


With each iteration, I'd increment j, then set the current element of temp pointed to by i, to the element in arr pointed to by j. The idea was to transfer all elements, except for the element to be deleted, from arr to temp.
function delFromArray(arr, pos)
{
    var temp = [];

    if (pos < arr.length && pos > arr.length)
    {
        temp = arr;
    }
    else
    {
        var j = -1;

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

            temp[i] = arr[j];
        }  
    }
   
    return temp;
}


Here, I checked if j was equal to pos after incrementing. If it was, I incremented it again. The net effect of this was that arr would skip that position in j to be transferred.
function delFromArray(arr, pos)
{
    var temp = [];

    if (pos < arr.length && pos > arr.length)
    {
        temp = arr;
    }
    else
    {
        var j = -1;

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

            if (i == pos)
            {
                j++;
            }

            temp[i] = arr[j];
        }  
    }
   
    return temp;
}


And of course, I had to cater for one more case. If j, after being incremented, inevitably arrived at an index that was not valid for arr, the transfer would not take place.
function delFromArray(arr, pos)
{
    var temp = [];

    if (pos < arr.length && pos > arr.length)
    {
        temp = arr;
    }
    else
    {
        var j = -1;

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

            if (i == pos)
            {
                j++;
            }

            if (j <= arr.length - 1)
            {
                temp[i] = arr[j];
            }
        }  
    }
   
    return temp;
}


And yes, it worked!
["cobra", "viper", "adder", "python", "aconada"];


Looks so simple now, but I had to take a while to get my head around all of it.

What was the point of this, again?

Why did I write this long-rambling story about how much of a noob I was? Well, see, the point was to illustrate how even the simplest things aren't all that simple. People take it for granted today - functions to add and delete elements from arrays are ubiquitous in almost every language I've written in - JavaScript (obviously), Ruby, PHP and so on. But what goes on behind those function calls is a lot of logic.

And while I pretty much suffered for nothing back then, here's hoping you can learn something from this, even if it's only "don't be a hardworking idiot".

Thanksssssss for reading!
T___T

Friday 21 August 2020

JavaScript array operations, the hard way (Part 1/2)

Back when I was a JavaScript novice (which, believe it or not, wasn't all that long ago), I often struggled with arrays. I knew perfectly well what arrays were; I just had trouble making them do exactly what I wanted.

For instance, sometimes I wanted to add an element in the middle of an array, or remove an element. And I had to jump through all sorts of hoops to achieve the desired results. It was hours of intensive concentration, catering for edge cases and generally tearing my hair out. But guess what - I eventually succeeded.

So imagine my consternation when I found out that all I was trying to achieve, could have been easily accomplished using the splice() method. (Here's a description of that method, if you're so inclined.)

Still, let's not let my youthful folly go to waste. Today, let's walk through how I managed to write functions to add and delete from arrays. For that, I'll be using an array of snake names...

Ssssay what?


Why snakes?!

Hey, don't diss snakes. Snakes are cool, yo. Also kind of creepy, but mostly cool.

Adding elements

What I was trying to do was add a single element into an array. So if I created this array...
var snakes = ["cobra", "viper", "adder", "python", "aconada"];


...and ran this function, addToArray(), passing in snakes, "mamba" as the element and 2 as the position I wanted to slot it in...
var snakes = ["cobra", "viper", "adder", "python", "aconada"];

function addToArray(arr, elem, pos)
{
    //TBA
}

snakes = addToArray(snakes, "mamba", 2)


...I would get this.
["cobra", "viper", "mamba", "adder", "python", "aconada"]


Yes yes, I know now that all I really had to do was this.
var snakes = ["cobra", "viper", "adder", "python", "aconada"];

snakes.splice(2, 0, "mamba");


But me being young and stupid, I naturally had to do it the hard way. Let's see what I did. First, I declared an array, temp, set the value to arr, and returned it.
function addToArray(arr, elem, pos)
{
    var temp = arr;

    return temp;
}


I checked if the length of arr was 0. If so, I set the first element of arr to elem, and that was it.
function addToArray(arr, elem, pos)
{
    var temp = arr;

    if (arr.length == 0)
    {
        temp[0] = elem;
    }

    return temp;
}


The key thing was the Else block. I had to ensure that all the other elements at the position pos and after, got shifted one position upwards. To do this, I used a For loop, iterating through the end position of arr plus one (because we'd be creating one extra slot), until pos.
function addToArray(arr, elem, pos)
{
    var temp = arr;

    if (arr.length == 0)
    {
        temp[0] = elem;
    }
    else
    {
        for(var i = arr.length; i > pos; i--)
        {

        }
    }

    return temp;
}


And then "shift" the values.
function addToArray(arr, elem, pos)
{
    var temp = arr;

    if (arr.length == 0)
    {
        temp[0] = elem;
    }
    else
    {
        for(var i = temp.length; i > pos; i--)
        {
            temp[i] = temp[i - 1];
        }
    }

    return temp;
}


Using snakes, and 2 as pos, I would get this so far.
["cobra", "viper", "adder", "adder", "python", "aconada"]


And of course, the coup de grace...
function addToArray(arr, elem, pos)
{
    var temp = arr;

    if (arr.length == 0)
    {
        temp[0] = elem;
    }
    else
    {
        for(var i = temp.length; i > pos; i--)
        {
            temp[i] = temp[i - 1];
        }

        temp[pos] = elem;
    }

    return temp;
}


And that would be it. This would work for all cases where the position to be added were in the middle of the array.
["cobra", "viper", "mamba", "adder", "python", "aconada"]


But what if I wanted to append the new element to the end? This would be the easy way.
snakes.push("mamba");


But no, again, I had to do it the hard way. This would require another If-Else block.
function addToArray(arr, elem, pos)
{
    var temp = arr;

    if (arr.length == 0)
    {
        temp[0] = elem;
    }
    else
    {
        if (pos >= arr.length)
        {

        }
        else
        {
            for(var i = temp.length; i > pos; i--)
            {
                temp[i] = temp[i - 1];
            }

            temp[pos] = elem;       
        }
    }

    return temp;
}


And set the last element of temp, plus one, to elem.
function addToArray(arr, elem, pos)
{
    var temp = arr;

    if (arr.length == 0)
    {
        temp[0] = elem;
    }
    else
    {
        if (pos >= arr.length)
        {
            temp[arr.length] = elem;
        }
        else
        {
            for(var i = temp.length; i > pos; i--)
            {
                temp[i] = temp[i - 1];
            }

            temp[pos] = elem;       
        }
    }

    return temp;
}


So if I did this...
var snakes = ["cobra", "viper", "adder", "python", "aconada"];

snakes = addToArray(snakes, "mamba", snakes.length);


... I would get this.
["cobra", "viper", "adder", "python", "aconada", "mamba"]


This was relatively straightforward, though. The nightmare was just beginning!

Next

Removing elements the hard way.

Sunday 16 August 2020

Web Tutorial: The Fade Carousel

Welcome to another carousel-based web tutorial.

This one's gonna be short, because we will be, in classic lazy-bastard programmer fashion, leveraging upon the code from the last carousel-based web tutorial. This time, instead of making the images slide in and out of view, we'll make them fade instead.

So let's go ahead and just copy all the code over. And then change all instances of "slide" to "fade". Which means that instead of calling them "slides", we will now call them "fades". Which is kind of lame, but it works for keeping things consistent, so there you go.
<!DOCTYPE html>
<html>
    <head>
        <title>Fade Carousel</title>

        <style>
            div {outline: 0px solid #FF0000;}
           
            body
            {
                font-size: 12px;
                font-family: verdana;
            }

            #carouselContainer
            {
                width: 800px;
                height: 600px;
                margin: 0 auto 0 auto;
            }

            .margin
            {
                width: 10%;
                height: 100px;
                float: left;
                text-align: center;
                font-size: 5em;
            }

            .margin input[type="button"]
            {
                color: rgba(0, 0, 0, 1);
                cursor: pointer;
                margin-top: 250px;
                width: 50px;
                height: 50px;
                border-radius: 50%;
                border: 0px solid red;
                background-color: rgba(255, 200, 0, 1);
                color: rgba(255, 255, 255, 1);
            }

            .margin input[type="button"]:hover
            {
                background-color: rgba(255, 200, 0, 0.5);
            }

            #viewport
            {
                width: 80%;
                height: 100%;
                float: left;
                overflow: hidden;
                border-radius: 15px;
            }

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

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

            .content div
            {
                padding: 1em;
                background-color: rgba(255, 255, 255, 0.5);
                color: rgba(0, 0, 0, 1);
                font-weight: bold;
                width: 80%;
                border-radius: 5px;
                margin: 0 auto 0 auto;
            }
        </style>

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

                    this.setContent(this.currentFadeIndex + 1, "left");

                    setInterval
                    (
                        function()
                        {
                            carousel.animateFader();
                        },
                        5000
                    );                   
                },       
                animateFader: function()
                {
                    if (this.canMove)
                    {
                        this.fade("left");   
                    }
                },               
                fade: function(dir)
                {
                    var nextFade;
                    var currentMarginLeft;
                    var nextMarginLeft;

                    this.setTransitionSpeed(0);

                    if (dir == "left")
                    {
                        if (this.currentFadeIndex == this.fades.length - 1)
                        {
                            nextFade = 0;
                        }
                        else
                        {
                            nextFade = this.currentFadeIndex + 1;
                        }

                        currentMarginLeft = 0;
                        nextMarginLeft = -100;
                    }

                    if (dir == "right")
                    {
                        if (this.currentFadeIndex == 0)
                        {
                            nextFade = this.fades.length - 1;
                        }
                        else
                        {
                            nextFade = this.currentFadeIndex - 1;
                        }   

                        currentMarginLeft = -100;
                        nextMarginLeft = 0;
                    }

                    this.setMargin(currentMarginLeft);
                    this.setContent(nextFade, dir);

                    setTimeout
                    (
                        function()
                        {
                            carousel.setTransitionSpeed(1);
                        },
                        250
                    );

                    setTimeout
                    (
                        function()
                        {
                            carousel.setMargin(nextMarginLeft);
                            carousel.currentFadeIndex = nextFade;
                        },
                        500
                    );

                    setTimeout
                    (
                        function()
                        {
                            carousel.canMove = true;
                        },
                        5000
                    );
                },
                setTransitionSpeed: function(interval)
                {
                    this.contentContainer.style.webKitTransition = interval + "s all";
                    this.contentContainer.style.transition = interval + "s all";
                },
                setMargin: function(margin)
                {
                    this.contentContainer.style.marginLeft = margin + "%";
                },
                setContent: function(next, dir)
                {
                    if (dir == "left")
                    {
                        this.c0.innerHTML = this.fades[this.currentFadeIndex].content;
                        this.c0.style.backgroundImage = "url(img/" + this.fades[this.currentFadeIndex].bg + ")";

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

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

                        this.c1.innerHTML = this.fades[this.currentFadeIndex].content;
                        this.c1.style.backgroundImage = "url(img/" + this.fades[this.currentFadeIndex].bg + ")";
                    }
                },
                faderButtonClick: function(dir)
                {
                    this.canMove = false;
                    this.fade(dir);
                }
            }
        </script>
    </head>

    <body onload = "carousel.begin();">
        <div id="carouselContainer">
            <div class="margin">
                <input type="button" onclick="carousel.faderButtonClick('left')" value="&#9668;"/>
            </div>

            <div id="viewport">
                <div id="contentContainer">
                    <div id="content0" class="content"></div>
                    <div id="content1" class="content"></div>
                </div>
            </div>

            <div class="margin">
                <input type="button" onclick="carousel.faderButtonClick('right')" value="&#9658;"/>
            </div>   
        </div>
    </body>
</html>


Set divs to have that red outline, because we don't want to be developing blind.
div {outline: 1px solid #FF0000;}


There will be no change to the HTML, and very little to the CSS. For the contentContainer div, instead of having it 200% of its parent, it now fits the parent's width exactly at 100%. For the content CSS class, we remove the background-color property because now we're doing a fade-in/fade-out thing and we don't want the background to show up unexpectedly. Also, change its width to 100%.
#contentContainer
{
    width: 100%;
    height: 100%;
    background-color: #004400;
}

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


And add a new rule for content0, setting its margin-right property to negative 100%. This means that the divs content0 and content1 will overlap each other within contentContainer.
.content
{
    width: 100%;
    height: 100%;
    float: left;
    background-size: cover;
    background-position: 50% 50%;
    background-repeat: no-repeat;
    padding-top: 1em;               
}

#content0
{
    margin-right: -100%;
}


The result of this is that now your slide carousel doesn't show the next slide while sliding. Remember now content0 and content1 overlap and contentContainer is no longer twice as wide as viewport? That's OK, because we no longer want it to slide and that's the next thing we will take care of.


The JavaScript

The begin() method of the carousel object operates on the same principle as the Slide Carousel's, and no change needs to be made here save for the naming of the callback, which is now animateFader(). animateFader(), in turn, has no change except that the callback is now fade() rather than slide(). It's fade() that we have to tweak.

The If blocks that determine which slide fade comes next, is logically the same, so no change there. However, we no longer want to change the margin-left property. Instead, we want to change the opacity property. So let's remove the variables. And instead of callng the setMargin() method, we call the setOpacity() method, passing in 100 and 0 as arguments, in different orders. You'll see why later.

Also remove the dir argument for the call to setContent().
fade: function(dir)
{
    var nextFade;
    //var currentMarginLeft;
    //var nextMarginLeft;

    this.setTransitionSpeed(0);

    if (dir == "left")
    {
        if (this.currentFadeIndex == this.fades.length - 1)
        {
            nextFade = 0;
        }
        else
        {
            nextFade = this.currentFadeIndex + 1;
        }

        //currentMarginLeft = 0;
        //nextMarginLeft = -100;
    }

    if (dir == "right")
    {
        if (this.currentFadeIndex == 0)
        {
            nextFade = this.fades.length - 1;
        }
        else
        {
            nextFade = this.currentFadeIndex - 1;
        }   

        //currentMarginLeft = -100;
        //nextMarginLeft = 0;
    }

    //this.setMargin(currentMarginLeft);
    this.setOpacity(100, 0);
    this.setContent(nextFade/*, dir*/);

    setTimeout
    (
        function()
        {
            carousel.setTransitionSpeed(1);
        },
        250
    );

    setTimeout
    (
        function()
        {
            //carousel.setMargin(nextMarginLeft);
            carousel.setOpacity(0, 100);
            carousel.currentFadeIndex = nextFade;
        },
        500
    );

    setTimeout
    (
        function()
        {
            carousel.canMove = true;
        },
        5000
    );
},


Next, we rename the setMargin() method to setOpacity(). It will now accept two parameters, c0_opacity and c1_opacity.
setOpacity: function(c0_opacity, c1_opacity)
{
    //this.contentContainer.style.marginLeft = margin + "%";
},


In here, we want c0 and c1 to take on the opacities specified by their respective arguments. So if one is 100 and the other is 0, and there's a transition speed of more than 0 involved, it will look like one is fading out in favor of the other! Now you see why the need for the 100 and 0 values passed to the setOpacity() method earlier?
setOpacity: function(c0_opacity, c1_opacity)
{
    //this.contentContainer.style.marginLeft = margin + "%";
    this.c0.style.filter = "opacity(" + c0_opacity + "%)";
    this.c1.style.filter = "opacity(" + c1_opacity + "%)";
},


The setTransitionSpeed() method also needs changing. Remove the code inside.
setTransitionSpeed: function(interval)
{
    //this.contentContainer.style.webKitTransition = interval + "s all";
    //this.contentContainer.style.transition = interval + "s all";
},


Instead of altering the properties of contentContainer, we will alter the properties of content0 and content1.
setTransitionSpeed: function(interval)
{
    //this.contentContainer.style.webKitTransition = interval + "s all";
    //this.contentContainer.style.transition = interval + "s all";

    this.c0.style.webKitTransition = interval + "s all";
    this.c0.style.transition = interval + "s all";

    this.c1.style.webKitTransition = interval + "s all";
    this.c1.style.transition = interval + "s all";
},


OK, now we can modify the setContent() method. Get rid of all the code inside because we won't need it. Remember a fade is different from a slide - whether you're transitioning through the array in ascending or descending order, visually it's all the same. In a slide motion, you gave to cater for sliding left and right; in a fade motion, you simply adjust the opacity. You also won't need dir (as mentioned, it no longer matters what direction the animation is in), so remove it..
setContent: function(next/*, dir*/)
{
    /*if (dir == "left")
    {
        this.c0.innerHTML = this.fades[this.currentFadeIndex].content;
        this.c0.style.backgroundImage = "url(img/" + this.fades[this.currentFadeIndex].bg + ")";

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

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

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


What you need to do is ensure that content0 shows the current fade, and content1 shows the next fade. Always.
setContent: function(next/*, dir*/)
{
    /*if (dir == "left")
    {
        this.c0.innerHTML = this.fades[this.currentFadeIndex].content;
        this.c0.style.backgroundImage = "url(img/" + this.fades[this.currentFadeIndex].bg + ")";

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

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

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

    this.c0.innerHTML = this.fades[this.currentFadeIndex].content;
    this.c0.style.backgroundImage = "url(img/" + this.fades[this.currentFadeIndex].bg + ")";

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


Remember how I said we didn't have to change the begin() method? That wasn't exactly true. setContent() now only accepts a single argument, so you have to remove the second argument. Also, since we're no longer preparing for a slide left motion, we can take away the plus one from currentFadeIndex.
begin: function()
{
    this.contentContainer = document.getElementById("contentContainer");
    this.c0 = document.getElementById("content0");
    this.c1 = document.getElementById("content1");

    this.setContent(this.currentFadeIndex/* +1, "left"*/);

    setInterval
    (
        function()
        {
            carousel.animateFader();
        },
        5000
    );                   
},


Can you see the fade-in/fade-out effect?


Remove the red outline now, and things will look even prettier.
div {outline: 0px solid #FF0000;}




You might want to increase the transition speed here to make it fade even slower so the user can properly appreciate it. But be careful not to set it to a value greater or equal to the interval specified in animateFader(). Because that would be just messy.
setTimeout
(
    function()
    {
        carousel.setTransitionSpeed(3);
    },
    250
);


That's it, we're done!

I promised this would be short.

Unfortunately, this tutorial will make a whole lot more sense if you were present during the previous one. But this tutorial was maybe 80% similar to the last one and I hate repeating myself, so...

Fading out,
T___T

Wednesday 12 August 2020

When hubris is not a virtue

Months ago, I vacated my well-paying position as a web developer for other pastures. My former company is a fairly big outfit, one of the biggest in the world. And over the years, I've increasingly marveled at the fact that someone of my (admittedly, not extremely high) caliber managed to survive that long despite the fact that I was surrounded by younger and obviously more talented people.

In the weeks that followed, I've had time to understand how. It wasn't so much the circumstances, but rather, how I reacted to them.

Remember when I said a certain amount of hubris was helpful for a programmer? It's still true, but equally true is the fact that your ego is your ego and often you need to show it who's boss. Allowing it to take over is a complete recipe for unmitigated disaster.

Asking for help

You see, being the oldest developer among a bunch of energetic and ambitious thirty-somethings have afforded me a certain perspective. I've lived through situations where I personally witnessed (or in some cases, personally experienced) how pride can be a drawback... and I applied this experience by giving absolutely zero fucks about my professional reputation. In fact, I was pretty open about the fact that I didn't think I was that good.

For example, I wasn't shy about asking for help or showing how little I knew. People love helping because it allows them to showcase how much they know. If ever there was any doubt about this, try posting a question on StackOverflow sometime.

Help!

Developers can be a competitive (though unfortunately not always competent) bunch and may subconsciously view you as a threat. Asking for help puts you in the position of supplicant, and if you express appreciation appropriately after receiving said help, you cease to be seen as a threat and perhaps even begin to be perceived as a great way to gain street cred.

I should also mention that older developers need to break out of the mindset that they're experienced and therefore can't "lose" to younger developers. And by "lose", I mean - take orders from, earn less than and learn from. What's next, you can't take orders from a woman? From a foreigner? There's no room in tech for that kind of rubbish. This is the kind of ego that is most definitely unhelpful. I'm not automatically superior because I'm older. That's like if you repeated secondary school and insisted that this made you more educated than others because you had one extra year of schooling.

Taking up unpopular tasks

There are certain tasks that some of my colleagues didn't want to deal with, mostly because those tasks involved a steep learning curve and were outside their area of expertise. I, on the other hand, laid claim to no expertise of any kind. Remember, I had paid my dues in an era where there were no specialists in web development; where everyone was a full-stack dev. Therefore, I accepted these foreign tasks as they were thrown to me. Automated DevOps. API gateways. I already didn't have a reputation to uphold for being particularly skilled. That was my singular greatest edge - I didn't need to be afraid of appearing incompetent.

Unfamiliar work.

And if I was handling something totally new, I didn't have to worry about proving a damn thing. Not only did I not fail miserably, any mistakes I made in the process resulted in me leveling up. And it also resulted in me being able to hold more intelligent tech conversations.

After all, if you're starting from the bottom at something, there's only one direction you can go.

Accepting blame

In my third year, I was given the task of attending to one particular user. This user was notoriously difficult to handle; there was an incident where a back-and-forth email exchange between her and a fellow dev went on for almost three days while they argued heatedly over some discrepancy in production code and whose fault it was. You know the kind of user who types in ALL CAPS and bright red font? Yep. My team sent me to handle her. In their words, I was "shameless". I had no feelings to hurt.

Taking the heat.

Sure enough, eventually something cropped up and I was given the ALL CAPS and bright red font treatment, with plenty of people in the CC loop. What did I do? Not to brag, but I took it like a champ. I readily admitted it was my fault, explained exactly how it had transpired, and outlined a proposed plan to prevent this from happening again. The back-and-forth took three hours to resolve; maybe less. Three hours! Not days!

What made the difference? Easy. Firstly, I was a contractor and didn't feel particularly threatened; this job was never meant to last that long. Secondly, I had a pretty clear idea why that user was doing this - she had way more to lose than me if the blame fell on her and she needed her blamelessness visibly broadcast. Again, I was a contractor; it wasn't like taking the blame was going to cost me a promotion or something.

And thirdly, I went in with the mindset that I was here to solve a problem... and I certainly didn't define the problem as "someone shouted at me through email, I gotta stand up for myself!".

The funny thing was, a day later, the user and I were having an impromptu meeting. At the end of it, she apologized for the way she had spoken during the email... and it took me a couple minutes to figure out exactly what she was talking about because I'd already moved on.

Facing rejection

I've spoken about the job-hunting process before, and how I usually use actual interviews as practice. It surprised me to learn that almost none of my colleagues ever do this... and usually it's because they're afraid of rejection. Because rejection hurts, or some shit like that. Well, here's the thing - you get rejected enough times, eventually you're gonna get numb to it. Trust me, I know.

If you don't shoot, you don't score.


Not only do I expect to get rejected, I consider it way better than not receiving an answer after the interview. A nice formal (and of course, politely-worded) fuck-off is still a response. It's respect. It's professionalism. You may think rejection stings, but that's probably just your ego rankling at being told you're not as good as you think you are. Rejection is a valuable opportunity to find out where your knowledge gaps are. That's how I ended up learning ReactJS and D3 - because enough companies I interviewed for listed it as a requirement. Being rejected at interviews at least pointed me in the right direction. And believe me when I say I wasn't shy about asking for advice from my interviewers after they rejected me. Why not? I mean, I was already there. I probably wouldn't see them ever again. Might as well extract every last ounce of value from our time together, right?

And boy, you'll be surprised how some of the toughest interviewers were so willing to drop the hard-ass act and try to help. "Bro, next time somebody asks you this, you need to stop giving that answer." "Dude, I really can't make sense of your resume. Try this next time..." "I think it would help if you tried to specialize in something." People want to help you and share their know-how. All you gotta do is ask. Encourage them to do so. Especially if they're in the position to do so!

Finally...

Now, you may think that all this sounds really weak. Really... Beta Male. Honestly, if you're still trapped in this Alpha-Beta dichotomy, you've probably got issues that no amount of reading my blogposts will help resolve.

Remember what they say about the reed that bends with the wind, and the mighty oak. Be the reed. Acting tough is easy. Being tough while acting meek, is several levels higher.

Loud, proud and still unbowed,
T___T

Saturday 8 August 2020

Ten Problematic Tech Terms

The tech world has been a-buzz recently. Following the Black Lives Matter riots across the USA, some tech firms have declared their intention to help eradicate racism - by erasing problematic language from their code bases. The overall objective is to be inclusive and avoid insensitive references.

How this is going to help exactly, remains a mystery to many of us. It's tempting to simply dismiss all this as just another poorly-disguised attempt at virtue-signalling. But in the spirit of joining in the fun, let's take a look at some of the terms slated for erasure and their proposed replacements. And some terms that haven't yet had the dubious honor.

1. Master/Slave

This term is used in tech to describe situations where one process or entity controls another, or where one is an original and the others (the "slaves") take reference from it. Ostensibly, tech such as GitHub, Python and Twitter (and even MySQL!) have decided that they will no longer use the terms "master" or "slave" in their code repositories. Instead, terms such as "main" and "replica" will be used.

No more master-slave relationships!

It's a bit of a stretch of the imagination to equate a "Master" branch in GitHub with slavery, but what do I know, right? I'm not a marginalized race in the US. Hell, I don't even live in the US!

2. Black/White

Google claims that the terms "blacklist" and "blackhat" have negative connotations associated with the color, and this is somehow denigrating African-Americans. Instead, we should be using terms such as "rejectlist" and "allowlist" to replace "blacklist" and "whitelist", respectively.

We can't be blackhats
anymore?

To be fair, "rejectlist" and "allowlist" are a lot more obvious than "blacklist" and "whitelist". It's objectively a good change. I just think the reasons behind it feel kind of forced. It's almost like someone's trying a little too hard not to offend black people.

3. Chief Technical Officer

Hey, how about "Chief" Technical Officer? Or "Chief" anything? Isn't that insulting to Native Americans who actually earned that title? Non-native Americans, you can do better.

So Sioux me!

Native American cultural appropriation is a real thing, yo. Just ask Chris Hemsworth.


4. Ninja

Eradicating the word "ninja" from tech vocabulary will be welcome. That's also cultural appropriation. Imagine how the real ninjas feel, having that term co-opted by a bunch of computer geeks who probably couldn't throw a shuriken worth a damn.

Won't someone please
think of the ninjas?

Also, it's incredibly lame to describe yourself as a "code ninja". Please just fucking stop.

5. Kanban

Another case of cultural appropriation. The Kanban was originally used in manufacturing operations in Japan. Then this got taken to the USA, and some time later, the tech industry took to using this to manage software development processes. We can still use Kanban boards, though maybe we should start calling them something else?

Yeah, call 'em something else!

I seriously doubt the Japanese are anything less than smug that the Kanban got adopted by the Americans. But wait... does this whole movement actually have anything to do with the feelings of other cultures, or is it just an excuse to make tech companies feel all progressive and shit?

6. Sanity Check

The term "sanity check" is usually in the context of testing. However, the word "sanity" might be sensitive to people who suffer from mental illness. I'm no expert here, and far be it for me to sound unsympathetic, but could that be because they suffer from mental illness?

Who're you
calling insane?!

The term "smoke test" has been suggested as a substitute. But it appears that the term already exists, and there's actually a difference. You know what, this is crazy (no pun intended) and I'm just gonna let the experts sort this out.


7. Dummy

A "dummy" anything is usually used in software development as a stand-in for the real thing. A dummy account. A dummy file. Dummy content. Just like crash-test dummies are used in place of real people.

I surrender to the awesome power
of your Wokeness.

But no, what if it triggers people who are, say, not the brightest bulb in the chandelier? We want to be inclusive, right? Twitter has suggested "placeholder", which actually isn't that bad. Unlike the case of "blacklist" and whitelist", however,  the term "dummy" was actually pretty obvious already and I don't think it needed changing.

8. Throttling

In software, "throttling" is the process of regulating the rate of processing, because sometimes you gotta slow stuff down in order for things to go smoothly. After all, resources are limited, and we don't want the system to bite off more than it can chew.

Just choking, folks!

But geez, this term is just so violent. It brings to mind wringing of necks and MMA chokeholds. Why such a hostile term? How about "hugging"?

9. Penetration Testing

This is actually a term used in computer security, to assess the defenses and robustness of any particular system. But it's kind of lewd, isn't it? Penetration?

Such penetrating insight!

Think of all the locker room jokes we computer nerds could make if this term were still in use. How about we just scrap it so people don't feel, y'know,  uncomfortable?

10. Alpha/Beta

Alpha release. Beta release. These terms are commonly used to describe software versions. You know what else they're used to describe? Males.

That's Alpha AF.

Maybe usage of this term encourages toxic masculinity. Should we chance it? I mean, if we're going to deprecate the use of "master" and "slave", surely this is next!

Conclusion

Yes, I'm being really facetious here. But let's be real for a minute.

Naming things is one of the great struggles of software development. Congratulations, we just made it a whole lot harder.

It's not that I think tech terms are set in stone and shouldn't change at all. Obviously, some change is for the better. Even more obviously, it would be better if they were done for the right reasons. If done to improve clarity or sustainability of maintenance; some objectively beneficial metric, yes I'm all for it.

But if it's done just for the sake of appealing to the Social Justice mob, I think it's ill-advised. Because there's no end to this sort of thing. People are always going to be offended by something or other. The world doesn't revolve around the USA and their great struggle with racism and their history as slave-owners, and it's time people realized that.

Now that's a master main stroke!
T___T