Create A Mobile Web App with Sencha Touch @ jamespearce
Single device Multi device Sedentary user Mobile user * Declarative Imperative Thin client Thick client Documents Applications * or supine, or sedentary, or passive, or...
A badge for all these ways the 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 Creation Linkability User Experience Architecture
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)
Introducing Sencha Touch
A JavaScript framework for building rich mobile apps with 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 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] });
Common patterns 2 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-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/
The Valley App http://senchalearn.github.com/valley http://sencha.com/x/dr
https://github.com/ senchalearn/valley
Development sequence 1 Structure the app 5 Detail page 2 Layout the UI 6 Add a map 3 Model the data 7 More data 4 Load the list 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.js va  =  new  Ext.Application({        launch:  function()  {                this.viewport  =  new  Ext.Panel({                        layout:  'card',                        fullscreen:  true,                        html:  "Hello  world!"                        });        } });
2 Layout the UI toolbar toolbar dataList listCard detailCard na.viewport
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]                });        } });
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' });
detailCard this.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 namespace this.data  =  {};
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"},        ] });
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'                }        } });
4 Load the list this.listCardDataList  =  new  Ext.List({        store:  this.data.restaurants,        itemTpl:  '{name}' });
A more interesting template itemTpl:        '<img  class="photo"  src="{photo_url}"  width="40"  height="40"/>'  +        '{name}<br/>'  +        '<img  src="{rating_img_url_small}"/>&nbsp;'  +        '<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 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')                                );                        }                }        } });
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'}                        );                }        }] });
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>'        ] });
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 toolbar toolbar dataList dataList detailCard listCard detailTabs va.viewport
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 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()        );
Improve the tab bar this.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 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...
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', ...
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' });
Pull-to-refresh this.ListCardDataList  =  Ext.extend(Ext.List,  {        ...        plugins:  [{                ptype:  'pullrefresh'        }] });
8 Customize theme
http://sass-lang.com/
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; }
$> sudo gem install compass http://rubyinstaller.org/
$> 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)
Start by copying sencha-touch.scss
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
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 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' });
_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%);
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 5 Detail page 2 Layout the UI 6 Add a map 3 Model the data 7 More data 4 Load the list 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/cy http://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.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
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
built with Apps vs Web technology
James Pearce @ jamespearce

Create a mobile web app with Sencha Touch

  • 1.
    Create A MobileWeb App with Sencha Touch @ jamespearce
  • 2.
    Single device Multi device Sedentary user Mobile user * Declarative Imperative Thin client Thick client Documents Applications * or supine, or sedentary, or passive, or...
  • 3.
    A badge forall these ways the web is changing
  • 4.
    HTML5 is anew 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
  • 5.
  • 6.
    Consumption vs Creation Linkability User Experience Architecture
  • 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)
  • 10.
  • 11.
    A JavaScript framework for building rich mobile apps with web standards
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 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)  {..} });
  • 29.
    Carousels var  carousel  =  new  Ext.Carousel({        direction:  'horizontal',        indicator:  true,        items:  [                ..        ] });
  • 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        }] });
  • 33.
  • 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/
  • 35.
  • 36.
  • 37.
    Development sequence 1 Structurethe app 5 Detail page 2 Layout the UI 6 Add a map 3 Model the data 7 More data 4 Load the list 8 Customize theme
  • 38.
  • 39.
    index.html <!doctype  html> <html>        <head>                <title>Valley  Guide</title>        </head> <body></body> </html>
  • 40.
    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"  />
  • 41.
    app.js va  =  new  Ext.Application({        launch:  function()  {                this.viewport  =  new  Ext.Panel({                        layout:  'card',                        fullscreen:  true,                        html:  "Hello  world!"                        });        } });
  • 42.
    2 Layout theUI 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' });
  • 45.
    detailCard this.detailCardToolbar  =  new  Ext.Toolbar({        dock:  'top',        title:  '...' }); this.detailCard  =  new  Ext.Panel({        dockedItems:  [this.detailCardToolbar] });
  • 46.
    3 Model thedata http://api.yelp.com/business_review_search ?ywsid=YELP_KEY &term=Restaurants &location=Silicon%20Valley
  • 48.
  • 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":  [            ...",          ],          ...
  • 50.
  • 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 ofthose 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 thelist this.listCardDataList  =  new  Ext.List({        store:  this.data.restaurants,        itemTpl:  '{name}' });
  • 54.
    A more interestingtemplate itemTpl:        '<img  class="photo"  src="{photo_url}"  width="40"  height="40"/>'  +        '{name}<br/>'  +        '<img  src="{rating_img_url_small}"/>&nbsp;'  +        '<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>
  • 56.
    Get images resized... ...width="40"  height="40"  />
  • 57.
  • 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 amap toolbar toolbar dataList dataList detailCard listCard detailTabs va.viewport
  • 63.
    6 Add amap 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] });
  • 64.
    Tab titles this.detailCard  =  new  Ext.Panel({        ...        title:  'Info' }); this.detailMap  =  new  Ext.Map({        title:  'Map' });
  • 65.
    Google Maps script <script  type="text/javascript"    src="http://maps.google.com/maps/api/js?sensor=true"> </script>
  • 66.
    Update the maplocation 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 tabbar this.detailTabs  =  new  Ext.TabPanel({        dockedItems:  [this.detailCardToolbar],        items:  [this.detailCard,  this.detailMap],        tabBar:  {                ui:  'light',                layout:  {  pack:  'center'  }        } });
  • 68.
  • 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 intoa ‘class’ this.ListCardDataList  =  Ext.extend(Ext.List,  {        store:  null,        itemTpl:                '<img  class="photo"  ...
  • 71.
    Instantiate that 3times 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 intotabs 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' });
  • 74.
    Pull-to-refresh this.ListCardDataList  =  Ext.extend(Ext.List,  {        ...        plugins:  [{                ptype:  'pullrefresh'        }] });
  • 75.
  • 76.
  • 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 geminstall compass http://rubyinstaller.org/
  • 79.
    $> compass -v Compass0.11.1 (Antares) Copyright (c) 2008-2011 Chris Eppstein Released under the MIT License. $> sass -v Sass 3.1.1 (Brainy Betty)
  • 80.
    Start by copyingsencha-touch.scss
  • 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
  • 83.
  • 84.
    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; ...
  • 85.
    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;
  • 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%);
  • 90.
  • 91.
    Sass is asuperset 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; } ...
  • 92.
    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; }
  • 93.
  • 94.
    Development sequence 1 Structurethe app 5 Detail page 2 Layout the UI 6 Add a map 3 Model the data 7 More data 4 Load the list 8 Customize theme
  • 95.
  • 96.
    And if we’dhad 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 TakingYelp data o ine Taking images o ine - src.sencha.io to generate cross-origin B64 Detecting network connection changes http://sencha.com/x/df
  • 99.
  • 100.
    built with Apps vsWeb technology
  • 101.