Book Image

jQuery HOTSHOT

By : Dan Wellman
Book Image

jQuery HOTSHOT

By: Dan Wellman

Overview of this book

jQuery is used by millions of people to write JavaScript more easily and more quickly. It has become the standard tool for web developers and designers to add dynamic, interactive elements to their sites, smoothing out browser inconsistencies and reducing costly development time.jQuery Hotshot walks you step by step through 10 projects designed to familiarise you with the jQuery library and related technologies. Each project focuses on a particular subject or section of the API, but also looks at something related, like jQuery's official templates, or an HTML5 feature like localStorage. Build your knowledge of jQuery and related technologies.Learn a large swathe of the API, up to and including jQuery 1.9, by completing the ten individual projects covered in the book. Some of the projects that we'll work through over the course of this book include a drag-and-drop puzzle game, a browser extension, a multi-file drag-and-drop uploader, an infinite scroller, a sortable table, and a heat map. Learn which jQuery methods and techniques to use in which situations with jQuery Hotshots.
Table of Contents (18 chapters)
jQuery HOTSHOT
Credits
Foreword
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Making the puzzle pieces draggable


Now it's time to kickstart jQuery UI to make the individual pieces of the puzzle draggable.

jQuery UI is a suite of jQuery plugins used to build interactive and efficient user interfaces. It is stable, mature, and is recognized as the official, although not the only UI library for jQuery.

Prepare for Lift Off

In this task we'll cover the following steps:

  • Making the puzzle pieces draggable using jQuery UI's Draggable component

  • Configuring the draggables so that only pieces directly next to the empty space can be moved

  • Configuring the draggables so that pieces can only be moved into the empty space

Engage Thrusters

First we'll make the pieces draggable and set some of the configuration options that the component exposes. This code should be added to sliding-puzzle.js, directly after the code added in the previous task:

pieces.draggable({
    containment: "parent",
    grid: [pieceW, pieceH],
    start: function (e, ui) {

    },
    drag: function (e, ui) {

    },
    stop: function (e, ui) {

    }
});

The next few steps in this task will see additional code added to the start, drag, and stop callback functions in the previous code sample.

We also need to configure the draggability so that the pieces can only be moved into the empty space, and not over each other, and so that only pieces directly adjacent to the empty space can be moved at all.

Next add the following code in to the start callback function that we just added:

var current = getPosition(ui.helper);

if (current.left === empty.left) {
    ui.helper.draggable("option", "axis", "y");
} else if (current.top === empty.top) {
    ui.helper.draggable("option", "axis", "x");
} else {
    ui.helper.trigger("mouseup");
    return false;
}

if (current.bottom < empty.top || 
    current.top > empty.bottom ||
    current.left > empty.right || 
    current.right < empty.left) {
        ui.helper.trigger("mouseup");
        return false;
    }

    previous.top = current.top;
    previous.left = current.left;

Next, add the following code to the drag callback function:

var current = getPosition(ui.helper);

ui.helper.draggable("option", "revert", false);

if (current.top === empty.top && current.left === empty.left) {
    ui.helper.trigger("mouseup");
    return false;
}

if (current.top > empty.bottom ||
    current.bottom < empty.top || 
    current.left > empty.right || 
    current.right < empty.left) {
        ui.helper.trigger("mouseup")
                 .css({ 
                     top: previous.top, 
                     left: previous.left 
                 });
        return false;
}

Finally, we should add the following code to the stop callback function:

var current = getPosition(ui.helper);

if (current.top === empty.top && current.left === empty.left) {

    empty.top = previous.top;
    empty.left = previous.left;
    empty.bottom = previous.top + pieceH;
    empty.right = previous.left + pieceW;
}

In each of our callbacks we've used a helper function that returns the exact position of the current draggable. We should also add this function after the draggable()method:

function getPosition(el) {
    return {
        top: parseInt(el.css("top")),
        bottom: parseInt(el.css("top")) + pieceH,
        left: parseInt(el.css("left")),
        right: parseInt(el.css("left")) + pieceW
    }
}

Objective Complete - Mini Debriefing

We wrote a lot of code in that last task, so let's break it down and see what we did. We started by making the pieces draggable using the jQuery UI draggable component. We did this by calling the draggable() method, passing in an object literal that sets various options that the draggable component exposes.

First we set the containment option to parent, which stops any of the pieces being dragged out of the <figure> element that they are within. We also set the grid option, which allows us to specify a grid of points that the piece being dragged should snap to. We set an array as the value of this option.

The first item in this array sets the horizontal points on the grid and the second item sets the vertical points on the grid. Setting these options gives the movement of the pieces a more realistic and tactile experience.

The next and final three options that we set are actually callback functions that are invoked at different points in the life-cycle of a drag. We use the start, drag, and stop callbacks.

When the drag begins

The start callback will be invoked once at the very start of the drag interaction following a mousedown event on a draggable. The stop callback will be invoked once at the very end of a drag interaction, once a mouseup event has registered. The drag callback will fire almost continuously while a piece is being dragged as it is invoked for every pixel the dragged element moves.

Let's look at the start callback first. Each callback is passed two arguments by jQuery UI when it is invoked. The first of these is the event object, which we don't require in this project, while the second is an object containing useful properties about the current draggable.

At the beginning of the function we first get the exact position of the piece that dragging has started on. When we call our getPosition() function, we pass in the helper property of the ui object, which is a jQuery-wrapped reference to the underlying DOM element that has started to be dragged.

Once we have the element's position, we first check whether the element is in the same row as the empty space by comparing the left property of the current object (the object returned by getPosition()) with the left property of the empty object.

If the two properties are equal, we set the axis option of the draggable to y so that it can only move horizontally. Configuration options can be set in any jQuery UI widget or component using the option method.

If it isn't in the same row, we check whether it is in the same column instead by comparing the top properties of the current and empty objects. If these two properties are equal, we instead set the axis option to x so that the piece may only move vertically.

If neither of these conditions is true, the piece cannot be adjacent to the empty space, so we manually trigger a mouseup event to stop the drag using jQuery's trigger() method, and also return false from the function so that our stop handler is not triggered.

We need to make sure that only squares in the same row or column as the empty space are draggable, but we also need to make sure that any pieces that are not directly adjacent to the empty space cannot be dragged either.

To stop any pieces not adjacent to the empty space being dragged, we just check that:

  • The bottom of the current piece is less than the top of the empty space

  • The top of the current piece is greater than the bottom of the empty space

  • The left of the current piece is greater than the right of the empty space

  • The right of the current piece is less than the left of the empty space

If any of these conditions are true, we again stop the drag by triggering a mouseup event manually, and stop any further event handlers on the draggable being called (but only for the current drag interaction) by returning false.

If the callback function has not returned at this point, we know we are dealing with a draggable that is adjacent to the empty space, thereby constituting a valid drag object. We therefore store its current position at the start of the drag for later use by setting the top and left properties of the previous object that we initialized at the start of the project.

Tip

The position of ui.helper

The ui object passed to our callback function actually contains an object called position, which can be used to obtain the current draggable's position. However, because we are using the grid option, the values contained in this object may not be granular enough for our needs.

During the drag

Next we can walk through the drag callback, which will be called every time the position of the current draggable changes. This will occur during a mousedown event.

First of all we need to know where the piece that's being dragged is, so we call our getPosition() helper function again.

Then we want to check whether the piece being dragged is in the empty space. If it is, we can stop the drag in the same way that we did before – by manually triggering a mouseup event and returning false.

During the drag, only valid pieces will be draggable because we've already filtered out any pieces that are not directly adjacent to the empty space. However, we still need to check that the piece being dragged is not being dragged away from the empty space. We do this in the same way that we filtered out pieces not adjacent to the empty space in the start callback.

When the drag ends

The stop callback is the simplest of the three callbacks. We get the position of the piece that was dragged, and if it's definitely in the empty space, we move the empty space so that it is in the position the dragged piece was in when the drag began. Remember we stored this information in an object called previous.