Skip to content

Super small JavaScript sort library for sortable lists, grid, and more

License

Notifications You must be signed in to change notification settings

rokobuljan/jsort

Repository files navigation

JSort logo

JSort

Small yet powerful drag and drop sortable library with touch support, smooth animations, for a great UX.

NPM   JSort on GitHub   JSort GitHub Stars   Full-JS | JSort Homepage
NPM: @rbuljan/jsort
Demo & examples: JSort — Homepage

Features

  • Multiple items select (coming soon)
  • No external dependencies
  • Smooth animations for a better UE/UX
  • Touch & Mouse support (with Pointer events)
  • Dual customization options (via instance argument or data-jsort HTML attribute)
  • Grab handler
  • Linked groups
  • Nested groups
  • Swap group items
  • Scroll-intent on touch devices
  • Dynamic items support (delegated events)
  • Scroll parent on near-edge drag-move
  • Respects selection on inner action elements
  • In-place animated sorting beta

Installation

npm install @rbuljan/jsort

Syntax

const JSortInstance = new JSort(HTMLElement, { /* Options */ });

Usage example

<ul id="list">
    <li>1</li>
    <li>2</li>
    <li>
        <div class="jsort-handler"></div>
        3 Grab me by the handler
    </li>
    <li>4</li>
</ul>

<script type="module">
    import JSort from '@rbuljan/jsort';
    const jsortList = new JSort(document.querySelector("#list"), {
        duration: 360,
        // Other Options here
        onGrab(data) {
            console.log(this, data);
        }
        onMove(data) {
            console.log(this, data);
        }
        onDrop(data) {
            console.log(this, data);
        }
    });
</script>

Tip:
Works with any parent Tag

Options

JSort(HTMLElement, options)

The second parameter accepts an options object to customize the sorting behavior and appearance.

Option Type Default Description
Event Callbacks
onBeforeGrab(data) function Called before item grab
onGrab(data) function Called on item grab
onMove(data) function Called on item move
onBeforeDrop(data) function Called before itme drop
onDrop(data) function Called on drop
onAnimationEnd() function Called on drop animation end
Behavior
group string "" Group sortable parents
swap boolean false Swap elements (instead of reordering)
parentDrop boolean true Allow drop onto parent container
moveThreshold number 0 Px before it's considered a pointer move
grabTimeout number 140 Grab delay in ms (touch devices only)
Selectors
selectorParent string ".jsort" Parent container
selectorItems string "*" Sortable items (parent's immediate children
selectorItemsIgnore string ".jsort-ignore" Ignored sortable's immediate children
selectorIgnoreTarget string "" Prevent grab on item descendant selectors
selectorIgnoreFields string Read Tip below Prevent grab on item descendant action elements
selectorHandler string ".jsort-handler" Drag handle selector
CSS Classes
classGrab string "is-jsort-grab" Grabbed item
classGhost string "is-jsort-ghost" Ghost element
classTarget string "is-jsort-target" Hovered target element
classAnimated string "is-jsort-animated" All affected and animated items
classAnimatedDrop string "is-jsort-animated-drop" Grabbed item during drop animation
classInvalid string "is-jsort-invalid" Ghost element over invalid drop zones
classTouch string "is-jsort-touch" Grabbed item if Event is touchstart (touch)
Animation
duration number 420 Sort animation duration in ms
easing string "cubic-bezier()" Animation easing function
Ghost Styles
scale number 1.1 Ghost element scale
opacity number 0.8 Ghost element opacity (0-1)
zIndex number 2147483647 Ghost element z-index
Scroll
scrollThreshold number 8 Px before considering auto-scroll
edgeThreshold number 50 Autoscroll distance to edge
scrollSpeed number 10 Auto-scroll speed in pixels per step

Tip:
By defafult, the selectorIgnoreFields provides a good sane selector in order to prevent grabbing an item if the direct target is an action element, like: Anchor, Button, Input, Contenteditable, etc:

/* DO NOT GRAB ITEM IF POINTER TARGET IS AN ACTION FIELD: */
:is(input, select, textarea, button, label, [contenteditable=""], [contenteditable="true"], [tabindex]:not([tabindex^="-"]), a[href]:not(a[href=""]), area[href]):not(:disabled)

Tip:
Internally, the Options selectors will be concatenated like selectorParent > selectorItems:not( selectorItemsIgnore ) into i.e: .jsort > *:not(.jsort-ignore) (or like: .jsort > * if you pass an empty string to the selectorItemsIgnore). The > immediate child selector will always be injected by default.

Options example

const sortable = new JSort(document.querySelector("#mySortable"), {
  selectorItems: ".item",
  group: "shared-group",
  swap: true,
  duration: 300,
  scale: 1.05,
  onDrop: (data) => {
    console.log(this, data); // JSort, data{}
  },
  // More options here...
});

Tip:
Options (that are not callbacks) can be assigned directly in your HTML markup using the data-jsort attribute in this format option: value; option: value

<div id="mySortable" data-jsort="
    group: a;
    selectorItems: .item;
    selectorHandler: .my-handler;
    swap: true;
    duration: 300;
    easing: ease-out;
    zIndex: 999;
    parentDrop: false;
  ">
  <div class="item"><div class="my-handler"></div>Item 1</div>
  <div class="item"><div class="my-handler"></div>Item 2</div>
</div>

just remember that data-jsort options will override any instance options you passed to the constructor (analogue to stylesheet vs. style="" attribute). Having that in consideration, you can define some JSort global, shared options from JavaScript, and customize specific elements using the data-jsort="" attribute, if needed.

Custom validation

To manually abort some actions depending on a condition, you can use the onBeforeGrab() and onBeforeDrop() callbacks

new JSort(myListElement, {
    onBeforeGrab(data) {
        if (data.indexGrab === 0) {
            console.error("Cannot grab first item");
            return false;
        }
        if (data.elGrab.closest(".no-grab")) {
            console.error("Grabbed an invalid item");
            return false;
        }
    },
    onGrab(data) {
        console.log(`Grabbed index ${data.indexGrab}`);
    },
    onBeforeDrop(data) {
        if (data.indexDrop === 0) {
            console.error("Cannot drop into first item");
            return false;
        }
        if (data.elDrop.closest(".no-drop")) {
            console.error("Cannot drop here");
            return false;
        }
    },
    onDrop(data) {
        console.log(`Dropped index ${data.indexGrab} into ${data.indexDrop}`, this);
    }
})

If you returned false from one of the callbacks, the respective onGrab or onDrop actions will not be called.

Methods

Method Description
init({/*options*/}) Re-initialize the instance with updated Options
destroy() Destroys the instance and removes the event listeners
insert(Element, Target) Insert item at Target (parent or item)
sort(fn) (Beta) In-place sort parent items (with animation)

Properties

JSort

Property Type Default Description
indexGrab number -1 The index of the grabbed item
indexDrop number -1 The new index on drop
elGrab HTMLElement null The grabbed item
elGrabParent HTMLElement null The grabbed item's parent
elGhost HTMLElement null Element that follows the pointer
elTarget HTMLElement null The hovered target (item or parent)
elDrop HTMLElement null Same as elTarget but on drop
elDropParent HTMLElement null The drop (target) item's parent on drop
affectedElements HTMLElement[] [] Array of drop-affected (animated) elements

Static Properties

JSort

Property Type Description
version string Current library version

Linked Groups

JSort allows to drag & drop into a linked group by defining a group property

<div class="shared">
    <div>A 1</div>
    <div>A 2</div>
    <div>A 3</div>
</div>
<div class="shared">
    <div>B 1</div>
    <div>B 2</div>
</div>

<script type="module">
    import JSort from '@rbuljan/jsort';
    document.querySelectorAll(".shared").forEach((el) => {
        new JSort(el, { 
            group: "shared"
        });
    });
</script>

or by adding the data-jsort attribute in HTML:

View code
<div class="shared" data-jsort="group:shared">
    <div>A 1</div>
    <div>A 2</div>
    <div>A 3</div>
</div>
<div class="shared" data-jsort="group:shared">
    <div>B 1</div>
    <div>B 2</div>
</div>

<script type="module">
    import JSort from '@rbuljan/jsort';
    document.querySelectorAll(".shared").forEach((el) => new JSort(el));
</script>

Swap items

By default JSort reorders the items on drop. If instead you want to swap, you can set the swap option to true to your element or grouped elements:

<div class="players">
    <div>Mark</div>
    <div>Jack</div>
    <div>Theo</div>
</div>
<div class="players">
    <div>Luke</div>
    <div>John</div>
    <div>Roko</div>
</div>

<script type="module">
    import JSort from '@rbuljan/jsort';
    document.querySelectorAll(".players").forEach((el) => {
        new JSort(el, {
            group: "team",
            swap: true,
        })
    });
</script>

PS: Instead of using the constructor options, you can use the data-jsort:

View code
<div class="players" data-jsort="group:team; swap:true">
    <div>Mark</div>
    <div>Jack</div>
    <div>Theo</div>
</div>
<div class="players" data-jsort="group:team; swap:true">
    <div>Luke</div>
    <div>John</div>
    <div>Roko</div>
</div>

<script type="module">
    import JSort from '@rbuljan/jsort';
    document.querySelectorAll(".players").forEach((el) => new JSort(el));
</script>

Find more examples on JSort Homepage.

Styling

Is there a minimal CSS styling I might want to use to get started?

Yes! This is the minimal CSS styling you might want to use to get the best from JSort defaults:

/* JSort — Minimal suggested styles */

.is-jsort-active.is-jsort-touch {
    outline: 2px solid currentColor; /* Useful hint on touch devices */
}

.is-jsort-grab {
    opacity: 0; /* Dim the original grabbed element */
}

.is-jsort-target {
    z-index: 1;
    outline: 2px dashed currentColor;
}

.is-jsort-invalid {
    outline: 2px solid red;
}

The above was not hardcoded into the library since everyone wants to style their UI differently, i.e: set the grabbed element's opacity at a different value, change the targeted element styles, etc.

For custom styling JSort provides several classes you could use in your CSS to further style your UI: (See: Options → CSS Classes)


See the JSort Homepage for examples and inspiration.

Motivation

I needed a sortable library. After reviewing some popular ones like SortableJS, Dragula, jQueryUI/Sortable, and others, I found that they do not work the way I want, fast, smoothly, touch/mobile friendly. An important factor was also to minimize motion, only when necessary (on drop) and to animate all affected elements naturally and smoothly to support visual cognitive feedback and make the experience overall more natural and pleasant.
JSort was born to fill this necessity.


Licence: MIT

About

Super small JavaScript sort library for sortable lists, grid, and more

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published