sencha touch

Post on 17-Nov-2014

10.746 Views

Category:

Technology

3 Downloads

Preview:

Click to see full reader

DESCRIPTION

Talk I did at the Wellington JS Meetup on 24 Feb 2011 on Sencha Touch and PhoneGap.

TRANSCRIPT

Craig Walker, Chief Technology Officer www.xero.com

Craig Walker, Chief Technology Officer www.xero.com

http://www.xero.com/signup/

What is Xero?

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

Scroll Event Manager

• Scrolling with momentum & bounce physics

• Native & natural• Hardware accelerated

UI Toolkit

Buttons

FormsSliders

Pickers

Lists

NestedLists

ToolbarsTabs

PanelsCarousels

Maps

Overlays

Layouts

• Container layout specifies how its children components are rendered

fit

card

vbox

hbox

ModelsViews

Controllers

Stores

RoutesMVC

Theming

• SASS & Compass – sass-lang.com– compass-style.org

• CSS3 is awesome – SCSS is awesomer

SCSS CSS

$blue: #3bbfce;$margin: 16px;$padding: 4px;

.example1 { border-color: $blue;}

.example2 { margin: $margin; color: $blue;}

.example3 { margin: ($margin / 2px) * $padding;}

/* line 5, variables.scss */.example1 { border-color: #3bbfce;}

/* line 9, variables.scss */.example2 { margin: 16px; color: #3bbfce;}

/* line 14, variables.scss */.example3 { margin: 32px;}

@mixin add-child($color) { color: $color; background-color: lighten($color, 50); .child { padding: 5px; &:first { background-color: darken($color, 10) }; span { color: mix($color, blue); } }}

.example { @include add-child(#F00);}

/* line 18, mixins.scss */.example { color: red; background-color: white;}/* line 5, mixins.scss */.example .child { padding: 5px;}/* line 8, mixins.scss */.example .child:first { background-color: #cc0000;}/* line 12, mixins.scss */.example .child span { color: #7f007f;}

SCSS CSS

@import "compass";

$width: 100px;

.button { width: $width; .round { @include border-radius(5px); } .linear { @include linear-gradient( color-stops(white, #c39 30%, #b7f 70%, #aaa) ); }}

/* line 5, gradients.scss */.button { width: 100px;}/* line 8, gradients.scss */.button .round { -moz-border-radius: 5px; -webkit-border-radius: 5px; -o-border-radius: 5px; -ms-border-radius: 5px; -khtml-border-radius: 5px; border-radius: 5px;}/* line 12, gradients.scss */.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%);}

SCSS CSS

Let’s look at some code...

Demo: From Desktop to Mobile

<!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>

index.html

// utilsdocument.write('<script type="text/javascript" src="app/utils/string.js"></script>');

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

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

// viewsdocument.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>');

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

app/xerohelp.js

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

app/routes.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 }); } });

app/app.js

XERO.Viewport = Ext.extend(Ext.Panel, {

id: 'viewport', 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); }, ...

app/views/viewport.js

setTitle: function(title) { this.down("#help-toolbar").setTitle(title); }, 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/views/viewport.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/controllers/help.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/helpfile.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 });}());

app/models/toc.js

XERO.views.HelpCarousel = Ext.extend(Ext.Carousel, {

cls: "cards", 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 }); } },

app/views/helpcarousel.js

loadHelpPage: function(id, href) { var bookmark; 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); } },

app/views/helpcarousel.js

app/views/helpcarousel.js onTap: function(e, target) { var id = target.getAttribute('xero:id'), 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);

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(); },...

app/views/helppanel.js

loadHelpPage: function() { Ext.util.JSONP.request({ url: String.format("{0}/help/", XERO.apiUrl), 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);

app/views/helppanel.js

XERO.views.TOCPanel = Ext.extend(Ext.NestedList, { title: "Index", showAnimation: { 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 }); }});

app/views/helppanel.js

Touchable help...

going native

• Cross-platform• Open source• Extensible• Instantiates chromeless

web view• Adds JavaScript access

to native APIs

PhoneGap

Web App

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

• Offline 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

top related