Creating a code wrapper and defining variables
All of our code will need to be contained within a wrapper function that is executed once the page has finished loading.
Prepare for Lift Off
The steps that we'll complete in this part of the project are as follows:
Add a wrapper function for our code that will execute as soon as the page has finished loading
Define the variables that we'll use throughout the script
Engage Thrusters
The first step is to create a wrapper function for our code that will be executed as soon as the page has loaded. Add the following code to a new script file called sliding-puzzle.js
, which should be saved in the js
directory we created earlier:
$(function () { //all our code will be in here... });
Most jQuery code that we see in the wild resides within some kind of wrapper like this. Using $(function(){});
is a shortcut to jQuery's document.ready
function, which is fired once the DOM for the page has loaded.
Tip
Using $
We wouldn't normally use $
in the global scope like this if we were sharing our code with other developers, as there may be other libraries on the page also using it. Best practice is to alias the $
character within an automatically invoked anonymous function, or an immediately invoked function expression if you prefer. This can be done using the syntax (function($) { … }(jQuery));
.
Next we can set some variables near the top of the script file. This is so that we don't have lots of values that we may want to change later distributed throughout the file. Organization is one of the keys to writing maintainable code, and we should always strive to make our code, as well as our intentions, as clear as possible.
Next add the following code inside the function we just defined, replacing the comment shown in the previous code sample:
var numberOfPieces = 12, aspect = "3:4", aspectW = parseInt(aspect.split(":")[0]), aspectH = parseInt(aspect.split(":")[1]), container = $("#puzzle"), imgContainer = container.find("figure"), img = imgContainer.find("img"), path = img.attr("src"), piece = $("<div/>"), pieceW = Math.floor(img.width() / aspectW), pieceH = Math.floor(img.height() / aspectH), idCounter = 0, positions = [], empty = { top: 0, left: 0, bottom: pieceH, right: pieceW }, previous = {}, timer, currentTime = {}, timerDisplay = container.find("#time").find("span");
These aren't all of the variables that we'll use, just the majority of them. The list also includes any variables that we'll need to use inside callback functions so that we don't run into scope issues.
Objective Complete - Mini Debriefing
The variables we defined first are a combination of simple (primitive) values and objects or arrays that we'll use throughout the code, and cached jQuery elements. For best performance when using jQuery, it's best to select elements from the page and store them in variables instead of repeatedly selecting them from the page.
Although none of our variables are directly assigned to window
, and are therefore not actually global, because we are defining them right at the top of our outermost function, they will be visible throughout our code and we can consider them as global. This gives us the visibility of globals, without actually cluttering the global namespace.
Note
It is best practice to define variables at the top of the function they are scoped to because of a phenomenon known as
hoisting, in which variables defined deep inside a function, inside a for
loop for example, are "hoisted" to the top of the function in some situations, potentially causing errors that are hard to track down.
Defining variables at the top of the function where possible is a simple way to avoid this occurring and is considered a good practice when writing jQuery, or JavaScript in general.
Most of the variables are quite straightforward. We store the number of puzzle pieces we'd like to use and the aspect ratio of the image being used. It's important that the number of pieces can be equally divided by both the width
and height
components of the ratio.
We split the aspect ratio into its component parts using JavaScript's split()
function and specifying the colon as the character to split on. We also use the JavaScript parseInt()
function to ensure we end up with actual numbers and not strings in the aspectW
and aspectH
variables.
The next three variables are all different elements selected from the page that we need to manipulate. Following this is a new element that we create using jQuery.
Next we calculate the width
and height
each piece of the puzzle will need to be sized to, based on the width
and height
of the original image and the aspect ratio, and we initialize a counter variable that we'll use to add a unique, ordered id
attribute to each puzzle piece. We also add an empty array called positions
, which we'll use to store the top
and left
positions of each new piece.
We'll need a way of keeping track of the empty space as the pieces are moved around the board, so we create an object called empty
and give it top
, left
, bottom
, and right
properties so that we'll know exactly where the blank is space at any given moment. We'll also want to keep track of the previous location of any given piece so we create an empty object called previous
that we'll populate with properties when required.
The remaining three variables are all concerned with keeping track of the time it takes to solve the puzzle. We defined, but didn't initialize the timer
variable that we'll use to store a reference to a JavaScript setInterval()
-based timer later in the script. We also created an empty object called currentTime
, which again we'll populate when required, and cached a reference to the element that we'll use to display the current time.