Building a Custom Drag-and-Drop Extender for a Multicolumn Drop Zone - ASP.NET

I am first considered a plain vanilla JavaScript-based solution for my drag-and-drop functionality.It required less code, less architectural complexity, and was faster.Another reason was the high learning curve for properly making extenders in ASP.NET AJAX, given that there’s hardly any documentation available on the Web (or at least that was the case when I was writing this book). However, writing a proper extender that pushes ASP.NET AJAX to the limit is a very good way to learn the ASP.NET AJAX framework’s under-the-hood secrets.So,the two extenders introduced here will tell you almost everything you need to know about ASP.NET AJAX extenders.

Before I wrote my own implementation of drag and drop, I carefully looked at existing solutions.The Ajax Control Toolkit comes with a DragPanel extender that could be used to provide drag-and-drop support to panels.It also has a ReorderList control which could reorder the items into a single list. Widgets are basically panels that flow vertically in each column. So, it could be possible to create a reorder list in each column and use the DragPanel to drag the widgets.But ReorderList couldn’t be used because:

  • It strictly uses the HTML table to render its items in a column. But I have no table inside the columns, only one UpdatePanel per column.
  • It takes a drag handle template and creates a drag handle for each item at runtime.But there already is a drag handle created inside a widget, which is the widget header, so ReorderList can’t create another drag handle.
  • It must have client-side callback to JavaScript functions during drag and drop to make Ajax calls and persist the widget positions.The callback must provide the Panel where the widget is dropped,depending on which widget is dropped, and at what position.

The next challenge is with the DragPanel extender.The default implementation of drag and drop in the Ajax Control Toolkit doesn’t work for these reasons:

  • When you start dragging, the item becomes absolutely positioned, but when you drop it, it does not become statically positioned. A small hack is needed for restoring the original positioning to static.
  • It does not bring the dragging item on top of all the items. As a result, when you start dragging, you see the item being dragged below other items, which makes the drag get stuck, especially when there’s an IFrame.

For all these reasons, I made Custom Drag Drop Extender and Custom Floating Extender. Custom Drag Drop Extender is for the column containers where widgets are placed.It provides the reordering support.You can attach this extender to any Panel control.

Example shows how you can attach this extender to any Panel and make that Panel support dragging and dropping widgets.

How to attach CustomDragDropExtender to a Panel

<cdd:CustomDragDropExtender>offers the following properties

Target Control ID
ID of the Panel that becomes the drop zone.

DragItem Class
All child elements inside the Panel having this class will become draggable, e.g.,the widget DIV has this class so that it can become draggable.

Any child element having this class inside the draggable elements will become the drag handle for the draggable element, e.g., the widget header area has this class, so it acts as the drag handle for the widget.

Drop Cue ID
ID of an element inside the Panel, which acts as DropCue.

On Clien t Drop
Name of a JavaScript function that is called when the widget is dropped on the Panel.

LeftPanel becomes a widget container that allows widgets to be dropped on it and reordered.The Drag Item Class attribute on the extender defines the items that can be ordered.This prevents nonwidget HTML DIVs from getting ordered. Only the DIVs of the class “widget” are ordered.Say there are five DIVs with the class named widget.It will allow reordering of only the five DIVs, not any other element (see Example).

Custom Drag Drop Extender allows only drag-and-drop support for elements with a specific class

When a widget is dropped on the panel, the extender fires the function specified in OnClientDrop.It offers standard Ajax events. But unlike basic Ajax events where you have to programmatically bind to events, you can bind the event handler declaratively. So, instead of doing this:

When the event is raised,the function named onDrop gets called. This is done with the help of some library functions available in ACT project. When the event is fired, it passes the container, the widget, and the position of where the widget is dropped as an event argument, as specified by the code in Example

Client-side JavaScript event handler for receiving drag-and-drop notification

The widget location is updated on the server by calling the WidgetService.Mov eWid get Instance.

CustomDragDropExtender has three files:

The server side extender implementation

Designer class for the extender

Client-side script for the extender

The code for the server-side class CustomDragDropExtender.cs is shown in Example

[assembly: System.Web.UI.WebResource("CustomDragDrop.CustomDragDropBehavior.js","text/javascript")]

namespace CustomDragDrop. Code for CustomDragDropExtender.cs(continued)
Code for CustomDragDropExtender.cs (continued) Most of the code in the extender defines the properties. The important part is the declaration of the class shown in Example.
The extender class inherits from ExtenderControlBase as defined in the Ajax Control Toolkit (ACT) project.This base class has additional features beyond those found with the extender base class that ships with ASP.NET AJAX.The ACT extender allows you to use the RequiredScript attribute,which makes sure all the required scripts are downloaded before the extender script is downloaded and initialized.The Custom Drag Drop extender has a dependency on another extender named CustomFloatingBehavior.It also depends on ACT’s DragDropManager.So, the RequiredScript attribute makes sure required scripts are downloaded before the extender script downloads.The ExtenderControlBase is a pretty big class and does a lot of work for us.It contains default implementations for discovering all the script files for the extender and then renders them in proper order so that the browser downloads the scripts in correct order.

The [assembly:System.Web.UI.WebResource] attribute defines the script file containing the script for the extender.The script file is an embedded resource file.

The [ClientScriptResource] attribute defines the scripts required for the extender.This class is also defined in ACT. ExtenderControlBase uses this attribute to find out which JavaScript files are working for the extender and renders them properly.

The challenge is in writing the client side JavaScript for the extender. On the Custom Drag Drop.js file, there’s a JavaScript class that is the extender implementation,as shown in Example.

The JavaScript implementation of the extender class’s constructor]

During initialization, this extender hooks on the Panel and the DropCue while the widget is being dragged and dropped over the Panel. See Example

Initialize the CustomDragDrop extender and hook on the items

After initializing the DragDropManager and marking the Panel as a drop target, a timer is started to discover the draggable items inside the Panel and apply FloatingBehavior to them. Floating Behavior makes a DIV draggable.

Initialize the CustomDragDrop extender and hook on the items

Discovering and initializing FloatingBehavior for the draggable items is challenging work, as you see in Example

Discovering draggable items and creating Floating Behavior for each of item

Here’s the algorithm:

• Run through all immediate child elements of the control to which the extender is attached.

• If the child item has the class for draggable item, then:

— Find any element under the child item that has the class for a drag handle; if such an item is found, then attach a Custom Floating Behavior with the child item.

The _find Child By Class function recursively iterates through all the child elements and looks for an element that has the defined class.The code is shown in Example.It’s an expensive process.So, it is important that the drag handle is very close to the draggable element. Ideally, the drag handle should be the first child of the draggable element, so the search for a widget header doesn’t have to iterate through too many elements.

Handy function to find HTML elements by class name

When a user drags an item over the Panel to which the extender is attached, DragDropManager fires the events shown in Example

Events raised by DragDropManager

While drag and drop is going on, you need to deal with the drop cue.The challenge is to find out the right position for the drop cue (see Figure).

We need to find out where we should show the drop cue based on where the use wants to put the item.The idea is to find the widget that is immediately underneath the dragged item.The item underneath is pushed down by one position and the drop cue takes its place. While dragging,the position of the drag item can be found easily.Based on that, you can locate the widget below the drag item with the _findItemAt function shown in Example

When you drag a widget, a drop cue shows you where the widget will be dropped when mouse is released.

When you drag a widget, a drop cue shows you where the widget will be dropped when mouse is released.

Find the widget at the x,y coordinate of the mouse

The _findItemAt function returns the widget that is immediately underneath the dragged item.Now you can add the drop cue immediately above the widget to show the user where the widget being dragged can be dropped.The _repositionDropCue function, whose code is shown in Example, relocates the drop cue to the position where a widget can be dropped.

Move the drop cue to the place where a widget can be dropped

One exception to consider here is that there may be no widget immediately below the dragged item.This happens when the user is trying to drop the widget at the bottom of a column.In that case, the drop cue is shown at the bottom of the column.

When the user releases the widget, it drops right on top of the drop cue, and the drop cue disappears.After the drop, the onDrop event is raised to notify where the widget is dropped, as shown in Example

Place the dropped widget on the right place and raise the onDrop event

Generally, you can define events in extenders by adding two functions in the extender as shown in Example

Provide event subscription support in ASP.NET Ajax Extenders

But this does not give you the support for defining the event listener name in the ASP.NET declaration:

Such declarative approaches allows only properties of a control.To support such a declarative assignment of events,you need to first introduce a property named OnClientDrop in the extender.Then, during assignment of the property, you need to find the specified function there and attach an event notificationto that function.The discovery of the function from its name is done by Common Tool kit Scripts. resolveFunction, which is available in the ACT project and used in Example

Allow the event name to be specified as a property on the extender,

Raising the event is the same as basic Ajax events:

The next challenge is to make CustomFloatingBehavior. The server-side class Custom Floating Behavior.cs is declared, as shown in Example

CustomFloatingBehavior.cs content

There’s only one property—DragHandleID, in which the widget’s header works as the drag handle. So, the header ID is specified here.This extender has dependency on DragDropManager,which requires the [RequiredScript(typeof(DragDropScripts))] attribute.

Besides the designer class, there’s one more class that Custom Drag Drop Extender needs to specify its dependency over this FloatingBehavior:

This class can be used inside the RequiredScript attribute. It defines only which script file contains the client-side code for the extender.

The client-side JavaScript is same as FloatingBehavior, which comes with ACT. The only difference is a hack when the drag starts.DragDropManager does not return the item being dragged to the static position once it makes it absolute.It also does not increase the zIndex of the item.

If the drag item does not become the top-most item on the page,then it goes below other elements on the page during drag. So,I have made some changes in the mouse Down Handler attribute of the behavior to add these features, shown in Example

Revised mouseDownhandler in CustomFloatingBehavior.js

Setting el.originalPosition = "static" fixes the bug in DragDropManager.It incorrectly stores absolute as the originalPosition when startDragDrop is called.So, after calling this function, reset to the correct originalPosition, which is “static.”

When drag starts, zIndex is set to a very high value so that the dragged item remains non top of everything on the page.When drag completes, the original zIndex is restored and the left, top, width, and height attributes are cleared.DragDropManager makes the item position static, but it does not clear the left,top, width, and height attributes.This moves the element away from the place where it is dropped. This bug is fixed in the onDragEnd event, as coded in Example

onDragEnd event fixes the zIndex related problem

All rights reserved © 2020 Wisdom IT Services India Pvt. Ltd Protection Status

ASP.NET Topics