More Related Content Similar to Create a mobile web app with Sencha Touch Similar to Create a mobile web app with Sencha Touch (20) More from James Pearce (16) Create a mobile web app with Sencha Touch2. Single device Multi device
Sedentary user Mobile user
*
Declarative Imperative
Thin client Thick client
Documents Applications
* or supine, or sedentary, or passive, or...
4. 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
7. WebFont Video Audio Graphics
Device Access
Camera CSS Styling & Layout Network
Location HTTP
JavaScript
Contacts AJAX
SMS Semantic HTML Events
Orientation Sockets
File Systems Workers &
Cross-App
Gyro Databases Parallel SSL
Messaging
App Caches Processing
(all the elements of a modern application platform)
24. <!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>
25. 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!'
});
}
});
27. Lists
var
list
=
new
Ext.List({
store:
store,
itemTpl:
'{firstName}
{lastName}',
grouped:
true,
indexBar:
true
});
28. Nested Lists
var
list
=
new
Ext.NestedList({
store:
store,
displayField:
'name',
title:
'My
List',
updateTitleText:
true,
getDetailCard:
function(record,
parent)
{..}
});
30. Sheets
var
sheet
=
new
Ext.ActionSheet({
items:
[
{
text:
'Delete
draft',
ui:
'decline'
},
{
text:
'Save
draft'
},
{
text:
'Cancel',
}
]
});
sheet.show();
31. Common patterns
1
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]
});
32. Common patterns
2
var
panel
=
new
Ext.Panel({
fullscreen:
true,
layout:
'fit',
items:
[{
xtype:
'list',
store:
store,
itemTpl:
'{firstName}
{lastName}',
grouped:
true,
indexBar:
true
}]
});
34. Pre-requisites
Sencha 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/
41. app.js
va
=
new
Ext.Application({
launch:
function()
{
this.viewport
=
new
Ext.Panel({
layout:
'card',
fullscreen:
true,
html:
"Hello
world!"
});
}
});
42. 2 Layout the UI
toolbar toolbar
dataList
listCard detailCard
na.viewport
43. 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]
});
}
});
44. listCard
this.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'
});
46. 3 Model the data
http://api.yelp.com/business_review_search
?ywsid=YELP_KEY
&term=Restaurants
&location=Silicon%20Valley
49. "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":
[
...",
],
...
51. The ‘Business’ model
this.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"},
]
});
52. A store of those models
this.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'
}
}
});
53. 4 Load the list
this.listCardDataList
=
new
Ext.List({
store:
this.data.restaurants,
itemTpl:
'{name}'
});
54. A more interesting template
itemTpl:
'<img
class="photo"
src="{photo_url}"
width="40"
height="40"/>'
+
'{name}<br/>'
+
'<img
src="{rating_img_url_small}"/> '
+
'<small>{address1}</small>'
55. Hack the style
<style>
.photo
{
float:left;
margin:0
8px
16px
0;
border:1px
solid
#ccc;
-‐webkit-‐box-‐shadow:
0
2px
4px
#777;
}
</style>
58. 5 Detail page
this.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')
);
}
}
}
});
59. A back button
this.detailCardToolbar
=
new
Ext.Toolbar({
dock:
'top',
title:
'...',
items:
[{
text:
'Back',
ui:
'back',
handler:
function
()
{
va.viewport.setActiveItem(
va.listCard,
{type:
'slide',
direction:
'right'}
);
}
}]
});
60. Detail template
this.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>'
]
});
61. A little styling
.x-‐html
h2
{
margin-‐bottom:0;
}
.phone,
.link
{
clear:both;
font-‐weight:bold;
display:block;
text-‐align:center;
margin-‐top:8px;
}
62. 6 Add a map
toolbar toolbar
dataList dataList
detailCard
listCard detailTabs
va.viewport
63. 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]
});
66. Update the map location
selectionchange:
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()
);
67. Improve the tab bar
this.detailTabs
=
new
Ext.TabPanel({
dockedItems:
[this.detailCardToolbar],
items:
[this.detailCard,
this.detailMap],
tabBar:
{
ui:
'light',
layout:
{
pack:
'center'
}
}
});
69. 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'
}
}
});
});
70. Make list into a ‘class’
this.ListCardDataList
=
Ext.extend(Ext.List,
{
store:
null,
itemTpl:
'<img
class="photo"
...
71. Instantiate that 3 times
this.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...
72. Turn container into tabs too
this.listCard
=
new
Ext.TabPanel({
items:
[
this.stayCardDataList,
this.eatCardDataList,
this.drinkCardDataList
],
tabBar:
{
ui:
'light',
layout:
{
pack:
'center'
},
dock:
'bottom'
},
cardSwitchAnimation:
'flip',
...
73. And add titles & icons
this.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'
});
77. Variables
/* SCSS */ /* CSS */
$blue: #3bbfce; .content-navigation {
$margin: 16px; border-color: #3bbfce;
color: #2b9eab;
.content-navigation { }
border-color: $blue;
color: .border {
darken($blue, 9%); padding: 8px;
} margin: 8px;
border-color: #3bbfce;
.border { }
padding: $margin / 2;
margin: $margin / 2;
border-color: $blue;
}
78. $> sudo gem install compass
http://rubyinstaller.org/
79. $> compass -v
Compass 0.11.1 (Antares)
Copyright (c) 2008-2011 Chris Eppstein
Released under the MIT License.
$> sass -v
Sass 3.1.1 (Brainy Betty)
81. config.rb
dir
=
File.dirname(__FILE__)
load
File.join(dir,
'..',
'lib',
'touch',
'resources',
'themes')
#
Compass
configurations
sass_path
=
dir
css_path
=
dir
environment
=
:production
output_style
=
:compressed
#
or
:nested,
:expanded,
:compact
82. 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
87. 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');
88. Specify iconCls
this.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'
});
89. _variables.scss
$include-html-style: true; $base-color: #354F6E;
$include-default-icons: true; $base-gradient: 'matte';
$include-form-sliders: true; $alert-color: red;
$include-floating-panels: true; $confirm-color: #92cf00;
$include-default-uis: true; $page-bg-color: #eee;
$include-highlights: true; $global-row-height: 2.6em;
$include-border-radius: true; $active-color: darken(
saturate($base-color, 55%),
$basic-slider: false; 10%);
91. 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;
}
...
96. And if we’d had time...
Add to home screen
- Icon
- Splash screen
Hybrid app; PhoneGap / NimbleKit
- Contacts API
- Geolocation
http://sencha.com/x/cy
http://sencha.com/x/de
97. 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.js
app/yelp.js
http://cdn.sencha.io/touch/1.1.0/sencha-‐touch.js
http://maps.google.com/maps/api/js?sensor=true
http://maps.gstatic.com/intl/en_us/mapfiles/api-‐3/6/4/main.js
theming/valley.css
NETWORK:
*
http://github.com/jamesgpearce/confess
98. 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