Drag and Drop using Sencha Touch


senchadd

Introduction

Drag & Drop provides an ability to move an object from one location to another using tracking device or mouse. Drag & Drop feature helps users and makes it easier for them to use the application, hence attracts more people.

This article describes how we can implement drag and drop in Sencha Touch and also helps us understand the Drag & Drop events in Sencha Touch.

Problem statement

To see how we can implement Drag & Drop in Sencha Touch. We would use the following use case for the discussion and demonstration:

Create one circle and two rectangle and user should be able to drag circle to rectangles.

Pre-requisites

  • Working knowledge of Sencha Touch
  • Sencha Touch 2.1.1

Details

Step 1: Create an Sencha Touch app, for how to do, you can refer the below link

Create, Build & Package Sencha Touch project using Sencha Cmd

Step 2: To meet our use case, we need container in rectangle and circle shape which can be
done with following code

Ext.define('DragNDrop.view.Main', {
 extend: 'Ext.tab.Panel',
 xtype: 'main',
 requires: [
 'Ext.TitleBar',
 'Ext.Video'
 ],
 config: {
 tabBarPosition: 'bottom',
 items: [
 {
 title: 'Drag & Drop',
 iconCls: 'home',
 styleHtmlContent: true,
 scrollable: true,
 items: [{
 docked: 'top',
 xtype: 'titlebar',
 title: 'Drag and Drop in sencha touch'
 },
 {
 xtype: 'container',
 layout:{
 type: 'hbox',
 align: 'center',
 pack: 'center'
 },
 items:{
 xtype:'container',
 cls:'circlecls'

 }
 },
 {
 layout:'hbox',
 items:[
 {
 xtype:'container',
 cls:'rectangle1cls',
 flex:1
 },
 {
 xtype:'spacer'
 },
 {
 xtype:'container',
 cls:'rectangle2cls',
 flex:1
 }
 ],
 },

 ],
 }
 ]
 }
});

Following is the css code used in above code

.circlecls {
background-color: #c06;
height: 100px;
width: 100px;
display:block;
-moz-border-radius:75px;
-webkit-border-radius: 75px;
border-radius: 75px;
}
.rectangle1cls {
display:block;
width:225px;
height:150px;
background-color:#344567;
-moz-border-radius:20px;
-webkit-border-radius: 20px;
border-radius: 20px;
}
.rectangle2cls {
display:block;
width:225px;
height:150px;
background-color:#347C17;
-moz-border-radius:20px;
-webkit-border-radius: 20px;
border-radius: 20px;
text-align: center;
}

dd1

Step 3: Now allow circle to drag, to do this use below code i.e.  draggable: true 

{
 id: 'draggsCnt',
 xtype: 'container',
 layout: {
 type: 'hbox',
 align: 'center',
 pack: 'center'
 },items:{
 xtype:'container',
 cls:'circlecls',
 draggable: true

 }
 }

Step 4: Override the Ext.util.Droppable as below

Ext.define('Ext.ux.util.Droppable', {
 mixins: {
 observable: 'Ext.mixin.Observable'
 },

 requires: [
 'Ext.util.Region'
 ],

 /**
 * @event dropactivate
 * @param {Ext.ux.util.Droppable} this
 * @param {Ext.util.Draggable} draggable
 * @param {Ext.event.Event} e
 */
/**
 * @event dropdeactivate
 * @param {Ext.ux.util.Droppable} this
 * @param {Ext.util.Draggable} draggable
 * @param {Ext.event.Event} e
 */
/**
 * @event dropenter
 * @param {Ext.ux.util.Droppable} this
 * @param {Ext.util.Draggable} draggable
 * @param {Ext.event.Event} e
 */
/**
 * @event dropleave
 * @param {Ext.ux.util.Droppable} this
 * @param {Ext.util.Draggable} draggable
 * @param {Ext.event.Event} e
 */
/**
 * @event drop
 * @param {Ext.ux.util.Droppable} this
 * @param {Ext.util.Draggable} draggable
 * @param {Ext.event.Event} e
 */
config: {

 baseCls: Ext.baseCSSPrefix + 'droppable',
 activeCls: Ext.baseCSSPrefix + 'drop-active',
 invalidCls: Ext.baseCSSPrefix + 'drop-invalid',
 hoverCls: Ext.baseCSSPrefix + 'drop-hover',

 element: null
 },
/**
 * @cfg {String} validDropMode
 * Valid values are: 'intersects' or 'contains'
 */
 validDropMode: 'intersect',
/**
 * @cfg {Boolean} disabled
 */
 disabled: false,
/**
 * @cfg {String} group
 * Draggable and Droppable objects can participate in a group which are
 * capable of interacting.
 */
 group: 'base',
// not yet implemented
 tolerance: null,
// @private
 monitoring: false,
/**
 * Creates new Droppable
 * @param {Object} config Configuration options for this class.
 */
 constructor: function(config) {
 var me = this;
 var element;

 me.initialConfig = config;

 if (config && config.element) {
 element = config.element;
 delete config.element;
this.setElement(element);
 }

 if (!config.disabled) {
 me.enable();
 }

 return me;
 },

 updateBaseCls: function(cls) {
 this.getElement().addCls(cls);
 },

 applyElement: function(element) {
 if (!element) {
 return;
 }

 return Ext.get(element);
 },
updateElement: function(element) {
 element.on(this.listeners);
 this.initConfig(this.initialConfig);
 },

 updateCls: function(cls) {
 this.getElement().addCls(cls);
 },
// @private
 onDragStart: function(draggable, e) {
 var me = this; 

 if (draggable.group === me.group) {
 me.monitoring = true;
 me.getElement().addCls(me.getActiveCls());

 me.region = me.getElement().getPageBox(true);

 draggable.on({
 drag: me.onDrag,
 dragend: me.onDragEnd,
 scope: me
 });
if (me.isDragOver(draggable)) {
 me.setCanDrop(true, draggable, e);
 }
me.fireEvent('dropactivate', me, draggable, e);
 } else {
 draggable.on({
 dragend: function() {
 me.getElement().removeCls(me.getInvalidCls());
 },
 scope: me,
 single: true
 });

 me.getElement().addCls(me.getInvalidCls());
 }
 },
// @private
 isDragOver: function(draggable) {
 var dRegion = draggable.getElement().getPageBox(true);
 return this.region[this.validDropMode](dRegion);
 },
// @private
 onDrag: function(draggable, e) {
 this.setCanDrop(this.isDragOver(draggable), draggable, e);

 },
// @private
 setCanDrop: function(canDrop, draggable, e) {
 if (canDrop && !this.canDrop) {
 this.canDrop = true;
 this.getElement().addCls(this.getHoverCls());
 this.fireEvent('dropenter', this, draggable, e);
 }
 else if (!canDrop && this.canDrop) {
 this.canDrop = false;
 this.getElement().removeCls(this.getHoverCls());
 this.fireEvent('dropleave', this, draggable, e);
 }
 },
// @private
 onDragEnd: function(draggable, e) {
 this.monitoring = false;
 this.getElement().removeCls(this.getActiveCls());
draggable.un({
 drag: this.onDrag,
 dragend: this.onDragEnd,
 scope: this
 });

 if (this.canDrop) {
 this.canDrop = false;
 this.getElement().removeCls(this.getHoverCls());
 this.fireEvent('drop', this, draggable, e);
 }
this.fireEvent('dropdeactivate', this, draggable, e);
 },
/**
 * Enable the Droppable target.
 * This is invoked immediately after constructing a Droppable if the
 * disabled parameter is NOT set to true.
 */
 enable: function() {
 if (!this.draggables) {
 this.draggables = [];

 var publs = this.getEventDispatcher().activePublishers;
 for (var i in publs) {
 if (i === 'element') {
 var elems = publs[i]; 
 for (var y in elems) {
 if (Ext.isDefined(elems[y].subscribers['dragstart'] && 
 elems[y].subscribers['dragstart'].id)) {
 var draggs = elems[y].subscribers['dragstart'].id;
 for (var x in draggs) {
 if (x !== '$length') {
 var draggable = Ext.getCmp(x).getDraggableBehavior().draggable;
 this.draggables.push(draggable);
 }
 }
 }
 }
 }
 }
 }

 for (var i in this.draggables) {
 this.draggables[i].on({
 scope: this,
 dragstart: this.onDragStart
 });
 }

 this.disabled = false;
 },
/**
 * Disable the Droppable
 */
 disable: function() {
 if (this.draggables) {
 for (var i in this.draggables) {
 this.draggables[i].un({
 scope: this,
 dragstart: this.onDragStart
 });
 }
 }

 this.disabled = true;
 },
/**
 * Method to determine whether this Component is currently disabled.
 * @return {Boolean} the disabled state of this Component.
 */
 isDisabled: function() {
 return this.disabled;
 },
/**
 * Method to determine whether this Droppable is currently monitoring drag operations of Draggables.
 * @return {Boolean} the monitoring state of this Droppable
 */
 isMonitoring: function() {
 return this.monitoring;
 }
});
This implementation is taken from https://github.com/kostysh/Drag-Drop-example-for-Sencha-Touch

Step 6: Write an application controller as below to work for drag and drop

Ext.define('DragNDrop.controller.Main', {
 extend: 'Ext.app.Controller',

 requires: [
 'Ext.util.Draggable',
 'Ext.ux.util.Droppable'
 ],

 config: {
 refs: {
 draggsCnt: '#draggsCnt',
 dropCnt: '#dropCnt',
 dropCntTwo:'#dropCntTwo'
 },

 control: {
 draggsCnt: {
 initialize: 'onDraggsCntInit'
 },

 dropCnt: {
 initialize: 'onDropCntInit'
 },
 dropCntTwo:{
initialize: 'onDropCntTwoInit'
 }
 }
 },

 onDraggsCntInit: function(cnt) {
 var me = this;
 console.log('Init draggs');

 Ext.each(cnt.getInnerItems(), function(item) {
 if (Ext.isDefined(item.draggableBehavior)) {
 var draggable = item.draggableBehavior.getDraggable();

 draggable.group = 'base';// Default group for droppable
 draggable.revert = true;

 draggable.setConstraint({
 min: { x: -Infinity, y: -Infinity },
 max: { x: Infinity, y: Infinity }
 });

 draggable.on({
 scope: me,
 drag:me.onDrag,
 dragstart: me.onDragStart,
 dragend: me.onDragEnd
 });
 }
 });
 },

 onDrag: function() {
var me = this;
 console.log('drag event');
 },
onDragStart: function() {
var me = this;
 console.log('dragging event started');
 },

 onDragEnd: function() {
 console.log('dragging event ends');
 },

 onDropCntInit: function(cnt) {
 var me = this;

 var drop = Ext.create('Ext.ux.util.Droppable', {
 element: cnt.element
 });

 console.log('Droppable init');
drop.on({
 scope: me,
 dropactivate:me.onDropActivate,
 dropenter:me.onDropEnter,
 dropleave:me.onDropLeave,
 drop: me.onDrop
 });

 drop.cleared = false;

 },

 onDropActivate: function( me, draggable, e, eOpts){
console.log('drop activated');
 },

 onDropEnter: function(me, draggable, e, eOpts ){

 console.log('drop area entered');
 },
onDropLeave:function( me, draggable, e, eOpts ){

 console.log('drop area leave');
},

 onDrop: function(droppable, draggable) {
 var me = this;
 console.log('Dropped');

 var draggsCnt = me.getDraggsCnt();
 var dropCnt = me.getDropCnt();
 var dragg = Ext.getCmp(draggable.getElement().getId());

 if (!droppable.cleared) {
 dropCnt.setHtml('');
 droppable.cleared = true;
 }

 dropCnt.insert(0, Ext.create('Ext.Container', {
 cls: draggsCnt._activeItem._cls[0],
 layout: {
 type: 'hbox',
 align: 'center',
 pack: 'center'
 }
 }));

 dragg.destroy();
 },

 onDropCntTwoInit: function(cnt) {
 var me = this;

 var drop = Ext.create('Ext.ux.util.Droppable', {
 element: cnt.element
 });

 drop.on({
 scope: me,
 dropactivate:me.onDropActivate,
 dropenter:me.onDropEnter,
 dropleave:me.onDropLeave,
 drop: me.onDropTwo
 });

 drop.cleared = false;

 console.log('Droppable init');
 },

 onDropTwo: function(droppable, draggable) {
 var me = this;
 console.log('Dropped');

 var draggsCnt = me.getDraggsCnt();
 var dropCnt = me.getDropCntTwo();
 var dragg = Ext.getCmp(draggable.getElement().getId());

 if (!droppable.cleared) {
 dropCnt.setHtml('Circle dropped');
 droppable.cleared = true;
 }

 dropCnt.insert(0, Ext.create('Ext.Container', {
 cls: draggsCnt._activeItem._cls[0],
 layout: {
 type: 'hbox',
 align: 'center',
 pack: 'center'
 }
 }));

 dragg.destroy();
 }

});

This implementation is taken from https://github.com/kostysh/Drag-Drop-example-for-Sencha-Touch

Step 7: Observe the following different events for Draggable behavior

– drag
– dragend
– dragstart

When you select and starts draggin circle above events will be raised which we can see in below image as console output

Screenshot

Step 8:  Observe the dropenter event as below, using this event you can show the user that dropable area started or activated by changing the color etc, as part of this article just showing the log message that drop area entered

dropareaenter

onDropEnter: function(me, draggable, e, eOpts ){

 console.log('drop area entered');
 }

Step 9: dropleave event

When user drags the draggable container to droppable area and leaves the area then we can identify that draggable content is leaving the droppable area with dropleave event, observe the following image which show the log message while leaving the rectangle.

droparealeave

onDropLeave:function( me, draggable, e, eOpts ){

 console.log('drop area leave');
}

Step 10: drop event

Observe the drop event when draggable container dropped at the droppable area, with this event we can change the color of droppable area or add text as shown in below image, i.e. when circle is dropped at rectangle, showing the text that “Circle dropped

dropped

Step 11: Pinch event

In order to zoom an image or any component, we can pinch event as below, add following code in to an Main.js view at the end.

listeners:{
 'painted':function(){

 var myCircle = Ext.getCmp('circle').element;
 myCircle.origHeight = myCircle.getHeight();
 myCircle.origWidth = myCircle.getWidth();

 myCircle.on('pinch',function (event, node, options, eOpts) {
 var scale = event.scale;
 var calHeight = myCircle.origHeight * ( ( scale > 1 ) ? scale:1 ) ;
 var calWidth = myCircle.origWidth * ( ( scale > 1 ) ? scale :1 );
 myCircle.setHeight( calHeight );
 myCircle.setWidth( calWidth );
 });
myCircle.on('doubletap',function (event, node, options, eOpts) {
 this.currentX = event.pageX;
 this.currentY = event.pageY;
 this.dragged = false;
myCircle.setWidth(myCircle.origHeight);
 myCircle.setHeight(myCircle.origHeight);
 this.redraw();
 });

 }
 }

This event we can test in mobile device only . Following image shows the pinch event results

pinch

Pinch result as below

pinch_zoom

Conclusion

In this article we saw how to achieve drag and drop using Sencha Touch, events that are available during the drag-n-drop, and how we can build interesting login inside those handlers.

References

http://docs.sencha.com/touch/2.2.1/#!/api/Ext.util.Draggable

http://docs.sencha.com/touch/2.2.1/#!/api/Ext.util.Droppable

https://github.com/kostysh/Drag-Drop-example-for-Sencha-Touch

Create, Build & Package Sencha Touch project using Sencha Cmd

About

Technical Lead at Walking Tree. Full-stack Mobile and Web developer with expertise in Sencha Ext JS/Sencha Touch/MEEN area. Involved in leading and developing products and solutions of high complexity.

Tagged with: , , , ,
Posted in Sencha Touch
2 comments on “Drag and Drop using Sencha Touch
  1. Manpreet says:

    Hi,

    Thanks for your tutorial but After putting this into my project I am unable to run the command sencha app build.

    Please help me ..

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

We Have Moved Our Blog!

We have moved our blog to our company site. Check out https://walkingtree.tech/index.php/blog for all latest blogs.

Sencha Select Partner Sencha Training Partner
Xamarin Authorized Partner
Do More. With Sencha.

Recent Publication
%d bloggers like this: