Drag & Drop

Introduction

In most web applications I used HTML 5 based drag and drag to me never seemed to be tuned to the max in terms of flexibility and usability. Moving entries up and down a list or dropping files into an upload area are quite simple use cases. But what if

  • a source has multiple destinations that invoke completely different drop actions? As a user, how to know where are the different destinations and what do they do?
  • the same sources and destinations imply completely different actions in different contexts?
  • in different contexts, a source may be destination and vice-versa?

And, last but not least: How can these challenging use cases be implemented as transparently and easy-to-use as possible?

Conceptual approach

The first challenge is addressed by flexibly using CSS classes that are dynamically assign to and removed from the destination objects. Due to the fact that per object there may be assigned as many CSS classes as required, there is an unlimited variety of styles to highlight the destination objects while dragging the source object. The only way to implement such dynamic behavior is by using Javascript.

As for the second and third challenge: Assuming that a drop action isn’t just a client-only one, the basic concept of this approach is to not even try to guess on the client what to do on a given drop. Instead, the drop information is sent to the back-end which then applies any rule that may be imagined.

This is depicted in the following UML diagrams

Drag & drop

PHLEX drag & drop implementation

This implementation is initially used by version 31 of the Housekeeping application.

For examples see the following screen records.

Single source and multiple destinations

  • The report icon may by dropped on any of the supported object classes which are marked by a red frame once the drag starts
  • Dropping the icon will call the respective report
  • Dropping the icon on a non-supported object class nothing happens

Multiple sources and single destination

  • The icon of any supporting object may be dropped on the trash can
  • This will mark this entry as deleted
  • Entering the trash can each object class may only be restored to its corresponding view

Dynamic styles (the CSS part)

Using the flexibility of CSS classes, depending on the drop object, any combination of styles may be assigned to the same when a source object is started to be dragged. Following the classes used to mark drop targets like trash or report within the Housekeeping application.

CSS

.drop_target
{
 
}
 
.drag_target_style_text
{
  color: red;
}
 
.drag_target_style_border
{
  border: 2px solid red;
}

Note that class “drop target” is empty hence solely used as a tag to mark the targets.

On dropping the source onto the destination or when canceling the drag, the respective classes are being removed from the drop object leaving the original styles. As this is a behavioral task, Javascript is used to do this job.

The Javascript part

First the functions to add or remove CSS classes to and from drop objects. These functions will then be called on Javascript events.

Javascript

/**
 * Adds one or multiple CSS classes to an HTML element 
 * Separate multiple classes using whitespaces
 */
 
function AddCssClasses( objDomElement, strCssClasses )
{
    var strCurrentCssClasses = objDomElement.getAttribute( 'class' );
    var strNewCssClasses = strCurrentCssClasses + ' ' + strCssClasses;
    objDomElement.setAttribute( 'class', strNewCssClasses );
}
 
/////////////////////////////////////////////////////////////
 
/**
 * Removes one or multiple CSS classes from an HTML element 
 * Separate multiple classes using whitespaces
 */
 
function RemoveCssClasses( objDomElement, strCssClasses )
{
    var strCurrentCssClasses = objDomElement.getAttribute( 'class' );
    var arrCssClasses = strCssClasses.split( ' ' );
 
    for ( var i = 0; i < arrCssClasses.length; i++ )
    {
        if ( strCurrentCssClasses.search( arrCssClasses[ i ] + ' ' ) != -1 )
        {
            strNewCssClasses = strCurrentCssClasses.replace( arrCssClasses[ i ] + ' ', '' );
        }
        else
        {
            strNewCssClasses = strCurrentCssClasses.replace( ' ' + arrCssClasses[ i ], '' );
        }
    }
 
    objDomElement.setAttribute( 'class', strNewCssClasses );
}

These functions are now called by the drag and drop functions.
The StartDrag() function assigns the CSS classes to the drop targets and copies its own class type and ID to the clipboard. Optionally, an alternative drag image may be defined that will then be shown while the source is dragged.

Javascript

function StartDrag( objEvent,
		    strSearchClasses,
		    strAddClasses,
		    strObjectClass,
		    intOID,
		    strDragImageID
		  )
{
  var strClipboardParameterValues = strObjectClass + ',' + intOID;
  objEvent.dataTransfer.setData( 'text/value', strClipboardParameterValues );
 
  if ( strDragImageID )
  {
    var objDragImage = document.getElementById( strDragImageID );
    objEvent.dataTransfer.setDragImage( objDragImage, 20, 20 );
  }
 
  var arrDropTargets = document.getElementsByClassName( strSearchClasses );
 
  for ( var i = 0; i < arrDropTargets.length; i++ )
  {
    var objDropTarget = arrDropTargets[i];
    AddCssClasses( objDropTarget, strAddClasses );
  }
}

When dragging is interrupted or on drop, the StopDrag() function restores the original CSS classes.

Javascript

function StopDrag( strSearchClasses, strRemoveClasses )
{
  var arrDropTargets = document.getElementsByClassName( strSearchClasses );
 
  for ( var i = 0; i < arrDropTargets.length; i++ )
  {
    var objDropTarget = arrDropTargets[i];
 
    RemoveCssClasses( objDropTarget, strRemoveClasses );
  }
}

Once the source is dropped on the target, the Drop() function is called. It gets the source information stored at the clipboard, calls StopDrag() (see above) and the back-end rule caller.

Javascript

function Drop(  objEvent,
		strApplicationName,
		strPageName,
		strSearchClasses,
		strRemoveClasses,
		strTargetClass,
		boolDimPage
	     )
{
  objEvent.preventDefault();
  var mixedParamValue = objEvent.dataTransfer.getData( 'text/value' );
  StopDrag( strSearchClasses, strRemoveClasses );
  var arrSourceArguments = mixedParamValue.split( "," );
 
  if ( boolDimPage )
  {
    // See how-to "confirmation boxes"
    BusyPage();
  }
 
  CallDropAction( strApplicationName, strPageName, arrSourceArguments[0], arrSourceArguments[1], strTargetClass ); }

CallDropAction() sends the arguments to the back-end via Ajax. The back-end drop rules module then executes the rules that correspond to the context and returns the result(s) via JSON to the client which checks the return code and, if successful, performs the action (which in most cases is to (re)load a page).

Javascript

function CallDropAction( strApplicationName,
                         strPageName,
                         strSourceObject,
                         intSourceObjectID,
                         strTargetClass
                       )
{
  var strCallUrl = null;
  objHTTPEntryObject = GetHTTPObject();
 
  if ( objHTTPEntryObject != null )
  {
    strCallUrl = "../ajax/ajax_dragndrop_action.php?app=" + strApplicationName + "&page=" + strPageName + "&src=" + strSourceObject + "&sid=" + intSourceObjectID + "&target=" + strTargetClass;
    objHTTPEntryObject.open( "GET", strCallUrl, true );
    objHTTPEntryObject.send( null );
    objHTTPEntryObject.onreadystatechange = function()
    {
      if ( objHTTPEntryObject.readyState === 4 && objHTTPEntryObject.status === 200 )
      {
        // The back-end function returns a JSON object
        var objJsonResults = JSON.parse( objHTTPEntryObject.responseText );
        // The response url is supposed to update the page, if the request has been successful
        if ( !objJsonResults.success )
        {
          FadeInConfirmation( objJsonResults.error, null );
        }
        else
        {
          Go( objJsonResults.url );
        }
      }
    };
  }
  else
  {
    // See how-to "confirmation boxes"
    FadeInConfirmation( 'Your web browser does not seem to support Ajax' );
  }
};

The HTML part

The drag and drop functions by themselves are called by the event handlers. An example matching the functions listed above may be as follows

HTML

<!-- A draggable source (image) ... -->
<img id="transactions_drag_image_1" title="Drag to highlight drop actions" draggable="true" src="/inftec/img/drag_transactions_small.png" alt="" />
 
<!-- ... and the respective (table header) drop target -->
<a draggable="false" href="report.php?report=reuse"><img class="tab" title="Recycle bin" draggable="false" src="/inftec/img/trash.jpg" alt="" /></a>

Have a look at the Housekeeping demo instance to see how this works. Drag the object thumbnails to the targets marked as red and drop them to launch the corresponding action.

Leave a comment

0 Comments.

Leave a Reply

( Ctrl + Enter )