Post on 12-Jan-2015
description
James Pearce Director, Developer Relations @ jamespearce jamesp@sencha.com
Build AMobile Web App
with
CSSHTML JS
http://www.sencha.com/products/touch
Sencha TouchA JavaScript framework for building
rich mobile apps with web standards
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/
CityBars
http://sencha.com/x/bu
http://sencha.com/x/bv
Development sequence
1 Structure the app
2 Layout the UI
3 Model the data
4 Load the list
5 Attach events
6 Detail page
7 Add mapping
8 Customize theme
1 Structure the app
index.html
<!doctype html><html> <head> <title>City Guide</title> </head>
<body></body></html>
index.html<script src="lib/touch/sencha-‐touch.js" type="text/javascript"></script>
<script type="text/javascript"> YELP_KEY = 'G3HueY_I5a8WZX-‐_bFo3Mw'; ...</script>
<script src="app/app.js" type="text/javascript"></script>
<link href="lib/touch/resources/css/sencha-‐touch.css" rel="stylesheet" type="text/css" />
app.jscb = new Ext.Application({
launch: function() { new Ext.Panel({
layout : 'card', fullscreen: true,
html: "Hello world!" }); }
});
cb.cards
2 Layout the UI
listCard detailCard
toolbar toolbar
dataList
index.htmlcb = new Ext.Application({ launch: function() { cb.cards = new Ext.Panel({ layout : 'card', fullscreen: true,
cardSwitchAnimation: 'slide',
items: [ {id: 'listCard' ...}, {id: 'detailCard' ...} ] }); }});
listCard{ id: 'listCard', layout: 'fit', dockedItems: [{ dock : 'top', xtype: 'toolbar', title: 'Please wait' }], items: [{ id: 'dataList', xtype: 'list', store: null, itemTpl: '{name}' }]}
detailCard{ id: 'listCard', dockedItems: [{ dock : 'top', xtype: 'toolbar', title: '' }]}
3 Model the data
http://api.yelp.com/business_review_search?ywsid=YELP_KEY&term=BUSINESS_TYPE&location=CITY
Apigee console
"businesses": [ { "rating_img_url" : "http://media4.px.yelpcdn.com/...", "country_code" : "US", "id" : "BHpAlynD9dIGIaQDRqHCTA", "is_closed" : false, "city" : "Brooklyn", "mobile_url" : "http://mobile.yelp.com/biz/...", "review_count" : 50, "zip" : "11231", "state" : "NY", "latitude" : 40.675758, "address1" : "253 Conover St", "address2" : "", "address3" : "", "phone" : "7186258211", "state_code" : "NY", "categories": [ ...", ], ...
index.html<script type="text/javascript"> YELP_KEY = 'G3HueY_I5a8WZX-‐_bFo3Mw'; DEFAULT_CITY = 'New York'; BUSINESS_TYPE = 'Bars';</script>
app.jsExt.regModel("Business", { 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"}, ]});
app.jsExt.regStore("businesses", { model: 'Business', autoLoad: true, proxy: { type: 'scripttag', url: 'http://api.yelp.com/business_review_search' + '?ywsid=' + YELP_KEY + '&term=' + escape(BUSINESS_TYPE) + '&location=' + escape(city) , reader: { type: 'json', root: 'businesses' } },
app.jslisteners: {
'afterrender': function () {
cb.getCity(function (city) {
cb.getBusinesses(city, function (store) {
console.log(store.data.items);
}); });
}}
app.jsgetCity: function (callback) { callback(DEFAULT_CITY);},
getBusinesses: function (city, callback) { Ext.regModel("Business", {...});
Ext.regStore("businesses", { ... listeners: { 'load': function (store) { callback(store); } } })}
4 Load the list
app.jsvar cards = this;cards.listCard = cards.getComponent('listCard');cards.dataList = cards.listCard.getComponent('dataList');cards.detailCard = cards.getComponent('detailCard');
cb.getCity(function (city) {
cards.listCard.getDockedItems()[0] .setTitle(city + ' ' + BUSINESS_TYPE);
cb.getBusinesses(city, function (store) {
cards.dataList.bindStore(store); cards.setActiveItem(cards.listCard);
});});
app.jscards.setLoading(true);
...
cards.setLoading(false);
5 Attach events
A more interesting list template
‘selection’ event to switch to detail
app.jsitemTpl: '<img class="photo" src="{photo_url}" width="40" height="40"/>' + '{name}<br/>' + '<img src="{rating_img_url_small}"/> ' + '<small>{address1}</small>'
index.html<style> .photo { float:left; margin:0 8px 16px 0; border:1px solid #ccc; -‐webkit-‐box-‐shadow: 0 2px 4px #777; }</style>
app.jslisteners: {
selectionchange: function (selectionModel, records) {
if (records[0]) { cb.cards.setActiveItem(cb.cards.detailCard); cb.cards.detailCard.update(records[0].data); }
}
}
6 Detail page
Template for the detail card
Back button with tap to switch back to list
app.jsstyleHtmlContent: 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>']
app.jsdockedItems: [{ dock : 'top', xtype: 'toolbar', title: '', items: [{ text: 'Back', ui: 'back', listeners: { tap: function () { cb.cards.setActiveItem( cb.cards.listCard, {type:'slide', direction: 'right'} ); } } }]}],
index.html.x-‐html h2 { margin-‐bottom:0;}.phone, .link { clear:both; font-‐weight:bold; display:block; text-‐align:center; margin-‐top:8px;}
7 Add mapping
Change detail page to tabbed
Add map control
Update data on both tabs
app.js{ id: 'detailCard', xtype: 'tabpanel', dockedItems: [...] items: [ { title: 'Contact', tpl: [...] }, { title: 'Map', xtype: 'map', ... marker: new google.maps.Marker() } ]}
index.html<script src="http://maps.google.com/maps/api/js?sensor=true" type="text/javascript"></script>
app.js{ xtype: 'map', ... update: function (data) {
this.map.setCenter( new google.maps.LatLng(data.latitude, data.longitude )); this.marker.setPosition( this.map.getCenter() );
this.marker.setMap(this.map); },}
app.jstabBar: { dock: 'top', ui: 'light', layout: { pack: 'center' }}
app.jsupdate: function(data) {
Ext.each(this.items.items, function(item) { item.update(data); });
this.getDockedItems()[0].setTitle(data.name);
}
8 Customize theme
Sass & Compass
Compile & link new 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)
citybars.scss$base-‐color: #666;$base-‐gradient: 'glossy';$include-‐default-‐icons: false;
@import 'sencha-‐touch/default/all';
@include sencha-‐panel;@include sencha-‐buttons;@include sencha-‐tabs;@include sencha-‐toolbar;@include sencha-‐list;@include sencha-‐layout;@include sencha-‐loading-‐spinner;
$> compass compile citybars.scss
overwrite citybars.css
index.html<link href="theming/citybars.css" rel="stylesheet" type="text/css"/>
James Pearce Director, Developer Relations @ jamespearce jamesp@sencha.com