Persisting the position of jQuery Draggables in ASP.NET

It was bound to happen - you knock up an article on jQuery draggables one day, and the next, someone like Jim ;-) comes along and asks about persisting the position of the dragged item across Postbacks, or even sessions. I suppose it's my fault - I mentioned using draggables in a previous life in the context of saving the position, so it's only fair I share how that's done.

For the purposes of this example, the markup for the page is extremely simple:

 

<form id="form1" runat="server"> 

<div> 

  <img src="images/alfie.jpg" alt="" id="d1" runat="server" />

</div> 

</form> 

 

This is a simple html image, that has been converted to an ASP.NET HtmlControl by adding runat="server". I need to do this, because later, I want to reference the image from Code-Behind. In the meantime, I'll get to the Javascript that makes the image draggable:

 

<script src="script/jquery-1.3.1.min.js" type="text/javascript"></script> 

<script src="script/ui.core.min.js" type="text/javascript"></script> 

<script src="script/ui.draggable.min.js" type="text/javascript"></script> 

<script type="text/javascript"> 

  $(function() {

    $("#d1").draggable(

    {

      drag: function(event, ui) {

        $("#d1").css("opacity", "0.6"); // Semi-transparent when dragging

      },

      stop: function(event, ui) {

        saveCoords(ui.absolutePosition.left, ui.absolutePosition.top, ui.helper.attr('id'));

        $("#d1").css("opacity", "1.0"); // Full opacity when stopped

      },

      cursor: "move"

    });

  });

</script> 

 

Most of this is familiar if you read the previous article, so I won't dwell on it. The major difference, however is that a call to a new function, saveCoords() is made within the stop callback. 3 arguments are supplied: the x coordinate, y coordinate and the id of the current draggable - ui.helper.attr('id'). Let's look at what that function does:

 

function saveCoords(x, y, el, id) {

  $.ajax({

    type: "POST",

    url: "Services/Coordinates.asmx/SaveCoords",

    data: "{x: '" + x + "', y: '" + y + "', element: '" + el + "', userid: '1'}",

    contentType: "application/json; charset=utf-8",

    dataType: "json",

    success: function(response) {

      if (response.d != '1') {

        alert('Not Saved!');

      }

    },

    error: function(response) {

      alert(response.responseText);

    }

  });

}

 

If you have seen my previous articles on Web Services with jQuery, you will immediately spot that this function makes a call to a Web Service called Coordinates, invoking its method, SaveCoords(). It passes in the x and y coordinates, the id of the element and a user ID (hardcoded for thos example, bit could be provided by a session variable, for instance). Here's the Web Service:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Web;

using System.Web.Services;

using System.Web.Script.Services;

using System.Data.SqlClient;

using System.Data;

 

/// <summary>

/// Summary description for SaveCoords

/// </summary>

[WebService(Namespace = "http://tempuri.org/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.

 

[ScriptService]

public class Coordinates : WebService {

 

    [WebMethod]

    public int SaveCoords(int x, int y, string element, int userid)

    {

      string connect = "Server=MyServer;Database=Tests;Trusted_Connection=True;";

      int result = 0;

      using (SqlConnection conn = new SqlConnection(connect))

      {

        string query = "UPDATE Coords SET xPos = @xPos, yPos = @yPos WHERE Element = @Element AND UserID = @UserID";

        using (SqlCommand cmd = new SqlCommand(query, conn))

        {

          cmd.Parameters.AddWithValue("xPos", x);

          cmd.Parameters.AddWithValue("yPos", y);

          cmd.Parameters.AddWithValue("Element", element);

          cmd.Parameters.AddWithValue("UserID", userid);

          conn.Open();

          result = (int)cmd.ExecuteNonQuery();

        }

      }

      return result;

    }

}

 

The WebMethod simply takes the arguments passed to it and saves them to the database. It returns the number of rows affected for error checking. Great. So the position of the element is saved. How does that get translated to a draggable being positioned where it was left when the page is next requested by the user with the ID of 1? We need another Web Method:

 

[WebMethod]

public DataTable GetSavedCoords(int userid)

{

  DataTable dt = new DataTable();

  string connect = "Server=MyServer;Database=Tests;Trusted_Connection=True;";

  using (SqlConnection conn = new SqlConnection(connect))

  {

    string query = "SELECT xPos, yPos, Element FROM Coords WHERE UserID = @UserID";

    using (SqlCommand cmd = new SqlCommand(query, conn))

    {

      cmd.Parameters.AddWithValue("UserID", userid);

      SqlDataAdapter da = new SqlDataAdapter(cmd);

      da.Fill(dt);

      return dt;

    }

  }

}

 

Taking the User ID as an argument, this method returns a DataTable filled with the positions of all elements saved against the user, and it is invoked in the Page_Load() event in the Code Behind:

 

public partial class PersistDraggable : Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

      Coordinates coords = new Coordinates();

      DataTable dt = coords.GetSavedCoords(1);

      foreach (DataRow row in dt.Rows)

      {

        HtmlControl ctl = (HtmlControl)this.FindControl(row["element"].ToString());

        if (ctl != null)

        {

          ctl.Style.Add("left", row["xPos"].ToString() + "px");

          ctl.Style.Add("top", row["yPos"].ToString() + "px");

        }

      }

    }

}

 

The Web Service class is instantiated and its GetSavedCoords() method is called. For each row in the returned DataTable, an element on the page is sought with the same id as one saved in the database. If it is found, it has a dash of CSS applied to it through Attributes.Add(). These set the CSS top and left values, and Voila! When the page is rendered again, the image is exactly where it was left last time.