Monday 26 February 2024

Take ownership of your career, please

Often, I have remarked on how important it is to own your career. And never was it more apparent a few years ago, when I came upon a Facebook post by a self-styled entrepreneur, who claimed to be the kind of boss who sends his employees for skills upgrading. He further asserted that it is the employer's responsibility to do so. Now, in all fairness, this fellow's English is less than ideal, which may account for the fact that he sounds like an obnoxious virtue-signaller. (Of course, him actually being obnoxious may be the main reason he sounds obnoxious.)

That was his assertion, and now this is mine, and I recognize that this may be an unpopular opinion, but here goes. Your career is not, and should not ever be, your employer's responsibility, no matter what they may claim. It is yours. Letting - or worse, expecting - your employer to take care of your career, is tantamount to abdicating that responsibility.

Would it be nice if your employer took care of the costs associated with pursuing your professional training? Of course. It's also potentially dangerous to allow your employer to determine what parts of your game to upgrade. You, and you alone, should be deciding the direction in which you train. And if your chosen direction does not align with that of your company, perhaps it's time you parted ways. Remember, your career will span more than one company. The company you are currently a part of, is but another step in the journey. It's not the entire journey.

Be the Captain of your ship.

If I'm going to upgrade my skills, I want to be the one to decide which skills to upgrade. Think of your career as a ship, of which you are Captain. Not your employer, not your wife, not your mother. You.

Then again, perhaps I speak from a position of privilege. I've spent my entire career ensuring as much as possible that I am not beholden to any one employer beyond the terms of our contract. And I have the luxury of knowing I have a healthy bank balance, and other income, to pay for the costs of my decisions. Everything I say assumes that you enjoy similar privileges - that you are not in a position where you are underqualified and therefore at the complete mercy of your employer in order to stay employed.

And if that's the case, if indeed you are in that kind of position, you should absolutely eat whatever your employer graciously gives you, and be properly grateful for it, peasant.

Your Employer's Responsibility

Getting back to the subject of your skill upgrades being the employer's responsibility...

Employers control many things. In particular, they control whether or not you are employed at their company. They, to some extent, control your working conditions, and whether or not you are able to pay your bills. The more people they employ, the more they control. Thus, it's almost inevitable that employers tend to develop some kind of Big Brother complex (or, as I like to call it, the Chinese Towkay Syndrome). That is not a slight against their character; put that amount of power in anybody's hands, it's almost guaranteed to happen at some point. Employers are only human, after all.

However, regardless of their feelings towards how much control they have over your career, your career is yours and yours alone.

Your employer does have responsibilities. Managing your career is not one of them. Employers determine company direction. They manage the business. They drive profits. And if your career progression has a large bearing on the aforementioned, then sure, it is the employer's responsibility to ensure you succeed. Not because of you. Because it benefits the company, which the employer is responsible for.

However, in isolation, you don't matter. In the context of the entire company, your career is a drop in the proverbial ocean.

Your career is a
drop in the ocean.

Let's, for a moment, switch the context to that of the programmer, a role I'm most familiar with. A programmer has an influence on product quality. A programmer may be able to lead entire teams. A programmer may even be able to indirectly drive profitability. But the most control programmers have, is over what technologies they master, what skills they pick up, and what industry experiences they acquire.

Our careers are only thing we can have control over. How does it make any sense to cede that control to someone else? Someone that, I may add, has less skin in the game than you do. If you're not good enough, your employer suffers a temporary setback. But your employer can replace you just like that. You, on the other hand, are a hundred times more affected by your own professional success, or lack thereof. Depend on your employer to upgrade your skills and send you for training? What, are you insane or simply just spineless?

Learn what makes sense to you. Steer your career in directions that you want to go.

Final words

Relying on another person to "take care of you" is a sure recipe for calamity. Expecting someone else to care more than yourself about your own career, is nothing short of folly. Never ever make the mistake of thinking that your career is in someone else's hands.

Steady the course, Captain!
T___T

Tuesday 20 February 2024

Becoming an employer (sort of) for a day

On the very first day of this year, I began the daunting and emotionally-wrenching process of clearing out my apartment.

For context, this was one of the final parts of a journey that began in 2015, when the Singapore Government decided to reclaim the land on which my apartment building stood, which meant that I would be relocated and suitably compensated. COVID-19 turned out to be a two-year spanner in the works. In the tail end of 2023, the new homes were finally completed and we could begin moving over.

So yes, I now have a new home which I just moved into.

The surroundings of my
spanking new home.

That wasn't the end of the story, however. I still had to clear out my old apartment and return it to the Government. This meant that all the furniture, appliances and unwanted rubbish in the apartment had to be properly disposed of. Removals company were quoting me an outrageous fee for this service. Some asked for SGD 400. One quote even went up to SGD 750.

I mean, ultimately, it's just money. But still.

An ex-schoolmate had some Bangladeshi workers he knew, who were looking to make a quick buck during the public holidays. He hooked us up, and we made a date for the first of January.

How it went

Honestly? They showed up on time, did the job without fuss, and finished in maybe three hours. At the end of it, the big-ass pine dining table, computer desk, queen-sized bed and mattress, three single beds were all dismantled and cleared. My old washing machine, refrigerator (now that was a nightmare) and other miscellenous bits of furniture were removed. And finally, everything that wasn't nailed down, was taken out in garbage bags.

They toiled, strained, and grunted. But at the end of it, it was done. Done.

Clearing out the
old place.

And they charged me a hundred Singapore dollars each, which I paid. Happily. Without haggling. Because I've been a freelancer before, and I absolutely fucking hate it when people ask me to justify my price. No, bro. My price is my price. You get to take it or leave it, but you don't get to question it.

Thus, this being the standard I set with regard to naming my price, it seemed only reasonable to extend others the same courtesy when the roles were reversed.

Was it too expensive?

This led to a question that people inevitably asked when I told them. Is this too expensive? After all, they were charging me for a full day's work, which they finished in three hours.

Let's examine the facts, then.

Firstly, remember that the typical removals company would have charged me more than double what these guys cost me, for less work.

Secondly, it was the New Year's Day, a public holiday. Many people asking this question would actually get paid overtime if they were recalled in to work on this day. In a full day's work, as a software developer, I estimate that I earn more than double what these guys were charging me. On that evidence, I saw no reason to begrudge them this.

The last point is major for someone who works with software. I'm not concerned about the fact that they worked three hours but charged me for an entire's day work, at all. Good for them; we should all aim to be this productive. The staggering amount of work they did in these few hours would have taken me an entire day. And I was more than happy to not have to sit around an entire day waiting for them to be done. It's a failure of several employers I see, a trait of The Chinese Towkay Syndrome, to value the efforts of someone by how long they work.

This is a syndrome that begrudges people money unless these people sweat buckets for that money. This obsession with having people work hard for your money, is pure foolishness. That is akin to judging the monetary worth of a software product by the number of days it took to produce it!

Get a beast of burden.

This is why I feel that Singaporeans generally make poor employers. They bitch a lot about how they're undervalued and exploited at work, but once the shoe's on the other foot, they become the exploiters with tragic alacrity, becoming the very assholes they constantly rail against. You want hard work as opposed to results? Get a fucking camel. I don't know what else to tell you.

Why would I feel inclined to pay less just because someone took less time to do the job? FFS, I'm a software developer. In our world, we believe in results, not perspiration. You generally want to encourage people to work faster, not necessarily harder, and definitely not longer hours, to produce the same result. Paying them less for it is counterintuitive, or at least it is for people who engage in logic for a living.

Are there migrant workers who could have done it for less? Maybe. Probably. But these are the ones I was handed. They did the job for less than I would have paid otherwise. They got paid well for their trouble. We both benefited from the arrangement, and everything else is just noise.

Conclusion

We should measure the value of things by work done, not by hours worked. There is a stark difference. Quantity does not equate to quality.

Don't work hard. Get work done.
T___T

Friday 16 February 2024

Why Mask The Password Field?

There is a delicate balancing act to be maintained where web security is concerned. Scratch that; this happens with all security. The more secure a system is, the more inconvenient it tends to be for the user.

The example we will examine today is the humble Password field. In HTML, this is how you do it. This is a typical input tag.
<input value="This is a text field" />

Nothing fancy here.

All we need to do, is use the type attribute, and give it a value of "password".
<input type="password" value="This is a text field" />

Simple, eh? Now the input is masked.

But this isn't just about HTML. There are plenty of form builders out there which will also provide masking once a field is described as a Password field. Why this convention?

The reason why passwords are masked, are to prevent others from being able to view the user's password, on the off-chance that these others are shoulder-surfing at the time. But hold on, won't other things like keystroke loggers do the job of stealing passwords much better? And in an age where people work remotely (during the COVID-19 pandemic anyway) why is shoulder-surfing still a concern?

No, remote working can actually be an even more compelling reason for password masking. Remote working tends to mean video calls and screen presentations. What if the user needs to enter a password during that presentation?

The inconvenience of masking

Of course, hiding any kind of input the user creates on screen, just by definition, is inconvenient. If people can't see what they are typing, mistakes occur. And in the context of passwords where even capitalization is important and typically passwords of a minimal length are mandatory, that inconvenience is magnified.

Effectively blind.

In effect, you're typing blind.

This assumes that there is nothing wrong with the keyboard (not stuck, etc) and that the user isn't suffering any kind of impairment, like dyslexia. Needless to say, all that can only add to the inconvenience.

Mitigating features

Not masking passwords is out of the question. So what, then, is the way around that inconvenience?

One way is to implement a feature for temporarily revealing the password. You may see this take the form of an eye icon on the right side of the field.

Masked...


... and unmasked!


Is it perfect? Well, it kind of solves the problem while kind of maintaining security. It's a long way from watertight, but until we come up with a better idea, it stays.

Finally...

This blogpost was inspired by an interview candidate who, while demonstrating a project she had made, showed me a web form she had created where the password was represented as a... you guessed it, standard text field. I waited for her to bring that up and maybe make an excuse for it. Honestly, I would have taken any excuse, I just needed to know she was aware of the need to mask the password. This isn't entirely why she failed the interview, but it did get me thinking about password masking.

Yours ***************,
T___T

Sunday 11 February 2024

Web Tutorial: Valentine's Day jQuery Animation (Part 2/2)

Welcome back, and let's get back to creating the transition() method!

We begin by declaring contents as an array of all elements styled using the CSS class square_contents. And imgURL as the string returned by getRandomImage().
transition: function(ms, squareSize)
{
  var contents = $(".square_contents");
  var imgUrl = this.getRandomImage();

}


animationType and sequenceType are defined as random numbers between 0 and 3 and 5, respectively.
transition: function(ms, squareSize)
{
  var contents = $(".square_contents");
  var imgUrl = this.getRandomImage();

  var animationType = Math.floor(Math.random() * 3);
  var sequenceType = Math.floor(Math.random() * 5);

}


We then want to iterate through contents using a For loop. We could do a Foreach loop, but we'll be using the value of i, so...
transition: function(ms, squareSize)
{
  var contents = $(".square_contents");
  var imgUrl = this.getRandomImage();

  var animationType = Math.floor(Math.random() * 3);
  var sequenceType = Math.floor(Math.random() * 5);

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

  }

}


Each element in contents will have a data-row and data-col attribute. Define row and col so that they take on those values.
for(var i = 0; i < contents.length; i++)
{
  var row = $(contents[i]).attr("data-row");
  var col = $(contents[i]).attr("data-col");

}


Then use a Switch statement on animationType. Let's set the default case first.
for(var i = 0; i < contents.length; i++)
{
  var row = $(contents[i]).attr("data-row");
  var col = $(contents[i]).attr("data-col");

  switch (animationType)
  {
    default:
  }

}


Now we will declare transitionTime as a random integer between 0 and ms.
switch (animationType)
{
  default:
    var transitionTime = Math.floor(Math.random() * ms);
}


We then declare sequenceTime and set its value using the getSequenceTime() method, passing in the values of i, transitionTime and sequenceType as arguments.
switch (animationType)
{
  default:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);
}


We will, of course, have to declare the getSequenceTime() method. It will have the parameters index, ms and type.
getRandomImage: function()
{
  var index = Math.floor(Math.random() * 9);

  return "'img" + index + ".jpg'";
},
getSequenceTime: function(index, ms, type)
{

},   
   
fillContainer: function()
{


In here, we use a Switch statement on type. Again, we declare the default case first. Since we're returning a value in every case, we don't need break statements. In this case, we return a formula based on index and ms. Basically, the higher the value of index, the greater the returned value. Adding ms to the end is just a way of randomizing the result.
getRandomImage: function()
{
  var index = Math.floor(Math.random() * 9);

  return "'img" + index + ".jpg'";
},
getSequenceTime: function(index, ms, type)
{
  switch (type)
  {
    default: return (index * 1.5) + ms;
  }

},      
fillContainer: function()
{


Where were we? Ah, yes. Inside the transition() method, fulfilling the first (and only, so far) case for animationType. We will run the animate() method on the current element of contents. Make sure to put that element in a jQuery wrapper so that animate() can be run on it.
switch (animationType)
{
  default:
  var transitionTime = Math.floor(Math.random() * ms);
  var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);

  $(contents[i])
  .animate()

}


We want to animate the opacity attribute, to 0.
switch (animationType)
{
  default:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);

    $(contents[i])
    .animate(
       {
          opacity: 0
       }
    )
}


We want it to use sequenceTime as the duration of the animation.
switch (animationType)
{
  default:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);

    $(contents[i])
    .animate(
      {
        opacity: 0
      },
      sequenceTime
    )
}


Once the animation is done, we want to replace the background with the next image, represented by imgUrl. We then manipulate the background-position property using the data-col and data-row attributes of the current element, along with squareSize. squareSize also determines the width and height. We will also make sure the opacity property is still at 0.
switch (animationType)
{
  default:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);

    $(contents[i])
    .animate(
      {
        opacity: 0
      },
      sequenceTime,
      function() {
        $(this).attr("style", "background-image:url(" + imgUrl + ");background-position:-" + ($(this).attr("data-col") * squareSize) + "px -" + ($(this).attr("data-row") * squareSize) + "px;opacity:0;width:" + squareSize + "px;height:" + squareSize + "px")
      }
    )
}


Once that's done, we want a slight delay. I recommend double ms.
switch (animationType)
{
  default:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);

    $(contents[i])
    .animate(
      {
        opacity: 0
      },
      sequenceTime,
      function() {
        $(this).attr("style", "background-image:url(" + imgUrl + ");background-position:-" + ($(this).attr("data-col") * squareSize) + "px -" + ($(this).attr("data-row") * squareSize) + "px;opacity:0;width:" + squareSize + "px;height:" + squareSize + "px")
      }
     )
    .delay(ms * 2)
}


And after the delay, we want to animate the opacity property back to full opacity.
switch (animationType)
{
  default:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);

    $(contents[i])
    .animate(
      {
        opacity: 0
      },
      sequenceTime,
      function() {
        $(this).attr("style", "background-image:url(" + imgUrl + ");background-position:-" + ($(this).attr("data-col") * squareSize) + "px -" + ($(this).attr("data-row") * squareSize) + "px;opacity:0;width:" + squareSize + "px;height:" + squareSize + "px")
      }
    )
    .delay(ms * 2)
    .animate(
      {
        opacity: 1
      },
      sequenceTime
    );
}


Don't forget the break statement!
switch (animationType)
{
  default:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);

    $(contents[i])
    .animate(
      {
        opacity: 0
      },
      sequenceTime,
      function() {
      $(this).attr("style", "background-image:url(" + imgUrl + ");background-position:-" + ($(this).attr("data-col") * squareSize) + "px -" + ($(this).attr("data-row") * squareSize) + "px;opacity:0;width:" + squareSize + "px;height:" + squareSize + "px")
      }
    )
    .delay(ms * 2)
    .animate(
      {
        opacity: 1
      },
      sequenceTime
    );

    break;
}


So now, we first see the image appear.



Then it starts fading away in bits starting from the top.



Then it starts appearing again, in bits, from the top.



And you see that it's a different picture that is being assembled.



Viola!



More animations!

Now that you've set one up, let's experiment with more. The first one was merely fidding with the opacity property. Let's try something else! Make a copy of the code in the transition() method, and paste it as the first case, like so.
switch (animationType)
{
  case 0:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);  
    $(contents[i])
    .animate(
      {
        opacity: 0
      },
      sequenceTime,
      function() {
        $(this).attr("style", "background-image:url(" + imgUrl + ");background-position:-" + ($(this).attr("data-col") * squareSize) + "px -" + ($(this).attr("data-row") * squareSize) + "px;opacity:0;width:" + squareSize + "px;height:" + squareSize + "px")
      }
    )
    .delay(ms * 2)
    .animate(
      {
        opacity: 1
      },
      sequenceTime
    );

    break;

  default:
    var transitionTime = Math.floor(Math.random() * ms);
    var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);  
    $(contents[i])
    .animate(
      {
        opacity: 0
      },
      sequenceTime,
      function() {
        $(this).attr("style", "background-image:url(" + imgUrl + ");background-position:-" + ($(this).attr("data-col") * squareSize) + "px -" + ($(this).attr("data-row") * squareSize) + "px;opacity:0;width:" + squareSize + "px;height:" + squareSize + "px")
      }
    )
    .delay(ms * 2)
    .animate(
      {
        opacity: 1
      },
      sequenceTime
    );

    break;
}


Then let's work on the default case. We want to alter the size this time. So in the first object inside the first call to animate(), we specify that width and height are both set to 0 pixels. In the second call to animate(), we want to set width and height back to squareSize pixels.
default:
  var transitionTime = Math.floor(Math.random() * ms);
  var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);  
  $(contents[i])
  .animate(
    {
      width: "0px",
      height: "0px"
    },
    sequenceTime,
    function() {
      $(this).attr("style", "background-image:url(" + imgUrl + ");background-position:-" + ($(this).attr("data-col") * squareSize) + "px -" + ($(this).attr("data-row") * squareSize) + "px;opacity:0;width:" + squareSize + "px;height:" + squareSize + "px")
      }
   )
   .delay(ms * 2)
   .animate(
    {
      width: squareSize + "px",
      height: squareSize + "px"
    },
    sequenceTime
  );

  break;


And we need to make sure that at the part where the picture changes, opacity is full (because it's not opacity we're fiddling with this time) and width and height are both 0 pixels.
default:
  var transitionTime = Math.floor(Math.random() * ms);
  var sequenceTime = this.getSequenceTime(i, transitionTime, sequenceType);  
  $(contents[i])
  .animate(
  {
    width: "0px",
    height: "0px"
  },
  sequenceTime,
  function() {
    $(this).attr("style", "background-image:url(" + imgUrl + ");background-position:-" + ($(this).attr("data-col") * squareSize) + "px -" + ($(this).attr("data-row") * squareSize) + "px;opacity:1;width:0px;height:0px")
  }
  )
  .delay(ms * 2)
  .animate(
    {
      width: squareSize + "px",
      height: squareSize + "px"
    },
    sequenceTime
  );

  break;


We might want to do this for the time being, to ensure that the first case never gets triggered.
var animationType = Math.floor(Math.random() * 3) + 10;
var sequenceType = Math.floor(Math.random() * 5);


Again, the picture animates from the top, the squares all growing smaller...



...till it disappears...



...and reappears from the top.



Gradually, all the way down.



Till it forms another, different picture.



Right, so there's another animation. But what if you don't want it to always start from the top? Well, we've made a provision for it, so go to your getSequenceTime() method. Make a copy of the default case, and set it as the case for value 0.
getSequenceTime: function(index, ms, type)
{
  switch (type)
  {
    case 0: return (index * 1.5) + ms;
    default: return (index * 1.5) + ms;
  }
},


Then change the formula to this.
getSequenceTime: function(index, ms, type)
{
  switch (type)
  {
    case 0: return (index * 1.5) + ms;
    default: return ((index % 10) * 10) + ms;
  }
},


Again, for testing purposes, change the value of sequenceType in the transition() method so that the Switch statement in getSequenceTime() always hits the default case, which is currently anything but 0.
var animationType = Math.floor(Math.random() * 3) + 10;
var sequenceType = Math.floor(Math.random() * 5) + 10;


You see it starts off like this.



And the squares animate almost spread evenly.



They start reappearing, again almost evenly.



Until they almost form a picture...



..and there it is!



Now let's remove the temp stuff we did.
var animationType = Math.floor(Math.random() * 3); // + 10;
var sequenceType = Math.floor(Math.random() * 5); // + 10;


In the startTransition() method, we make sure that transition() is also called every ms milliseconds. Now you should have a varied animation sequence, depending on random animation and sequence type combinations! Of course, you may went to build on it as I have here.
startTransition: function(ms)
{
  this.transition(ms / 10, this.squareSize);

  setInterval(
    ()=>{
      this.transition(ms / 10, this.squareSize);
    },
    ms
  );
},


In your heart of hearts,
T___T

Thursday 8 February 2024

Web Tutorial: Valentine's Day jQuery Animation (Part 1/2)

Hi and welcome, lovebirds!

Valentine's Day is coming up, and I'd like to play around with some jQuery animation. There will be no color animations, so no links to jQuery UI are required. Our objective today is to take a series of AI-generated Valentine-themed images, and transition through them randomly using a fancy animation. Sound good?

These are the images. Each is 500 by 750 pixels.

img0.jpg

img1.jpg

img2.jpg

img3.jpg

img4.jpg

img5.jpg

img6.jpg

img7.jpg

img8.jpg

img9.jpg

Here's some starting HTML with a link to jQuery. Background of the screen has been set to white.
<!DOCTYPE html>
<html>
  <head>
  <title>Happy Valentine's Day!</title>

  <style>
    body
    {
      background-color: rgb(255, 255, 255);
    }
  </style>

  <script src="https://code.jquery.com/jquery-3.7.0.js"></script>

  <script>

  </script>
  </head>

  <body>

  </body>
</html>


We will need a container div inside the HTML. Its id will be container. We will also add the images to the HTML for pre-loading.
<body>
  <div id="container">

  </div>

  <img src="img0.jpg" />
  <img src="img1.jpg" />
  <img src="img2.jpg" />
  <img src="img3.jpg" />
  <img src="img4.jpg" />
  <img src="img5.jpg" />
  <img src="img6.jpg" />
  <img src="img7.jpg" />
  <img src="img8.jpg" />
  <img src="img9.jpg" />

</body>


Here's the CSS. Width and height are the same as the image, and we use the margin property to align it to the middle of the screen. img tags are set to not display.
<style>
  body
  {
    background-color: rgb(255, 255, 255);
  }

  #container
  {
    width: 500px;
    height: 750px;
    margin: 10px auto 0 auto;
  }

  img
  {
    display: none;
  }

</style>


We want to fill the grid with smaller squares. So set the page to run the fillContainer() method of the vday object using the onload attribute.
<body onload="vday.fillContainer();">


And create the vday object in the script tag, with the fillContainer() method. While you're there, also create maxTransitionTime and squareSize properties. These are configuration settings that can be tweaked for performance.
<script>
  let vday =
  {
    maxTransitionTime: 10000,
    squareSize: 25,      
    fillContainer: function()
    {

    }
  }
</script>


We start by declaring the string imgURL, and calling getRandomImage() to assign a value to it.
fillContainer: function()
{
  var imgUrl = this.getRandomImage();
}


We'll then define the method getRandomImage(). All it really does is declare index as a random number between 0 and 9, and then return a string based on index. So it will be any one of the image file names we have.
<script>
  let vday =
  {
    maxTransitionTime: 10000,
    squareSize: 25,  
    getRandomImage: function()
    {
      var index = Math.floor(Math.random() * 9);

      return "'img" + index + ".jpg'";
    },
    fillContainer: function()
    {
      var imgUrl = this.getRandomImage();
    }
  }
</script>


We create a For loop to traverse through the number of rows in the container div, which is basically the height of container divided by squareSize. So what's going to happen here is that we create row as a div, style it using the CSS class row and set the style to have the height property at squareSize pixels. Append it inside container.
fillContainer: function()
{
  var imgUrl = this.getRandomImage();

  for(var i = 0; i < 750 / this.squareSize; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");
    row.attr("style", "height:" + this.squareSize + "px");

    $("#container").append(row);
  }
}


Here's the CSS class row. Full width, and a red outline temporarily, for visibility.
#container
{
  width: 500px;
  height: 750px;
  margin: 10px auto 0 auto;
}

.row
{
  width: 100%;
  outline: 1px solid red;
}


img
{
  display: none;
}


So far so good...



Now we are going to fill in squares. Have another For loop within the first one, this time traversing the number of squares per row. This will be the width of container (500 pixels) divided by squareSize.
fillContainer: function()
{
  var imgUrl = this.getRandomImage();

  for(var i = 0; i < 750 / this.squareSize; i++)
  {
    var row = $("<div></div>");
    row.addClass("row");
    row.attr("style", "height:" + this.squareSize + "px");

    for(var j = 0; j < 500 / this.squareSize; j++)
    {

    }

    $("#container").append(row);
  }
}


In here, create a div, style it using the square CSS class, and make sure both height and width are squareSize pixels. And then append it to row.
for(var j = 0; j < 500 / this.squareSize; j++)
{
  var square = $("<div></div>");
  square.addClass("square");
  square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

  row.append(square);

}


In the CSS, square is floated left and outline set to a solid red.
.row
{
  width: 100%;
  outline: 1px solid red;
}

.square
{
  float: left;
  outline: 1px solid red;
}


img
{
  display: none;
}


And here's a nice grid!



Next, we fill in those square divs using another div, styled with the CSS class square_contents.
for(var j = 0; j < 500 / this.squareSize; j++)
{
  var square = $("<div></div>");
  square.addClass("square");
  square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

  var content = $("<div></div>");
  content.addClass("square_contents");

  square.append(content);

  row.append(square);
}


Here's the CSS. It's mostly background-related CSS. You'll see why in a bit.
.square
{
  float: left;
  outline: 1px solid red;
}

.square_contents
{
  background-repeat: no-repeat;
  background-size: 500px 750px;
  margin: 0 auto 0 auto;
}


img
{
  display: none;
}


Back to the For loop... add in data-row and data-col values using i and j. Those will be useful later.
for(var j = 0; j < 500 / this.squareSize; j++)
{
  var square = $("<div></div>");
  square.addClass("square");
  square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

  var content = $("<div></div>");
  content.addClass("square_contents");
  content.attr("data-row", i);
  content.attr("data-col", j);


  square.append(content);
  row.append(square);
}


And then load the current image (represented by imgURL) in the background-image property. Use background-position to ensure that the background of each square div's content is offset by the appropriate number of pixels, using the values of i, j and squareSize.
for(var j = 0; j < 500 / this.squareSize; j++)
{
  var square = $("<div></div>");
  square.addClass("square");
  square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

  var content = $("<div></div>");
  content.addClass("square_contents");
  content.attr("data-row", i);
  content.attr("data-col", j);
  content.attr("style", "background-image:url(" + imgUrl + ");background-position:-" + (j * this.squareSize) + "px -" + (i * this.squareSize) + "px;opacity:1;width:" +  this.squareSize + "px;height:" + this.squareSize + "px");

  square.append(content);
  row.append(square);
}


And you see the image! Each square has its image offset, but put it all together, it still forms a coherent picture!



Now, let's remove the red lines. We won't be needing them.
.row
{
  width: 100%;
  outline: 0px solid red;
}

.square
{
  float: left;
  outline: 0px solid red;
}


After that, run the startTransition() method. Pass in the maxTransitionTime property as an argument.
fillContainer: function()
{
  var imgUrl = this.getRandomImage();

  for(var i = 0; i < 750 / this.squareSize; i++)
  {
  var row = $("<div></div>");
  row.addClass("row");
  row.attr("style", "height:" + this.squareSize + "px");

  for(var j = 0; j < 500 / this.squareSize; j++)
  {
    var square = $("<div></div>");
    square.addClass("square");
    square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

    var content = $("<div></div>");
    content.addClass("square_contents");
    content.attr("data-row", i);
    content.attr("data-col", j);
    content.attr("style", "background-image:url(" + imgUrl + ");background-position:-" + (j * this.squareSize) + "px -" + (i * this.squareSize) + "px;opacity:1;width:" +  this.squareSize + "px;height:" + this.squareSize + "px");

    square.append(content);
    row.append(square);
  }

  $("#container").append(row);
  }

  this.startTransition(this.maxTransitionTime);
}


We then create startTransition(). It has ms as a parameter. In it, we run the transition() method, passing in a tenth of ms and the squareSize property as arguments.
    
fillContainer: function()
{
  var imgUrl = this.getRandomImage();

  for(var i = 0; i < 750 / this.squareSize; i++)
  {
  var row = $("<div></div>");
  row.addClass("row");
  row.attr("style", "height:" + this.squareSize + "px");

  for(var j = 0; j < 500 / this.squareSize; j++)
  {
    var square = $("<div></div>");
    square.addClass("square");
    square.attr("style", "height:" + this.squareSize + "px;width:" + this.squareSize + "px");

    var content = $("<div></div>");
    content.addClass("square_contents");
    content.attr("data-row", i);
    content.attr("data-col", j);
    content.attr("style", "background-image:url(" + imgUrl + ");background-position:-" + (j * this.squareSize) + "px -" + (i * this.squareSize) + "px;opacity:1;width:" +  this.squareSize + "px;height:" + this.squareSize + "px");

    square.append(content);
    row.append(square);
  }

  $("#container").append(row);
  }

  this.startTransition(this.maxTransitionTime);
},
startTransition: function(ms)
{
  this.transition(ms / 10, this.squareSize);
}


Then create transition(). It will have ms and squareSize as parameters.
startTransition: function(ms)
{
  this.transition(ms / 10, this.squareSize);
},
transition: function(ms, squareSize)
{

}


We should stop here for now. It's about to get fun, but also a little complicated.

Next

Animating the display.