2

Using the great work of Don Robins' presentation on LWC Drag and Drop at https://forcementor.github.io/lwc_dnd_20/#/ I've implemented the majority of a solution that allows a drag and drop enabled version of the classic dual listview approach (move items from list A to list B).

The list items themselves are rendered as rich text, but essentially there's three key LWC components here

  1. reportSectionItem - the individual draggable item
  2. reportSectionList - the list container(s) that act as drag targets
  3. reportSectionPicker - the component housing two lists and handling the events

I have the drag and drop between lists working and updating a backend object

Backend objects, managed by an Apex class ReportSectionPicker, are

  • Report_Section__c - items that are selectable
  • Report_Section_List__c - items that are selected, for a given recordId (where the object in this case is WorkOrder, but that shouldn't be relevant)

The latter object, Report_Section_List__c has a field "Sequence__c" to track the order of selected items (and the Apex class SOQLs them out ordered by that field).

What I'm struggling with is the last hurdle - the ability to sort selected items within the selected list. I've seen examples of drag and drop sorting of table rows in LWC, such as https://lovesalesforceyes.blogspot.com/2020/12/sorting-table-rows-by-drag-and-drop-in.html but I'm not sure how to implement comparable functionality in my component model.

I think I need to track not just the list as a drop target, but track the items within that list it's being dragged over and do some kind of shuffling (both visually in terms of moving visual elements and in the underlying data) based on that. The impact would be not just re-sequencing the dragged element, but potentially the elements surrounding it.

Currently, I don't even know where to start. Architecturally, events are fired from child to parent to be eventually handled by the reportSectionPicker.js code, which follows Don's original design, but I think I need to "enrich" the events being passed with more data to handle this scenario.

Any pointers on how to handle this requirement?

GitHub repo containing the Apex handler, the LWCs, a Flexipage and the objects at https://github.com/klivian/ReportPicker but it's more for showing the implementation than having something immediately runnable

2
  • Would you prefer a model that allows the user to arrange things, then takes an action to save that arrangement, or save to the database on each drop/change?
    – Jeff Kranz
    Jan 5, 2022 at 0:28
  • Not sure it matter too much in this scenario
    – Rob Cowell
    Jan 5, 2022 at 8:46

1 Answer 1

7

I wrote a very simplified drag-and-drop implementation, derived from my older drag-and-drop component written in Aura. You should not use this code directly in production, it requires some polishing to make it fully functional. However, I hope it successfully demonstrates how to do what you're trying to do in a generic manner.

Container Logic

  availableItems = []
  selectedItems = []
  // Temp storage set on drag start
  dragInfo
  handleDragStart(event) {
    // Keep track of the list and item id
    this.dragInfo = { ...event.detail }
  }
  handleDragComplete(event) {
    // Keep reference to the lists. Start and end lists may be the same list.
    let startList = this.dragInfo.name === 'available'? this.availableItems: this.selectedItems
    let endList = event.detail.name === 'available'? this.availableItems: this.selectedItems
    // Indices for the items to move in their respective lists
    let startIndex = startList.findIndex(item => item.id === this.dragInfo.id)
    let endIndex = endList.findIndex(item => item.id === event.detail.id)
    // Remove from old index, move to new index
    endList.splice(endIndex, 0, startList.splice(startIndex, 1)[0])
    // Trigger a render cycle on copy. You could also use @track.
    this.availableItems = [...this.availableItems]
    this.selectedItems = [...this.selectedItems]
  }

Container Markup

<lightning-layout>
    <lightning-layout-item size="6">
        <c-drag-list-container name="available" items={availableItems} onstartmove={handleDragStart}
            onendmove={handleDragComplete}>
        </c-drag-list-container>
    </lightning-layout-item>
    <lightning-layout-item size="6">
        <c-drag-list-container name="selected" items={selectedItems} onstartmove={handleDragStart}
            onendmove={handleDragComplete}>
        </c-drag-list-container>
    </lightning-layout-item>
</lightning-layout>

List Logic

  @api items
  @api name
  cancel(event) {
    event.preventDefault();
    event.stopPropagation();
  }
  handleDragComplete(event) {
    // Fire custom event when drag stops
    const detail = { name: this.name, id: event.target.dataset?.id }
    console.log(detail)
    this.dispatchEvent(
      new CustomEvent(
        'endmove', 
        { detail }
      )
    )
  }
  // Fire custom event whend drag starts
  handleDragStart(event) {
    const detail = { name: this.name, id: event.target.dataset?.id }
    this.dispatchEvent(
      new CustomEvent(
        'startmove', 
        { detail }
      )
    )
  }

List Markup

  <div class="droparea" ondragover={cancel} ondragenter={cancel} ondrop={handleDragComplete}>
    <div key={item.id} class="row" for:each={items} for:item="item" for:index="index" draggable="true"
      ondragstart={handleDragStart} data-id={item.id}>
      {item.value}
    </div>
  </div>

Demo.

4
  • I've looked at/forked the demo (easier to read that way) and I can see the general principle of how you've solved that - will have a try at using similar approach, thanks!
    – Rob Cowell
    Jan 5, 2022 at 10:38
  • For a given dragged element within it's own list, how are you tracking where it is dropped in that list so you know where to do the re-indexing? I'd have thought some kind of dragOver event would handle this, but it's just dragstart and drop
    – Rob Cowell
    Jan 5, 2022 at 20:51
  • 1
    @RobCowell the drop event includes the id of the value to move, which I then look to the index to. I then remove it from the old location and put it in the precalculated new index. This happens to work out to always move the value to the correct place.
    – sfdcfox
    Jan 5, 2022 at 21:24
  • @sfdcfox - Fantastic demo. Thank you! Simple, to-the-point. It's also a great demo for using Lightning Web Components outside of Salesforce. (It's amazing how ridiculously difficult it can be to find good demos like this.)
    – Jon Freed
    Oct 25, 2023 at 12:41

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .