View
11.795
Download
0
Category
Tags:
Preview:
DESCRIPTION
Citation preview
Create A Mobile Web Appwith
Sencha Touch@ jamespearce
Single deviceSedentary userDeclarativeThin client Documents
Multi deviceMobile userImperative
Thick client Applications
*
* or supine, or sedentary, or passive, or...
A badge for all these waysthe web is changing
HTML5 is a new version of HTML4, XHTML1, and DOM Level 2 HTML addressing many of the issues of those specifications while at the same time enhancing (X)HTML
to more adequately address Web applications.
- WHATWG Wiki
What is an app?
Consumption vs CreationLinkability
User ExperienceArchitecture
JavaScript
Semantic HTML
CSS Styling & Layout
WebFont Video Audio Graphics
Workers & Parallel
Processing
File SystemsDatabasesApp Caches
Cross-AppMessaging
Device Access
Camera
Location
Contacts
SMS
Orientation
Gyro
Network
HTTP
AJAX
Events
Sockets
SSL
(all the elements of a modern application platform)
IntroducingSencha Touch
A JavaScript frameworkfor building
rich mobile appswith web standards
http://sencha.com/touch
Components
Lists
Theming
Forms
Scrolling
Touch Events
Data access & MVC
Charts
Kitchen Sink
http://sencha.com/x/5e
Hello World
http://sencha.com/x/d5
<!DOCTYPE html><html> <head> <title>Hello World</title>
<script src="lib/touch/sencha-‐touch.js"></script> <script src="app/app.js"></script> <link href="lib/touch/resources/css/sencha-‐touch.css" rel="stylesheet" type="text/css" /> </head> <body></body></html>
new Ext.Application({
launch: function() {
new Ext.Panel({ fullscreen: true, dockedItems: [{xtype:'toolbar', title:'My First App'}], layout: 'fit', styleHtmlContent: true, html: '<h2>Hello World!</h2>I did it!' });
}
});
Lists
var list = new Ext.List({ store: store, itemTpl: '{firstName} {lastName}', grouped: true, indexBar: true});
Nested Lists
var list = new Ext.NestedList({ store: store, displayField: 'name', title: 'My List', updateTitleText: true, getDetailCard: function(record, parent) {..}});
Carousels
var carousel = new Ext.Carousel({ direction: 'horizontal', indicator: true, items: [ .. ]});
Sheets
var sheet = new Ext.ActionSheet({ items: [ { text: 'Delete draft', ui: 'decline' }, { text: 'Save draft' }, { text: 'Cancel', } ]});
sheet.show();
Common patterns1
var list = new Ext.List({ store: store, itemTpl: '{firstName} {lastName}', grouped: true, indexBar: true});
var panel = new Ext.Panel({ fullscreen: true, layout: 'fit', items: [list]});
Common patterns2
var panel = new Ext.Panel({ fullscreen: true, layout: 'fit', items: [{ xtype: 'list', store: store, itemTpl: '{firstName} {lastName}', grouped: true, indexBar: true }]});
A more complex app
Pre-requisitesSencha Touch SDK:
http://sencha.com/products/touch/
Yelp developer API key: http://www.yelp.com/developers/getting_started/
api_overview
Install Sass and Compass: http://sass-lang.com/download.html
http://compass-style.org/install/
The Valley App
http://senchalearn.github.com/valleyhttp://sencha.com/x/dr
https://github.com/senchalearn/valley
Development sequence
1 Structure the app
2 Layout the UI
3 Model the data
4 Load the list
5 Detail page
6 Add a map
7 More data
8 Customize theme
1 Structure the app
index.html
<!doctype html><html> <head> <title>Valley Guide</title> </head>
<body></body></html>
index.html
<script src="lib/touch/sencha-‐touch.js"></script>
<script src="app/yelp.js"></script><script src="app/app.js"></script>
<link href="lib/touch/resources/css/sencha-‐touch.css" rel="stylesheet" type="text/css" />
app.jsva = new Ext.Application({
launch: function() {
this.viewport = new Ext.Panel({
layout: 'card', fullscreen: true, html: "Hello world!" });
}
});
na.viewport
2 Layout the UI
listCard detailCard
toolbar toolbar
dataList
The app...var va = new Ext.Application({ launch: function() {
this.listCard = new Ext.Panel({ html: 'I am the list' });
this.detailCard = new Ext.Panel({ html: 'I am the detail' });
this.viewport = new Ext.Panel({ layout: 'card', fullscreen: true, cardSwitchAnimation: 'slide', items: [this.listCard, this.detailCard] }); }});
listCardthis.listCardToolbar = new Ext.Toolbar({ dock: 'top', title: 'Valley Guide'});
this.listCardDataList = new Ext.List({ store: null, itemTpl: ''});
this.listCard = new Ext.Panel({ dockedItems: [this.listCardToolbar], items: [this.listCardDataList], layout: 'fit'});
detailCardthis.detailCardToolbar = new Ext.Toolbar({ dock: 'top', title: '...'});
this.detailCard = new Ext.Panel({ dockedItems: [this.detailCardToolbar]});
3 Model the data
http://api.yelp.com/business_review_search?ywsid=YELP_KEY&term=Restaurants&location=Silicon%20Valley
Apigee console
"businesses": [ { "rating_img_url" : "http://media4.px.yelpcdn.com/...", "country_code" : "US", "id" : "BHpAlynD9dIGIaQDRqHCTA", "is_closed" : false, "city" : "Nashville", "mobile_url" : "http://mobile.yelp.com/biz/...", "review_count" : 50, "zip" : "11231", "state" : "TN", "latitude" : 40.675758, "address1" : "253 Conover St", "address2" : "", "address3" : "", "phone" : "7186258211", "state_code" : "TN", "categories": [ ...", ], ...
A data namespacethis.data = {};
The ‘Business’ modelthis.data.Business = Ext.regModel('', { fields: [ {name: "id", type: "int"}, {name: "name", type: "string"}, {name: "latitude", type: "string"}, {name: "longitude", type: "string"}, {name: "address1", type: "string"}, {name: "address2", type: "string"}, {name: "address3", type: "string"}, {name: "phone", type: "string"}, {name: "state_code", type: "string"}, {name: "mobile_url", type: "string"}, {name: "rating_img_url_small", type: "string"}, {name: "photo_url", type: "string"}, ]});
A store of those modelsthis.data.restaurants = new Ext.data.Store({ model: this.data.Business, autoLoad: true, proxy: { type: 'scripttag', url: 'http://api.yelp.com/business_review_search' + '?ywsid=' + YELP_KEY + '&term=Restaurant' + '&location=Silicon%20Valley', reader: { type: 'json', root: 'businesses' } }});
4 Load the listthis.listCardDataList = new Ext.List({ store: this.data.restaurants, itemTpl: '{name}'});
A more interesting templateitemTpl: '<img class="photo" src="{photo_url}" width="40" height="40"/>' + '{name}<br/>' + '<img src="{rating_img_url_small}"/> ' + '<small>{address1}</small>'
Hack the style<style> .photo { float:left; margin:0 8px 16px 0; border:1px solid #ccc; -‐webkit-‐box-‐shadow: 0 2px 4px #777; }</style>
Get images resized...
...width="40" height="40" />
...in the cloud
src="http://src.sencha.io/40/{photo_url}" width="40" height="40"/>
5 Detail pagethis.listCardDataList = new Ext.List({ store: this.data.restaurants, itemTpl: ... listeners: { selectionchange: function (selectionModel, records) { if (records[0]) { va.viewport.setActiveItem(va.detailCard); va.detailCardToolbar.setTitle( records[0].get('name') ); } } }});
A back buttonthis.detailCardToolbar = new Ext.Toolbar({ dock: 'top', title: '...', items: [{ text: 'Back', ui: 'back', handler: function () { va.viewport.setActiveItem( va.listCard, {type: 'slide', direction: 'right'} ); } }]});
Detail templatethis.detailCard = new Ext.Panel({ dockedItems: [this.detailCardToolbar], styleHtmlContent: true, cls: 'detail', tpl: [ '<img class="photo" src="{photo_url}" width="100" height="100"/>', '<h2>{name}</h2>', '<div class="info">', '{address1}<br/>', '<img src="{rating_img_url_small}"/>', '</div>', '<div class="phone x-‐button">', '<a href="tel:{phone}">{phone}</a>', '</div>', '<div class="link x-‐button">', '<a href="{mobile_url}">Read more</a>', '</div>' ]});
A little styling.x-‐html h2 { margin-‐bottom:0;}.phone, .link { clear:both; font-‐weight:bold; display:block; text-‐align:center; margin-‐top:8px;}
6 Add a map
va.viewport
listCard detailTabs
toolbar toolbar
dataList dataListdetailCard
6 Add a map
va.viewport.setActiveItem(va.detailTabs);
...
this.detailMap = new Ext.Map({});
this.detailTabs = new Ext.TabPanel({ dockedItems: [this.detailCardToolbar], items: [this.detailCard, this.detailMap]});
va.viewport = new Ext.Panel({ layout: 'card', fullscreen: true, cardSwitchAnimation: 'slide', items: [this.listCard, this.detailTabs]});
Tab titles
this.detailCard = new Ext.Panel({ ... title: 'Info'});
this.detailMap = new Ext.Map({ title: 'Map'});
Google Maps script
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
Update the map locationselectionchange: function (selectionModel, records) { ... var map = va.detailMap.map;
if (!map.marker) { map.marker = new google.maps.Marker(); map.marker.setMap(map); }
map.setCenter( new google.maps.LatLng( records[0].get('latitude'), records[0].get('longitude') ) );
map.marker.setPosition( map.getCenter() );
Improve the tab barthis.detailTabs = new Ext.TabPanel({ dockedItems: [this.detailCardToolbar], items: [this.detailCard, this.detailMap],
tabBar: { ui: 'light', layout: { pack: 'center' } }
});
7 More?
More data...['hotels', 'bars', 'restaurants'].forEach( function (type) { va.data[type] = new Ext.data.Store({ model: va.data.Business, autoLoad: true, proxy: { type: 'scripttag', url: 'http://api.yelp.com/business_review_search' + '?ywsid=' + YELP_KEY + '&term=' + type + '&location=Silicon%20Valley', reader: { type: 'json', root: 'businesses' } } });});
Make list into a ‘class’this.ListCardDataList = Ext.extend(Ext.List, { store: null, itemTpl: '<img class="photo" ...
Instantiate that 3 timesthis.stayCardDataList = new this.ListCardDataList({ store: this.data.hotels});
this.eatCardDataList = new this.ListCardDataList({ store: this.data.restaurants});
this.drinkCardDataList = new this.ListCardDataList({ store: this.data.bars});
Consider lazy-loading...
Turn container into tabs toothis.listCard = new Ext.TabPanel({ items: [ this.stayCardDataList, this.eatCardDataList, this.drinkCardDataList ], tabBar: { ui: 'light', layout: { pack: 'center' }, dock: 'bottom' }, cardSwitchAnimation: 'flip',...
And add titles & iconsthis.stayCardDataList = new this.ListCardDataList({ store: this.data.hotels, title: 'Stay', iconCls: 'home'});
this.eatCardDataList = new this.ListCardDataList({ store: this.data.restaurants, title: 'Eat', iconCls: 'locate'});
this.drinkCardDataList = new this.ListCardDataList({ store: this.data.bars, title: 'Drink', iconCls: 'star'});
Pull-to-refreshthis.ListCardDataList = Ext.extend(Ext.List, { ... plugins: [{ ptype: 'pullrefresh' }]});
8 Customize theme
http://sass-lang.com/
/* SCSS */
$blue: #3bbfce;$margin: 16px;
.content-navigation { border-color: $blue; color: darken($blue, 9%);}
.border { padding: $margin / 2; margin: $margin / 2; border-color: $blue;}
/* CSS */
.content-navigation { border-color: #3bbfce; color: #2b9eab;}
.border { padding: 8px; margin: 8px; border-color: #3bbfce;}
Variables
$> sudo gem install compass
http://rubyinstaller.org/
$> compass -v
Compass 0.11.1 (Antares)Copyright (c) 2008-2011 Chris EppsteinReleased under the MIT License.
$> sass -v
Sass 3.1.1 (Brainy Betty)
Start by copying sencha-touch.scss
config.rbdir = File.dirname(__FILE__)
load File.join(dir, '..', 'lib', 'touch', 'resources', 'themes')
# Compass configurationssass_path = dircss_path = direnvironment = :productionoutput_style = :compressed
# or :nested, :expanded, :compact
Compile...$> cd theming
$> compass compile valley.scss create valley.css
$> compass compile valley.scss identical valley.css
[edit file]$> compass compile valley.scss overwrite valley.css
$> compass watch valley.scss >>> Change detected to: valley.scss overwrite valley.css
Link...<link href="theming/valley.css" rel="stylesheet" type="text/css" />
valley.scss@import 'sencha-‐touch/default/all';
@include sencha-‐panel;@include sencha-‐buttons;@include sencha-‐sheet;@include sencha-‐tabs;@include sencha-‐toolbar;@include sencha-‐list;@include sencha-‐list-‐pullrefresh;@include sencha-‐layout;@include sencha-‐loading-‐spinner;...
valley.scss$base-‐color: #9D9E00;
@import 'sencha-‐touch/default/all';
@include sencha-‐panel;@include sencha-‐buttons;@include sencha-‐sheet;@include sencha-‐tabs;@include sencha-‐toolbar;@include sencha-‐list;@include sencha-‐list-‐pullrefresh;@include sencha-‐layout;@include sencha-‐loading-‐spinner;
Choose own icons$base-‐color: #9D9E00;$include-‐default-‐icons: false;
@import 'sencha-‐touch/default/all';
@include sencha-‐panel;@include sencha-‐buttons;...
@include pictos-‐iconmask('briefcase1');@include pictos-‐iconmask('heart');@include pictos-‐iconmask('music1');
Specify iconClsthis.stayCardDataList = new this.ListCardDataList({ store: this.data.hotels, title: 'Stay', iconCls: 'briefcase1'});
this.eatCardDataList = new this.ListCardDataList({ store: this.data.restaurants, title: 'Eat', iconCls: 'heart'});
this.drinkCardDataList = new this.ListCardDataList({ store: this.data.bars, title: 'Drink', iconCls: 'music1'});
_variables.scss
$include-html-style: true;
$include-default-icons: true;
$include-form-sliders: true;
$include-floating-panels: true;
$include-default-uis: true;
$include-highlights: true;
$include-border-radius: true;
$basic-slider: false;
$base-color: #354F6E;
$base-gradient: 'matte';
$alert-color: red;
$confirm-color: #92cf00;
$page-bg-color: #eee;
$global-row-height: 2.6em;
$active-color: darken( saturate($base-color, 55%), 10%);
http://dev.sencha.com/deploy/touch/docs/theme/
Sass is a superset of CSS$base-‐color: #9D9E00;$include-‐default-‐icons: false;
@import 'sencha-‐touch/default/all';...
@include pictos-‐iconmask('briefcase1');@include pictos-‐iconmask('heart');@include pictos-‐iconmask('music1');
.photo { float:left; margin:0 8px 16px 0; border:1px solid #ccc; -‐webkit-‐box-‐shadow: 0 2px 4px #777;}...
WebFonts@import url(http://fonts.googleapis.com/css?family=Voltaire);
.x-‐toolbar-‐title { font-‐family: Voltaire; font-‐weight: normal; font-‐size: 1.7em; line-‐height: 1.7em; letter-‐spacing: 0.05em;}
Done?
Development sequence
1 Structure the app
2 Layout the UI
3 Model the data
4 Load the list
5 Detail page
6 Add a map
7 More data
8 Customize theme
A ‘responsive’ app...
http://sencha.com/x/cv
And if we’d had time...
Add to home screen - Icon - Splash screen
Hybrid app; PhoneGap / NimbleKit - Contacts API - Geolocation
http://sencha.com/x/cyhttp://sencha.com/x/de
O!ine app$> phantomjs confess.js http://github/valley/
CACHE MANIFEST
# This manifest was created by confess.js# Time: Wed Sep 14 2011 10:14:45 GMT-‐0700 (PDT)# User-‐agent: Mozilla/5.0 ...
CACHE:app/app.jsapp/yelp.jshttp://cdn.sencha.io/touch/1.1.0/sencha-‐touch.jshttp://maps.google.com/maps/api/js?sensor=truehttp://maps.gstatic.com/intl/en_us/mapfiles/api-‐3/6/4/main.jstheming/valley.css
NETWORK:*
http://github.com/jamesgpearce/confess
O!ine data
Taking Yelp data o!ine
Taking images o!ine - src.sencha.io to generate cross-origin B64
Detecting network connection changes
http://sencha.com/x/df
Weinre
http://phonegap.github.com/weinre
Apps vs Web technologybuilt with
James Pearce @ jamespearce
Recommended