Daily Archives: April 7, 2013

Tap and Drag Animation Domination Part 2: Implementing Custom Animations with Ext.Anim


Note: This is part of a three-part series on drag and drop animation in Sencha Touch 2. Click here to read part 1.

Use the Ext.Anim.run() method to execute custom animations from your Sencha Touch apps. Note that Ext.Anim.run() can only operate on DOM elements. Therefore, if you wanted to define a custom fade effect on an Ext.Container component, your code would need to appear similar to the following:


Ext.Viewport.add(
 {
  xtype: 'component',
  html: 'Beam me up, scotty!',
  style: 'background-color: silver; opacity: 0.0',
  height: 200,
  width: 200,
  listeners: {
    show: function(cmp) {

     // define custom fade animation on the component
      Ext.Anim.run(cmp.element, new Ext.Anim({
          autoClear: true,    // clear final animation effect
          easing: 'ease-in',
          duration: 1000,     // transition over 1 sec
          from: {
           'opacity': 0.0     // fade from fully transparent
          },
          to: {
           'opacity' : 1.0    // end effect will be opaque
          },
          after: function(el) {
            // after animation is complete,
            // set the component to be fully opaque
            var cmp =  Ext.getCmp(el.getId());
            cmp.setStyle('background-color: silver; opacity: 1.0');
          }
       }));
     }
   }
}

Note that prior to invoking the Ext.Anim singleton, you must load the class into memory using either the Ext.require() method or the requires config property of a component.

You can put any CSS3 property that is supported by your device into the from/to config blocks. Note, however, that any property that is present in the from config object must also be specified in the to object.

Before you consider implementing all kinds of zany CSS-3 based animations, consider that not all CSS3 animations are performant across all devices. Performance in Desktop Chrome and the iOS Simulator is not indicative of how well your animation will perform on an actual iPhone 5, iPad, or Android device. I strongly urge you to set up a small test harness, as indicated above, to test complex animations on physical devices before dropping them into your app’s codebase.

Tap and Drag Animation Domination Part 1: Working with the DOM

Working with DOM elements in Sencha Touch 2 is a topic that has very little coverage from within the official documentation and the community at-large. Officially, you are strongly encouraged to develop apps using only the rich set of components that Sencha has provided to you in their framework. However, if you need to impress your manager by creating custom Touch 2 components or extend out-of-the-box components with custom gestures and animations, it is vital that you understand how the DOM system operates. Because nothing impresses I.T. managers more than a completely gratuitous, well-executed animation!

Working with DOM elements in Touch 2

The first thing to understand about Components is that they generate DOM elements as illustrated by the following example

Sencha Touch Generated Output
 Ext.Viewport.add({
  xtype: 'component',
  html: 'Hello World',
  itemId: 'mycomponent',
  cls: 'customstyle'
 });
<div class="customstyle" 
     id="ext-component-1">
  <div class="x-innerhtml" 
      id="ext-element-9">
     Hello World
  </div>
</div>

You can programmatically access the top level DOM element that is created by a component (typically a <div>) by referencing the component.element property as illustrated below:

var el = Ext.ComponentQuery.query("#mycomponent")[0].element;

You can also access the DOM elements generated by components by invoking the Ext.select() method, targeting any CSS style classes that are generated by the component as illustrated by the following snippet:

// get first div tag with css style class of 'customstyle'
var htmlEl = Ext.select('div.customstyle').elements[0];

// convert to Ext.dom.Element reference
var el = Ext.get(htmlEl);

Moving between Ext.dom.Element and their corresponding components

Since the DOM element typically has the same ID property as the Sencha Touch Component that generated it, you can usually get a pointer back to the Component by using the following syntax:

// get DOM element from component
var el = Ext.ComponentQuery.query("#mycomponent")[0].element;

// get Component reference from DOM component
var cmp = Ext.getCmp(el.dom.id);

Ext.dom.Element instances are wrappers for HTML DOM elements. They give you a programmatic interface that normalizes the differences between different browser implementations. Much like another wrapper, Vanilla Ice, they don’t look like much on first glance:

dom1

However, once you dig deeper, realize that you’ve actually pulled an instance of the Sencha Touch 2 Ext.dom.Element class, and discover that there are 117 additional methods that you can invoke as well as 19 events that you can listen for (including pinch, rotate, and swipe gestures), a joyous nerdgasm is sure to follow!

Attaching DOM event listeners to Component Elements

Attaching a DOM event listener, such as a taphold gesture to an element generated by a component now becomes a relatively straightforward affair. Here’s an example that echoes the high-brow conversation of my children as I drive them to school:

el.on('taphold',
      function(event, node, options,eOpts) { 
        Ext.Msg.alert("Stop touching me!",
                      "Would you stop touching me??"
        );
      }
);

You can also attach a DOM element event listener directly to a component using the component’s listeners config property as illustrated below:

Ext.Viewport.add({
 xtype: 'component',
 html: 'Hello World',
 cls: 'customstyle',
 itemId: 'mycomponent',
 listeners: {
  element: 'element',
  'taphold': function(event, node, options,eOpts) { 
     Ext.Msg.alert("Stop touching me!",
                   "Would you stop touching me??"
     );
  }
 }
});

Now, a potential problem here is that the event listener for the taphold event returns a reference to the DOM element that fired the event (the node param) instead of the component instance that generated the DOM element. (In our case, this is likely to return back the DOM element “ext-element-9”). Fortunately, if you bind the DOM event listener to the component directly, the eOpts argument contains the id of the top-level container that had the event listener attached to it, e.g. #ext-component-1. You can simply strip the # from the string and pass it to Ext.getCmp() to get a pointer back to the Component.

Ext.Viewport.add({
 xtype: 'component',
 html: 'Hello World',
 cls: 'customstyle',
 itemId: 'mycomponent',
 listeners: {
  element: 'element',
  'taphold': function(event, node, options,eOpts) { 
              Ext.Msg.alert("Stop touching me!",
                            "Would you stop touching me??"
              );
              console.log('component ref:',   
               Ext.getCmp(eOpts.info.target.substring(1))
              );
            } // end function
  } // end listeners
}); // end viewport add

Now that you know how to access DOM elements from their generating Components (and vice-versa), you can enhance components by adding new behaviors and animations. I’ll cover this in more detail in another blog post later today.