Many to many relationships - Drag and Drop (part 4)
In my last article I talked about using Drag and Drop in the browser as a tool to streamline user interfaces - especially those controlling many-to-many relationships in your data, and I demonstrated a drag and drop users/groups interface which posts back to the database via LINQ-to-SQL.
Given how difficult I found learning this part of the ASP.NET AJAX framework, I thought I'd share my source code.
You can download it from the bottom of the page, and you can see it in action here (Link removed temporarily). Go do both of those now, then come back.
The first thing to look at upon opening the solution is the references node. The two important references are v1.0.61025.0 of the Microsoft.Web.Preview dll, and version 3.5 of the System.Web.Extensions dll. I've included the dlls so you don't need to hunt for them. The former is the July 2007 Microsoft ASP.NET Futures Preview. The latter includes the ASP.NET 3.5 version of the ASP.NET Ajax Extensions.
We'll be using the SQL Express database under App_Data, along with the dmbl definition under Data. Notice JunctionList.cs, and the partial declaration for the User and Group classes in Data.cs from part 2. I've chosen to use Mitsu's original Singleton DataContext pattern to get my DataContext in-scope for the delegates in the declaration of User.Groups and Group.Users.
The code for the drag and drop behaviour in the browser is a bit complicated - the ASP.NET AJAX drag and drop framework is still quite primative, but it's improving over time. If you've not already used ASP.NET Ajax, you might want to look up the Scriptaculous or JQuery equivalents (ClientScript.GetPostBackEventReference lets you make an ASP.NET postback from any client javascript) instead.
First, let's take a look at the relevant part of default.aspx:-
<asp:Repeater ID="Repeater1" runat="server">
<ItemTemplate>
<div
id='<%# "user" + Eval("uid") %>'
class="DragSource"
onmouseover="showtip('<%# Eval("Name") %>',event)"
onmouseout="hidetip()"
onmousemove="movetip(event)"
style="background-image:url('./Images/Avatars/<%# Eval("Avatar") %>');">
</div>
<script type="text/javascript">
var source = new LDD.UI.ElementDragSourceBehavior
($get('<%# "user" + Eval("uid") %>'),
<%# Eval("uid") %>,
'user',
true);
source.initialize();
</script>
</ItemTemplate>
</asp:Repeater>
This repeater is databound with a collection of users (either those who are members, or who are not members of the group we're looking at). The repeater creates a div for each user, and sets the appropriate background image. You'd put in any other data you wanted to display here.
Then there's some javascript. The LDD.UI.ElementDragSourceBehaviour is defined in Resources\ElementDragSource.js. This behaviour defines a draggable element on the page. The "constructor" for this behaviour takes the id of the HTML element we want it to apply to, the "data" that this DragSource contains (the user's ID), the data type that this drag-source represents, and a flag telling us whether or not the user is part of the group.
The important parts of ElementDragSource.js are repeated below:-
...
LDD.UI.ElementDragSourceBehavior.prototype =
{
...
onDragEnd: function(cancelled)
{
if (this._visual)
this._parentNode.removeChild(this._visual);
// Apparantly setting the opacity to '1' causes problems in Mozilla
this.get_element().style.opacity = '0.999';
this.get_element().style.filter = "";
hidetip();
},
// Other methods
mouseDownHandler: function(ev)
{
window._event = ev; // Needed internally by _DragDropManager
this.get_element().style.opacity = '0.3';
this.get_element().style.filter =
'progid:DXImageTransform.Microsoft.BasicImage(opacity=0.3)';
this._visual = this.get_element().cloneNode(true);
this._visual.style.opacity = '0.7';
this._visual.style.filter =
'progid:DXImageTransform.Microsoft.BasicImage(opacity=0.7)';
this._visual.style.zIndex = 99999;
this.get_element().parentNode.appendChild(this._visual);
var location =
Sys.UI.DomElement.getLocation(this.get_element());
Sys.UI.DomElement.setLocation(this._visual, location.x,
location.y);
this._parentNode = this.get_element().parentNode;
Sys.Preview.UI.DragDropManager.startDragDrop(this,
this._visual, null);
},
...
}
the mouseDownHandler sets the opacity of the original element, creates a "drag visual", and attaches it to your mouse cursor. onDragEnd removes the drag visual and restores the opacity of the original element.
The magic that happens when the DragSource is dropped is defined in Resources\ElementDropTarget.js:-
LDD.UI.ElementDropTargetBehavior.prototype =
{
...
canDrop: function(dragMode, dataType, data)
{
return (dataType == this._acceptedDataType && data);
},
drop: function(dragMode, dataType, data)
{
if (dataType == this._acceptedDataType && data)
{
this.get_element().className = this._style;
data._assigned = this._assigned;
this.addElementGraphic(data.get_element());
var postbackCode = postBackScript.replace("_~ARG~_", data._id + ";" + this._assigned);
window.setTimeout(postbackCode, 0);
}
},
onDragEnterTarget: function(dragMode, dataType, data)
{
if (dataType == this._acceptedDataType && data)
{
this._style = this.get_element().className;
this.get_element().className = this._hoverStyle;
}
},
onDragLeaveTarget: function(dragMode, dataType, data)
{
if (dataType == this._acceptedDataType && data)
{
this.get_element().className = this._style;
}
},
...
addElementGraphic: function(e)
{
e.parentNode.removeChild(e);
this.get_element().appendChild(e);
},
...
}
...
The "drop" method here is the one doing the work, moving the element to its new home, and calling the postback function. The postback function is created in the codebehind file Default.aspx.cs:-
private void WritePostBackJavascript()
{
StringBuilder sb = new StringBuilder();
sb.Append("");
ClientScript.RegisterStartupScript(this.GetType(), "PostBackScript", sb.ToString());
}
- and allows us to make ASP.NET postbacks from our client-side javascript. The post-back is handled by the IPostBackHandler.RaisePostBackEvent method - which is where our database-handling code lives:-
public void RaisePostBackEvent(string eventArgument)
{
string[] eventArgs = eventArgument.Split(';');
Data.Group group = db.Groups.Single(g => g.gid == Int32.Parse(DropDownList1.SelectedValue));
Data.User user = db.Users.Single(u => u.uid == Int32.Parse(eventArgs[0]));
bool addRemoveFlag = Convert.ToBoolean(eventArgs[1]);
if (addRemoveFlag)
{
group.Users.Add(user);
}
else
{
group.Users.Remove(user);
}
db.SubmitChanges();
BindRepeaters();
}
It's that kind of code that has me really enjoying working with LINQ.
Enjoy!
< Previous Article: Many to many relationships: Putting it Together (part 3)
| File | Size |
|---|---|
| LinqManyMany.zip | 746.06 KB |
Post new comment