the city bars app with sencha touch 2

Post on 25-Apr-2015

14.101 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

 

TRANSCRIPT

The City Bars Appwith

Sencha Touch 2

A JavaScript framework for building rich mobile apps with web standards

Sencha Touch

http://sencha.com/x/d5http://sencha.com/x/ed

Basically...

Get a WebKit-based desktop browser

Get some emulators & real devices

Download the Sencha Touch 2 PR2 SDK

Develop against a local web server

Optional, but highly recommended!

http://sencha.com/touch

stylesheet

script framework

Introducing theCity Bars App

http://senchalearn.github.com/citybars2

http://sencha.com/x/ee

Pre-requisites

 Yelp developer API key:  http://www.yelp.com/developers/

 Install Sass and Compass:  http://sass-lang.com/download.html

http://compass-style.org/install/

http://github.com/senchalearn/citybars2

Development sequence

1 App Architecture

2 UI Structure

3 Data Modeling

4 List Binding

5 List Event Handler

6 Detail Page

7 Customize Theme

Application Architecture

1_app_architecture

application

JS + CSS in SDK

entry point

<!doctype  html><html>        <head>

               <title>City  Guide</title>

               <script  src="lib/touch2/sencha-­‐touch-­‐all-­‐debug.js"></script>                <link  href="lib/touch2/resources/css/sencha-­‐touch.css"                              rel="stylesheet"  />

               <script  src="app/app.js"></script>

       </head>        <body></body></html>

index.html

our app

don’t panic

JS + CSS*

*or from CDN

yay! HTML5

var  CB;Ext.application({

       launch:  function()  {                CB  =  this;                CB.cards  =  Ext.create('Ext.Panel',  {                        fullscreen:  true,                        html:  'Hello  world'                });        }

});config object

global namespace

instantiates application

create main UI panel

launch event

app.js

UI Structure

2_ui_structure

CB.cards

listCard detailCard

toolbar toolbar

dataList

click

back

var  CB;Ext.application({

       launch:  function()  {                CB  =  this;                CB.cards  =  Ext.create('Ext.Panel',  {                        fullscreen:  true,                        layout:  'card',                        items:  [{                                id:  'listCard',                                html:  'List'                        },  {                                id:  'detailCard',                                html:  'Detail'                        }]                });        }

});

id-based ref

UI children

variable ref

how children lay out

<aside>about layouts

and components

Layoutscard

fit hbox

vbox

Child component patterns I

var  list  =  new  Ext.List({        store:  store,        ...});

var  panel  =  new  Ext.Panel({        items:  [list,  ...],        ...});

reference component by var

instantiate component

Child component patterns II

var  list  =  Ext.create('Ext.List',  {        store:  store,        ...});

var  panel  =  Ext.create('Ext.Panel',  {        items:  [list,  ...]        ...});

preferable in ST2

Child component patterns III

var  panel  =  Ext.create('Ext.Panel',  {        items:  [                {                        xtype:  'list',                        store:  store,                        ...                },  ...        ],        ...});

deferred creation*

* a lightweight object until then

</aside>

{        //  the  list  card        id:  'listCard',        layout:  'fit',        items:  [{                //  main  top  toolbar                xtype:  'toolbar',                docked:  'top',                title:  'Please  wait'                //  will  get  added  once  city  known        },  {                //  the  list  itself                //  gets  bound  to  the  store  once  city  known                id:  'dataList',                xtype:  'list'        }]}

* list will be bound to a store later

list*

The list card

list should fill whole card

docked top toolbar

{        //  the  details  card        id:  'detailCard',        items:  [{                //  detail  page  also  has  a  toolbar                docked  :  'top',                xtype:  'toolbar',                title:  ''        },  {                //  textual  detail        }]}

detail page to come later...

The detail card

another docked toolbar*

* title will be dynamically set

note:list already

scrollable

Data modeling

3_data_modeling

http://api.yelp.com/business_review_search

?ywsid=YELP_KEY

&term=BUSINESS_TYPE

&location=CITY

The YELP API...

free, rate limited

business type, and city name

...returns a nested JSON array

mmm, json

‘businesses’ array

Apigee API 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":  [...],        ...    },  ...]

some fields areuseful for our app

Ext.define("Business",  {        extend:  "Ext.data.Model",        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"},        ]});

give the ‘class’ a name

Create a data model

extending base model

and with these named, typed fields

<aside>

</aside>

Models can be associated

with other models

Fields can also have default values,

conversion functions, and validation

var  store  =  Ext.create('Ext.data.Store',  {        model:  "Business",        ...});

Create a model store

Think of a store as a ‘table’ of model instance ‘rows’

create the store

containing thistype of model

var  store  =  Ext.create('Ext.data.Store',  {        model:  'Business',        autoLoad:  true,        proxy:  {                //  call  Yelp  to  get  business  data                type:  'scripttag',                url:  'http://api.yelp.com/business_review_search'  +                          '?ywsid='  +  YELP_KEY  +                          '&term='  +  escape(BUSINESS_TYPE)  +                          '&location='  +  escape(DEFAULT_CITY)                ,                reader:  {                        type:  'json',                        root:  'businesses'                }        }});

Configure data sourceloads as soon as possible

construct API URL

source

read array from inside JSON

JSONP

<script>        YELP_KEY  =  'G3HueY_I5a8WZX-­‐_bAAAA';        DEFAULT_CITY  =  'San  Francisco';        BUSINESS_TYPE  =  'Bars';</script>

Create constants

please change this!

We can make the proxy URL dynamic,

which would allow geolocation.

But this requires an async

callback sequence.

getCity:  function  (callback)  {        callback(DEFAULT_CITY);        //  this  could  now  be  a  geo  lookup  to        //  get  the  nearest  city},

getBusinesses:  function  (city,  callback)  {

       Ext.define("Business",  {                ...        });                var  store  =  Ext.create('Ext.data.Store',  {                ...        });

}

Two-phase async sequence

and this will need to fire the callback with store when it autoloads

the data codewe just wrote

use this in the URL

call whenUI ready

var  store  =  Ext.create('Ext.data.Store',  {        ...        listeners:  {                //  when  the  records  load,  fire  the  callback                load:  function  (store)  {                        callback(store);                }        }}); fire the callback with store

when loaded

eventlisteners

store

records

cheeky callback

List Binding

4_list_binding

//  get  the  cityCB.getCity(function  (city)  {

       //  then  use  Yelp  to  get  the  businesses        CB.getBusinesses(city,  function  (store)  {

               //  then  bind  data  to  list  and  show  it                CB.cards.query('#dataList')[0].setStore(store);

       });});

bind the store to itget dataList by its id

our 2 async functions

but we haz records!

:-(

CB.getCity(function  (city)  {

       cards.query('#listCard  toolbar')[0]                .setTitle(city  +  '  '  +  BUSINESS_TYPE);

       ...

now title will always match city

another component query

{        id:  'dataList',        xtype:  'list',        store:  null,        itemTpl:  '{name}'}

model fields in curly braces

List items are templated

Ext.create('Ext.LoadMask',  Ext.getBody(),  {        store:  store,        msg:  ''});

instantiate mask

Spinner bound to store

over body

will show when store is loading

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

HTML allowed

<style>        .photo  {                float:left;                margin:0  8px  16px  0;                border:1px  solid  #ccc;                -­‐webkit-­‐box-­‐shadow:  0  2px  4px  #777;        }</style>

Hack the style

...width="40"  height="40"  />

seems likea waste

src.sencha.io<img  src="http://src.sencha.io/40/{photo_url}"          width="40"  height="40"/>

4 times smaller

List Event Handler

5_list_event_handler

{        id:  'dataList',        ...        listeners:  {                selectionchange:  function  (selectionModel,  records)  {                        //  if  selection  made,  slide  to  detail  card                        if  (records[0])  {

                               CB.cards.setActiveItem(1);

                               CB.cards.getActiveItem().setData(                                        records[0].data                                );

                       }                }        }}

when list itemsare selected

selection

also fires on deselection

detail card

apply record data...

...to detail page template

A back button

items:  [{        //  detail  page  also  has  a  toolbar        docked  :  'top',        xtype:  'toolbar',        title:  '',        items:  [{                //  containing  a  back  button                //  that  slides  back  to  list  card                text:  'Back',                ui:  'back',                listeners:  {                        tap:  function  ()  {                                CB.cards.setActiveItem(0);                        }                }        }],  ...

back to list

when tapped

arrow style

children of toolbarsare implicitlyxtype: ‘button’

Detail Page

6_detail_page

{        //  textual  detail        styleHtmlContent:  true,        cls:  'detail',        tpl:  [                '<img  class="photo"  src="{photo_url}"  />',                '<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>'        ]}]

style this card as regular HTML

CSS class for styling

template fora whole panel

:-(

CB.cards.getActiveItem().setData(        records[0].data);

setData does not cascade into child items!

Remember this?

setData:  function  (data)  {        this.query('toolbar')[0].setTitle(data.name);        this.query('[cls="detail"]')[0].setData(data);},

set title on toolbar

Override setData

apply data to template on inner panel

good

not so much

A little styling

.x-­‐html  h2  {        margin-­‐bottom:0;}.phone,  .link  {        clear:both;        font-­‐weight:bold;        display:block;        text-­‐align:center;        margin-­‐top:8px;}

.detail  {        -­‐webkit-­‐box-­‐orient:  vertical;}.detail  .photo  {        float:none;}

formattingthe buttons

temporaryfixes

One final tweak

{        //  textual  detail        cls:  'detail',        styleHtmlContent:  true,        ...

...to outer card

move frominner panel...

{        //  the  details  card        id:  'detailCard',        styleHtmlContent:  true,

complete with<h2> </h2>

Development sequence

1 App Architecture

2 UI Structure

3 Data Modeling

4 List Binding

5 List Event Handler

6 Detail Page

7 Customize Theme

Other ideas...

‘Responsive’ Apps

http://sencha.com/x/cv

Packaging

Add to home screen - Icon - Splash screen

Hybrid app; PhoneGap / NimbleKit - Contacts API - Geolocation

http://sencha.com/x/cyhttp://sencha.com/x/de

Geolocation

O!ine app

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

Debugging

http://phonegap.github.com/weinre

James Pearce @ jamespearce

top related