Drag and Drop between PCFs (Canvas Apps, Custom Pages, Model-Driven Forms)

I love the questions that challenges me. One of the latest ones, was about drag&drop inside a Custom Page or a Canvas App.

I know about the Power Drag Drop PCF created by Scott Durow, which created a great way to easily drag and drop between collections. But I needed a little more like a “free style way” to drag and drop, being able to drop on a specific place inside my own PCF. It’s not about moving element between collections; it’s about a way to define an action having a source and a target, where the target was somewhere inside my own PCF.

The requirement (Custom Pages / Canvas Apps)

Let’s consider a Gallery with countries (in the screenshot below, on the left side). We need to plan deliveries to those countries, so we need to manage the corresponding appointments. We see the appointments on the right side, in an own implemented PCF.

The requirement is to generate appointments by dragging a specific country onto a day inside the calendar PCF.

I didn’t wanted to make changes in the Custom Page. It’s great that we can work with the Gallery on the left side, and I didn’t wanted to change that.

I consider always a good idea to work with smaller code components, and to implement a code component only where it’s needed. Have a big PCF covering the whole Custom Page would add to much complexity or would make the Page less configurable. But that means that I need to be able to do drag&drop between two different PCFs. I had to figure out first, how to do that.

The HTML5 drag and drop

I know about some drag&drop React libraries, but this time the source and the target were not inside the same component. I’ve found the docs about HTML5 drag&drop (have a look to the w3schooks tutorial).

In order to implement drag&drop we need:

  • a HTML element representing the source
  • a HTML element representing the target where we can drop

The source

The source should include the “draggable” attribute and should specify the data we want to transfer. Here is an extract from the tutorial from w3schools

The “drag” function will define the data to be dragged. In this example the data will be specified under the name “text”

function drag(ev) {
  ev.dataTransfer.setData("text", ev.target.id);
}

<img ... draggable="true" ondragstart="drag(event)">

The target

The target HTML element will need to specify two methods:

  • ondragover function – this will tell if the user is allowed to drop over this element
  • the ondrop method

From the same example, here we have for the target

The allowDrop function just need to overwrite the default dropping behavior in HTML (so whe only need preventDefault), while the ondrop function will get the data from the event (and in this case, will move the source to the target)

<div id="div1" ondrop="drop(event)" ondragover="allowDrop(event)"></div>

function allowDrop(ev) {
  ev.preventDefault();
}

function drop(ev) {
  ev.preventDefault();
  var data = ev.dataTransfer.getData("text");
  ev.target.appendChild(document.getElementById(data));
}

That’s it. You can try out this example on w3schools.com

The implementation idea

The PCFs are not Iframes. They are parts of the same page (basically DIVs). So following this idea, I can define DIVs inside the source and target PCF, where I implement the drag&drop functionality. The two PCFs only need to know where to find the data inside the event.

But my source in my use-case is not a PCF; it’s just a Gallery. And I wanted to keep it that way, in order to allow full flexibility for the UI representation of the Gallery items. My solution was to place a transparent PCF over the gallery items. This would be a generic PCF, which I can be used in any other Custom Page.

The concept of the page would look like this:

  • with red we see that DragAndDrop component inside the Gallery
  • with green we see target- the Calendar PCF

Implementing a generic “draggable” PCF (DragAndDrop PCF)

First I’ll implement the “Source” PCF: “DragAndDrop”. Since I want to be able to test the PCF, I’ll allow the same PCF to also be used as the target for a drag&drop action.

This PCF will be a transparent DIV which can show an icon too. Maybe is a good idea to show that this DIV can be dragged, so we can choose what icon should be displayed (the default will be “DragObject” icon from Fluent UI icons)

Since the Manifest of a PCF tells a lot about how I’ve designed it, here it is

<property name="Name" ... of-type="SingleLine.Text" usage="bound" required="true" />
<property name="Data" .. of-type="SingleLine.Text" usage="bound" />
<property name="IconName" ... of-type="SingleLine.Text" usage="input" />
<property name="Dropped" ... of-type="Object" usage="output" />
   
<property name="IsDraggable" ... of-type="TwoOptions" usage="bound" />
<property name="IsDroppable" ... of-type="TwoOptions" usage="bound" />


<property name="DroppedSchema" display-name-key="DroppedSchema" description-key="DroppedSchema" of-type="SingleLine.Text" usage="input" hidden="true"/> 
<property-dependencies>
  <property-dependency input="DroppedSchema" output="Dropped" required-for="schema" />
</property-dependencies>
  • Name: this property will be passen through the event. It’s there only to identify the source of the object being dropped
  • Data: using this property we can define the data being dragged
  • IconName: The icon to be shown inside the DIV
  • IsDraggable / IsDroppable : specify if this PCF should be able to be dragged or if it can be a target to drop on. It could be both
  • Dropped: the dropped data. I’ve defined this as an output object, and the schema contains
    • From: the name of the PCF which was dragged
    • Data: the data being dragged and dropped

The core part of the DragAndDrop PCF

function DraggableComponent ({name, data, setDroppedData, isDraggable, isDroppable, iconName}: IDraggableProps) {
  const dragStart = (event: React.DragEvent<HTMLDivElement>) => {
    event.dataTransfer.setData(`Dianamics.DragAndDrop`,
                 JSON.stringify({From: name, Data: data}));      
  }  

  const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
    event.preventDefault();
    var data = event.dataTransfer.getData("Dianamics.DragAndDrop");
    setDroppedData(JSON.parse(data));
  }

  const allowDrop= (event: React.DragEvent<HTMLDivElement>) => {    
    event.preventDefault();    
  }


  return (
    <div onDrop={onDrop} onDragOver={allowDrop} style={{width: "100%", height: "100%"}}>
      <div draggable={true} onDragStart={dragStart} style={{width: "100%", height: "100%"}}>
        <Icon iconName={iconName} style={{fontSize: "xx-large"}} />
      </div>
    </div>
  ) 
}

The complete code of the DragAndDropPCF can be found on my GitHub Repository: https://github.com/brasov2de/DragAndDropPCF

Given this implementation, I can place now the DragAndDrop PCF inside the Gallery, and set the properties:

  • Name: Country
  • Data: ThisItem.Name (so I can use low-code to define the data)
  • IconName: “DragObject”
  • IsDraggable: true

And I can use the same PCF also as a “drop zone”. When the “OnChange” event of the PCF is triggered, I can use the property “Dropped.Data” and get the information about the country being dragged. I’ll collect that in colDropped, which I show on the right side of the screen, in another Gallery.

I’ve added the second drop-zone for Sunday (green) where the I add records to the colDropped, having “By = Sunday”.

And here is the result:

I know, this use case is not very interesting. I’ve implemented only as a test, until I can move on to the “Calendar” as a drop target.

Please note that the drga&drop functionality doesn’t work inside the Custom Page designer. The drag&drop events seems to be blocked / used by the page designer itself. But the drag&drop works after we save and publish the page, inside the App itself.

The idea of placing the draggable PCF over the Gallery items works only if this PCF is on the top of the other controls . If the Gallery has elements that need user-interaction too, the DragAndDrop PCF can be placed on a smaller area inside the Gallery. In that case, the dragging is available only using the icon provided by the PCF.

Simulate the dragging image

My DragAndDrop PCF doesn’t actually render real content. It’s only placed inside a zone I want to drag.

Usually when you drag content inside a HTML page, there is a visual feedback. Some kind of “ghost” image of the content being dragged will automatically be generated. Since I don’t really drag the content I’m intending, I’ve searched for ways to emulate that too… And found the DataTransfer.setDragImage. setDragImage allows me to set that image used during dragging to something different.

setDragImage(imgElement, xOffset, yOffset)

The “imgElement” cand be an image or another HTML element which has to be visible inside the page.

I’ve created a simple HTML Page demo . The page contains a yellow zone, and inside that I have a red zone. The red zone is the draggable one, and I want to be able to drop it on the green zone.

When I drag the red zone, I see the red ghost-image being moved.

But if I want to create the illusion that I drag the whole yellow zone, I can use the “onDragStart” event, and change the image with the parentElement by calling there the setDragImage

When I drag now, I get the visual feedback including the yellow zone, even if only the red zone is draggable.

This idea can be used also when placing the DragAndDrop PCF on a Gallery. It’s true, it’s a little unsupported, because I need to call several times parentElement, which leads outside the PCF DIV. The tricky point here is to know which parentElement…parentElement should be used.

Have a look to the demo here

Drop on my calendar PCF

Now I’m done with the “Source” to be dragged. Now I can modify my Calendar PCF, and define inside each day a DIV which is ready to listen to the dragging event. In order to understand the data being transferred, the source and the target PCFs need a handshake: they need to know the name of the data being transferred. All I need to add is:

  const onDrop = (ev: React.DragEvent<HTMLDivElement>) => {
    ev.preventDefault();
    var data = ev.dataTransfer.getData("Dianamics.DragAndDrop");
    const dropped = JSON.parse(data ?? "{}").Data;
    console.log('dropping ', dropped);  
    console.log("dropped on", event.eventDate); 
    alert(`Dropped ${dropped} on ${new Date(event.eventDate).toLocaleDateString()}`);
  }
  const allowDrop= (ev: React.DragEvent<HTMLDivElement>) => {    
    ev.preventDefault();    
  }

<div className="rbc-event-content" onDrop={onDrop} onDragOver={allowDrop} >{event.title}</div>

Now I have an event where I know what has been dragged and where it was dropped. Now I can choose if I want the Calendar PCF to trigger an event (then I would use PowerFx to generate the appointment). Or I can choose to generate the appointment right inside my PCF. It depends on the use-case.

Drag and Drop on Model-Driven Forms

Until now we saw how to implement a drag&drop functionality inside Custom Pages or Canvas Apps.But what about Model-Driven Forms? The PCFs are not IFrames, so they are placed inside the same HTML. The drag&drop could work there too.

And yes, it works. I wasn’t able to use exactly the same DragAndDrop as a source PCF, since it wasn’t available in the list of PCFs I could register for the AccountNumber. I guess it was because my DragAndDrop PCF defined the output as an object. But I’ve used a slightly modified version (without an output), and I could drag the PCF registered for the AccountNumber onto my Calendar Subgrid PCF. I drag here the content bound to the PCF (you see the number control again, right below the DragAndDrop PCF).

Conclusion

The implementation worked for me for all kind of Apps (Canvas Apps, Custom Pages, Model-Driven Apps). It’s not something provided by the sdk, but I think it’s not unsupported, since it’s based on standard HTML5. But this works only as far the Power Apps runtime doesn’t block it. Right now the designer prevents custom dragging, but inside the Power Apps Runtime it works. As long nothing changes, we can allow the user to tell us more with drag&drop. What do you think?

Photo by Polesie Toys: https://www.pexels.com/photo/girl-in-pink-jacket-dragging-a-toy-while-walking-on-snow-6138736/

Advertisement

One thought on “Drag and Drop between PCFs (Canvas Apps, Custom Pages, Model-Driven Forms)

Add yours

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Website Powered by WordPress.com.

Up ↑

%d bloggers like this: