JavaScript Drag and Drop

Last Updated on September 18, 2024 by E. Scott

Not long ago I was tasked with creating a complex JavaScript drag and drop UI. One in which gave users the ability to move MULTIPLE items at once. This was a feature I’d never done prior. And this was a project I was the lead frontend developer for. Additionally, the client was the United States Senate. Not only would members of Congress be using a UI I created, but every mouse or hand gesture was a POST request.

This project is a mock of what I built before the kick off meeting. I’d certainly explored drag and drop APIs in the past, but for rudimentary tasks. This was an entire UI based on JavaScript drag and drop functions with lots of API calls.

Most of which is irrelevant. I’m a developer, and therefore need to ensure that whatever I build works flawlessly. Regardless of the client. I wanted to be 100% prepared for the upcoming task. In turn, I created this UI. Though far from exact, it demonstrates creative drag abilities via JSON content. Working demo here. Code analysis below.

Git Repo

Dynamic User Interface

Sometimes a small amount of markup is all that’s needed to power lots of JavaScript or TypeScript or even Node or C#. In this case, it’s a good balance. So let’s familiarize ourselves with the markup.

This first block is the ‘Add Directory’.

<header [ngClass]="{ 'border-btm': items.length > 0 }">
    <p>Drag-Drop UI</p>
    <small
      >—  No Libraries or Packages  —</small
    >
    <div class="add-btn">Add Directory</div>
    <div class="add-new">
      <input
        placeholder="Add directory name"
        #newDirectory
        [(ngModel)]="itemTitle"
        (keydown)="getKeyCode($event)"
      />
      <button (click)="itemAdded()">Add</button>
    </div>
  </header>

In the TypeScript, the below function is fired when the user hits return.

  getKeyCode(e: any) {
    e.code === 'Enter' ? this.itemAdded() : '';
  }

If the key is in fact the return button, this next block executes.

itemAdded() {
    if (this.itemTitle != undefined) {
      this.newDirectory.nativeElement.classList.remove('required-field');
      this.items.push({ title: this.itemTitle });
      this.itemTitle = '';
      this.removeInputs();
    } else {
      this.newDirectory.nativeElement.classList.add('required-field');
    }
    this.itemTitle = undefined;
  }

By entering ‘Cities’ into the Directory field, we add a drag and drop section to the UI. In the markup, notice we are looping over the array we’re pushing items into.

There’s a lot happening in each drag and drop block as shown below.

<div
    class="selected-content"
    [ngClass]="{ hasDirectories: items.length > 0 }"
  >
    <div
      class="child-container"
      *ngFor="let item of items; let i = index"
      id="{{ 'group' + i }}"
    >
      <hr />
      <p class="group-index">{{ items[i].title }}</p>
      <div class="delete" (click)="deleteItem(i)">
        <i>×</i>
        <div class="faux-btn"></div>
      </div>
      <div class="drop-zone">
        <p class="ddText">Drag & Drop</p>
      </div>
      <div class="toggle-block">
        <p *ngIf="uploadedCount[i] != undefined && uploadedCount[i].length > 0">
          {{ uploadedCount[i].length }} Items
        </p>
        <ul class="dragged-items"></ul>
        <span
          (click)="toggleBlock(i)"
          class="arrow"
          *ngIf="uploadedCount[i] != undefined && uploadedCount[i].length > 0"
          >▼</span
        >
      </div>
    </div>
  </div>

If anything else other than return is pressed, nothing happens. This is the dynamic area without JavaScript drag and drop zones. Notice how it’s an empty div.

After that is the list of available data which is derived from a JSON file in the assets directory.

The green plus icons have this code. When the pushNamedItem is fired, the below function is called. Succinctly, we’re pushing data to the result array. There’s a tremendous amount happening here. Far too much for a blog post. To explain everything, I’d need to create a YouTube video or Udemy class. This is a summation however of the top level items.

<button (click)="pushNamedItem(i)">Add</button>

  pushNamedItem(i: number) {
    this.groupItem.length > 10
      ? (this.groupItem = this.groupItem.slice(0, 10) + ' . . .')
      : '';
    this.result[i].items.push({ name: this.groupItem });
    this.groupItem = undefined;
    this.addNewItem[i].active = false;
  }

Bring it All Together

There’s no way around it. If you’re creating a JavaScript drag and drop UI like this without a library, it’s gonna be complex. Plus, factoring in POST requests at every drop like my project called for—not fun. Though many would advocate using a library, I try to avoid using libs whenever possible. Why? I find lots of feature based libraries to be more problematic than helpful simply because every project is unique. And using one creates boundaries. In turn, I need to fight the feature to have it act the way the project calls for.

Side note: I certainly use libraries like every other dev, but sparingly. This project demonstrates one such example. It's a ChartJS UI. To see larger fully developed projects however, view them here. Alternatively, I really like this dynamic table.

Such is the case with a draggable plugin. You’ll spend more time trying to get the lib to mimic the intended behavior vs creating a system of your own. At the end of the day, you’ve got a flexible system. One in which you know what every line is doing.

This could no doubt be improved with more specified data types. But time was of the essence when I built a low-fi mock. One in which to emulate an actual feature in a proprietary application.

In conclusion, see additional fully developed projects right here. If you need a working version of this JavaScript drag and drop UI, this is the demo.

2 Trackbacks / Pingbacks

  1. Rest Countries API App - Frontend Development
  2. ChartJS Bar Chart UI & 4 Real World Charts - Frontend Development

Leave a Reply

Your email address will not be published.


*