1

I am trying to use draggable functionality of jQuery UI in LWC.

I am loading the jQuery Base Library 3.6.0, jQuery UI 1.12.4 (js & css)

below is my code

dragTest.html

<template>
   <template if:true={scriptsLoaded}>
      <div class="draggable-container"  lwc:dom="manual">
      </div>
      <div class="buttons">
         <lightning-button label="Toggle" name="toggle" variant="brand" onclick={handleClick}></lightning-button>
         <lightning-button label="Implement Draggable" name="implementDraggable" variant="brand" onclick={handleClick}></lightning-button>
         <lightning-button label="Add Draggable Component" name="addDraggableComponent" variant="brand" onclick={handleClick}></lightning-button>
      </div>
   </template>
</template>

dragTest.js

import jQuery_ui_1_12_1 from '@salesforce/resourceUrl/jquery_ui_1_12_1';
import jQuery_3_6_0 from '@salesforce/resourceUrl/jquery_3_6_0';
import {
    loadScript,
    loadStyle
} from 'lightning/platformResourceLoader';
import {
    LightningElement
} from 'lwc';

export default class DragTest extends LightningElement {
    scriptsLoaded = false;
    callOnInit = false;
    async connectedCallback() {
        let self = this;
        await self.loadScripts();
    }

    async renderedCallback() {
        let self = this;
        //self.addDraggable();

    }

    addDraggable() {
        let self = this;

        let draggableElementContainer = self.template.querySelector(".draggable-container");
        if (draggableElementContainer) {
            console.log("draggable element found");
            let draggableElement = document.createElement("div");
            draggableElement.className = "draggable ui-widget-content";
            draggableElementContainer.appendChild(draggableElement);
            let p = document.createElement("p");
            p.innerText = "Hello World";
            draggableElement.appendChild(p);
        }
        console.log("draggable element found 2");
    }

    async loadScripts() {
        let self = this;
        console.log("load script called");
        //self.scriptsLoaded = false;
        //await loadScript(self, jQuery_js)
        await loadScript(self, jQuery_3_6_0)
            //.then((resp) => resp.json())
            .then((respJSON) => {
                console.log("dt : resp jq : "); // + JSON.stringify(respJSON) 
            }).catch((error) => {
                console.log("dt - error jq : "); // + JSON.stringify(error)
            });
        await loadStyle(self, jQuery_ui_1_12_1 + '/jquery-ui.min.css')
            //.then((resp) => resp.json())
            .then((respJSON) => {
                console.log("dt : resp jq ui css :"); // + JSON.stringify(respJSON)
            }).catch((error) => {
                console.log("dt - error 1 jq ui css : " + JSON.stringify(error)); // + JSON.stringify(error)
            });
        await loadScript(self, jQuery_ui_1_12_1 + '/jquery-ui.min.js')
            //.then((resp) => resp.json())
            .then((respJSON) => {
                console.log("dt : resp jq ui js : " + JSON.stringify(respJSON)); // + JSON.stringify(respJSON)
            }).catch((error) => {
                console.log("dt - error jq ui js: "); // + JSON.stringify(error)
            });
        self.scriptsLoaded = true;
    }

    handleClick(event) {
        let self = this;
        let targetName = event.target.name;

        if (jQuery) {
            console.log("jquery is loaded");
        }
        if (jQuery.ui) {
            console.log("jQuery ui has loaded");
        }
        if (targetName === "toggle") {
            $(self.template.querySelector(".draggable-container")).toggle();
        } else if (targetName === "implementDraggable") {
            $(self.template.querySelector(".draggable")).draggable({
                //$(".draggable").draggable({
                addClasses: false,
                containment: ".draggable-container",
                start: function() {
                    console.log("start");
                },
                drag: function() {
                    console.log("drag");
                },
                end: function() {
                    console.log("end");
                }
            });
        } else if (targetName === "addDraggableComponent") {
            self.addDraggable();
        }
    }
}

dragTest.css

.draggable { 
        width: 150px; 
        height: 150px; 
        padding: 0.5em; 
        background-color: white;
}

.draggable-container {
        width: 100%;
        height: 500px;
        background-color: yellow;
}

Below screenshot shows how the UI looks. The hello world block should be draggable across the yellow area. But currently it is not draggable.

enter image description here

Thanks in advance for the help!

3
  • why do you need to use jquery UI ? you hardly need a library to make drag and drop
    – glls
    Jul 3 at 14:45
  • 1
    @glls this lol.
    – sfdcfox
    Jul 3 at 14:52
  • @sfdcfox hahahaha - nice one
    – glls
    Jul 3 at 15:16
2

For performance reasons, you should load all your scripts and styles at once, as below.

loadScripts() {
    Promise.all([
        loadScript(this, jQuery_3_6_0),
        loadStyle(this, jQuery_ui_1_12_1 + '/jquery-ui.min.css'),
        loadScript(this, jQuery_ui_1_12_1 + '/jquery-ui.min.js')
    ]).then((results) => {
        this.scriptsLoaded = true;
    }).catch(error => {
        console.log(error);
    })
}

Or:

async loadScripts() {
    try {
        await Promise.all([
            loadScript(this, jQuery_3_6_0),
            loadStyle(this, jQuery_ui_1_12_1 + '/jquery-ui.min.css'),
            loadScript(this, jQuery_ui_1_12_1 + '/jquery-ui.min.js')
        ]);
        this.scriptsLoaded = true;
    } catch (e) {
        console.log(e);
    }
}

By chaining them together, you're making the code load slower for literally no reason at all. jQuery and its various modules can load in any order, so there's no reason to specify an order and wait.


I'm not sure where you got let self = this; from (Aura?), but it's rarely necessary in LWC. That would be a good habit to get out of. You have no closures here, so you have no need to use the this-preserving self technique here. Also, arrow functions makes this trick obsolete, use them when you can (as demonstrated above).


You don't need to combine async/await with .then and .catch. Pick one (see above).


Your addDraggable method should be using jQuery:

addDraggable() {
    if(!this.scriptsLoaded) {
        alert('Please wait, still loading...');
        return;
    }
    $(this.template.querySelector(".draggable-container"))
    .append($('<div></div>')
        .addClass('ui-widget-content')
        .append($('<p></p>')
            .text('Hello World')
        )
        .draggable()
    );
}

document.createElement may cause problems attaching scripts and so on, as the element is sanitized. Only your imported code is allowed to make changes to the DOM, and only the elements you specify. I suspect this is the root problem in your code.

4
  • @sfdxfox, thank you for the detailed response :) I have updated the code, addDraggable method now uses jQuery to add the draggable element as you had suggested. But it is still not working. Is there anything else that I can try here? Also, I have a doubt related to promise.all. When we use promise.all is the sequence of loading scripts garunted? As, I understand we have to load the jQuery library before loading the jQuery UI. Jul 3 at 19:47
  • @AtharwaSamant jQuery modules can be loaded in any order. Have you tried any debugging? I'll see if I can make a working version when I get home.
    – sfdcfox
    Jul 3 at 21:21
  • @AtharwaSamant it works if you keep your code for loading the scripts but use sfdxfox's code addDraggable. The jquery UI load fails if jquery is not loaded.
    – Rajiv
    Jul 3 at 22:59
  • @Rajiv, I have already placed the load script in connectedCallback. And have updated my code as per sfdcfox's suggestion. Jul 4 at 9:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.