Dragula drag and drop in a Lightning Component

Dragula is a great library that makes adding drag and drop easy. It handles creating a copy of the element being dragged that moves with the cursor and also shows where the element will go in the drop area. It has a simple API and is self contained. (The Locker Service is happy with version 3.7.2 of Dragula that I used.)

Here is how I hooked it into a Lightning Component to produce this result:

Drag and drop screen shot

In the component’s controller, this code connects the DOM element oriented Dragula up with the component oriented Lightning straight after Dragula is loaded. There is a bit of extra logic to add and remove a placekeeper drop area when there are no items:

({
afterDragulaLoaded: function(component, event, helper) {

    // Components
    var container = component.find('container');
    var from = component.find('from-draggable');
    var to = component.find('to-draggable');

    // Dragula needs the DOM elements
    var drake = dragula([from.getElement(), to.getElement()], {
        direction: 'vertical',
        mirrorContainer: container.getElement()
    });

    // Show/hide the "Drag and Drop Here" item
    // $A.getCallback makes safe to invoke from outside the Lightning Component lifecycle
    drake.on('drop', $A.getCallback(function(el, target, source, sibling) {
        if (source.children.length <= 1) {
            $A.util.removeClass(component.find(helper.placekeeperAuraIdFor(source)), 'slds-hide');
        }
        $A.util.addClass(component.find(helper.placekeeperAuraIdFor(target)), 'slds-hide');
    }));
}
})

This helper is used:

({
placekeeperAuraIdFor: function(element) {
    // Hard to get from DOM back to aura:id so using classes as markers
    if (element.classList.contains('from-draggable')) return 'from-placekeeper';
    else if (element.classList.contains('to-draggable')) return 'to-placekeeper';
    else return null;
}
})

Here is the component markup: it is just hard-coded data and styling to keep this example simple and references the Dragula JavaScript and CSS static resources via a ltng:require at the bottom:

<aura:component >
    
<div aura:id="container">
    
    <div class="slds-text-heading--medium">Candidates</div>
    
    <ul aura:id="from-draggable" class="from-draggable">
        <li class="slds-p-around--xx-small">
            <article class="slds-card">
                <div class="slds-card__body">
                    <div class="slds-tile slds-tile--board">
                        <h3 class="slds-truncate" title="Anypoint Connectors">
                            <a href="javascript:void(0);">Anypoint Connectors</a>
                        </h3>
                        <p class="slds-text-heading--medium">$500,000</p>
                    </div>
                </div>
            </article>
        </li>
        <li class="slds-p-around--xx-small">
            <article class="slds-card">
                <div class="slds-card__body">
                    <div class="slds-tile slds-tile--board">
                        <h3 class="slds-truncate" title="Cloudhub">
                            <a href="javascript:void(0);">Cloudhub</a>
                        </h3>
                        <p class="slds-text-heading--medium">$185,000</p>
                    </div>
                </div>
            </article>
        </li>
        
        <li class="slds-p-around--xx-small slds-hide" aura:id="from-placekeeper">
            <div class="slds-file-selector__dropzone" style="height: 50px">
                <span class="slds-file-selector__text">Drag and Drop Here</span>
            </div>
        </li>
    </ul>
    
    <div class="slds-text-heading--medium">Selected</div>
    
    <div class="slds-panel slds-grid slds-grid--vertical slds-nowrap">
        <ul aura:id="to-draggable" class="to-draggable">
            <li class="slds-p-around--xx-small" aura:id="to-placekeeper">
                <div class="slds-file-selector__dropzone" style="height: 50px">
                    <span class="slds-file-selector__text">Drag and Drop Here</span>
                </div>
            </li>
        </ul>
    </div>
</div>

<ltng:require styles="{!$Resource.DragulaCss}"
              scripts="{!$Resource.DragulaJs}"
              afterScriptsLoaded="{!c.afterDragulaLoaded}"
              />
    
</aura:component>

This CSS is also used:

.THIS li {
    list-style-type: none;
}
.THIS article.slds-card:hover {
    border-color: #1589ee ;
}

There were two awkward areas:

  • As far as I know, there is no way to go from a DOM element back to an aura:id so in Dragula’s “drop” callback I resorted to using a marker class instead.
  • The $A.getCallback wrapping function is needed as explained in Modifying Components Outside the Framework Lifecycle.
Advertisements

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s