Mobile Drupal with Sencha Touch

Another Mobile Technology?

The mobile development landscape is very fractious.

The different paths of mobile development

  • Native Development
  • Native Hybrid Frameworks
  • Mobile Web Application + Apache Cordova

Native Development

Pros:

  • Performance
  • Small memory footprint

Cons:

  • Each platform must be developed individually.

Native Hybrid Frameworks

Utilize a standard SDK that compiles into a Native application.


Titanium

Corona

Pros:

  • Cross Platform.
  • Performance

Cons:

  • License constraints.
  • Costs money.
  • Cannot be ran as web application.

Mobile Web Apps + Apache Cordova

Wraps a web application in Native Web View.


jQuery Mobile

Sencha Touch

Pros:

  • Uses web technology.
  • Cross platform.
  • Use as your mobile website.
  • Common User Interface
  • Free (kindof)

Cons:

  • Performance.
  • Common User Interface (doesn't feel native).

Which one do I use?

It depends on your requirements...

  • Performance: Native
  • Non-Web Application: Native Hybrid Frameworks
  • Web Application: Mobile Web App + Apache Cordova

Why Sencha Touch?

  • Enterprise Web Application Framework
  • 'Seems' more performant than other frameworks.
  • MVC Object Oriented Architecture.
  • Beautiful User Interface
  • Flexible + Customizable
  • Great Documentation
  • but...
  • Steep learning curve.
  • JavaScript Heavy.

Sencha Touch: Getting Started

http://docs.sencha.com/touch/2.2.1/#!/guide/getting_started

Code Layout

MVC Architecture

Model - View - Controller

Object Oriented Design

Derive functionality from a base object (class).

Ext.define('Example1.view.Main', {
  extend: 'Ext.tab.Panel'
});
http://docs.sencha.com/touch/2.2.1/#!/api/Ext.tab.Panel

Great Documentation

http://docs.sencha.com/touch/2.2.1

Fun with Views, Layouts & Controllers

Creating Layouts

Ext.define('Example.view.Main', {
  extend: 'Ext.Container',
  config: {
    layout: 'vbox',
    style: 'text-align: center;',
    items: [
      {
        flex: 1,
        style: 'background-color: #ccc;',
        html: 'Top'
      },
      {
        flex: 1,
        style: 'background-color: #ddd;',
        html: 'Bottom'
      }
    ]
  }
});
examples/example2/app/view/Main.js

Creating a Grid Layout

Ext.define('Example.view.Main', {
  extend: 'Ext.Container',
  config: {
    layout: 'vbox',
    items: [
      {
        flex: 1,
        layout: 'hbox',
        items: [
          {
            flex: 1,
            style: 'background-color: #aaa;'
          },
          {
            flex: 1,
            style: 'background-color: #bbb;'
          }
        ]
      },
      {
        flex: 1,
        layout: 'hbox',
        items: [
          {
            flex: 1,
            style: 'background-color: #ccc;'
          },
          {
            flex: 1,
            style: 'background-color: #ddd;'
          }
        ]
      }
    ]
  }
});
examples/example3/app/view/Main.js

Creating a Home Page

Ext.define('Example.view.Main', {
  extend: 'Ext.tab.Panel',
  requires: 'Ext.TitleBar',
  config: {
    tabBarPosition: 'bottom',
    items: [
      {
        title: 'Home',
        iconCls: 'home',
        scrollable: true,
        layout: 'vbox',
        items: [
          {
            docked: 'top',
            xtype: 'titlebar',
            title: 'Home'
          },
          {
            flex: 1,
            layout: 'hbox',
            items: [
              {
                flex: 1,
                items: [{
                  xtype: 'button',
                  iconCls: 'user',
                  text: 'Profile',
                  iconAlign: 'top',
                  ui: 'action',
                  padding: '25 0 0 0',
                  centered: true,
                  width: 100,
                  height: 100
                }]
              },
              {
                flex: 1,
                style: 'background-color: #ccc;',
                items: [{
                  xtype: 'button',
                  iconCls: 'team',
                  text: 'Users',
                  iconAlign: 'top',
                  ui: 'action',
                  padding: '25 0 0 0',
                  centered: true,
                  width: 100,
                  height: 100
                }]
              }
            ]
          },
          {
            flex: 1,
            layout: 'hbox',
            items: [
              {
                flex: 1,
                style: 'background-color: #ccc;',
                items: [{
                  xtype: 'button',
                  iconCls: 'favorites',
                  text: 'Favorites',
                  iconAlign: 'top',
                  ui: 'action',
                  padding: '25 0 0 0',
                  centered: true,
                  width: 100,
                  height: 100
                }]
              },
              {
                flex: 1,
                items: [{
                  xtype: 'button',
                  iconCls: 'settings',
                  text: 'Settings',
                  iconAlign: 'top',
                  ui: 'action',
                  padding: '25 0 0 0',
                  centered: true,
                  width: 100,
                  height: 100
                }]
              }
            ]
          }
        ]
      }
    ]
  }
});
examples/example4/app/view/Main.js

Creating other Views

Ext.define('Example.view.User', {
  extend: 'Ext.Container',
  xtype: 'userpage',
  config: {
    title: 'User',
    styleHtmlContent: true,
    html: "<h2>Welcome You!</h2>"
  }
});
examples/example5/app/view/User.js
Ext.application({
  name: 'Example',
  requires: [
    'Ext.MessageBox'
  ],
  views: [
    'Main',
    'User'
  ],
  ...
  ...
examples/example5/app/app.js

Creating a Controller

Ext.define('Example.controller.Main', {
  extend: 'Ext.app.Controller',
  config: {
    refs: {
      mainPage: 'mainpage'
    },
    control: {
      'button[action=user]': {
        tap: 'onButton'
      },
      ...
      ...
    }
  },
  onButton: function(event) {
    this.getMainPage().push({
      xtype: event.action + 'page'
    });
  }
});
examples/example5/app/controller/Main.js

Creating a Controller: Part II

Ext.application({
  name: 'Example',
  requires: [
    'Ext.MessageBox'
  ],
  controllers: [
    'Main'
  ],
  views: [
    'Main',
    'User',
    'Users',
    'Favorites',
    'Settings'
  ],
  ...
  ...
examples/example5/app/controller/Main.js

Sencha Architect: GUI Editor

A visual application builder for Sencha Touch.

http://www.sencha.com/products/architect

Sencha Architect Pricing

Drupal as the backend to Sencha Touch

Drupal: Services Module

https://drupal.org/project/services

Installation


/admin/modules

Add New Service

/admin/structure/services

Service Settings

/admin/structure/services/add

Service Endpoint Formats

/admin/structure/services/list/api/server

Service Endpoint Formats

/admin/structure/services/list/api/resources

Quick Test

Open your Drupal website in Chrome. In the Console of the Developer Tools, type the following...

jQuery.get('/api/user.json', function(users) {
  console.log(users);
});

Connecting Sencha to Drupal.

Creating a User List view.

Define the Model

The model represents a single 'object'.

Ext.define('Example.model.User', {
  extend: 'Ext.data.Model',
  config: {
    fields: [
      { name: 'uid', type: 'int' },
      { name: 'name', type: 'string' },
      { name: 'sid', type: 'string' },
      { name: 'mail', type: 'string' },
      { name: 'access', type: 'date', dateFormat: 'U' },
      { name: 'created', type: 'date', dateFormat: 'U' },
      { name: 'picture', type: 'string' }
    ]
  }
});
examples/example6/app/model/User.js
Ext.application({
  name: 'Example',
  models: [
    'User'
  ],
examples/example6/app/app.js

Define the Store

A store represents a list of objects (views module).

Ext.define('Example.store.Users', {
  extend: 'Ext.data.Store',
  requires: ['Ext.data.proxy.JsonP'],
  config: {
    autoLoad: true,
    model: 'Example.model.User',
    proxy: {
      type: 'jsonp',
      url: 'http://www.example.com/api/user.jsonp',
      reader: {
        type: 'json'
      }
    }
  }
});
examples/example6/app/store/Users.js

But there is a problem...

Define a custom Proxy

Sencha Touch makes assumptions about the paging parameters. To fix this, we will create our own custom proxy and derive from JsonP.

Ext.define('Example.proxy.Drupal', {
  extend: 'Ext.data.proxy.JsonP',
  alias: 'proxy.drupal',
  getParams: function(operation) {
    return {
      page: operation.getPage() - 1,
      limit: operation.getLimit()
    };
  }
});
examples/example6/app/proxy/Drupal.js

Fixing the Users Store.

Ext.define('Example.store.Users', {
  extend: 'Ext.data.Store',
  requires: ['Example.proxy.Drupal'],
  config: {
    autoLoad: true,
    model: 'Example.model.User',
    proxy: {
      type: 'drupal',
      url: 'http://localhost:8888/api/user.jsonp',
      reader: {
        type: 'json'
      }
    }
  }
});
examples/example6/app/store/Users.js
Ext.application({
  name: 'Example',
  stores: [
    'Users'
  ],
examples/example6/app/app.js

Create the User List view

Ext.define('Example.view.Users', {
  extend: 'Ext.List',
  xtype: 'userspage',
  requires: ['Example.store.Users'],
  config: {
    store: 'Users',
    styleHtmlContent: true,
    itemTpl: new Ext.XTemplate(
      '<strong>{name}</strong>',
      '<p>{mail}</p>'
    ),
    onItemDisclosure: true
  }
});
examples/example6/app/view/Users.js

Setup the User view

Ext.define('Example.view.User', {
  extend: 'Ext.Container',
  xtype: 'userpage',
  config: {
    title: 'User',
    styleHtmlContent: true,
    tpl: new Ext.XTemplate('{name} - {mail}'),
  }
});
examples/example6/app/view/User.js

Hook up the Controller

Ext.define('Example.controller.Main', {
  extend: 'Ext.app.Controller',
  config: {
    refs: {
      mainPage: 'mainpage',
      usersPage: 'userspage'
    },
    control: {
      ...
      'userspage': {
        itemtap: 'showUser'
      }
    }
  },
  ...
  showUser: function(list, index, target, record) {
    this.getMainPage().push({
      xtype: 'userpage',
      title: record.get('name'),
      data: record.data
    });
  }
});
examples/example6/app/controller/Main.js

Form Handling

Enable CORS on your server.

In order to get POST to work in Sencha Touch, you need to enable Cross-Origin Resource Sharing (CORS). Go to http://enable-cors.org/index.html to find out more.

Create a NodeForm View

Ext.define('Example.view.NodeForm', {
  extend: 'Ext.form.Panel',
  xtype: 'nodeformpage',
  requires: 'Ext.form.FieldSet',
  config: {
    title: 'New Article',
    items: [
      {
        xtype: 'fieldset',
        flex: 1,
        items: [
          {
            xtype: 'textfield',
            name: 'title',
            label: 'Title:'
          },
          {
            xtype: 'textareafield',
            name: 'description',
            label: 'Description:',
            cls: 'nodedesc',
            flex: 1
          }
        ]
      },
      {
        xtype: 'button',
        text: 'Save',
        ui: 'action',
        margin: '10px',
        action: 'nodeSave'
      }
    ]
  }
});
examples/example7/app/view/NodeView.js

Setup the Controller

Ext.define('Example.controller.Main', {
  extend: 'Ext.app.Controller',
  requires: ['Ext.device.Notification'],
  config: {
    refs: {
      ...
      nodeForm: 'nodeformpage'
    },
    control: {
      'button[action=nodeform]': {
        tap: 'onButton'
      },
      'button[action=nodeSave]': {
        tap: 'onNodeSave'
      },
      ...
    }
  },
  ...
});
examples/example7/app/controller/Main.js

Saving a node

onNodeSave: function() {
  var self = this;

  // Get the node values.
  var values = this.getNodeForm().getValues();

  // Set the POST params.
  var params = {
    'node[type]': 'article',
    'node[title]': values.title,
    'node[body][und][0][value]': values.description
  };

  // Get the request token.
  Ext.Ajax.request({
    url: 'http://localhost:8888/services/session/token',
    method: 'GET',
    withCredentials: true,
    success: function(response) {
      var token = response.responseText;
      Ext.Ajax.request({
        method: 'POST',
        url: 'http://localhost:8888/api/node.json',
        withCredentials: true,
        headers: {'X-CSRF-Token': token},
        params: params,
        success: function(response) {
          response = Ext.JSON.decode(response.responseText);
          Ext.Msg.alert(response.title, 'Node created!', Ext.emptyFn);
          self.getMainPage().pop();
        }
      });
    }
  });
}
examples/example7/app/controller/Main.js

DrupalTouch.js

https://github.com/travist/drupaltouch

Walkthough

Apache Cordova (PhoneGap)

Provides a way to deploy a web application as a Native App.

Setup

Create Project

$ cd ~/Documents/cordova/cordova-android/bin
$ ./create ~/Documents/projects/cordova-sencha com.TravisTidwell.CordovaSencha CordovaSencha

Symlink Sencha Application to Cordova

$ rm -rf ~/Documents/projects/cordova-sencha/assets/www
$ ln -s ~/Documents/projects/drupal-sencha ~/Documents/projects/cordova-sencha/assets/www

Build and Deploy

Sencha Command allows you to build your application and make it ready for production.

$ sencha app build production

Now enable USB Debugging on your phone and you can deploy on your Android!

Thanks