Thursday, September 19, 2024

JQuery UI Draggable and Droppable: Use Cases



Key Concepts

Drag and drop is a common functionality in web applications. jQuery offers robust features like ‘draggable’ and ‘droppable’ that enable drag-and-drop functionality and enhance user-friendly interfaces. Let’s explore the key features of this functionality and how to implement various drag-and-drop scenarios using jQuery.


Draggable

You can make an element draggable using jQuery UI if you apply the ‘draggable()’ method on that element. We introduce and explain the main options and events of the draggable feature in the two following tables.


Main Draggable Options

Option

Definition

Possible Value

Syntax

axis

It limits the dragging of the element to a particular axis.

"x": dragging only horizontally

"y": dragging only vertically

axis: "y"

containment

It restricts the dragging area to a container element.

- "parent"

- "document"

- "window"

- jQuery selector

containment: "parent"

cursor

It applies a cursor when the element is being dragged.

CSS cursor value

cursor: "move"

handle

It limits dragging to a specific child of the draggable element.

jQuery selector

handle: ".handle"

helper

It specifies the helper element for displaying the dragging action.

- "original": the original element (default)

- "clone": a copy of the element

- Custom: function which returns a DOM element

helper: "clone"

revert

It helps return the draggable element to its original position after dragging it.

- true: always revert the dragging

- false: never revert the dragging

- "invalid": revert the dragging if the element dropped in a non-droppable area

revert: "invalid"

scroll

It determines whether the page should scroll when the draggable element is being dragged near the edge of the viewport.

- true: enable the auto-scrolling (default)

- false: disable the auto-scrolling

scroll: false

opacity

It is the opacity value of the draggable element during a dragging action.

A valid opacity value, which is a number between 0 and 10.

opacity: 0.5

cancel

It disables dragging particular children of the draggable element.

jQuery selector

cancel: ".non-draggable"



Main Draggable Events

Event

Definition

Syntax

start

It is triggered when the user starts dragging the element.

start: function(event, ui) {

console.log("Dragging started!");

}

drag

It is triggered repeatedly as the element is being dragged.

drag: function(event, ui) {

console.log("Dragging in progress!");

}

stop

It is triggered when the dragging stops.

stop: function(event, ui) {

console.log("Dragging stopped!");

}

Main Event Parameters

event

The corresponding event.

drag: function(event, ui) {

console.log("event:", event);

}

ui

A jQuery UI object that contains the draggable element's properties like ‘helper’, ‘position’, and ‘offset’.

drag: function(event, ui) {

console.log("ui:", ui);

}

ui.helper

The draggable element at the time it is dragged.


ui.position

The current position of the draggable element (top, left), relative to the parent.


ui.offset

The current position of the draggable element (top, left), relative to the document.




Droppable

Similarly, you can make an element droppable using jQuery UI if you apply the ‘droppable()’ method on that element. The element marked as droppable will react when a draggable element is dropped on it. The main options and events of the droppable functionality are summarized in the following two tables.


Main Droppable Options

Option

Definition

Value

Syntax

accept

It describes which draggable elements are accepted by the droppable element.

jQuery selector

accept: "#specific-draggable"

activeClass

It applies a CSS class to the droppable element when a draggable is being dragged.

CSS class

activeClass: "ui-state-hover"

tolerance

It determines whether the drop can be performed based on the intersection area between them when a draggable element hovers over a droppable.

"fit": The draggable must be entirely inside the droppable.

"intersect": The draggable must overlap the droppable by at least 50% (default).

"pointer": The mouse pointer must overlap the droppable.

"touch": The draggable must overlap the droppable at any point.

tolerance: "pointer"



Main Droppable Events

Event

Definition

Syntax

drop

It is triggered when the draggable is dropped on the droppable.

drop: function(event, ui) {

console.log("Element dropped:", ui.draggable);

}

activate

It is triggered when the draggable starts being dragged, and the droppable is prepared to be dropped on it.

activate: function(event, ui) {

$(this).css("border", "2px dashed red");

},

deactivate

It is triggered when dragging ends.

deactivate: function(event, ui) {

$(this).css("border", "");

}

over

It is triggered when a draggable is being dragged over the droppable.

over: function(event, ui) {

$(this).css("background-color", "lightyellow");

}

out

It is triggered when a draggable is dragged out of the droppable.

out: function(event, ui) {

$(this).css("background-color", "");

}



Dragging and Dropping an Element

Here is an example of dragging and dropping an original element, which is the contents of a table cell. The draggable element will replace the contents of another cell if the user confirms the drop action (confirm dialog box will be opened). If the user rejects the replacement action, the dragged object will return to each initial position.

Note. You can download the full example here:

https://github.com/noura-github/drag-drop-jquery


<!DOCTYPE html>

<html>

<head>

    <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">

    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

<script>

$(document).ready(function(){

    $(".draggable").draggable({

        containment: "#dragDropTab",   

        axis: "y",

        tolerance: "pointer",

        cursor: "move",

        revert: "invalid",

        helper: "original", //clone

        scroll: true,

        opacity: 0.5,

        cancel: ".non-draggable"

    });

    $(".droppable").droppable({

        accept: ".draggable",

        activeClass: "ui-state-hover",

        hoverClass: "ui-state-active",

        greedy: true,

        drop: function(event, ui) {

        let source = ui.draggable.text();

        let target = $(event.target).find('div').text();

        if (confirm("Are you sure to move " + source + " to " + target + "?")) { 

            $(this).find('div').replaceWith(ui.draggable.html())

                ui.draggable.remove();

            } else {

                ui.draggable.draggable('option', 'revert', true);

            }

        }

    });

});

</script>

<style>

table {

    font-family: arial, sans-serif;

    border-collapse: collapse;

    width: 100%;

}

td, th {

    border: 1px solid lightgrey;

    text-align: left;

     padding: 10px;

}

tr:nth-child(even) {

    background-color: lightgrey;

}

</style>

</head>

<body>

<table id="dragDropTab">

  <thead>

  <tr>

    <th>Firstname</th>

    <th>Lastname</th>

    <th>Email</th>

  </tr>

  </thead>

  <tbody id="myTable">

  <tr>

    <td><div class="draggable">Jack</div></td>

    <td><div class="draggable">Sparrow</div></td>

    <td><div class="draggable">jack@gmail.com</div></td>

  </tr>

  <tr>

    <td>Mary</td>

    <td>Cruze</td>

    <td>mary@gmail.com</td>

  </tr>

  <tr>

    <td><div class="non-draggable">Hony</div></td>

    <td><div class="non-draggable">Moon</div></td>

    <td><div class="non-draggable">hony@gmail.com</div></td>

  </tr>

  <tr>

    <td class="droppable"><div>Tanya</div></td>

    <td class="droppable"><div>Crood</div></td>

    <td class="droppable"><div>tanya@yahoo.com</div></td>

  </tr>

  </tbody>

</table>

</body>

</html>


Swapping two Elements

Here is an example of how to swap the contents of two table cells. Take the example above and replace the custom script section with the following script. To write this code, we defined a function ‘swapWith’ to swap two elements using the JavaScript ‘replaceWith’ method. This method replaces existing content in the DOM with new content.


<script>
$(document).ready(function(){
   jQuery.fn.swapWith = function(to) {
    return this.each(function() {
        var clone_to = $(to).clone(true);
        var clone_from = $(this).clone(true);
        $(to).replaceWith(clone_from);
        $(this).replaceWith(clone_to);
    });
};
$(".draggable").draggable({
    containment: "#dragDropTab",   
    axis: "y",
    tolerance: "pointer",
    cursor: "move",
    revert: "invalid",
    helper: "clone",
    scroll: true,
    cancel: ".non-draggable"
});
$(".droppable").droppable({
    accept: ".draggable",
    activeClass: "ui-state-hover",
    hoverClass: "ui-state-active",
    greedy: true,
    drop: function(event, ui) {
    let source = ui.draggable.text();
    let target = $(event.target).find('div').text();
    if (confirm("Are you sure to swap " + source + " and " + target + "?")) { 
        $($(this).find('div')).swapWith($(ui.draggable));
        } else {
            ui.draggable.draggable('option', 'revert', true);
        }
    }
});
});
</script>

Drag & Drop a Group of Elements

Here is an example of how to drag the contents of multiple table cells (one row) onto another row. Take the entire example above and replace the custom script section with the following:


<script>

$(document).ready(function(){

    $(".draggable").draggable({

        containment: "#dragDropTab",   

        axis: "y",

        tolerance: "pointer",

        cursor: "move",

        revert: "invalid",

        helper: "clone",

        scroll: true,

        opacity: 0.5,

        cancel: ".non-draggable",    

        helper: function(){

            var selected = $('#dragDropTab tr td').children(".draggable");

            if (selected.length === 0) {

                selected = $(this);

            }

            var container = $('<div/>').attr('id', 'draggingContainer');

            container.append(selected.clone());

            return container; 

        }

});


$(".droppable").droppable({

    accept: ".draggable", 

    activeClass: "ui-state-hover",

    hoverClass: "ui-state-active",

    greedy: true,

    drop: function(event, ui) {

        let source = "(" + $.map(ui.helper.children(), function(element) { return $(element).text()}).join(", ") + ")";

        let target = "(" + $.map($(event.target).parent('tr').find('td'), function(element) { return $(element).text()}).join(", ") + ")";

        if (confirm("Are you sure to move " + source + " to " + target + "?")) { 

        let cell = $(this);

               $.each(ui.helper.children(), function() {

                   let child = $(this);

                   cell.empty();

                   cell.append(child);

                   cell = cell.next();

              });

        } else {

            ui.draggable.draggable('option', 'revert', true);

        }

    }

});

});

</script>




JQuery Parent vs Parents



What is the difference between ‘paren’t and ‘parents’ in jQuery?


HTML Example:

<body>body (great-great-grandparent)

  <div>div (great-grandparent)

    <ul>ul (grandparent)  

      <li>li (parent)

        <span id="sid-1">span</span>

      </li>

      <li>li (parent)

        <span id="sid-2">span</span>

      </li>

    </ul>   

  </div>

</body>



parent()

parents()

Return

immediate parent element

all ancestor elements

Syntax 1 (without selectors)

$("#sid-1").parent();

$("#sid-1").parents();

Result

li

li, ul, div, body, html

Syntax 2 (one selector)

$("#sid-1").parent("li");

$("#sid-1").parents("ul");

Result

li

ul

Syntax 3 (multiple selectors)

$("#sid-1").parent("ul, div");

$("#sid-1").parents("ul, div");

Result

-

ul, div

Anonymous Functions in JavaScript

 


1. Anonymous Functions

Anonymous functions are functions defined without a name. In JavaScript, anonymous functions are powerful features and can be used in several contexts. It is common in JavaScript to find an anonymous function assigned to a variable, passed as an argument to another function, declared to handle an event, or a callback to array methods such as map, filter, etc.


2. Benefits of Using Anonymous Functions

Anonymous functions have more concise syntax and are often more readable, especially after launching arrow functions.


3. Contexts of Use of Anonymous Functions

The following tables show the main contexts in which anonymous functions are used:


Usage of Anonymous Function

Code Snippet

Code Output

Assigned to a Variable

let sayHello = function() {

console.log("Hello, world!");

};

sayHello();


let sum = function(a, b) {

return a+b;

};

console.log("Result of addition is:", sum(4, 7));


Hello, world!

11

Argument to Another Function

setTimeout(function() {

console.log("You waited two seconds to get this message.");

}, 2000);


You waited two seconds to get this message.

Immediately Invoked Function Expression (IIFE)

(function() {

console.log("You get this message immediately.");

})();


You get this message immediately.

Callback in Event Handlers

<button id="button1">Mouse over me!</button>

<script>

document.getElementById("button1").addEventListener("mouseover", function() {

console.log("Thanks for hovering over me!");

});

</script>


Thanks for hovering over me!

In Array Methods (filter, map, etc)

let numbers = [10, 12, 345, 63, 15];

let evenNumbers = numbers.filter(function(num) {

return num % 2 === 0;

});

console.log(evenNumbers);


[10, 12]

Arrow Function Expression

let nExp = (a, n) => a ** n;

console.log(nExp(5, 4));


625


Iterables in JavaScript



Key Concepts

Iterable: it is a sequence of elements that we can iterate over. In JavaScript, iterable must implement the ‘Symbol.iterator’ method.


Iterator Protocol: an object is an iterator when it implements a ‘next()’ method which returns an object with the properties:

value: next value in the iteration sequence.
done: A boolean to specify if the iteration is done.


Iterable Protocol: an object is iterable when it implements the ‘Symbol.iterator’ method.

Symbol.iterator: a built-in symbol that defines the default iterator for an object. In other words, an object that implements the ‘Symbol.iterator’ method becomes an iterator and gets the ‘next()’ method.

Built-in Iterable: JavaScript objects implementing collections such as ‘String’, ‘Array’, ‘TypedArray’, ‘Map’, and ‘Set’ are iterable because their prototype objects implement the ‘Symbol.iterator’ method.

Custom Iterable: new iterable objects can be defined by implementing the ‘Symbol.iterator’ method.

Custom Data Structure as Iterable: new data structures like stacks or queues can be created and made iterable by implementing the ‘Symbol.iterator’ method.

Generator: it is a specific type of function. The generator does not return a single value as a result like normal functions but multiple values over time. The generator can be used to implement the iterator and iterable protocol.

Iterable and Spread Syntax: a spread operator can be applied to an iterable to expand its elements.

Loop: the ‘for...of’ loop and ‘while’ loop can be used to iterate over iterators. However, only ‘for...of’ can used to loop through an iterable.


Note: it is possible to create a custom object that adheres to the protocol iterator but is not iterable. I.e, it can implement the ‘next()’ method but not the ‘Symbol.iterator’ method. This object is not iterable.

Usage Examples: Iterables

Iterable

Code Snippet

Code Output

String

let str = "Hi!";

let strIterator = str[Symbol.iterator]();

console.log(strIterator.next());

console.log(strIterator.next());

console.log(strIterator.next());

console.log(strIterator.next());

{value: 'H', done: false}

{value: 'i', done: false}

{value: '!', done: false}

{value: undefined, done: true}

Array

let numArray = [16, 27, 43];

let iterator = numArray[Symbol.iterator]();

console.log(iterator.next());

console.log(iterator.next());

console.log(iterator.next());

console.log(iterator.next());

{value: 16, done: false}

{value: 27, done: false}

{value: 43, done: false}

{value: undefined, done: true}

TypedArray

const uint8Array = new Uint8Array([10, 123, 45]);

let uint8Iter = uint8Array[Symbol.iterator]();

console.log(uint8Iter.next());

console.log(uint8Iter.next());

console.log(uint8Iter.next());

console.log(uint8Iter.next());

{value: 10, done: false}

{value: 123, done: false}

{value: 45, done: false}

{value: undefined, done: true}

Set

const fruits = new Set(["Orange", "Banana", "Strawberry"]);

let fruitIter = fruits[Symbol.iterator]();

console.log(fruitIter.next());

console.log(fruitIter.next());

console.log(fruitIter.next());

console.log(fruitIter.next());


{value: 'Orange', done: false}

{value: 'Banana', done: false}

{value: 'Strawberry', done: false}

{value: undefined, done: true}

Map

const fruitPrice = new Map([

  ["Orange", 3.25],

  ["Banana", 4.75],

["Strawberry", 6.5]

]);


let fruitPrIter = fruitPrice[Symbol.iterator]();

console.log(fruitPrIter.next());

console.log(fruitPrIter.next());

console.log(fruitPrIter.next());

console.log(fruitPrIter.next());


{value: Array(2), done: false}

{value: Array(2), done: false}

{value: Array(2), done: false}

{value: undefined, done: true}

Custom Data Structure (Stack)

class Stack {

constructor() {

this.items = [];

}

push(item) {

this.items.push(item);

}

pop() {

if (this.isEmpty()) {

return "Stack is empty";

}

return this.items.pop();

}

peek() {

if (this.isEmpty()) {

return "Stack is empty";

}

return this.items[this.items.length - 1];

}

isEmpty() {

return this.items.length === 0;

}

size() {

return this.items.length;

}

print() {

console.log(this.items.toString());

}

// Define the iterator method

[Symbol.iterator]() {

let index = this.items.length - 1;

let items = this.items;

return {

next() {

if (index >= 0) {

return { value: items[index--], done: false };

} else {

return { value: undefined, done: true };

}

}

};

}

}


// Create a new stack

let stack = new Stack();

stack.push(15);

stack.push(25);

stack.push(35);


let stackIter = stack[Symbol.iterator]();

console.log(stackIter.next());

console.log(stackIter.next());

console.log(stackIter.next());

console.log(stackIter.next());

{value: 35, done: false}

{value: 25, done: false}

{value: 15, done: false}

{value: undefined, done: true}

Generator

function* numGenerator() {

yield 100;

yield 200;

yield 300;

}


let numGen = numGenerator();

console.log(numGen.next());

console.log(numGen.next());

console.log(numGen.next());

console.log(numGen.next());

{value: 100, done: false}

{value: 200, done: false}

{value: 300, done: false}

{value: undefined, done: true}

Custom Iterable

let customIterable = {

data: [10, 15, 20],

[Symbol.iterator]() {

let index = 0;

let data = this.data;

return {

next() {

if (index < data.length) {

return { value: data[index++], done: false };

} else {

return { value: undefined, done: true };

}

}

};

}

};


let customIter = customIterable[Symbol.iterator]();

console.log(customIter.next());

console.log(customIter.next());

console.log(customIter.next());

console.log(customIter.next());

{value: 10, done: false}

{value: 15, done: false}

{value: 20, done: false}

{value: undefined, done: true}


Usage Examples: Action on Iterables

Action

Code Snippet

Code Output

Iterable with Spread Syntax

const spreadArray = [...customIterable];
console.log(spreadArray); 

[10, 15, 20]



Usage Examples: Loop through Iterables

Loop

Iterates over

Code Snippet

Code Output

for..of

String

const str = "Hi!";

for (const s of str) {

  console.log(s);

}

H

i

!


Array

const nums = [16, 27, 43];

for (const v of nums) {

  console.log(v);

}


16

27

43


TypedArray

const uint8Array = new Uint8Array([10, 123, 45]);

for (const v of uint8Array) {

  console.log(v);

}


10

123

45


Set

const fruits = new Set(["Orange", "Banana", "Strawberry"]);

for (const f of fruits) {

   console.log(f);

}


Orange

Banana

Strawberry


Map

const fruitPrice = new Map([

  ["Orange", 3.25],

  ["Banana", 4.75],

["Strawberry", 6.5]

]);

for (const f of fruitPrice) {

   console.log(f);

}


['Orange', 3.25]

['Banana', 4.75]

['Strawberry', 6.5]


Custom Data Structure (Stack)

for (let v of stack) {

console.log(v);

}


35

25

15


Generator

for (let v of numGen){

console.log(v);

}


100

200

300

while

Any Iterable (Built-in, Custom or Generator)

let iter = customIterable[Symbol.iterator]();

while (true) {

  const result = iter.next();

console.log('value:', result.value, 'done: ', result.done);

  if (result.done) break;

}

value: 10 done: false

value: 15 done: false

value: 20 done: false

value: undefined done: true



Usage Examples: Iterators

Iterator (not Iterable)

Code Snippet

Code

Custom Iterator

function digitsIteration() {

let digit = 0;

return {

next: function() {

digit += 1;

if (digit < 10) {

return { value: digit, done: false };

} else {

return { value: undefined, done: true };

}

}

};

}


const digit = digitsIteration();

while (true) {

  const result = digit.next();

console.log('value:', result.value, 'done: ', result.done);

  if (result.done) break;

}

value: 1 done: false

value: 2 done: false

value: 3 done: false

value: 4 done: false

value: 5 done: false

value: 6 done: false

value: 7 done: false

value: 8 done: false

value: 9 done: false

value: undefined done: true

Blog Posts

Enhancing Performance of Java-Web Applications

Applications built with a Java back-end, a relational database (such as Oracle or MySQL), and a JavaScript-based front-end form a common and...