Tag Archives: Sencha Touch

Localizing Sencha Touch Applications for the Cheese Eating Surrender Monkeys

Ever since I implemented Voice of America’s content managed web site (b. 1999, d. 2005) and had to support 44 different languages, I’ve had a rather strange interest in developing multilingual apps. I consider it to be ‘strange’ because no one could have predicted that the guy who once got a D+ in junior high French class would go on to accumulate a ton of experience in successful enterprise application localization. As you can tell from the title of this post, I’m not bitter about the grade that I received 30 years ago …not bitter…at…all. For as I told my teacher, “I already speak three languages: Basic, Pascal, and English. French seems rather superfluous.”

As an adult, I’ve repeatedly told my kids “You can learn any foreign language that you’d like, as long as it’s Java, C++, or Javascript. Just stay the hell away from COBOL – for it’s the modern day equivalent of Latin.” They stare at me blankly, quickly turning back to playing Minecraft.

But.. I digress.

Since my consulting practice is based in Washington, DC, I generally don’t get a lot of opportunity to use my i10N-fu…but when an opportunity comes around, I like to dig deep.

To support latin-based, left-to-right languages (English, Spanish, French, Italian, etc), there are four major concerns:

  1. All text prompts need to be translated into language
  2. Enough space needs to be allocated in order to accomodate variable string lengths in word spelling
  3. Text translations should be encapsulated into separate, easy to edit files so that translations can be easily outsourced to human beings (Google Translate just doesn’t cut it…yet)
  4. Dates and numbers need to be translated into the locale. The French, for example, swap their commas and periods which I’m fairly confident was a central cause of the European banking crisis.

Dynamically Assigning Text Prompts

As the code listing below illustrates, all on-screen prompts are implemented as properties of the View class. Prompts are assigned at runtime in the initialize() method of the view.

Ext.define('MyApp.view.Login', {
	extend: 'Ext.form.Panel',
	alias: 'widget.loginform',
	requires: [
		'Ext.field.Email',
		'Ext.field.Password',
		'Ext.Button'],

	txtEmailPrompt: 'Email:',
	txtPasswordPrompt: 'Password',
	btnSubmitPrompt: 'Submit',
	headerHtml: 'Heading Info',
	footerHtml: 'Footing Html',
	loginErrorMsg: 'Sorry, try again',

	config: {
		scrollable: null
	},

	initialize: function() {

		this.setItems(
		[{
			xtype: 'component',
			itemId: 'header',
			html: this.headerHtml
		}, {
			xtype: 'emailfield',
			name: 'email',
			label: this.txtEmailPrompt,
			labelWidth: '40%'
		}, {
			xtype: 'passwordfield',
			name: 'password',
			label: this.txtPasswordPrompt,
			labelWidth: '40%'
		}, {
			xtype: 'button',
			text: this.btnSubmitPrompt
		}, {
			xtype: 'component',
			itemId: 'footer',
			html: this.footerHtml
		}]);
		this.callParent(arguments);
	}
});

Localizing Text Prompts and Applying Translations to Views

The next step is to encapsulate the translations into a class that can be easily edited. In Ext JS 4, the standard is to place translations in separate files that are read in at runtime. However, since I’m dealing with a mobile app, I wanted to minimize the http overhead and decided to place all the translations into a singleton that is packaged along with the rest of the app.

Note the following:

  • Each language is represented by a two-character language code
  • Translations are grouped by the view in which they’ve been defined
  • The localize() method applies the translations to each of the view classes for which a translation is available.
Ext.define('MyApp.config.Locale', {
 singleton: true,
 config: {
  en: {
   Login: {
    txtEmailPrompt: 'Email:',
    txtPasswordPrompt: 'Password',
    btnSubmitPrompt: 'Submit',
    headerHtml: 'Heading in English',
    footerHtml: 'Footer in English',
    loginErrorMsg: 'Sorry, try again'
   }
  },
  fr: {
   Login: {
    txtEmailPrompt: 'Votre email:',
    txtPasswordPrompt: 'Votre mot de passe',
    btnSubmitPrompt: 'Transmettre',
    headerHtml: 'Heading en Francais',
    footerHtml: 'Footer en Francais',
    loginErrorMsg: 'Merde. Où sont les toilettes?'
   }
  }
},
localize: function(locale) {
 var translations = this.config[locale];
 for (var view in translations) {
   Ext.apply(MyApp.view[view].prototype,translations[view]);
 }
}
});

Using this technique, I’m able to easily apply the translations just prior to launching the main view of the app:

Ext.application({
 name: 'MyApp',
 requires: [
  'Ext.MessageBox',
  'MyApp.config.Locale'
 ],
 views: ['Main'],
 launch: function() {
      MyApp.config.Locale.localize('fr');
      Ext.Viewport.add(Ext.create('MyApp.view.Main'));
 }
});

dateselectorLocalizing Dates

Unfortunately, the aforementioned solution is still a “half-measure” in that we still need to localize the built-in Sencha Touch controls, such as the date selector. In order to do this, you’ll need to override the Ext.Date class (which loads the Ext.DateExtras class as a mixin). Fortunately, the Ext.Date/Ext.DateExtras classes in Sencha Touch closely parallel Ext JS 4, so you can repurpose Ext JS 4 locale files as illustrated below:

Ext.define('MyApp.config.Locale', {
 singleton: true,
 config: {
  en: {
   LoginView: {
    txtEmailPrompt: 'Email:',
    txtPasswordPrompt: 'Password',
    btnSubmitPrompt: 'Submit',
    headerHtml: 'Heading in English',
    footerHtml: 'Footer in English',
    loginErrorMsg: 'Sorry, try again'
   }
  },
  fr: {
   LoginView: {
    txtEmailPrompt: 'Le Email:',
    txtPasswordPrompt: 'Password',
    btnSubmitPrompt: 'Submit',
    headerHtml: 'Heading En Francais',
    footerHtml: 'Footer En Francais',
    loginErrorMsg: 'Sorry, try again'
   }
  }
 },

 fr: function() {
    // date localization borrowed from Ext JS 4 fr locale

   if (Ext.Date) {
    Ext.Date.shortMonthNames = ["Janv", "Févr", "Mars", "Avr", "Mai", "Juin", "Juil", "Août", "Sept", "Oct", "Nov", "Déc"];

    Ext.Date.getShortMonthName = function(month) {
	return Ext.Date.shortMonthNames[month];
    };

    Ext.Date.monthNames = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"];

    Ext.Date.monthNumbers = {
     "Janvier": 0,
     "Janv": 0,
     "Février": 1,
     "Févr": 1,
     "Mars": 2,
     "Mars": 2,
     "Avril": 3,
     "Avr": 3,
     "Mai": 4,
     "Juin": 5,
     "Juillet": 6,
     "Juil": 6,
     "Août": 7,
     "Septembre": 8, 
     "Sept": 8,
     "Octobre": 9,
     "Oct": 9,
     "Novembre": 10,
     "Nov": 10,
     "Décembre": 11,
     "Déc": 11
    };

    Ext.Date.getMonthNumber = function(name) {
	return Ext.Date.monthNumbers[Ext.util.Format.capitalize(name)];
    };

   Ext.Date.dayNames = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"];

   Ext.Date.getShortDayName = function(day) {
    return Ext.Date.dayNames[day].substring(0, 3);
   };

   Ext.Date.parseCodes.S.s = "(?:er)";

   Ext.override(Date, {
    getSuffix: function() {
     return (this.getDate() == 1) ? "er" : "";
    }
   });
 }
},

en: function() {
  // reset all dates to english here
},


localize: function(locale) {
  var translations = this.config[locale];
  for (var view in translations) {
    Ext.apply(MyApp.view[view].prototype, translations[view]);
  }
  this[locale]();
 }
});

So, as my old french teachers would have said…”C’est Bon!”..right after giving my latest effort a C-.

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.

Revisiting the Sencha Touch 2.2 Sql Proxy

As I mentioned in the comments of my previous blog post, the Sencha Touch Sql proxy introduced in Touch 2.1 had a number of debilitating bugs that made it largely unusable for production work. However, I’m pleased to report that all of the bugs that I had previously reported now seem to be fixed in the Touch 2.2 RC1 beta!

So, let’s do a little deep-dive into the proxy, shall we?

The Basics

Sencha Touch’s SQL proxy outputs model data into an HTML5 local database (SQLite).

There are two key advantages to using the SQL proxy over over the LocalStorage proxy:

  1. Unlike LocalStorage which is fixed at ~5 MB, the size of the WebSQL database can be set by the developer. It defaults to 5MB, but you can override this setting to request more space from the user.
  2. As illustrated by the figure below, the SQL proxy creates a table in the WebSQL database. Using native JavaScript methods you can write SQL to perform table joins.

Using the Proxy is a rather straightforward affair as illustrated by the following code snippet:

sqlproxy1


Ext.application({
 name: 'SqlProxyTest',

 // note that the Sql proxy has been renamed from Ext.data.proxy.SQL
 // in Touch 2.1 to Ext.data.proxy.Sql in Touch 2.2

 requires: [
  'Ext.MessageBox',
  'Ext.data.proxy.Sql',
  'Ext.data.Store'
 ],

 launch: function() {

    // define the model and proxy
    Ext.define("SqlProxyTest.model.User", {
        extend: "Ext.data.Model",
        config: {
          fields: [
             "firstName",
             "lastName",
             {
                name: "dateAdded",
                type: "date",
                defaultValue: new Date()
             }
          ],
          proxy: {
            type: "sql"
          }
        }
     });

     // define the Store
     Ext.create("Ext.data.Store", {
        model: "SqlProxyTest.model.User",
        storeId: 'Users'
     });

     // add a Record
     Ext.getStore('Users').add([{
        firstName: 'Steve',
        lastName: 'Drucker'
     }]);

     // write the record to disk
     Ext.getStore('Users').sync();
    }
});

You can load data back into the Store from LocalStorage by using the Ext.data.Store.load() method. Running local sql queries is an asynchronous process, so you may need to specify a callback function, depending on your specific use-case.

Note: Loading data back into the store was buggy in Touch 2.1.

Ext.getStore('Users').load();

Configuring the Proxy

As illustrated by the previous example, the Sql Proxy will automatically create a 5MB client-side database named “Sencha” and use the name of your model (“User”) as the name of the corresponding database table where records will be written. In theory, you can easily override the names of the table and the database as illustrated by the following snippet:

Ext.define("SqlProxyTest.model.User", {
 extend: "Ext.data.Model",
 config: {
  fields: [
   "firstName",
   "lastName",
   {
    name: "dateAdded",
    type: "date",
    defaultValue: new Date()
   }
  ],
  proxy: {
   type: "sql",
   database: "SqlProxyTest",
   table: "SenchaDevs2"
  }
 }
});

Unfortunately, setting the “table” config property causes an exception in Touch 2.2 RC1 – a rather minor bug, but let’s hope it gets resolved soon!

Also, note that in the prior example, the record’s id field is, by default, an autonumber. While this might work in certain use cases, it would be problematic if you were trying to syndicate and synchronize data. You can have Sencha Touch automatically substitute universally unique identifiers (UUID’s) for the default autonumber field by specifying the identifier config property as illustrated below:

sqluuid

Ext.define("SqlProxyTest.model.User", {
 extend: "Ext.data.Model",
 requires: ['Ext.data.identifier.Uuid'],
 config: {
  identifier: 'uuid',
  fields: [ 
   "firstName",
   "lastName", 
   {
    name: "dateAdded",
    type: "date",
    defaultValue: new Date()
   }
  ],
  proxy: {
   type: "sql",
   database: "SqlProxyTest"
  }
 }
});

Handling Updates to Table Schemas

Currently there are no built-in methods for updating your table’s schema if the structure of your Model has changed. You’ll need to develop an algorithm to handle this yourself. As illustrated by the following code snippet, my suggestion is to load the existing data into a Store, programmatically drop the table, and then write all the records back out to the db using the new schema:

Ext.getStore('Users').load(function(records, operation, success) {
       
 // destroy the table
 this.getModel().getProxy().dropTable();
 this.getModel().getProxy().setTableExists(false);
        
 var numRecs = this.getCount();
 
 // set the double-secret "phantom" bit to mark
 // records for re-insertion
     
 for (var i=0; i<numRecs; i++) {
   this.getAt(i).phantom = true; 
 }

 // create the db table and write all records
 this.sync();
});

Orchestrating Login and Roles-Based Security in Ext JS and Sencha Touch

The issue of logins and roles-based security often comes up in my Ext JS and Sencha Touch classes. The sad reality is that anyone who knows how to open the browser’s JavaScript debugger is able to “hack” your application. Therefore, security is something that *must& be handled at the application-server level as there is no current method to adequately secure your JavaScript code.

Having said that, most corporate apps still require logins and roles-based security as functional requirements. A user must enter credentials and their button/menu selections need to be tailored to a specified role as illustrated by the following video:

Typically, your app.js file simply invokes a login dialog as illustrated by the following code snippet:

// code listing 1: app.js
Ext.application({
    controllers: ["Main"],
    views: ["Main"],
    name: 'LoginAppDemo',
    autoCreateViewport: false,
    launch: function() {
    	Ext.create("LoginAppDemo.view.LoginForm")
    }
});

Let’s also assume that we’re going to put together a simple login form inside of a floating window. Why a floating window, you ask? Because login forms are always cooler if they float and are draggable, of course!

// code listing 2: LoginForm.js
Ext.define("LoginAppDemo.view.LoginForm", {
 extend: 'Ext.window.Window',
 alias: 'widget.loginform',
 requires: ['Ext.form.Panel'],
 title: 'Please Log In',
 autoShow: true,
 height: 150,
 width: 300,
 closable: false,
 resizable: false,
 layout: 'fit',
 items: [
  {
   xtype: 'form',
   bodyPadding: 5,
   defaults: {
    xtype: 'textfield',
    anchor: '100%'
   },
   items: [
    {
     fieldLabel: 'User Name:',
     name: 'username',
     allowBlank: false
    },
    {
     fieldLabel: 'Password:',
     name: 'password',
     allowBlank: false
    },
   ],
   buttons: [
    {
     text: 'Log in',
     formBind: true,
     disabled: true,
     handler: function(b,e) {
      var formDialog = b.up('loginform');
      var form = b.up('form');
      
      // fire custom event for the controller to handle
      formDialog.fireEvent('login',formDialog,form,form.getValues());
     } // handler
    } // login button
   ] // buttons 
  } // form
 ] // items
}) 

Note that in the preceding example, when the user clicks the ‘Log in’ button, I fire a custom ‘login’ event and pass references to the login window, the login credentials form, and the data that the user entered into the form up to the controller (listed below).

From within the login handler, I make an Ajax call to the server in order to retrieve information about the user profile. In a production environment you would make this call via HTTPS to your application server (.php,.aspx,.jsp,.cfc, etc.).

// code listing 3
Ext.define('LoginAppDemo.controller.Main', {
 extend: 'Ext.app.Controller',
 requires: ['LoginAppDemo.user.Profile'],
 views: ['LoginForm','Viewport'],
    
 init: function(application) {
  this.control({
   "loginform": {
     login: this.onLogin
    }
  });
 },

 onLogin: function(loginDialog,loginForm,loginCredentials) {
   
   var me = this;

   // authenticate
   Ext.Ajax.request({
    url: 'resources/sampledata/cred.json',
    params: {
     username: loginCredentials.username,
     password: loginCredentials.password
    },
    success: function(response) {
     
     // convert text response to javascript object
     var data = Ext.decode(response.responseText);
    
     // if server response contains "firstName" node, then success!			
     if (data.firstName) {
      // instantiate user info in global scope for easy referencing
      LoginAppDemo.User = Ext.create("LoginAppDemo.user.Profile", {
    					firstName: data.firstName,
    					lastName: data.lastName,
    					roles: data.roles
      });
     
      // destroy login dialog
      loginDialog.destroy();


      Ext.Msg.alert("Login Successful",
    		    Ext.String.format("Welcome {0} {1}",
    				      LoginAppDemo.User.getFirstName(),
    				      LoginAppDemo.User.getLastName())
      );

      // load main UI
      Ext.create("LoginAppDemo.view.Viewport");


  } else { // login failed
    Ext.Msg.alert("Invalid credentials",
                  "You entered invalid credentials.", 
                  function() {
    		   loginForm.getForm().reset();
                  }
    );
  }
 }
});
}
});

Your app server would subsequently run a custom sql query to ensure that the passed username and password were valid and return a response similar to the following:

// code listing 4: cred.json
{
 firstName: 'Steve',
 lastName: 'Drucker',
 roles: 'admin'
}

Your app server would also set up session management for the user, typically consisting of some sort of session-based cookie hack since not every browser supports the establishment of persistent (html5 websocket) connections. Each subsequent data request to the server would use this cookie to determine whether the user had the appropriate rights to download the data that was requested.

From a UI perspective, however, we are simply concerned with caching the user’s identity and roles information in a location that could be easily accessed by our view and controller logic. Lines 33-37 of code listing 3 above instantiate the custom component described in code listing 5 at the root of your application’s namespace for easy global access.

// code listing 5
Ext.define("LoginAppDemo.user.Profile", {
	
  config: {
    firstName: '',
    lastName: '',
    roles: []
  },

  isUserInRole: function(roles) {
   for (var i=0; i<roles.length; i++) {
    if (Ext.Array.contains(this.getRoles(),roles[i])) {
     return true
    }
   }
   return false;
  },

  constructor: function(config) {
    this.initConfig(config);
    this.callParent(arguments);
  }

});

As illustrated by the following toolbar class, once the framework described above is in place, you can simply test the features of your app before outputting them to the user.

Ext.define("LoginAppDemo.view.MainToolbar", {
 extend: 'Ext.toolbar.Toolbar',
 alias: 'widget.maintoolbar',
 requires: ['Ext.toolbar.TextItem'],
	
 initComponent: function() {
  var items = [
   {
    xtype: 'tbtext',
    text: 'Login and Roles-Based Security Simulator'
   }, 
   {
    xtype: 'tbfill'
   }
  ];

  if (LoginAppDemo.User.isUserInRole(["admin"])) {
   items.push({ xtype: 'button', text: 'For Admins'});
  }

  if (LoginAppDemo.User.isUserInRole(["admin","users"])) {
   items.push({xtype: 'button', text: 'For Admins or Users' });
  }

  if (LoginAppDemo.User.isUserInRole(["nobody"])) {
   items.push({xtype: 'button', text: 'For nobody' });
  }

  Ext.apply(this, {items: items});
  this.callParent(arguments);
 }
});