Integrating Infragistics Drag & Drop with jQuery and Microsoft Ajax Preview 6

Craig Shoemaker / Tuesday, November 10, 2009

The Concept

I created “Ugly Outfitters” to showcase the interaction between Infragistics Drag & Drop, jQuery and Microsoft Ajax Preview 6.

Why Ugly Outfitters? Well, I decided that rather than just dragging around some bland icons on the screen why not attempt to inject a little bit of humor into the process. Like I said... attempt ;)

If you want to jump right in take a look at this short video demonstrating the application with a quick overview of the code:

Note: Also available are higher quality versions of the video.

What follows is an overview discussion of what’s found in Ugly Outfitters. The best way to get acquainted with the details is always to download the code and review it’s design.

The Approach

The page is constructed to have a list of shirts on the top of the page, a list of pants along the bottom and a large “outfit designer” area to preview combinations. Along with the coupling of the garment images, when an image is dropped on the page the garment title and description are updated.

A typical approach of constructing a page like this is often to include a server-side repeater to generate the HTML required to list the shirt and pant images. Instead of explicitly rendering HTML for each list item, this page uses a series of client templates to create the thumb nail lists. The data for the page is served to the client in JSON arrays, rather than being embedded into blocks of HTML. Using the JSON arrays make coding interaction on the page easy as all the required data is available in a programmatically accessible format.

Variations

The sample is available in three different variations. The first sample uses the ASP.NET ScriptManager control to bring in the required scripts and leverages the code behind to render the JSON array to the page.

The second sample is exactly the same as the first except instead of using a ScriptManager, the JavaScript files are loaded into the page using the Microsoft Ajax Preview 6 Script Loader control. The Script Loader is used to bring in ASP.NET AJAX, the Client Templates, jQuery and the Infragistics Drag and Drop Framework.

The final variation extends the second sample, but removes the dependence on the code behind and is a static HTML file. Data is injected into the page by referencing a dynamic JavaScript file. This script file is added as a custom script and then loaded into the page using the Script Loader.

Variation 1: Using the ASP.NET ScriptManager

The first variation resembles the approach that most developers may be most comfortable with at first. The ASP.NET AJAX and Infragistics script files are included in the page using the ScriptManager control. The ASP.NET AJAX files are explicitly overridden to point to the new version of ASP.NET AJAX and the Client Templates:

<asp:ScriptManager runat="server">
<Scripts>
<asp:ScriptReference
Path="~/Scripts/MicrosoftAjax/MicrosoftAjax.js"
Name="MicrosoftAjax.js" />
<asp:ScriptReference
Path="~/Scripts/MicrosoftAjax/MicrosoftAjaxTemplates.js"
ScriptMode="Inherit" />
<asp:ScriptReference
Assembly="Infragistics35.Web.v9.2, Version=9.2.20092.224, Culture=neutral, PublicKeyToken=7DD5C3163F2CD0CB"
Name="Infragistics.Web.UI.SharedScripts.igDragDrop.js" />
<asp:ScriptReference
Assembly="Infragistics35.Web.v9.2, Version=9.2.20092.224, Culture=neutral, PublicKeyToken=7DD5C3163F2CD0CB"
Name="Infragistics.Web.UI.Scripts.5_igObjects.js" />
</Scripts>
</asp:ScriptManager>

The ScriptManager takes care of loading the AJAX and Infragistics scripts, and jQuery is included on the page using a standard script/src notation:

<script type="text/javascript" src="scripts/jQuery/jquery-1.3.2.js"></script>   

Data is emitted to the page in-line via properties from a view model class instantiated in the code behind.

var shirts = <%= this._vm.Shirts %>;
var pants = <%= this._vm.Pants %>;

The Shirts and Pants properties of the page’s view model are lazy loaded to fetch all the necessary data and then converted to JSON arrays before returning the final string to the client:

private string _pants = null;
private string _shirts = null;

public string Pants
{
get
{
if (this._pants == null)
{
JsonSerializer<IList<Garment>> s = new JsonSerializer<IList<Garment>>();
this._pants = s.Serialize(this.GetPants());
}
return this._pants;
}

set { this._pants = value; }
}

public string Shirts
{
get
{
if (this._shirts == null)
{
var s = new JsonSerializer<IList<Garment>>();
this._shirts = s.Serialize(this.GetShirts());
}
return this._shirts;
}
set { this._shirts = value; }
}

The JsonSerializer is a simple class that is responsible for creating serializing and de-serializing JSON arrays.

Variation 2: Using the Microsoft Ajax Script Loader

The script loader variation uses the new Script Loader control found in Microsoft Ajax Preview 6. Using this control ScriptManager is completely removed and the page accesses the script files directly.

The first step is the process is to locate the required Infragistics script files and place them in a folder under the root level Scripts folder of the site.

This page requires 0_igControlMain.js, 5_igObjects.js and igDragDrop.js to be pulled out of ASPNET_AJAX_Scripts.zip and place them in a folder named Infragistics.

By referencing the Microsoft Ajax start.js script and and designating the scripts and components needed on your page with the Sys.require command, the framework will add the requisite script references along with all script dependencies on to the page. In order to add the Infragistics Drag and Drop Framework to this page, two other scripts are required in order to load all the necessary resources.

Enter a load file.

To load the Infragistics scripts and the dependencies I’ve created a script named loader.js. This load file is customized to name the new scripts, provide reference to the framework of the location on the server, a flag to denote whether or not the script is already loaded in the browser and pointers to any dependencies a given script requires.

Here is the code for my load file:

Sys.loader.defineScripts(
{ releaseUrl: "%/../Infragistics/" + "{0}.js" },
[
{
name: "0_igControlMain"
, isLoaded: (window.Infragistics &&
window.Infragistics.Web &&
window.Infragistics.Web.UI)
}
]
);

Sys.loader.defineScripts(
{ releaseUrl: "%/../Infragistics/" + "{0}.js" },
[
{
name: "5_igObjects"
, executionDependencies: ["0_igControlMain"]
, isLoaded: (window.Infragistics &&
window.Infragistics.Web &&
window.Infragistics.Web.UI &&
Infragistics.Web.UI.ObjectBase)
}
]
);

Sys.loader.defineScripts(
{ releaseUrl: "%/../Infragistics/" + "{0}.js" },
[
{
name: "igDragDrop"
, executionDependencies: ["0_igControlMain","5_igObjects"]
, isLoaded: (window.Infragistics &&
window.Infragistics.Web &&
window.Infragistics.Web.UI &&
Infragistics.Web.UI.DragDropEffects)
}
]
);

Starting from the bottom, notice the definition for the script named igDragDrop. First, the releaseUrl is tokenized to inspect the sites structure and locate a folder named Infragistics while using the name property to resolve the file name.

This script requires 0_igControlMain.js and 5_igObjects.js to be available in the browser in order to operate correctly. Adding an array of the names of the dependent scripts into the executionDependencies property triggers the load of the dependent files.

Finally, the isLoaded property returns a boolean value that attempts to decide whether or not the file is already loaded in the client preventing multiple loads.

With the loader file in place then the page can load all the required scripts on to the page with this code:

<script type="text/javascript" src="scripts/MicrosoftAjax/start.js"></script>
<script type="text/javascript" src="scripts/loader.js"></script>
<script type="text/javascript">
Sys.require([
Sys.components.dataView,
Sys.scripts.jQuery,
Sys.scripts.igDragDrop
]);
</script>

After loading Microsoft Ajax start.js and the custom loader.js file, a script block is opened to tell the page which scripts are needed on the page.

This page uses the Microsoft Ajax Client Templates so the dataView component is required on the page. jQuery is used throughout the page for DOM selection and manipulation and is loaded with the statement on line 6. Since the loader.js file defined all the Infragistics files needed to load the Drag and Drop Framework, accessing this library follows the same syntax as a script native to the Ajax framework.

Note: You’ll even see custom scripts defined using Sys.loader.defineScripts in IntelliSense!

Now that the scripts are being loaded completely on the client, the next variation takes the next step and removes the need for code behind on the page.

Variation 3: Making a Static Page and Using the Script Loader

The final variation moves the generation of the JSON array out the page and into a script file that is referenced using the script loader. The first step is to place a file named GarmentData.aspx into the Scripts\Custom folder.

The markup for this file is minimal:

<%@ Page Language="C#" 
ContentType="application/x-javascript"
AutoEventWireup="true"
CodeFile="GarmentData.aspx.cs"
Inherits="scripts_Custom_GarmentData" %>

var shirts = <%= this._vm.Shirts %>;

var pants = <%= this._vm.Pants %>;

This file simply emits the JSON array file defining them as either the shirts or pants variable names. Make sure to note that the ContentType of the page is changed from the default value to application/x-javascript, which will ensure the browser interprets this file as JavaScript and not HTML.

Here is the code behind:

using System;

public partial class scripts_Custom_GarmentData : System.Web.UI.Page
{
protected BuildViewModel _vm = new BuildViewModel();
}

Next loader.js is updated to point to the new file:

Sys.loader.defineScripts(
{ releaseUrl: "%/../Custom/" + "{0}.aspx" },
[
{
name: "GarmentData"
, isLoaded: (window.pants && window.shirts)
}
]
);

To create the static file I simply made a copy of the original ASPX file and changed the extension to .htm and removed the lines that explicitly declared the shirts and pants variables in the procedural script on the page.

Then I updated the script references to load all the scripts at once using the Script Loader:

<script type="text/javascript" src="scripts/MicrosoftAjax/start.js"></script>
<script type="text/javascript" src="scripts/loader.js"></script>
<script type="text/javascript">
Sys.require([
Sys.components.dataView,
Sys.scripts.jQuery,
Sys.scripts.igDragDrop,
Sys.scripts.GarmentData
]);
</script>

With these changes the page now has the same functionality as the first variation, but all the work is being done on the client.

Now that you are familiar with the different variations, I’ll discuss some of the interesting implementation details I encountered while developing these samples.

Working with Microsoft Ajax Client Templates

When you think of a Client Template, consider it’s cousin the server-side Repeater control. The you get the behavior of ‘repeating’ a template for each item in the collection and you have full control over the markup, but data binding is deferred to execution on the client.

Generated Id Values

Client templates generate unique Id values in your markup. Before you gasp in horror thinking you are being hit with yet another instance of the framework mangling client Ids in a way that plagues ASP.NET, rest assured the generation happening here is reasonable.

Consider the following code:

<div id="desc">
<div id="shirtdesc">{{ShirtDesc}}</div>
<div id="pantsdesc">{{PantsDesc}}</div>
</div>

As the framework loops over this markup many instances of divs with the id of “desc” would exist on the page. The HTML specification states that id values must be unique on a page. Therefore “desc” turns into “desc0”, “desc1”, “desc2”, etc. The Id is appended with the item index in order to ensure Id uniqueness on the page.

Maintaining Client State with the Class Attribute

One of the tips given by Dave Ward during his interview jQuery Secrets with Dave Ward on the Polymorphic Podcast was to use the CSS class attribute of an element to maintain client state. On this page I wired up a single function to run when an item is dropped into a drop target. While trying to find a convenient place to differentiate dropped items as shirts versus pants I simply placed a type flag in the class attribute. Here is the markup for the shirts list:

<div
id="shirtListView"
class="thumbs sys-template">
<div class="shirt">
<img
sys:src="{{ 'images/thumb/' + FileNameFull }}"
sys:class="{{ $index + ' s' }}"
sys:alt="{{Title}}"
sys:title="{{Title}}"
width="65"
height="66" />
</div>
</div>

Having both the index and type value available in the class attribute provided the required arguments need to do the appropriate processing once the image was dropped on the page.

Concatenation

Notice in the above listing here I add some string literals to the binding for the src and class attributes. My first inclination was do use a value converter to take the value and return the full string. After spending some time with the code I realized that I could simply concatenate the string literals along with the bindings and that greatly simplified the code. The values converters are great, but they are best used when you have some true conversion of calculation to implement.

Working with Infragistics Drag & Drop

The basics of working with the Drag and Drop Framework are simple. You locate items on the page that you want to flag as drag source items, designate drop targets and then tell the framework which function to run once an item is dropped.

Here is the function I created to initialize the framework on the page:

function initDragDrop()
{
var dd = new $IG.DragDropBehavior();

$(".shirt img").each(function(index)
{
dd.addSourceElement(this);
});

$(".pant img").each(function(index)
{
dd.addSourceElement(this);
});

dd.addTargetElement($get("shirt0"), true);
dd.addTargetElement($get("pant0"), true);
dd.get_events().addDropHandler(onDropped);
}

This function is simply making each thumbnail a drag source and the large preview images the drop targets.

Tip: Interestingly when creating a drop target, you must pass in a raw DOM element as the first argument to addTargetElement. In other words I first tried to simply pass $(“shirt0”), but using the jQuery selector returns a jQuery object and not the DOM element, so I had to change it to use the MS AJAX selector $get, which is really just a wrapper for document.getElementById.

When the image is dropped then the page will update the large image, title and description:

function onDropped(source, eventArgs)
{
var src = eventArgs.get_manager().get_source().element;
var args = src.className.split(' ');
var index = args[0];
var type = args[1];

if (type == "s")
{
$("#shirt0").attr("src", "images/full/" + shirts[index].FileNameFull);
$("#shirtdesc0").html(shirts[index].Description);
$("#shirttitle0").html(shirts[index].Title);
}
else if (type == "p")
{
$("#pant0").attr("src", "images/full/" + pants[index].FileNameFull);
$("#pantsdesc0").html(pants[index].Description);
$("#pantstitle0").html(pants[index].Title);
}
}

Here is where a number of the concepts discussed so far begin to interact with one another. Notice how the args variable is filled by splitting the className property of the element by a space. This gives the function the values for the item index and knowledge of the type of garment being dropped.

Inside the either of the branches you begin to see how having all the data available to the page as a JSON array makes updating the page a trivial effort.

Working with jQuery

I won’t spend time discussing jQuery other than to say it just simply makes life bearable when working with JavaScript. The DOM selection and manipulation features and use of the each() method are well documented around the web.

Download

To see Ugly Outfitters in action for yourself download the code here.

Please note that you must have NetAdvantage ASP.NET controls installed on your machine to get copies of the JavaScript files used in this demo. If you are not a subscriber, perhaps you would like to download a trial?

Thanks

Thanks to Dave Reed and Luis Abreu for help on getting the loader file working correctly.