Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Jigsaw Puzzle Game using AJAX Drag and Drop

4.74/5 (18 votes)
20 Apr 2008CPOL5 min read 1   4K  
A jigsaw puzzle game using AJAX drag and drop (ASP.NET 2.0 AJAX Futures November CTP).

Screenshot.jpg

Introduction

This tutorial is intended to explain quickly how to implement drag and drop using the ASP.NET 2.0 AJAX Futures November CTP. To explain this technology, I've created a simple project with a custom AJAX control that implements a jigsaw puzzle game.

Background

When I started to learn the new ASP.NET AJAX framework, I asked my company to buy a book, and I chose an amazing book:

In this book, I found a chapter that explains how to use the PreviewDragDrop to implement drag and drop on web. I used this chapter and other information from Internet (Google) to create my AJAX control.

The ASP.NET AJAX Framework

The basic idea within the ASP.NET AJAX Framework is to have a possibility to use object-oriented programming in JavaScript (simulated OOP) and make it similar to C#. Most C# features are available in JavaScript: namespace, class, interface, enum etc. Besides the OOP features is available the possibility to implement visual custom controls (client side controls) that extend the HTML elements' functionalities.

Basically, we have two types of controls (visual controls):

  • Behavior (extending Sys.UI.Behavior)
  • Control (extending Sys.UI.Control)

The difference is just logic, but whilst generally Sys.UI.Behavior is used to extend HTML element behaviors for different types of elements, Sys.UI.Control is used to extend HTML element behaviors for a single type of element.

For example, if we want to implement a behavior that shows an alert on the click event, we can create a class that extends Sys.UI.Behavior, and then we can use this code with several HTML element types: DIV, SPAN, INPUT etc.

C#
//Namespace declaration
Type.registerNamespace("MyNamespace");

//Constructor
MyNamespace.MyBehavior = function(element)
{
   MyNamespace.MyBehavior.initializeBase(this, [element]);
}

MyNamespace.MyBehavior.prototype =
{
   initialize : function()
   {
     // Add event handler on click
      // Parameters: 
      // 1) The HTML element
      // 2) Event name without "on" ("onclick"="click")
      // 3) Control instance
      $addHandlers(this.get_element(), { "click" : this._onClick }, this);
   },

   dispose : function()
   {
      // Remove all events handlers for the current HTML element
      $clearHandlers(this.get_element());
   },

   // Event handler onclick
   _onClick : function(evt)
   {
      // Show the id of current HTML Element
      alert(this.get_id());
   }
};

// Register class
MyNamespace.MyBehavior.registerClass("MyNamespace.MyBehavior", Sys.UI.Behavior);

When we need to use it within our page, we can just write the following code:

JavaScript
// Create an instance of our Behavior
// and attach it to HTML element with id 'elementId'
// Parameters:
// 1) Class name with namespace
// 2) Properties in JSON format
// 3) Events in JSON format
// 4) References in JSON format
// 5) HMTL element
$create(MyNamespace.MyBehavior, {}, {}, {}, $get('elementId'));

If we need to implement behaviors for an element type or we need to implement a complex control (a visual control composed of different elements), we need to extend Sys.UI.Control.

AJAX Enabled Server Control

Generally, control are not manually created using $create in JavaScript, but automatically generated by an ASP.NET Server Control (server side version of the control). This way, we can implement a server control and have design-time support within Visual Studio. When we want to create a AJAX enabled Server Control, we need to extend ScriptControl (instead of WebControl). This base class contains all methods useful to make a relation between the client control (JavaScript) and the server control (.NET).

In ScriptControl, we need to override two methods:

  • GetScriptDescriptors: pass the name of the JavaScript class and the properties to the client control.
  • GetScriptReferences: pass the scripts used by our client control. (The ScriptManager automatically loads these scripts during page load).
C#
protected override IEnumerable<System.Web.UI.ScriptDescriptor> GetScriptDescriptors()
{
    if (!string.IsNullOrEmpty(this.Filename))
    {
        ScriptControlDescriptor descriptor = new 
          ScriptControlDescriptor("JigsawPuzzleGameControl.PuzzleGameAjax", 
          this.ClientID);
        descriptor.AddProperty("nRows", this.NRows);
        descriptor.AddProperty("nColumns", this.NColumns);

        yield return descriptor;
    }
}
         
protected override IEnumerable<System.Web.UI.ScriptReference> GetScriptReferences()
{
    if (!string.IsNullOrEmpty(this.Filename))
    {
        List<ScriptReference> scripts = new List<ScriptReference>();

        ScriptReference scriptReference1 = new ScriptReference("PreviewScript.js", 
                                           "Microsoft.Web.Preview");
        scripts.Add(scriptReference1);

        ScriptReference scriptReference2 = new ScriptReference("PreviewDragDrop.js", 
                                           "Microsoft.Web.Preview");
        scripts.Add(scriptReference2);

        ScriptReference scriptReference3 = new ScriptReference();
        scriptReference3.Path = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), 
                                "JigsawPuzzleGameControl.Resources.Helpers.js");
        scripts.Add(scriptReference3);

        ScriptReference scriptReference4 = new ScriptReference();
        scriptReference4.Path = this.Page.ClientScript.GetWebResourceUrl(this.GetType(), 
                                "JigsawPuzzleGameControl.Resources.PuzzleGameAjax.js");
        scripts.Add(scriptReference4);

        return scripts;
    }
    else
    {
        return new List<ScriptReference>();
    }
}

Drag and Drop

To implement drag and drop in our control, we need to implement two interfaces within two client controls:

  • Client control that extends Sys.Preview.UI.IDropTarget: This control represents the drop area
  • Client control that extends Sys.Preview.UI.IDragSource: This control represents the drag element

In Sys.Preview.UI.IDropTarget, we need to implements the following methods:

  • get_dropTargetElement: return the HTML element of the drop area
  • canDrop: check if drop is available for a particular drag element
  • drop: execute the drop action
  • onDragEnterTarget: normally used with onDragLeaveTarget to implement the visual effect
  • onDragLeaveTarget: normally used with onDragEnterTarget to implement the visual effect

We also need to add an event handler within the method Initialize for the MouseDown event and then call Sys.Preview.UI.DragDropManager.startDragDrop:

JavaScript
initialize : function()
{
    JigsawPuzzleGameControl.DragPuzzleGameAjaxElement.callBaseMethod(this, 
                                                            "initialize");
    $addHandlers(this.get_element(), 
      { "mousedown" : this._onMouseDown }, this);
},

_onMouseDown : function(evt)
{
    window._event = evt;
    evt.preventDefault();
    
    Sys.Preview.UI.DragDropManager.startDragDrop(this, 
                            this.get_element(), null);
},

In Sys.Preview.UI.IDragSource, we need to implement the following methods:

  • get_dragDataType: return the type of the drag element (the string used in canDrop to check if the drag item is compatible with the drop area)
  • getDragData: return data that drags the item and passes to the drop area (drop method)
  • get_dragMode: return if the drag operation is Move or Copy
  • onDragStart: is called when the drag operation starts
  • onDragEnd: is called when the drag operation ends
  • onDrag: is called when the drag operation is completed

We also need to register and unregister the client component as the drop area:

JavaScript
initialize : function()
{
    JigsawPuzzleGameControl.DropPuzzleGameAjaxElement.callBaseMethod(this, "initialize");
    Sys.Preview.UI.DragDropManager.registerDropTarget(this);
},

dispose : function()
{
    Sys.Preview.UI.DragDropManager.unregisterDropTarget(this);
    JigsawPuzzleGameControl.DropPuzzleGameAjaxElement.callBaseMethod(this, "dispose");
},

The Game

The project is divided into two projects:

  1. A web site containing an example of using the control
  2. A library project containing the control

Within the library project is a class called PuzzleGameAjax that extends ScriptControl (the base class for all custom controls in AJAX). This class is the server side code of my control.

On the client side, we have three JavaScript classes:

  • PuzzleGameAjax (contains code to implement the game)
  • DropPuzzleGameAjaxElement (contains code to implement a drop area)
  • DragPuzzleGameAjaxElement (contains code to implement a drag area)

DropPuzzleGameAjaxElement implements the Sys.Preview.UI.IDropTarget interface and DragPuzzleGameAjaxElement implements the Sys.Preview.UI.IDragSource interface. These two interfaces are used by the AJAX framework to handle the drag and drop in a generic way.

Points of Interest

AJAX ASP.NET is a very good technology, and I'm currently working with it to implement a very complex behavior to improve the user experience. The drag and drop feature is absolutely the most user friendly feature, and it makes a software very intuitive to use. Normally, on web, this feature takes a lot of JavaScript code, but with PreviewDragDrop, everything is easy.

History

  • 30 March 2008 - First release (very poor to be honest, but I wanted to make it better!).
  • 06 April 2008 - I made the article more complete.
  • 08 April 2008 - Fixed a problem within the solution.
  • 09 April 2008 - Now works with Visual Studio 2005/2008 (Express Edition included).
  • 22 April 2008 - Added online demo.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)