Implementing Drag and Drop Using Backbone and EaselJS

Share this article

In this article, we are going to build a simple drag and drop application using EaselJS and Backbone.js. Backbone will give structure to our application by providing models, collections, and views. Easel will make working with the HTML5 canvas element easy. Although we don’t necessarily need Backbone for such a simple application, it is fun to get started with Backbone in this way.

Get Started

First, we create our directory structure as follows:
.
|-- index.html
+-- js
    |-- main.js
    |-- models
    |   +-- stone.js
    +-- views
        +-- view.js
Next, in index.html include the JavaScript files and a canvas element, as shown in the following code sample. Once this is done, we’re ready to manipulate the canvas.
<body>
  <!-- Canvas Element -->
  <canvas id="testcanvas" height="640" width="480"/>

  <script src="/bower_components/jquery/jquery.min.js"></script>
  <!-- underscore is needed by backbone.js -->
  <script src="/bower_components/underscore/underscore-min.js"></script>
  <script src="/bower_components/backbone/backbone.js"></script>
  <script src="/bower_components/easeljs/lib/easeljs-0.7.1.min.js"></script>
  <!-- tweenjs is for some animations -->
  <script src="/bower_components/createjs-tweenjs/lib/tweenjs-0.5.1.min.js"></script>
  <script src="/js/models/stone.js"></script>
  <script src="/js/views/view.js"></script>
  <script src="/js/main.js"></script>
</body>

Backbone Models

By creating a Backbone model, we will have key-value bindings and custom events on that model. This means we can listen to changes for model properties and render our view accordingly. A Backbone collection, is an ordered set of models. You can bind change events to be notified when any model in the collection changes. Next, let’s create a stone model, and a stone collection. The following code belongs in js/models/stone.js.
var Stone = Backbone.Model.extend({

});

var StoneCollection = Backbone.Collection.extend({
  model: Stone
});

Initialize the Backbone View Using EaselJS

Backbone views don’t determine anything about HTML, and can be used with any JavaScript templating library. In our case we aren’t using a templating library. Instead, we manipulate the canvas. You can bind your view’s render() function to the model’s change event so that when the model data changes, the view is automatically updated. To get started with Easel, we create a stage that wraps the canvas element, and add objects as children. Later, we pass this stage to our backbone view. The code in js/main.js that accomplishes this is shown below.
$(document).ready(function() {
  var stage = new createjs.Stage("testcanvas");
  var view = new CanvasView({stage: stage}).render();
});
We’ve created our CanvasView and called its render() function to render it. We will revisit the implementation of render()
shortly. First, let’s see our initialize() function, which is defined in js/views/view.js.
var CanvasView = Backbone.View.extend({
  initialize: function(args) {
    // easeljs stage passed as argument.
    this.stage = args.stage;
    // enableMouseOver is necessary to enable mouseover event http://www.createjs.com/Docs/EaselJS/classes/DisplayObject.html#event_mouseover
    this.stage.enableMouseOver(20);

    // stone collection
    this.collection = new StoneCollection();

    // bounds of pink area and our stones. the pink area is called "rake".
    this.rakeOffsets = {
      x: 10,
      y: 400,
      height: 150,
      width: 300,
      stoneWidth: 50,
      stoneHeight: 50
    };

    // listen to collection's add remove and reset events and call the according function to reflect changes.
    this.listenTo(this.collection, "add", this.renderStone, this);
    this.listenTo(this.collection, "remove", this.renderRake, this);
    this.listenTo(this.collection, "reset", this.renderRake, this);
  },
  //...
});
listenTo() listens for model/collection changes and calls the function passed as the second argument. We pass the context the function is being called in as a third argument. When we add a stone to our collection, an add event will dispatch this.renderStone() and pass the new stone to the function. Similarly, when the collection is reset, a reset event will dispatch this.renderRake(). By implementing these render functions, the view will always be in sync with the collection.

Rendering the View

The render() function, shown below, just calls this.renderRake() and updates the stage.
render: function() {
  this.renderRake();

  // stage.update is needed to render the display to the canvas.
  // if we don't call this nothing will be seen.
  this.stage.update();

  // The Ticker provides a centralized tick at a set interval.
  // we set the fps for a smoother animation.
  createjs.Ticker.addEventListener("tick", this.stage);
  createjs.Ticker.setInterval(25);
  createjs.Ticker.setFPS(60);
},
The renderRake() method, which is also stored in js/views/view.js, is shown below.
renderRake: function() {
  // http://stackoverflow.com/questions/4886632/what-does-var-that-this-mean-in-javascript
  var that = this;

  // create the rake shape
  var rakeShape = new createjs.Shape();

  rakeShape.graphics.beginStroke("#000").beginFill("#daa").drawRect(this.rakeOffsets.x, this.rakeOffsets.y, this.rakeOffsets.width, this.rakeOffsets.height);

  // assign a click handler
  rakeShape.on("click", function(evt) {
    // When rake is clicked a new stone is added to the collection.
    // Note that we add a stone to our collection, and expect view to reflect that.
    that.collection.add(new Stone());
  });

  // add the shape to the stage
  this.stage.addChild(rakeShape);

  // a createjs container to hold all the stones.
  // we hold all the stones in a compound display so we can
  // easily change their z-index inside the container,
  // without messing with other display objects.
  this.stoneContainer = new createjs.Container();
  this.stage.addChild(this.stoneContainer);

  // for each stone in our collection, render it.
  this.collection.each(function(item) {
    this.renderStone(item);
  }, this);
},
renderRake() does two things. First, it renders the rake shape (pink rectangle) on the canvas, and creates a click handler on it. Second, it traverses the stone collection and calls renderStone() on each item. The click handler adds a new stone to the collection. Next, let’s look at the renderStone()
function.
renderStone: function(model) {
  // var that = this;
  var baseView = this;

  // build the stone shape
  var stoneShape = buildStoneShape();

  // make it draggable
  // the second argument is a callback called on drop
  // we snap the target stone to the rake.
  buildDraggable(stoneShape, function(target, x, y) {
    rakeSnap(target, false);
  });

  // add the stone to the stage and update
  this.stoneContainer.addChild(stoneShape);
  this.stage.update();

  function buildStoneShape() {
    var shape = new createjs.Shape();

    shape.graphics.beginStroke("#000").beginFill("#ddd").drawRect(0, 0, baseView.rakeOffsets.stoneWidth, baseView.rakeOffsets.stoneHeight);
    return shape;
  };
},
We’ve called the buildDraggable() function to make the stone draggable. We will see how to implement that next. But first, let’s review how our backbone view works. The CanvasView listens to the collection’s add event, and when a new stone is added, it calls renderStone(). The render() method renders the rake and calls renderStone() on each stone in the collection. When the rake is clicked, a new stone model is added to the stone collection, and then renderStone() is called on the new stone. Now, let’s look at the buildDraggable() function that implements the drag and drop functionality:
renderStone: function(model) {
  // ...

  function buildDraggable(s, end) {
    // on mouse over, change the cursor to pointer
    s.on("mouseover", function(evt) {
      evt.target.cursor = "pointer";
    });

    // on mouse down
    s.on("mousedown", function(evt) {
      // move the stone to the top
      baseView.stoneContainer.setChildIndex(evt.target, baseView.stoneContainer.getNumChildren() - 1);

      // save the clicked position
      evt.target.ox = evt.target.x - evt.stageX;
      evt.target.oy = evt.target.y - evt.stageY;

      // update the stage
      baseView.stage.update();
    });

    // on mouse pressed moving (drag)
    s.on("pressmove", function(evt) {
      // set the x and y properties of the stone and update
      evt.target.x = evt.target.ox + evt.stageX;
      evt.target.y = evt.target.oy + evt.stageY;
      baseView.stage.update();
    });

    // on mouse released call the end callback if there is one.
    s.on("pressup", function(evt) {
      if (end) {
        end(evt.target, evt.stageX + evt.target.ox, evt.stageY + evt.target.oy);
      }
    });
  };
  // ...
},
And for the constraint of snapping the stone to the rake, here are the final utility functions we need.
// drag the stone, either by animating or not
function dragStone(s, x, y, animate) {
  if (animate) {
    // Use tween js for animation.
    createjs.Tween.get(s).to({x: x, y: y}, 100, createjs.Ease.linear);
  } else {
    // set x and y attributes without animation
    s.x = x;
    s.y = y;
  }

  // update
  baseView.stage.update();
};

// calculate x position to snap the rake
function snapX(x) {
  if (x &lt; baseView.rakeOffsets.x) {
    x = baseView.rakeOffsets.x;
  } else if (x > baseView.rakeOffsets.x + baseView.rakeOffsets.width - baseView.rakeOffsets.stoneWidth) {
    x = baseView.rakeOffsets.x + baseView.rakeOffsets.width - baseView.rakeOffsets.stoneWidth;
  }

  return x;
};

// calculate y position to snap the rake
function snapY(y) {
  if (y &lt; baseView.rakeOffsets.y) {
    y = baseView.rakeOffsets.y;
  } else if (y > baseView.rakeOffsets.y + baseView.rakeOffsets.height - baseView.rakeOffsets.stoneHeight) {
    y = baseView.rakeOffsets.y + baseView.rakeOffsets.height - baseView.rakeOffsets.stoneHeight;
  }

  return y;
};

// drag stone within the rake bounds. animation is disabled if second argument is given. animation is enabled by default
function rakeSnap(s, animateDisabled) {
  dragStone(s, snapX(s.x), snapY(s.y), !animateDisabled);
};

Conclusion

In conclusion, Backbone is not restricted to DOM manipulation, and can be used anywhere that needs model-view structure. Though it can be used to build single page applications, it is not a complete framework, and we have only seen one side of Backbone in this article. If you like to use Backbone for large scale applications, I suggest using Marionette.js, which handles some primitive problems with Backbone. The full code for this article can be found on GitHub. A live demo is also available on Heroku. To get started, just click on the pink area to create a draggable stone. The stone will be draggable, and it will be constrained inside the pink area.

Frequently Asked Questions about Implementing Drag and Drop using Backbone and EaselJS

How does EaselJS work with Backbone.js in implementing drag and drop functionality?

EaselJS is a JavaScript library that simplifies working with the HTML5 Canvas element. It provides a full, hierarchical display list, a core interaction model, and helper classes to make working with Canvas much easier. Backbone.js, on the other hand, is a JavaScript framework that provides structure to web applications by offering models with key-value binding and custom events. When used together, EaselJS handles the visual aspects of the application, while Backbone.js manages the data and business logic. This combination allows for the efficient implementation of drag and drop functionality in web applications.

What are the key differences between the EaselJS library and other similar libraries?

EaselJS stands out for its simplicity and ease of use. It provides a full, hierarchical display list, a core interaction model, and helper classes to make working with Canvas much easier. Unlike other libraries, EaselJS is specifically designed to work with the HTML5 Canvas element, making it a great choice for creating rich, interactive web content.

How can I handle events in EaselJS?

EaselJS provides a robust event model that is compatible with touch devices and modern browsers. You can add event listeners to any display object, which will be notified when the user interacts with the object. The event object that is passed to the listener contains a wealth of information about the current interaction, including the type of event, the target object, and the current mouse position.

How can I create a draggable object in EaselJS?

To create a draggable object in EaselJS, you need to add a ‘mousedown’ event listener to the object. In the event handler, you can use the ‘startDrag’ method to start the drag operation. You can also add a ‘pressmove’ event listener to update the position of the object as it is being dragged.

How can I use EaselJS with other JavaScript libraries or frameworks?

EaselJS is designed to be flexible and can be used with any JavaScript library or framework. It does not have any dependencies and does not modify the global namespace, making it easy to integrate with other libraries. You can use EaselJS with libraries like jQuery or frameworks like AngularJS or React to create rich, interactive web applications.

How can I optimize performance when using EaselJS?

EaselJS provides several features to help optimize performance. For example, you can use the ‘cache’ method to cache a display object as a bitmap, which can significantly improve rendering performance. You can also use the ‘tick’ event to update your display objects only when necessary, reducing unnecessary redraws.

How can I handle complex shapes and paths in EaselJS?

EaselJS provides a powerful graphics API that allows you to draw complex shapes and paths. You can use the ‘Graphics’ class to create paths, fill shapes, and apply strokes. You can also use the ‘Shape’ class to display your graphics on the canvas.

How can I animate objects in EaselJS?

EaselJS provides a ‘Tween’ class that you can use to animate display objects. You can use this class to change the properties of an object over time, creating smooth animations. You can also chain tweens together to create complex animation sequences.

How can I load and display images in EaselJS?

EaselJS provides a ‘Bitmap’ class that you can use to load and display images. You can create a new Bitmap object by passing the path to the image file to the Bitmap constructor. Once the image is loaded, you can add the Bitmap object to the stage to display the image.

How can I handle user input in EaselJS?

EaselJS provides a robust event model that allows you to handle user input. You can add event listeners to any display object to be notified when the user interacts with the object. The event object that is passed to the listener contains information about the current interaction, including the type of event, the target object, and the current mouse position.

Emre GuneylerEmre Guneyler
View Author

Emre Guneyler is a CS student in Turkey. He is passionate about web and game development. Besides programming he enjoys chess, poker, and Call of Duty 4.

Backbone.jsdrag and dropEaselJS
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week