Article
14 comments

SPFX Sample – Drag and Drop elements across web parts

The SharePoint framework improved a lot during the last drops. I thought to myself what fancy sample can I build using the framework. During the Hackathon at the European SharePoint Conference we saw a lot of great uses of this new framework. One team was even able to add a framework web part to SharePoint 2007.
Actually, last weekend I came up with an idea. It was more a proof of concept I would like to try. I asked myself the question. Will I be able to drag and drop data between two individual web parts.

The base setup

I created a new SharePoint Framework project and added two web parts. It might would have been easy to move elements in just one web part, but I wanted to try this approach across different web parts.
The first thing I did was to created two React components. One of those components provides an area where items could be dropped. The other component defines how the entries will be rendered.
To use this component in both web parts I created a single reusable component apart from the actual web part code.

SPFX - Reusable core component

SPFX – Reusable core component

For styling, this component I attached the style sheets directly in my core component. I also disabled the auto prefixing of style sheet classes provided by the framework because I wanted to share the style sheet definitions across both web parts too.

SPFX Rendering both Web-parts - With and without data

SPFX Rendering both Web-parts – With and without data

From my perspective, it is a good approach to develop components that are intended to be used in multiple web parts beside the actual web part code.
It is an approach similar to asp.net web controls.

Add reusable component to web part

To add these components to the web parts all that needs to be done is to import the component first.

import * as DragNDrop from '../coreComponents/DropBoxes';

import * as strings from 'firstWebPartStrings';
import FirstWebPart, { IFirstWebPartProps } from './components/FirstWebPart';
import { IFirstWebPartWebPartProps } from './IFirstWebPartWebPartProps';

View on Github
At the top of ‘FirstWebPart.ts’ and ‘SecondWebPart.ts’ I imported the components as ‘DragNDrop’ from a folder that actually was one level higher than the web part code.
The next thing that needed to be done is to create instances of the control in the render method of the web part.

public render(): void {

  const element: React.ReactElement<IFirstWebPartProps> = React.createElement(FirstWebPart, {
      description: this.properties.description
    });
        
        // Create a new instance of the DropBox container
  const myDropBox = React.createElement(DragNDrop.DragNDropContainer, {
    items: [
      { title: "EAT" },
      { title: "SLEEP" },
      { title: "<Code>" },
      { title: "Repeat" }
    ],
    title: this.title
  });

  // add drop box to UI
  ReactDom.render(myDropBox, this.domElement);

}

View on GitHub
In the first web part code I added some predefined items while the second web part doesn’t contain any predefined items.
Now everything was ready to implement the drag and drop functionality.

Drag and Drop event handlers and handler binding

In general drag and drop on the web pages is no rocket science. All that needs to be done is to combine various events that will be fired on the start, during and after an item was dragged across the screen. Let’s take a look on which component what event needs to be registered.

DragNDropContainer

The drop container component is named ‘DragNDropContainer’. The events that need to be bound to this component are:

  • onDrop
    This event will be fired when a new element was dropped in the container.
  • onDragOver
    This event occurs during dragging the item around and contains useful information.

DragNDropItem

The name of the other component is ‘DragNDropItem’ and handles the following events

  • onDragStart
    This event occurs when you grab an item and start to move it around.
  • onDragEnd
    This event happens after the user stopped dragging the item around.

Last but not least the attribute ‘draggable’ needs to be defined on every instance of this component. Without this attribute the item is locked and cannot be moved.
So far so good, but now for the tricky parts. To be able to move items around the React data binding, and event firing needs to be implemented the following way.

Databinding

The first problem I faced with React was how to be able to access the parent container from every child item instance. Normally only data of the current element can be accessed through the events.
The solution was that I passed the parent container directly to the items. This way I was able to access both data, data of the child element and the parent container.
The generation and rendering of the ‘DragNDropContainer’ look like this.

render() {

  return (
    <div className="dropbox"
      onDrop={this.handleDrop.bind(this)}
      onDragOver={this.handleDragOver.bind(this)} >
        <div className="dropbox-title">
           <h2>{this.props.title}</h2>
       </div>
       <div className="dropbox-zone">
       {
         this.state.items.map(function (item) {
         return <DragNDropItem parentList={this} key={item.title} >
            {item}
            </DragNDropItem>
       }, this)
     }
     </div>
   </div>
 )
}

View on GitHub

During the generation of the ‘DropNDropItems’ I simply passed the current container as a ‘parentList’ parameter.
Through this method I was able to update the state of the parent container and remove the already moved element.

Event

Finally, let’s take a closer look on the event firing order.
When you drag items around you actually not drag the item itself, you simply drag the data or the DOM content of the element. After you moved an element, the originating element needs to be removed manually.
For the user it looks like dragging the item around.

onDragStart

This is the first event that will be fired when you start to drag an item. Now its time to attach information about the selected element to the event. For storing those the data the ‘dataTransfer’ object needs to be used.

event.dataTransfer.setData("data", JSON.stringify(itemData));
event.dataTransfer.dropEffect = "move";

View on GitHub
The itemData variable stores all information of the component and only was converted to a JSON string. This ‘data’ is persistent throughout the whole event.

onDrop

The second event that needs to be handled once the item was dropped in the drop container. This is attached to the ‘DragNDropContainer’. All that needs to be done in here is to create a new instance of the moved element.
All that this event does it to request the previously stored event data. Then the data were pushed to the state of the ‘DragNDropContainer’.

// Handle Drop event
handleDrop(event) {

    event.preventDefault();

    let data = null;
  
        // convert data back to a json object
    data = JSON.parse(event.dataTransfer.getData("data"));

        // manipulate the state of the draped container
    var newState = this.state;
    newState.items.push(data);
    this.setState(newState);
}

View on GitHub
The update of the state automatically adds a new item instance, as a child control of the user interface.
The last thing that needs to be done is to remove the origin element and identify if the drag was successful or failed.

onDragEnd

Again this event is registered on the child items but not on the container. This event contains the same stored data in the dataTransfer object. In addition the dataTransfer contains a variable named dropEffect. This ‘dropEffect’ indicates the state of the dragging transaction.

event.dataTransfer.dropEffect !== "none"

The value ‘none’ means that the move was canceled or unsuccessful. In this case the item will automatically return to its original position on the screen.
Other possible values of the dropEffect are:

  • copy
    A copy of the source item is made at the new location.
  • move
    An item is moved to a new location.
  • link
    A link is established to the source at the new location.
  • none
    The item may not be dropped.

If the transaction contains ‘move’ the originating item can be removed again by updating the state of the parent container.
I hope this helped a bit to demystify the drag and drop handling in components.
Finally, let’s take a look at the final solution.

The finished solution and thoughts

SPFX - Drag and Drop between the web parts

SPFX – Drag and Drop between the web parts

Like I promised. Drag and drop in web applications are no rocket Science and helps to improve the user experience for certain scenarios. Just to mention some. You might be able to create your own Kanban board and update the state of a task element.
Another scenario is that you will be able to drag and drop documents across libraries. I think there are many things this user interaction pattern can be useful.
Sadly drag and drop is currently not supported on mobile or tablet devices.
In future I expect that this will become more relevant because the number of devices that are touch enabled will increase in future.
If you like to try this solution yourself. Please feel free to clone it from my GitHub repository.
In case you experience any problem you can create a new issue on the repository or comment below.
I’ll love to hear from you and what you think about this solution.

14 Comments

  1. Hi Stefan when I try and run this I get this error :-

    Error: Cannot find module ‘@microsoft/sp-build-web’

    Regards

    Nigel

    Reply

    • Glad it works for you now. I tested and checked it once again. Couldn’t reproduce your issue after I install all node packages and ‘@microsoft/sp-build-web’ is referenced in the ‘package.json’ too.

      Reply

      • I am now getting no Blue around the words in the first web part and whilst I can drag from the first web part I am having trouble dropping into the second web part.(little red circle with diagonal top right bottom left bar across it.

        Reply

        • Thank you for pointing out. Fixed the style sheet problem for now but found another bug that currently just exists in Internet Explorer. Looking forward to solve this soon.

          Reply

  2. Great article! I have two questions though.

    1) You mention that you disabled the auto-prefixing of CSS classes. How did you do that? I’ve been trying to browse through your code in GitHub but can’t seem to find that part.

    2) Is there a reason why you modify the state of parent element from the child element? Couldn’t you also pass in an event handler function as a property to the child? That way the child could just inform the parent that the drag has ended and parent could manage its own state.

    Reply

  3. Hi just one query, can i package this using angularcli and copy the js files to the style lib on sharepoint and call the fles from a content editor webpart? please advise

    Reply

    • Currently this sample uses ReactJS only. I planing to do the exact same Sample in pure HTML only. This way it will be easier to adopt the sample to Angular and use it in a content editor web part.
      In addition there are many samples in the wild that use the same drag and drop pattern.

      Sorry for the bad news for now.

      Reply

  4. Hello Stefan very nice article . It saved a lot of time of mine . I have 1 issue . If we drag and drop last/bottom item just above item in same drop container , it should display same items but it is removing the item . Is there any workaround for it ?

    Reply

    • Honestly it is been I while I looked into this. Have you debugged the inner HTML once you updated/added the stack? Which browser have you used?

      Reply

      • Thanks for prompt reply . I am using chrome browser . I already debugged it . It seems that container is not able to distinguish whether the item is dragged & dropped from same container or different one . Is there any way by which we can get both target & source containers in same method ?

        Reply

  5. Hi Stefan I am facing issue when trying to drag drop in IE11 . Do you have any solution for this ?

    Reply

Leave a Reply

Required fields are marked *.


This site uses Akismet to reduce spam. Learn how your comment data is processed.