Kanban Board in Plain Javascript

Kanban Board in Plain Javascript

Building a Dynamic Kanban Board: Deep Dive into Drag-and-Drop Functionality

We will be building a simple kanban board in plain javascript , that can shuffle other task, so you can place the task at exact positions.

This is asked a lot of times in machine coding rounds for frontend interviews also.

Link : github.com/biohacker0/Kanban-Board

I am not discussing the html and css of the project, that you can see on the github.
We are focusing on the core logic.

Understanding the Setup:
We have draggable items (tasks) represented by HTML elements with the class name "task" and dropable areas (swimlanes) represented by HTML elements with the class name "swim-lane". We've attached event listeners to handle drag and drop events for these elements.

Base HTML Structure that is repeted , so we have swim-lane , which is the drop area driv.

And we have div's called task , there are things that we will drag , so these are our tasks.

every day an anime girl holding programming books (@AnimeGirlDev) / X

<div class="swim-lane">
  <div class="task" draggable="true">Task 1</div>
  <div class="task" draggable="true">Task 2</div>
  <!-- More tasks -->
</div>
<!-- More swim-lanes -->

Breakdown of Key Components:

  1. Retrieving Elements:

    • draggableItems = document.getElementsByClassName("task");

      • Selects all HTML elements with the class "task," which likely represent individual tasks.
    • dropableDivs = document.getElementsByClassName("swim-lane");

      • Selects all HTML elements with the class "swim-lane," which likely represent the different swim lanes or columns.
    •   let draggableItems = document.getElementsByClassName("task");
        let dropableDivs = document.getElementsByClassName("swim-lane");
      
  2. Event Listeners for Tasks:

    • dragCb(event): When a task is dragged (dragstart event):

      • Adds the class "is-dragging" to the task, indicating that it's currently being dragged.
    • dragendCB(event): When dragging stops (dragend event):

      • Removes the "is-dragging" class, visually signifying the end of dragging.
    function dragendCB(event) {
      event.target.classList.remove("is-dragging");
    }

    function dragCb(event) {
      event.target.classList.add("is-dragging");
    }

    for (let task of draggableItems) {
      task.addEventListener("dragstart", dragCb);
      task.addEventListener("dragend", dragendCB);
    }
  1. Drop Event Listener for Swim Lanes:

    • dragoverCB(event): When a dragged task is hovered over a swim lane (dragover event):

      • Prevents the default browser behavior, which might interfere with custom drag-and-drop logic.

      • Calls the helper function insertAboveTask to find the appropriate insertion point within the swim lane.

      • Retrieves the currently dragged task element using document.querySelector(".is-dragging").

      • If there's no task below the mouse pointer (meaning the task would be appended to the bottom):

        • Simply appends the dragged task to the swim lane using appendChild.
      • Otherwise, inserts the dragged task before the task that's closest below the mouse pointer using insertBefore.

          for (let dropableDiv of dropableDivs) {
            dropableDiv.addEventListener("dragover", function dragoverCB(event) {
              event.preventDefault();
              const bottomTask = insertAboveTask(dropableDiv, event.clientY);
              const currentTask = document.querySelector(".is-dragging");
        
              if (!bottomTask) dropableDiv.appendChild(currentTask);
              else dropableDiv.insertBefore(currentTask, bottomTask);
            });
          }
        
  2. Finding the Insertion Point (insertAboveTask function):

    • Takes the swim lane element and the mouse Y coordinate as input.

    • Initializes variables to track the closest task and its offset from the mouse pointer.

    • Loops through all task elements within the swim lane:

      • Uses getBoundingClientRect() to get the task's top position.

      • Calculates the offset between the mouse pointer and the task's top position.

      • If the offset is negative (i.e., the mouse pointer is above the task) and closer than the current closest offset:

        • Updates the closest offset and closest task variables.
    • Returns the closest task, which will be used to determine the insertion point.

    • Home | Anime Girls Holding Programming Books

        function insertAboveTask(dropableDiv, mouseY) {
          const currentDropableDivTasks = dropableDiv.getElementsByClassName("task");
          let closestTask = null;
          let closestOffset = Number.NEGATIVE_INFINITY;
      
          for (task of currentDropableDivTasks) {
            const { top } = task.getBoundingClientRect();
            const offset = mouseY - top;
      
            if (offset < 0 && offset > closestOffset) {
              closestOffset = offset;
              closestTask = task;
            }
          }
      
          return closestTask;
        }
      

Core Logic to decide who is closest task to use :

Core Logic Breakdown

  1. Event Listeners:

    • dragstart and dragend events are attached to draggable elements (tasks) to track the drag state and add/remove a visual indicator.

    • dragover event is attached to dropable elements (swimlanes) to handle the drag-and-drop process.

  2. insertAboveTask Function:

    • Takes a dropableDiv and mouseY as input.

    • Iterates through all task elements within the dropableDiv.

    • Calculates the offset between the mouse's Y coordinate (mouseY) and the top of each task.

    • Finds the task with the smallest negative offset (closest to the mouse cursor above its current position).

    • If no suitable task is found (the mouse is above all tasks), the new task is appended to the end of the dropableDiv.

    • Otherwise, the new task is inserted before the identified task.

      Update more than 130 programming anime latest - 3tdesign.edu.vn

Understanding Offset Calculations

The insertAboveTask function's key logic lies in its offset calculations. Here's a breakdown:

  1. offset = mouseY - top: This calculates the difference between the mouse's Y coordinate and the top edge of each task.

  2. offset < 0 && offset > closestOffset: This ensures the offset is negative (mouse is above the task) and closer to 0 than any previously encountered offset, indicating it's the closest task above the mouse.

  3. Finding the Closest Task: The loop iterates through all tasks, comparing offsets and updating closestTask if a closer one is found.

  4. Insertion Decision: If no suitable closestTask is found (offset === Number.NEGATIVE_INFINITY), the new task is appended to the end. Otherwise, it's inserted before the closestTask.

Blood-C~ | Anime, Anime girl, Programmer girl

Calculations of different scenarios :

  • Sample dropableDiv with 3 tasks:

    • Task 1: top = 100px

    • Task 2: top = 200px

    • Task 3: top = 300px

  • Mouse position (mouseY):

    • Case 1: mouseY = 150px

    • Case 2: mouseY = 250px

    • Case 3: mouseY = 350px

Calculations and Results:

  • Case 1:

    • Loop iterates through tasks.

    • Task 1: offset = 50 (positive, ignored)

    • Task 2: offset = -50 (negative and closest, closestTask updated)

    • Task 3: offset = -150 (negative but further, ignored)

    • insertAboveTask returns Task 2 (dragged task inserted below).

  • Case 2:

    • Similar logic, but closestTask will be Task 3 as offset is closer to 0.
  • Case 3:

    • No negative offsets found, closestTask remains null, and the dragged task is appended to the end.

With this the core logic of kanban is done , yay

Home | Anime Girls Holding Programming Books