SlideShare a Scribd company logo
Craig Walker, Chief Technology Officer   www.xero.com
Craig Walker, Chief Technology Officer   www.xero.com
What is Xero?




http://www.xero.com/signup/
Sencha Touch
Sencha Touch
Sencha Touch
BlackBerry 6
BlackBerry 6   2nd half 2011
Lots of frameworks…
•   Zepto.js
•   DynamicX
•   SproutCore
•   XUI
•   Appcelerator
•   iUI
•   iWebKit
•   jQuery Mobile
•   jQTouch
•   And lots more…
Sencha Touch
What is Sencha Touch?
   A JavaScript framework for
building rich mobile applications
Why Sencha Touch?
•   Cross-platform
•   Looks native, feels native
•   Faster, cheaper, easier to build with
•   Highly customisable
•   Flexible deployment
•   HTML5/CSS3 goodness
Yes - but WHY Sencha Touch?
Tap !== Click
Touch Event Manager
•   Built on native events
•   Abstracted for performance
•   Multi-touch & gesture support
Touch Event Manager

     Ext.fly("el").on({
       tap: function() {
          alert("You tapped me");
       },
       pinch: function() {
          alert("You pinched me");
       },
       swipe: function() {
          alert("Stop touching me...")
       }
     });
     28
Sencha Touch
Scroll Event Manager
•   Scrolling with momentum &
    bounce physics
•   Native & natural
•   Hardware accelerated
UI Toolkit
Buttons
Forms
Sliders
Pickers
Lists
Nested
Lists
Toolbars
Tabs
Panels
Carousels
Maps
Overlays
Layouts
•   Container layout specifies how its children
    components are rendered
fit
card
vbox
hbox
MVC
               Routes

              Controllers




                            Models
      Views
                            Stores
Theming
•   SASS & Compass
    – sass-lang.com
    – compass-style.org

•   CSS3 is awesome – SCSS is awesomer
SCSS                                   CSS
$blue: #3bbfce;                         /* line 5, variables.scss */
$margin: 16px;                          .example1 {
$padding: 4px;                            border-color: #3bbfce;
                                        }
.example1 {
  border-color: $blue;                  /* line 9, variables.scss */
}                                       .example2 {
                                          margin: 16px;
.example2 {                               color: #3bbfce;
  margin: $margin;                      }
  color: $blue;
}                                       /* line 14, variables.scss */
                                        .example3 {
.example3 {                               margin: 32px;
  margin: ($margin / 2px) * $padding;   }
}
SCSS                                   CSS
@mixin add-child($color) {                        /* line 18, mixins.scss */
  color: $color;                                  .example {
  background-color: lighten($color, 50);            color: red;
                                                    background-color: white;
    .child {                                      }
      padding: 5px;                               /* line 5, mixins.scss */
                                                  .example .child {
        &:first {                                   padding: 5px;
           background-color: darken($color, 10)   }
        };                                        /* line 8, mixins.scss */
                                                  .example .child:first {
        span {                                      background-color: #cc0000;
          color: mix($color, blue);               }
        }                                         /* line 12, mixins.scss */
    }                                             .example .child span {
}                                                   color: #7f007f;
                                                  }
.example {
  @include add-child(#F00);
}
SCSS                                      CSS
@import "compass";                      /* line 5, gradients.scss */
                                        .button {
$width: 100px;                            width: 100px;
                                        }
.button {                               /* line 8, gradients.scss */
  width: $width;                        .button .round {
                                          -moz-border-radius: 5px;
    .round {                              -webkit-border-radius: 5px;
      @include border-radius(5px);        -o-border-radius: 5px;
    }                                     -ms-border-radius: 5px;
                                          -khtml-border-radius: 5px;
    .linear {                             border-radius: 5px;
      @include linear-gradient(         }
         color-stops(white, #c39 30%,   /* line 12, gradients.scss */
           #b7f 70%, #aaa)              .button .linear {
      );                                  background-image: -webkit-gradient(
    }                                       linear, 0% 0%, 0% 100%,
}                                           color-stop(0%, #ffffff),
                                            color-stop(50%, #cc3399),
                                            color-stop(100%, #bb77ff));
                                          background-image: -moz-linear-gradient(top,
                                            #ffffff 0%, #cc3399 50%, #bb77ff 100%);
                                          background-image: linear-gradient(top,
                                            #ffffff 0%, #cc3399 50%, #bb77ff 100%);
                                        }
Let’s look at some code...
Demo: From Desktop to Mobile
index.html



<!doctype html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Xero Help</title>

  <link rel="stylesheet" href="resources/css/xerohelp.css" type="text/css">
</head>
<body>
  <script type="text/javascript" src="lib/sencha-touch-debug.js"></script>
  <script type="text/javascript" src="app/xerohelp.js"></script>
</body>
</html>
app/xerohelp.js
// utils
document.write('<script type="text/javascript" src="app/utils/string.js"></script>');

// application
document.write('<script type="text/javascript" src="app/routes.js"></script>');
document.write('<script type="text/javascript" src="app/app.js"></script>');

// models
document.write('<script type="text/javascript" src="app/models/TOC.js"></script>');
document.write('<script type="text/javascript" src="app/models/HelpFile.js"></script>');

// views
document.write('<script   type="text/javascript"   src="app/views/Viewport.js"></script>');
document.write('<script   type="text/javascript"   src="app/views/TOCPanel.js"></script>');
document.write('<script   type="text/javascript"   src="app/views/HelpCarousel.js"></script>');
document.write('<script   type="text/javascript"   src="app/views/HelpPanel.js"></script>');

// controllers
document.write('<script type="text/javascript" src="app/controllers/help.js"></script>');
app/routes.js




Ext.Router.draw(function(map) {
  map.connect("/help/home", {controller: 'help', action: 'home'});
  map.connect("/help/:id", {controller: 'help', action: 'show'});
});
app/app.js

Ext.regApplication({
  name: "XERO",

  defaultUrl: '/help/home',
  defaultTarget: "viewport",

  icon: 'resources/images/icon.png',
  glossOnIcon: false,

  region: "NZ",
  apiUrl: "http://help.stage.xero.com/api",

  launch: function() {
    this.viewport = new XERO.Viewport({
      application: this
    });
  }
});
XERO.Viewport = Ext.extend(Ext.Panel, {

  id: 'viewport',                                    app/views/viewport.js
  layout: 'card',
  fullscreen: true,

  initComponent: function() {
    Ext.apply(this, {
      dockedItems: [{
         xtype: "toolbar",
         dock : "top",
         title: "Help Center",
         itemId: "help-toolbar"
      },{
         xtype: "toolbar",
         dock: "bottom",
         items: [{
            xtype: "button",
            text: "Index",
            handler: this.onTOCButtonTap,
            scope: this
         },{ xtype: "spacer" }, {
            xtype: "button",
            text: "Switch Region",
            handler: this.onSwitchRegionTap,
            scope: this
         }]
      }]
    });
    XERO.Viewport.superclass.initComponent.apply(this, arguments);
  }, ...
setTitle: function(title) {
     this.down("#help-toolbar").setTitle(title);
  },                                                  app/views/viewport.js
  onTOCButtonTap: function() {
     if(! this.tocPanel) {
       this.tocPanel = Ext.create({
         xtype: "tocpanel"
       });
     }
     this.tocPanel.show();
  },

  onSwitchRegionTap: function() {
    if(! this.regionPicker) {
      this.regionPicker = new Ext.Picker({
        slots: [{
           name : 'region',
           title: 'Switch Region',
           data : [
             {text: 'New Zealand', value: "NZ"},
             {text: 'Australia', value: "AUS"},
             {text: 'United Kingdom', value: "UK"},
             {text: 'Global', value: "INT"}
           ]
        }]
      });
    }
    this.regionPicker.show();
  }
});
app/controllers/help.js

Ext.regController("help", {

  home: function(request) {
     this.show(Ext.apply(request, {
       id: "home"
     }));
  },

  show: function(request) {
    if(! this.helpCarousel) {
      this.helpCarousel = this.render({
        xtype: 'helpcarousel'
      });
    }

      XERO.viewport.setActiveItem(this.helpCarousel);
      XERO.viewport.setTitle("Loading...");

      this.helpCarousel.loadHelpPage(request.id, request.historyUrl);
  }
});
app/models/helpfile.js

Ext.regModel("HelpFile", {
  fields: [
     { name: "id", type: "int" },
     "abstractText",
     "body",
     "heading",
     "helpPage",
     { name: "createdDateUTC", type: "date", format: "M$" },
     { name: "success", type: "boolean" }
  ]
  /*,
  validations: [
     { type: "presence", field: "helpPage" }
  ],

  hasMany: [
     { model: "Region", name: "regions" }
  ]
  */
});
app/models/toc.js
Ext.regModel("TOC", {
  fields: [
     "id",
     "helpPage",
     "href",
     "text"
  ],

  proxy: {
    type: 'scripttag',
    url: String.format("{0}/toc/", XERO.apiUrl),
    reader: {
      type: "tree",
      root: "items"
    }
  }
});

(function() {
  new Ext.data.TreeStore({
    storeId: "TOCStore",
    model: "TOC",
    autoLoad: true
  });
}());
XERO.views.HelpCarousel = Ext.extend(Ext.Carousel, {

  cls: "cards",
                                                      app/views/helpcarousel.js
  layout: "fit",

  initComponent: function() {
     XERO.views.HelpCarousel.superclass.initComponent.apply(this, arguments);
  },

  onRender: function() {
    XERO.views.HelpCarousel.superclass.onRender.apply(this, arguments);

       if(this.el) {
         this.mon(this.el, {
           tap: this.onTap,
           click: Ext.emptyFn,
           delegate: "a",
           stopEvent: true,
           scope: this
         });

           this.mon(this, {
             cardswitch: function() {
                this.onHelpSwitch(this.getActiveItem().helpPage);
             },
             scope: this
           });
       }
  },
loadHelpPage: function(id, href) {
  var bookmark;                                app/views/helpcarousel.js
  if(href.indexOf("$") != -1) {
    bookmark = href.right(href.length - href.indexOf("$") - 1);
  }

     var item = this.getComponent(id);
     if(item) {
       this.setActiveItem(item);
         if(bookmark){
         item.scrollToBookmark(bookmark);
         }
     } else {
       var panel = this.add({
         xtype: "helppanel",
         id: id,
         goToBookmark: bookmark,
         listeners: {
           helploaded: this.onHelpSwitch,
           single: true,
           scope: this
         }
       });
       this.doComponentLayout();
       this.setActiveItem(panel);
     }
},
onTap: function(e, target) {
    var id = target.getAttribute('xero:id'),        app/views/helpcarousel.js
      type = target.getAttribute('xero:type'),
      bookmark = target.getAttribute('xero:bookmark'),
      url = target.getAttribute('xero:path');

      if(type && type.toLowerCase() == "help") {
        if(bookmark) {
          this.getActiveItem().scrollToBookmark(bookmark);
        } else {
          Ext.dispatch({
            controller: "help",
            action: "show",
            id: id,
            historyUrl: target.href
          });
        }
      } else {
        if(target.href.toLowerCase().startsWith("mailto:")) {
          location.href = target.href;
        } else {
          window.open(target.href);
        }
      }
  }
});

Ext.reg('helpcarousel', XERO.views.HelpCarousel);
app/views/helppanel.js

XERO.views.HelpPanel = Ext.extend(Ext.Panel, {

  cls: "help-panel",
  layout: "fit",
  scroll: "vertical",
  styleHtmlContent: true,
  goToBookmark: null,
  html: '<div class="loading-indicator"> </div>',

  initComponent: function() {
     this.addEvents("helploaded");
     XERO.views.HelpPanel.superclass.initComponent.apply(this, arguments);
  },

  onRender: function() {
    XERO.views.HelpPanel.superclass.onRender.apply(this, arguments);
    this.loadHelpPage();
  },...
loadHelpPage: function() {
    Ext.util.JSONP.request({
      url: String.format("{0}/help/", XERO.apiUrl),       app/views/helppanel.js
      callbackKey: "callback",
      params: {
         helpPage: this.id
      },
      callback: function(doc) {
         this.helpPage = doc;
         this.title = doc.heading;
         this.update(this.helpPage.body);

           if(this.goToBookmark) {
             this.scrollToBookmark(this.goToBookmark);
           }

           this.fireEvent("helploaded", this.helpPage);
         },
         scope: this
       });
  },

  scrollToBookmark: function(bookmark) {
     var el = this.getEl().down(String.format('a[name="{0}"]', bookmark));
     if (el) {
       this.scrollIntoView(el);
     }
  },
 });

Ext.reg('helppanel', XERO.views.HelpPanel);
XERO.views.TOCPanel = Ext.extend(Ext.NestedList, {
  title: "Index",
  showAnimation: {                                     app/views/helppanel.js
     type: "slide",
     direction: "up"
  },
  floating: true,

  initComponent: function() {
    this.store = Ext.getStore("TOCStore");
    this.toolbar = {...};

      XERO.views.TOCPanel.superclass.initComponent.apply(this, arguments);

      this.mon(this, {
        selectionchange: function(list, selection) {
           var record = selection[0];
           if(record.get("leaf") === true) {
             Ext.dispatch({
               controller: "help",
               action: "show",
               id: record.get("helpPage"),
               historyUrl: String.format("/help/{0}", record.get("helpPage"))
             });
             this.hide();
           }
        },
        scope: this
      });
  }
});
Touchable help...
going native
•   Cross-platform
•   Open source
•   Extensible
•   Instantiates chromeless
    web view
•   Adds JavaScript access
    to native APIs
Web App



PhoneGap   etc…
Native APIs
•   Device identification
•   Network access
•   Sensors
•   Camera/image sources
•   Contacts
•   File access
Everything else?
•   HTML for layout
•   JavaScript for accessing device APIs
•   CSS for look & feel
•   Offline storage for standalone clients
•   Ajax, JSONP for syncing to the cloud
    –   Runs on file:// protocol which is exempt from same-origin
        policy
•   Just use Sencha Touch!
PhoneGap Build
Tips & Tricks
Tools
•   Browsers – Safari the best (unfortunately)
•   Web Inspector
•   RemoteJS (Android debugging)
•   Souders’ bookmarklets
    – stevesouders.com/mobileperf

•   Jdrop
    – jdrop.org
Object-oriented
•   Use namespaces to define your library
•   Define components – code for reusability
•   Extend first, write plugins second (not at all
    if possible)
Events rock!
•   Use events to communicate between
    components
•   Use event delegation
Override appropriately
•   Do not edit the library files
•   DO NOT EDIT THE LIBRARY FILES!
•   Use an overrides file if you need to override
    the framework
•   Do the same with CSS (but you should be
    using cls, ui properties)
Define a directory structure
•   Break your code into small files
•   Use build tools to compile for performance
•   Use sencha-touch-debug.js during dev (but
    never prod!)
•   Keep the framework up-to-date – upgrade as
    often as you can
Worry about performance
•   Understand client-side performance rules &
    use them
•   Latency bad
•   JIT compilers – compilation time relates to
    size of file the method exists in
•   Keep DOM light
•   Destroy components that aren’t visible
•   concatenate, minify, compress!
Theming/Layouts
•   Use SCSS
•   Remove unnecessary CSS by only
    including required SCSS mixins
•   Understand XTemplate
•   Understand doComponentLayout
Sencha.com

Read the forums




                        Read the docs



           Read the source!
Any questions?




                 www.xero.com
www.xero.com/careers/

More Related Content

Sencha Touch

  • 1. Craig Walker, Chief Technology Officer www.xero.com
  • 2. Craig Walker, Chief Technology Officer www.xero.com
  • 8. BlackBerry 6 2nd half 2011
  • 9. Lots of frameworks… • Zepto.js • DynamicX • SproutCore • XUI • Appcelerator • iUI • iWebKit • jQuery Mobile • jQTouch • And lots more…
  • 11. What is Sencha Touch? A JavaScript framework for building rich mobile applications
  • 12. Why Sencha Touch? • Cross-platform • Looks native, feels native • Faster, cheaper, easier to build with • Highly customisable • Flexible deployment • HTML5/CSS3 goodness
  • 13. Yes - but WHY Sencha Touch?
  • 15. Touch Event Manager • Built on native events • Abstracted for performance • Multi-touch & gesture support
  • 16. Touch Event Manager Ext.fly("el").on({ tap: function() { alert("You tapped me"); }, pinch: function() { alert("You pinched me"); }, swipe: function() { alert("Stop touching me...") } }); 28
  • 18. Scroll Event Manager • Scrolling with momentum & bounce physics • Native & natural • Hardware accelerated
  • 23. Lists
  • 27. Maps
  • 29. Layouts • Container layout specifies how its children components are rendered
  • 30. fit
  • 31. card
  • 32. vbox
  • 33. hbox
  • 34. MVC Routes Controllers Models Views Stores
  • 35. Theming • SASS & Compass – sass-lang.com – compass-style.org • CSS3 is awesome – SCSS is awesomer
  • 36. SCSS CSS $blue: #3bbfce; /* line 5, variables.scss */ $margin: 16px; .example1 { $padding: 4px; border-color: #3bbfce; } .example1 { border-color: $blue; /* line 9, variables.scss */ } .example2 { margin: 16px; .example2 { color: #3bbfce; margin: $margin; } color: $blue; } /* line 14, variables.scss */ .example3 { .example3 { margin: 32px; margin: ($margin / 2px) * $padding; } }
  • 37. SCSS CSS @mixin add-child($color) { /* line 18, mixins.scss */ color: $color; .example { background-color: lighten($color, 50); color: red; background-color: white; .child { } padding: 5px; /* line 5, mixins.scss */ .example .child { &:first { padding: 5px; background-color: darken($color, 10) } }; /* line 8, mixins.scss */ .example .child:first { span { background-color: #cc0000; color: mix($color, blue); } } /* line 12, mixins.scss */ } .example .child span { } color: #7f007f; } .example { @include add-child(#F00); }
  • 38. SCSS CSS @import "compass"; /* line 5, gradients.scss */ .button { $width: 100px; width: 100px; } .button { /* line 8, gradients.scss */ width: $width; .button .round { -moz-border-radius: 5px; .round { -webkit-border-radius: 5px; @include border-radius(5px); -o-border-radius: 5px; } -ms-border-radius: 5px; -khtml-border-radius: 5px; .linear { border-radius: 5px; @include linear-gradient( } color-stops(white, #c39 30%, /* line 12, gradients.scss */ #b7f 70%, #aaa) .button .linear { ); background-image: -webkit-gradient( } linear, 0% 0%, 0% 100%, } color-stop(0%, #ffffff), color-stop(50%, #cc3399), color-stop(100%, #bb77ff)); background-image: -moz-linear-gradient(top, #ffffff 0%, #cc3399 50%, #bb77ff 100%); background-image: linear-gradient(top, #ffffff 0%, #cc3399 50%, #bb77ff 100%); }
  • 39. Let’s look at some code...
  • 40. Demo: From Desktop to Mobile
  • 41. index.html <!doctype html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Xero Help</title> <link rel="stylesheet" href="resources/css/xerohelp.css" type="text/css"> </head> <body> <script type="text/javascript" src="lib/sencha-touch-debug.js"></script> <script type="text/javascript" src="app/xerohelp.js"></script> </body> </html>
  • 42. app/xerohelp.js // utils document.write('<script type="text/javascript" src="app/utils/string.js"></script>'); // application document.write('<script type="text/javascript" src="app/routes.js"></script>'); document.write('<script type="text/javascript" src="app/app.js"></script>'); // models document.write('<script type="text/javascript" src="app/models/TOC.js"></script>'); document.write('<script type="text/javascript" src="app/models/HelpFile.js"></script>'); // views document.write('<script type="text/javascript" src="app/views/Viewport.js"></script>'); document.write('<script type="text/javascript" src="app/views/TOCPanel.js"></script>'); document.write('<script type="text/javascript" src="app/views/HelpCarousel.js"></script>'); document.write('<script type="text/javascript" src="app/views/HelpPanel.js"></script>'); // controllers document.write('<script type="text/javascript" src="app/controllers/help.js"></script>');
  • 43. app/routes.js Ext.Router.draw(function(map) { map.connect("/help/home", {controller: 'help', action: 'home'}); map.connect("/help/:id", {controller: 'help', action: 'show'}); });
  • 44. app/app.js Ext.regApplication({ name: "XERO", defaultUrl: '/help/home', defaultTarget: "viewport", icon: 'resources/images/icon.png', glossOnIcon: false, region: "NZ", apiUrl: "http://help.stage.xero.com/api", launch: function() { this.viewport = new XERO.Viewport({ application: this }); } });
  • 45. XERO.Viewport = Ext.extend(Ext.Panel, { id: 'viewport', app/views/viewport.js layout: 'card', fullscreen: true, initComponent: function() { Ext.apply(this, { dockedItems: [{ xtype: "toolbar", dock : "top", title: "Help Center", itemId: "help-toolbar" },{ xtype: "toolbar", dock: "bottom", items: [{ xtype: "button", text: "Index", handler: this.onTOCButtonTap, scope: this },{ xtype: "spacer" }, { xtype: "button", text: "Switch Region", handler: this.onSwitchRegionTap, scope: this }] }] }); XERO.Viewport.superclass.initComponent.apply(this, arguments); }, ...
  • 46. setTitle: function(title) { this.down("#help-toolbar").setTitle(title); }, app/views/viewport.js onTOCButtonTap: function() { if(! this.tocPanel) { this.tocPanel = Ext.create({ xtype: "tocpanel" }); } this.tocPanel.show(); }, onSwitchRegionTap: function() { if(! this.regionPicker) { this.regionPicker = new Ext.Picker({ slots: [{ name : 'region', title: 'Switch Region', data : [ {text: 'New Zealand', value: "NZ"}, {text: 'Australia', value: "AUS"}, {text: 'United Kingdom', value: "UK"}, {text: 'Global', value: "INT"} ] }] }); } this.regionPicker.show(); } });
  • 47. app/controllers/help.js Ext.regController("help", { home: function(request) { this.show(Ext.apply(request, { id: "home" })); }, show: function(request) { if(! this.helpCarousel) { this.helpCarousel = this.render({ xtype: 'helpcarousel' }); } XERO.viewport.setActiveItem(this.helpCarousel); XERO.viewport.setTitle("Loading..."); this.helpCarousel.loadHelpPage(request.id, request.historyUrl); } });
  • 48. app/models/helpfile.js Ext.regModel("HelpFile", { fields: [ { name: "id", type: "int" }, "abstractText", "body", "heading", "helpPage", { name: "createdDateUTC", type: "date", format: "M$" }, { name: "success", type: "boolean" } ] /*, validations: [ { type: "presence", field: "helpPage" } ], hasMany: [ { model: "Region", name: "regions" } ] */ });
  • 49. app/models/toc.js Ext.regModel("TOC", { fields: [ "id", "helpPage", "href", "text" ], proxy: { type: 'scripttag', url: String.format("{0}/toc/", XERO.apiUrl), reader: { type: "tree", root: "items" } } }); (function() { new Ext.data.TreeStore({ storeId: "TOCStore", model: "TOC", autoLoad: true }); }());
  • 50. XERO.views.HelpCarousel = Ext.extend(Ext.Carousel, { cls: "cards", app/views/helpcarousel.js layout: "fit", initComponent: function() { XERO.views.HelpCarousel.superclass.initComponent.apply(this, arguments); }, onRender: function() { XERO.views.HelpCarousel.superclass.onRender.apply(this, arguments); if(this.el) { this.mon(this.el, { tap: this.onTap, click: Ext.emptyFn, delegate: "a", stopEvent: true, scope: this }); this.mon(this, { cardswitch: function() { this.onHelpSwitch(this.getActiveItem().helpPage); }, scope: this }); } },
  • 51. loadHelpPage: function(id, href) { var bookmark; app/views/helpcarousel.js if(href.indexOf("$") != -1) { bookmark = href.right(href.length - href.indexOf("$") - 1); } var item = this.getComponent(id); if(item) { this.setActiveItem(item); if(bookmark){ item.scrollToBookmark(bookmark); } } else { var panel = this.add({ xtype: "helppanel", id: id, goToBookmark: bookmark, listeners: { helploaded: this.onHelpSwitch, single: true, scope: this } }); this.doComponentLayout(); this.setActiveItem(panel); } },
  • 52. onTap: function(e, target) { var id = target.getAttribute('xero:id'), app/views/helpcarousel.js type = target.getAttribute('xero:type'), bookmark = target.getAttribute('xero:bookmark'), url = target.getAttribute('xero:path'); if(type && type.toLowerCase() == "help") { if(bookmark) { this.getActiveItem().scrollToBookmark(bookmark); } else { Ext.dispatch({ controller: "help", action: "show", id: id, historyUrl: target.href }); } } else { if(target.href.toLowerCase().startsWith("mailto:")) { location.href = target.href; } else { window.open(target.href); } } } }); Ext.reg('helpcarousel', XERO.views.HelpCarousel);
  • 53. app/views/helppanel.js XERO.views.HelpPanel = Ext.extend(Ext.Panel, { cls: "help-panel", layout: "fit", scroll: "vertical", styleHtmlContent: true, goToBookmark: null, html: '<div class="loading-indicator"> </div>', initComponent: function() { this.addEvents("helploaded"); XERO.views.HelpPanel.superclass.initComponent.apply(this, arguments); }, onRender: function() { XERO.views.HelpPanel.superclass.onRender.apply(this, arguments); this.loadHelpPage(); },...
  • 54. loadHelpPage: function() { Ext.util.JSONP.request({ url: String.format("{0}/help/", XERO.apiUrl), app/views/helppanel.js callbackKey: "callback", params: { helpPage: this.id }, callback: function(doc) { this.helpPage = doc; this.title = doc.heading; this.update(this.helpPage.body); if(this.goToBookmark) { this.scrollToBookmark(this.goToBookmark); } this.fireEvent("helploaded", this.helpPage); }, scope: this }); }, scrollToBookmark: function(bookmark) { var el = this.getEl().down(String.format('a[name="{0}"]', bookmark)); if (el) { this.scrollIntoView(el); } }, }); Ext.reg('helppanel', XERO.views.HelpPanel);
  • 55. XERO.views.TOCPanel = Ext.extend(Ext.NestedList, { title: "Index", showAnimation: { app/views/helppanel.js type: "slide", direction: "up" }, floating: true, initComponent: function() { this.store = Ext.getStore("TOCStore"); this.toolbar = {...}; XERO.views.TOCPanel.superclass.initComponent.apply(this, arguments); this.mon(this, { selectionchange: function(list, selection) { var record = selection[0]; if(record.get("leaf") === true) { Ext.dispatch({ controller: "help", action: "show", id: record.get("helpPage"), historyUrl: String.format("/help/{0}", record.get("helpPage")) }); this.hide(); } }, scope: this }); } });
  • 58. Cross-platform • Open source • Extensible • Instantiates chromeless web view • Adds JavaScript access to native APIs
  • 60. Native APIs • Device identification • Network access • Sensors • Camera/image sources • Contacts • File access
  • 61. Everything else? • HTML for layout • JavaScript for accessing device APIs • CSS for look & feel • Offline storage for standalone clients • Ajax, JSONP for syncing to the cloud – Runs on file:// protocol which is exempt from same-origin policy • Just use Sencha Touch!
  • 64. Tools • Browsers – Safari the best (unfortunately) • Web Inspector • RemoteJS (Android debugging) • Souders’ bookmarklets – stevesouders.com/mobileperf • Jdrop – jdrop.org
  • 65. Object-oriented • Use namespaces to define your library • Define components – code for reusability • Extend first, write plugins second (not at all if possible)
  • 66. Events rock! • Use events to communicate between components • Use event delegation
  • 67. Override appropriately • Do not edit the library files • DO NOT EDIT THE LIBRARY FILES! • Use an overrides file if you need to override the framework • Do the same with CSS (but you should be using cls, ui properties)
  • 68. Define a directory structure • Break your code into small files • Use build tools to compile for performance • Use sencha-touch-debug.js during dev (but never prod!) • Keep the framework up-to-date – upgrade as often as you can
  • 69. Worry about performance • Understand client-side performance rules & use them • Latency bad • JIT compilers – compilation time relates to size of file the method exists in • Keep DOM light • Destroy components that aren’t visible • concatenate, minify, compress!
  • 70. Theming/Layouts • Use SCSS • Remove unnecessary CSS by only including required SCSS mixins • Understand XTemplate • Understand doComponentLayout
  • 71. Sencha.com Read the forums Read the docs Read the source!
  • 72. Any questions? www.xero.com