Jérémy
Gautrais

Drag and drop component with filter feature

/ Published on: 

February 09, 2022

Drag and drop component

The HTML Drag and Drop API allows us to handle and customize drag-and-drop events in browsers. In this post, I will explain how I used this API to create a custom component, combined with a search filter.
The final code and a demo is available at the following Codepen. In the article, I will explain how I customized a base template and implemented a filter feature to quickly search through available items to be dragged.

Creating the component skeleton

To build this component, I used various resources to create a skeleton of the drag and drop component, in particular:

The objective of this component is to select various skills. Therefore we will be manipulating skill items in this example. Using resources mentioned above, I ended up with the following component:

HTML<div class="container">    <!-- Filter item -->    <label for="search-input">Search skills:</label>    <input type="text" value="" placeholder="skill" id="search-input" />
    <ul class="skills">        <!-- Draggable items pool -->        <li class="skills__item" draggable="true" id="0">            Gardening            <input type="hidden" name="skill[]" value="0" />
            <button type="button" class="remove-skill">X</button>        </li>    </ul>
    <form method="POST">        <!-- Drop zone -->        <ul class="drop-zone"></ul>        <input type="submit" value="Submit" />    </form></div>

For readibility, I am omitting here Bootstrap specific class names. You can refer to the Codepen mentioned in the beginning to look at the detailed skeleton. We have from top to bottom:

  • filter component,
  • skills pool (<ul class="skills">),
  • drop zone (<ul class="drop-zone">)

The skills pool contains items which are made draggable. The drop zone is enclosed whithin a form so that the selected skills can be submitted and registered in database (we will not deal with this step here).
To render a functional dragging sequence, I am using various events provided but the drag and drop API:

  • for skills items:
    • dragstart,
    • dragend,
    • drop.
  • for drop zone:
    • dragenter,
    • dragleave,
    • dragover,
    • drop.

I will not go into the details on how to listen to these events and how to implement their handlers as many resources, including those mentioned above, go through this process in great detail.

Implementing the skills filter functionality

The filter process is handled in 3 steps:

  • retrieving the filter value,
  • filtering the skills pool based on that value,
  • updating the DOM with the filtered skills to display to the user

However, the initial step for us is to retrieve all skills available in the component, whether or not they are currently being selected by the user.

1. Retrieving the skills from the DOM

JSconst skillsPool = document.querySelector('.skills');const skillsInitial = [].slice.call(skillsPool.children);

In the snippet above, we are first retrieving all the skills elements, displayed in the pool (<ul class="skills">). Then we need to add to the array of inital skills (skillsInitial) the skills that are already selected by the user. Whenever a skill element is selected by the user, we are adding to it the checked attribute, so that we can identify them later in the process. Indeed, those selected skills should not be presented to the user unless the user unselect them at some point.

JSif (dropZone.children.length > 0) {    const skillsSelected = [].slice.call(dropZone.children);
    skillsSelected.forEach((skill) => {        skill.checked = true;        skillsInitial.push(skill);    });
    sortSkills(skillsInitial, 'id');}

In the snippet above, we are checking if the drop-zone element contains any skill element. In that case we are retrieving the selected skills, adding them the checked attribute and adding those to the array. Finally, we are sorting alphabetically the skills.
This skillsInitial array will be our source of data during the whole filtering process.

2. Listening to the filter input value

The filter is implemented through a text input, whose value is retrieved in javascript to update the skills displayed to the user. Therefore, we have to implement first an event listener, firing whenever the value of the input is changing:

JSfunction addFilterHandler(input, skillsInitial, skillsPool) {    function handleChangeValue(e) {        const target = e.target;
        if (target) {            filterSkills(input, skillsInitial, skillsPool);        }    }    input.addEventListener('input', handleChangeValue);}
const filterInput = document.querySelector('#search-input');addFilterHandler(filterInput, skillsInitial, skillsPool);

In the snippet above we are defining the handler (handleChangeValue), which is called whenever the input event is happening. This event fires whenever the value of an input has been changed. Finally, we are providing this handler to the event listener set up on the input element.
The handler itself is calling another function (filterSkills), which takes as parameters the filter value (input), the intial set of skills (skillsInitial) and the skills container element (skillsPool).

3. Filtering the skills

Once the event listener is set up, we have to filter the skills pool based on the value retrieved in the first step. The filterSkills function is taking charge of this:

JSfunction filterSkills(input, skills, skillsContainer) {    const filter = input.value.toLowerCase();
    if (filter === '') {        updateSkills(skillsContainer, skills);    } else {        const skillsFiltered = skills.filter((skill) =>            skill.firstChild.nodeValue.toLowerCase().includes(filter),        );        updateSkills(skillsContainer, skillsFiltered);    }}

In the snippet above, we are first checking the filter value, evaluating if it is empty. In that case, we don't filter the values and go directly to the DOM update step, with the updateSkills function. If the value is not empty, we filter the list of skills to match those that contains the string entered by the user in the input filter. The next step is to update the DOM with the skills that match the filter.

3. Updating the DOM

To update the DOM with filtered skills, we have to differentiate skills that are selected (displayed in the drop zone) from those that are not. Indeed, we do not want to present the user with skills that he already selected. To do that, we are checking the checked attribute of skills. Therefore, our update function is as below:

JSfunction updateSkills(container, skills) {    container.innerHTML = '';
    skills.forEach((skill) => {        if (!skill.checked) container.appendChild(skill);    });}

In the snippet above, we are iterating over all the filtered skills. If the skill is not already selected by the user (checked), we are adding the skill to the initial container (skillsPool variable in our script).

I let you look at the final code available at the following Codepen. In this pen, I have refined the component with initial alphabetical sorting of skills. The pen also contains the whole code for the drag and drop feature and the Bootstrap's class markup in the HTML.

/ Last updated: 

February 09, 2022