Friday, October 19, 2012

Drag & Drop & resize using Ext JS

Preface
If HTML is the structure of a website and CSS its facade which give the look and feel, JavaScript is a vibrant life of it. Incorporating JavaScript is a daunting task and brings whole lots of new difficulties such as cross browser compatibility. Using a framework can mitigate the challenges, provide a mechanism that handles this issue seamlessly and make it much easier to develop and maintain UI reach websites.

Scenario

We want to have the capability of drag and drop in our web page. Suppose we are a photo printing company. A user have uploaded few photos to our website. We show the thumbnail of the uploaded photos on the left side of our page. User can drag and drop thumbnails of the photos they want to have printed to the selectedPanel on the right side of the page. Then, they can resize the each to a bigger size. We distinguish between resize and drag & drop by single and double clicking of the mouse over the desired image thumbnail (div).

Topics covered in this tutorial:
  • Drag & drop
  • Resizer
  • Overriding
  • Dynamically adding/removing CSS classes
  • Iterating an array
  • Distinguishing between single click & double click
  • Query DOM nodes by selectors
Requirement
 Ext JS version 4.1 (This example is using 4.1. You can use other versions but some classes might have changed)

I'm not going to cover the basics of how to incorporate Ext JS. You can have a look at getting started guide and learn the basics and setup.

Consider the snippet below as our photo selection page. All the photos are in pics div and user can drag and drop them to the selectedPics div.
 <html>  
 <head>  
 <link href="ext-all.css" rel="stylesheet" type="text/css">  
 <style type="text/css">  
 .pic {  
   width: 55px;  
   height: 50px;  
   border: 1px solid;  
 }  
 .panel {  
   position: absolute;  
   top: 100px;  
   left: 100px;  
   width: 100px;  
   height: 500px;  
   border: 1px solid;  
 }  
 .selectedPanel {  
   position: absolute;  
   top: 100px;  
   left: 400px;  
   width: 500px;  
   height: 500px;  
   border: 1px solid;  
 }  
 .dropZoneOver {  
   background-color: #99FF99;  
 }  
 .dragStyle {  
   outline: red solid thin;  
 }  
 </style>  
 </head>  
 <body>  
   <div id="pics" class="panel">  
     <div id="pic1" class="pic">Pic1</div>  
     <div id="pic2" class="pic">Pic2</div>  
   </div>  
   <div id="selectedPics" class="selectedPanel"></div>  
 </body>  
 </html>  

Now lets discuss the javascript little bit. You can take a look at the comments in our snippet below for some insight.  In the onReady we are registering event listeners for click and dblClick on all the pic divs (each photo is wrapped in a div). We are differentiating between single click and double click by setting a flag and using a timeout. This distinction is recognized in the fireClickEvent. Before applying either drag and drop or resize  to the thumbnails (divs). We use destroy() method to clean and remove all listeners and destroy the passed object before creating new type of object. By doing these we make sure user's previous operation (either drag and drop or resize) is now canceled, all classes are gone and everything is clean and ready for new operation. 
Resize method gives the resize capability to the received id. Move method give drag and drop capability to all the thumbnails in the pics div. Move method enable drag and drop to the all thumbnails (html div) in the pics div.

For resizing we are using Ext.resizer.Resizer.
For drag and drop we are using Ext.dd.DDProxy.

The code below goes to the head section of our HTML page.

 <script type="text/javascript" src="ext-all-dev.js"></script>  
 <script type="text/javascript">  
   var dragProducts = new Array();  
   var dropTarget;  
   var resizer;  
   var dblClickFlag = false;    
   var clickFlag = true;
   //onReady ensures that all the scripts are loaded then it executes its function  
   Ext.onReady(function(){  
     dragIndex = 0;
     //Add event handler to the element  
     Ext.get('pic1').on('click',function(evt, el, o){  
       startSingleClick(el.id);  
     });  
     Ext.get('pic1').on('dblclick',function(evt, el, o){  
       startDblClick(el.id);  
     });  
     Ext.get('pic2').on('click',function(evt, el, o){  
       startSingleClick(el.id);  
     });  
     Ext.get('pic2').on('dblclick',function(evt, el, o){  
       startDblClick(el.id);  
     });  
   });  
   function startSingleClick(cmpId){  
     clickFlag = true;  
     setTimeout(function() { fireClickEvent(cmpId) }, 500);  
   }  
   function startDblClick(){  
     dblClickFlag = true;  
   }     
   function fireClickEvent(cmpId){  
     if (clickFlag){  
       clickFlag = false;  
       if (dblClickFlag){  
         startResize(cmpId);  
       } else{  
         startMove(cmpId);  
       }  
     }  
     dblClickFlag = false;   
   }      
   function destroy(){  
     if(resizer){  
       Ext.destroy(resizer);  
       resizer = null;  
     }  
     Ext.destroy(dropTarget);    
     Ext.Array.each(dragProducts, function(dragProduct) {  
         Ext.destroy(dragProduct);    
     });  
     dragProducts = new Array();  
   }  
   function resize(cmpId){  
     destroy();  
     var elem = Ext.get(cmpId);  
     var elWidth = elem.getWidth();  
     var elHeight = elem.getHeight();  
     resizer = Ext.create('Ext.resizer.Resizer', {  
       width: elWidth,  
       height: elHeight,  
       el: elem,  
       handles: 'all',  
       pinned: true,  
       dynamic: true    
     });  
   }  
   function move(){  
     var overrides = {  
       onDragEnter : function(evtObj, targetElId) {  
         var targetEl = Ext.get(targetElId);  
         targetEl.addCls('dropZoneOver');  
       },  
       onDragOut : function(evtObj, targetElId) {  
         var targetEl = Ext.get(targetElId);    
         targetEl.removeCls('dropZoneOver');  
       },  
       onDragDrop : function(evtObj, targetElId) {  
         var dragEl = Ext.get(this.getEl());  
         dragEl.removeCls('dragStyle');  
         var dropEl = Ext.get(targetElId);  
          if (dragEl.dom.parentNode.id != targetElId) {  
           dropEl.appendChild(dragEl);  
           this.onDragOut(evtObj, targetElId);  
         }  
       },  
       b4StartDrag : function(x, y) {  
         //Shows a frame while dragging the item  
         this.showFrame(x, y);  
         var dragEl = Ext.get(this.getEl());  
         //adds red border to the dragging item   
         dragEl.addCls('dragStyle');  
       },  
       onInvalidDrop : function() {  
         //indicates an invalid drop. Only related dragDrop objects are valid targets  
         this.invalidDrop = true;  
       },  
       endDrag : function() {  
         //The delete operator removes a property from an object.  
         delete this.invalidDrop;  
       }  
     }  
     destroy();  
     //Query DOM tree for all the elements containing the class "pic"   
     var pics = Ext.query("*[class*=pic]");  
     //Iterate over an array  
     Ext.Array.each(pics, function(pic){  
       dragProducts[dragIndex] = new Ext.dd.DDProxy(pic, 'pic', {  
         isTarget : false  
       });  
       //Apply the overrided functions to the DDProxy class  
       Ext.apply(dragProducts[dragIndex], overrides);  
       dragIndex++;  
     });  
     var sp = Ext.get('selectedPics');  
     //Create valid drop target  
     dropTarget = new Ext.dd.DDTarget(sp, 'pic');  
     }  
   function startResize(cmpId){  
     resize(cmpId);  
   }  
   function startMove(){  
     move();  
   }            
 </script>  

For the details of each class and its properties used in this tutorial please look at  Ext JS documentation. They have a well documented API.

No comments :

Post a Comment