[SalesForce] Enabling jQuery Draggable for a Lightning Component

I am struggling with how to get my component to render a list item that leverages a jQuery library. I have found that when I troubleshoot in the JAVA script console, I can get the function to work properly, however, other than that it will not work. I have seen several posts about render and rerender, but nothing seems to fit what I am attempting to do in this instance.

To explain:

I have my main component passing a variable through an event to a component within the application. Once the event fires, I am running a query and presenting a list of draggable items. The styling for the items render from the library, however the draggable ability is not there without the use of the troubleshooting. I have verified that the data is being passed correctly as well as the fact that all libraries are loading from static resources

Here is my component:

<aura:component controller="Evolve_lgt_Controller">
<aura:attribute name="userstory" type="object"/>
<aura:attribute name="sprint" type="object"/>
<aura:handler event="c:RefreshUserStories" action="{!c.list_Story_sprint}"/>
<style type="text/css">
li.draggable-story { width: 75px; height: 75px; padding: 0.5em; float: left; margin: 5px 5px 5px 5px; border-style: solid; text-align: center; list-style: none;}
#droppable { width: 200px; height: 400px; padding: 0.5em; float: left; margin: 10px; list-style: none; border-style: solid;}
</style>
<div class="userstory">
    <ul id="draggable">
        <aura:iteration items="{!v.userstory}" var="us">
            <li class="draggable-story ui-state-default">
                <p><ui:outputText value="{!us.Name}"/></p>
            </li>
        </aura:iteration>
    </ul>   
</div>

And here is my controller:

({
list_Story_sprint: function(component, event, helper) {
    var story = event.getParam("userstory");
    var sprint = event.getParam("sprint");
    helper.getStories(component, story);
    helper.getSprints(component, sprint);
    //$(document).ready(function() { 
        console.log($( "li.draggable-story" ).length);
        $( "li.draggable-story" ).draggable();
        $( "#droppable" ).droppable({
            drop: function( event, ui ) {
                $( this )
                .addClass( "ui-state-highlight" )
                .find( "p" );
            }
        });
    //});
},
})

Any assistance is appreciated.

Here is the App:

<aura:application >
<c:load filesInSeries="
/resource/bootstrap/bootstrap-3.3.5-dist/jquery-1.11.3.min.js,
/resource/bootstrap/bootstrap-3.3.5-dist/jquery-ui.min.js, 
/resource/bootstrap/bootstrap-3.3.5-dist/jquery-ui.min.css,
/resource/bootstrap/bootstrap-3.3.5-dist/js/bootstrap.min.js,
/resource/bootstrap/bootstrap-3.3.5-dist/css/bootstrap.min.css" />    
<c:ProjectViewer/>

Here is the loader:

Loader js:

({

init: function(component, event, helper) {

//Modified version of https://github.com/malko/l.js to support resource files without JS or CSS extensions.
!(function(t,e){var r=function(e){(t.execScript||function(e){t["eval"].call(t,e)})(e)},i=function(t,e){return t instanceof(e||Array)},s=document,n="getElementsByTagName",a="length",c="readyState",l="onreadystatechange",u=s[n]("script"),o=u[u[a]-1],f=o.innerHTML.replace(/^\s+|\s+$/g,"");if(!t.ljs){var h=o.src.match(/checkLoaded/)?1:0,d=s[n]("head")[0]||s.documentElement,p=function(t){var e={};e.u=t.replace(/#(=)?([^#]*)?/g,function(t,r,i){e[r?"f":"i"]=i;return""});return e},v=function(t,e,r){var i=s.createElement(t),n;if(r){if(i[c]){i[l]=function(){if(i[c]==="loaded"||i[c]==="complete"){i[l]=null;r()}}}else{i.onload=r}}for(n in e){e[n]&&(i[n]=e[n])}d.appendChild(i)},m=function(t,e){if(this.aliases&&this.aliases[t]){var r=this.aliases[t].slice(0);i(r)||(r=[r]);e&&r.push(e);return this.load.apply(this,r)}if(i(t)){for(var s=t[a];s--;){this.load(t[s])}e&&t.push(e);return this.load.apply(this,t)}if(t.match(/\.js\b/)||t.match(/\.sfjs\b/)){t=t.replace(".sfjs","");return this.loadjs(t,e)}else if(t.match(/\.css\b/)||t.match(/\.sfcss\b/)){t=t.replace(".sfcss","");return this.loadcss(t,e)}else{return this.loadjs(t,e)}},y={},g={aliases:{},loadjs:function(t,r){var i=p(t);t=i.u;if(y[t]===true){r&&r();return this}else if(y[t]!==e){if(r){y[t]=function(t,e){return function(){t&&t();e&&e()}}(y[t],r)}return this}y[t]=function(e){return function(){y[t]=true;e&&e()}}(r);r=function(){y[t]()};v("script",{type:"text/javascript",src:t,id:i.i,onerror:function(t){if(i.f){var e=t.currentTarget;e.parentNode.removeChild(e);v("script",{type:"text/javascript",src:i.f,id:i.i},r)}}},r);return this},loadcss:function(t,e){var r=p(t);t=r.u;y[t]||v("link",{type:"text/css",rel:"stylesheet",href:t,id:r.i});y[t]=true;e&&e();return this},load:function(){var t=arguments,r=t[a];if(r===1&&i(t[0],Function)){t[0]();return this}m.call(this,t[0],r<=1?e:function(){g.load.apply(g,[].slice.call(t,1))});return this},addAliases:function(t){for(var e in t){this.aliases[e]=i(t[e])?t[e].slice(0):t[e]}return this}};if(h){var j,b,x,A;for(j=0,b=u[a];j<b;j++){(A=u[j].getAttribute("src"))&&(y[A.replace(/#.*$/,"")]=true)}x=s[n]("link");for(j=0,b=x[a];j<b;j++){(x[j].rel==="stylesheet"||x[j].type==="text/css")&&(y[x[j].getAttribute("href").replace(/#.*$/,"")]=true)}}t.ljs=g}f&&r(f)})(window);

var loadInSeries = function(filesInSeries, cb) {
  filesInSeries.push(cb);
  ljs.load.apply(ljs, filesInSeries)
}

var loadInParallel = function(filesInParallel, cb) {
  ljs.load(filesInParallel, cb);
}

var finalCB = function() {
  $A.get("e.jam:staticResourcesLoaded").fire();
}

var filesInSeries = component.get("v.filesInSeries");
var filesInParallel = component.get("v.filesInParallel");

if (filesInParallel.length > 0) {
  loadInParallel(filesInParallel, function() {
    if (filesInSeries.length > 0) {
      loadInSeries(filesInSeries, finalCB);
    } else {
      finalCB();
    }
  });
} else if (filesInSeries.length > 0) {
  loadInSeries(filesInSeries, finalCB);
}

}

})

Evt to fire load:

<aura:event type="APPLICATION" description="Event template"/>

Main cmp:

<aura:component controller="Evolve_lgt_Controller">
<aura:attribute name="project" type="Projects__c" default="{'sobjectType':'Projects__c'}"/>
<aura:attribute name="projects" type="Projects__c[]"/>
<aura:attribute name="userstory" type="User_Story__c[]"/>
<aura:registerEvent name="refreshUserStoriesEvent" type="c:RefreshUserStories" />    
<aura:handler name="init" value="{!this}" action="{!c.doInit}" />
<style>
p.project {
    border-style: inset;
    border-width: 10px;
    border-color: DeepSkyBlue;
    height: 100%;
    width: 20%;
    float: left;
    }
p.userstory_sprint {
    height: 100%;
    width: 80%;
    float: right;
    }
div.project {
    width: 100%;
    height: 100%;
    background-color: #C9C9C9;
    float: left;
    }
div.userstory_sprint {
    width: 100%;
    height: 100%;
    float: right;
}
</style>

<p class="project">
    <div class="project">
        <h3>
        Select Project:
        </h3>
        <div class="btn-group" align="left">
            <select onchange="{!c.change}">
                <aura:iteration items="{!v.projects}" var="p">
                    <option>{!p.Name}</option>
                </aura:iteration>
            </select>
        </div>
        <div>
            <span class="title">
                <h4><b>Project Information</b></h4>
            </span>
            Project Name: {!v.project.Name}<br></br>
            Project Owner: {!v.project.CRMDOC2014__Client_Project_Owner__c}<br></br>
            Consultant: {!v.project.CRMDOC2014__Consulting_Project_Owner__c}<br></br>
            Days Open: {!v.project.CRMDOC2014__Days_Open__c}<br></br>
            Stakeholder: {!v.project.CRMDOC2014__Key_Stakeholder__c}<br></br>
        </div>
    </div>
</p>
<p class="userstory_sprint">
    <div class="userstory_sprint">
        <c:UserStories_Sprints />
    </div>
</p>

Main cmp js controller

({
doInit : function(component, event, helper) {
    helper.getProjectInformationFN(component,'Test Project');
    helper.getProjectNameFN(component);
},
change : function(component, event, helper) {
    // get the value of the select option
    selectedName = event.target.value;
    console.log(selectedName);
    helper.getProjectInformationFN(component, selectedName);
    var evt = $A.get("e.c:RefreshUserStories");
    evt.setParams({
      "userstory" : selectedName,
      "sprint" : selectedName
    });
    evt.fire();
},

})

Best Answer

The list_Story_sprint event (and javascript function) is being called before the component is rendered. So, when you call $( "li.draggable-story" ).draggable(); there aren't any <li>s to match and become activated.

You need to add a UserStories_SprintsRenderer.js with an afterRender() function that makes your jQuery calls. Your afterRender() function will take a component as it's argument. You'll need to call superAfterRender in the after renderer, and then call the jQuery.

Remember: You should never modify the DOM outside a renderer.