Blog Stats
  • Posts - 24
  • Articles - 0
  • Comments - 15
  • Trackbacks - 0

 

Sunday, August 7, 2011

Speed up drag and drop jQuery interface in IE


Javascript is a very powerful tool to enhance the user experience. However, poorly optimized javascript can have a very negative effect due to increased page load time. This becomes particularly apparent as the number of elements on a page increases thanks to controls bound to increasingly large data sets.

 

 

For those that are new to jQuery, assuming we have two divs (div_drag and div_drop), we can add drag and drop functionality by registering listeners during our form load:
 

$("#div_drag").draggable();
$("#div_drop").droppable({
   accept: "#draggable"
   });

Now instead let's assume we have two lists - active & inactive. We want to be able to drag items from left to right and back again. With a maximum height and overflow, we will have scrollbars on our list for when the length of the items extends beyond 400px. 

 <table>
       <tr>
         <td>
           <div class="DrgTgtList" >
             <div id="bktInactivate" class="DrgTgtHeader">
               <h3>Inactive Items</h3>
               (Drop here to remove an item)
             </div>
             <ul id="InactiveItems" class="InactiveList"
            style="list-style-type:none; height: 400px; overflow:auto;"
             onscroll="SetDraggable(this)">
               <li class="DrgInactive" id="1">ABC</li>
               <li class="DrgInactive" id="2">XYZ</li>
               <li class="DrgInactive" id="3">123</li>
               <li class="DrgInactive" id="4">456</li>
             </ul>
           </div>
         </td>
         <td>
           <div style="DrgTgtList" >
             <div id="bktActivate" class="DrgTgtHeader" >
               <h3>Active Items</h3>
               (Drop here to add a new item)
             </div>
             <ul id="ActiveItems" class="ActiveList"
              style="list-style-type:none; height: 400px; overflow:auto"
              onscroll="SetDraggable(this)">
               <li class="DrgActive" id="7">A</li>
               <li class="DrgActive" id="8">B</li>
               <li class="DrgActive" id="9">C</li>
               <li class="DrgActive" id="10">D</li>
             </ul>
           </div>
         </td>
       </tr>
     </table>

If we wanted to make the divs draggable, all we would need is to register the divs like below:

<script language="javascript" type="text/javascript">
 
   $(document).ready(function(){
 
            $("#bktInactivate").droppable({
                 accept: ".DrgActive",   
                hoverClass: "drop-state-hover",
                 drop: function(event, ui) {
                    alert('deactivate!')
                 });
                    
            $("#bktActivate").droppable({
                 accept: ".DrgInactive",
                 hoverClass: "drop-state-hover",
                 drop: function(event, ui) {
                     alert('activate!');
                     }
                 });
               
            $("#bktActivate li").draggable({ 
                         helper: "clone"
                     });

            $("#bktAInactivate li").draggable({ 
                         helper: "clone"
                     });

    }
 
              
</script>
 

This will work perfectly fine for a relatively small number of items in our list.  However, in the above code, every div that is to be draggable or droppable is registered at the same time (the document read event).  Once the number of draggable or droppable items in the list gets too high (upwards of 100) the performance of attaching the event listeners becomes very noticable in IE based browsers  (several seconds to several minutes).  To combat this slow, unwieldy page load, we decided to defer the event registration as long as possible.

In the below sample, we only want to register the items that are currently visible.  As we scroll up and down on the containing lists, we will then register the draggable events to the newly visible items.  jQuery will maintain which elements have already been registered more efficiently than we will be able to easily do so, so the responsibility of double registration is left to jQuery.

When the page loads, we will register our two droppable divs (the headers of each list) and initialize the currently visible draggable divs as draggable.  Only when the scroll bar moves other divs into visibility will they be registered.  We can now have a virtually unlimited number of draggable divs in our panel and feel no performance penalty on form load.

<script language="javascript" type="text/javascript">

   $(document).ready(function(){ 

            $("#bktInactivate").droppable({
                accept: ".DrgActive",   
                hoverClass: "drop-state-hover",
                drop: function(event, ui) {
                   alert('deactivate!')
                });
                   
            $("#bktActivate").droppable({
                accept: ".DrgInactive",
                hoverClass: "drop-state-hover",
                drop: function(event, ui) {
                    alert('activate!');
                    }
                });
               
            SetDraggable(document.getElementById('ActiveItems'));
            SetDraggable(document.getElementById('InactiveItems'));
   }
 

    function SetDraggable(draggableUL) {
            var scrollTop = draggableUL.scrollTop;
           
            // Activate li items currently visible
            for (var index = 0; index < draggableUL.childNodes.length; index++) {
                var child = draggableUL.childNodes[index];
                if (child.offsetTop < scrollTop) // skip down until we are at current level
                    continue;
                if (child.offsetTop > scrollTop + 400) // stop after we pass visible level
                    break;
                    
               $("#" + child.id).draggable({ 
                     helper: "clone"
                });
            }
        }
             
</script>

 

 

Copyright © jkrebsbach