Build your own weather app with Sencha Touch

Lee Boonstra shows you how to build a weather app using Sencha Touch 2.2 and Sencha Cmd.

If you're interested in learning how to make an app, the Sencha Touch framework makes it easy to build awesome multiplatform, touch-enabled applications. We'll use it to build 'Do I need my Umbrella?' – an app using weather information from http://api.worldweatheronline.com to determine if you need said umbrella.

You can see the app itself here, while the code can be found in this GitHub repo: the solution-tutorial directory contains the raw code, the final directory the code plus a Sass template. In addition, the goodies-tutorial directory on the repo contains the files you will need as you work through the tutorial.

Generate and run the demo application

Start by creating a sencha folder somewhere on your hard disk. Download the Sencha Touch 2.2.1 framework and extract it into the newly created sencha folder. Now create the folder for your app. I've called mine dinmu as it's shorter than 'DoINeedMyUmbrella', but it's up to you.

Now open your command line (MS-DOS Prompt or Terminal), navigate to the Sencha framework folder (cd sencha/touch-2.2.x folder) and run the following command to generate your Sencha Touch MVC folder structure:

sencha generate app -name Dinmu -path ../dinmu

This command generates your app's full MVC structure. It takes a namespace Dinmu, to prefix all your classes. Review the folder structure it has created.

Before starting work, you'll need to download Sencha Touch

Now it's time to start your webserver via the command line, using the command below, and replacing the filepath at the end with the path to your own sencha folder. (If you would rather use your own Apache webserver, start it yourself and skip this step.) On Mac OS X, you might need permissions for executing the command: if you run into permission errors, prefix it with sudo.

sencha fs web -p 80 start -map /path/to/sencha/folder/

This will start your built-in Jetty webserver. You need your CLI window open to keep the server running, so it makes sense to open a new CLI window for the next of our command-line commands.

Test the app: in a modern browser, run http://localhost/dinmu. You should see the Sencha demo app interface with a bottom tab panel and two slides.

The data package

In the next few steps you'll generate the model, which will define your data. There are a couple of settings you want to save in your app: id, city, country, units and geolocation. You will define these data settings as model fields. Sencha Cmd can scaffold this model for you. Run the following command from the dinmu folder on the command line:

sencha generate model -name Setting –fields id,city,country,units,geo:boolean

This command generates the model for your app. It takes the classname Setting and one string with all the field names to define all the different fields. Review your folder structure again.

After finishing the tutorial, your app should look something like this

Open app/model/Setting.js with your editor. Notice the namespace Dinmu. model.Setting is equal to app/model/Setting.js. This is your implementation of the Setting model: Sencha Cmd defined a Setting model class for you. It extends a model implementation from the Sencha Touch framework, Ext.data.Model, and it contains all the fields and field types.

The id field will define ids for every model record in your application. To let it behave as a unique ID, you will need to configure it. Before the fields array, configure an idProperty and an identifier:

idProperty: 'id',
identifier: 'uuid',

The logics for these unique IDs are in a Sencha class we need to 'import' into our app. We can use a requires: it needs the class Ext.data.identifier.Uuid.

requires: ['Ext.data.identifier.Uuid'],

The next step is to create some validations for our model by creating a validations array after the fields array. This contains two validation objects to check whether the data for these fields is present:

validations: [{
    type: 'presence',
    field: 'city',
    message: "Please provide a city."
  }, {
    type: 'presence',
    field: 'country',
    message: "Please provide a country."
    }
  ],

As we want to save the local settings data to the device, the last step is to add a client proxy. We will use localstorage for this. The localstorage proxy will make sure that all the data will persist into the browser localstorage. Define the proxy object directly after the validations array:

proxy: {
  type: 'localstorage',
  id: 'weathersettings'
}

The view components

The standard tab panel interface Sencha Cmd generates looks nice, but Do I need my Umbrella? needs a carousel. Open app/view/Main.js in your IDE or text editor: Dinmu.view.Main extends from the Sencha Touch Ext.tab.Panel class. It has a tabBarPosition property to add the tabs to the screen bottom.

The default demo app generated by Sencha Touch

We don't need this, so can remove tabBarPosition: bottom and change the extend to Ext.Carousel, to extend from the Sencha Touch Carousel class. Open your browser and run http://localhost/dinmu. You'll see the Sencha demo app, with the tab interface replaced by a carousel. You can slide views horizontally.

Let's remove more default components. You won't need the demo video, so remove the Ext.Video from the requires array. Empty the items array, since we'll replace it for two new items. The first item object (a container by default) contains only the html property. This can be set to a placeholder text (Settings) so you know that you can code it later. The second item object has a property itemId: 'mainview' and a cls property (for styling) set to the value textview. You need to add a direction property, to set the Carousel direction to vertical:

Ext.define('Dinmu.view.Main', {
  extend: 'Ext.Carousel',
  xtype: 'main',
  requires: [
    'Ext.TitleBar',
  ],
  config: {
    direction: 'vertical',
    items: [{
        html: 'Settings'
      }, {
        itemId: 'mainview',
        cls: 'textview'
      }
    ]
  }
});

Viewed in a browser, the app looks basic. Let's add a top titlebar and a bottom toolbar. Before the item with the Settings placeholder, create a new object:

{
  xtype: 'titlebar',
  cls: 'title',
  docked: 'top',
  title: 'Do I need my Umbrella?'
},

Let's do the same for the bottom toolbar: the xtype is toolbar not titlebar, and instead of the title property we'll use an html property set to a copyright string of our choosing, with a ui property set to light. Add Ext.Toolbar to the requires array in the top of the file, so the correct framework class loads into memory.

{
  xtype: 'toolbar',
  cls: 'footer',
  ui: 'light',
  docked: 'bottom',
  html: '<span>Powered by &copy; Sencha Touch</span>'
},

Next we'll create some buttons in the top titlebar. Below the title, add an array containing a back button, which will be displayed when you're on the settings screen, and a settings button, to be shown on the default screen. You needn't set the xtype to button, since the default items of an Ext.TitleBar are buttons. The back button is hidden by default and is left-aligned in the titlebar. The settings button shows an icon of a settings gear; it's right-aligned:

items: [{
    cls: 'back',
    hidden: true,
    ui: 'back',
    action: 'back',
    align: 'left',
    text: 'back'
  },
  {
    iconCls: 'settings',
    action: 'settings',
    ui: 'plain',
    align: 'right'
}]

Open your browser and run: http://localhost/dinmu again. You should see a button with a gear in the right-hand corner of the Ext.TitleBar.

The drop-down created by app/view/SettingsView.js shows temperature in Celsius or Fahrenheit

Creating forms

Now we can start with creating a form. Navigate to the dinmu folder on the command line and run the following to generate your Sencha Touch form:

sencha generate form -name SettingsView -fields geo:toggle,units:
select,city,country

Review the form class that's been scaffolded. Open app/view/SettingsView.js. The Dinmu.view.SettingsView class has an xtype set to settingsview. You can assign custom classes to item arrays with the xtype property. Let's do so now.

Open Dinmu.view.Main (app/view/Main.js), and find the settings item in your code. By default, if you don't specify its xtype, it's set to container. You'll need to refer to the new xtype, settingsview, so add xtype: 'settingsview' to the code. The placeholder string isn't needed any more: remove the html: settings property. Don't forget to add Dinmu.view.SettingsView to the requires array.

To make the form look better, let's add a fieldset to Ext.form.Panel. This contains the four new fields and the submit button. The fieldset will be a child of the formpanel, and has its own children: the form fields and the button.

Back in SettingsView.js, create a second items array in the config object (after the title property.) Nest the new items array as a child. The parent items array contains one fieldset xtype, with a title (Your location), and a line with instructions; the child array contains all of the fields and the button. The code should look like this:

Ext.define('Dinmu.view.SettingsView', {
  extend: 'Ext.form.Panel',
  xtype: 'settingsview',
  config: {
    items: [{
        xtype: 'fieldset',
        title: 'Your location',
        instructions: "In case you do not want the app to detect your location, you can prefill the city and country.",
        items: [
          //form fields here
        ]
      }
    ]
  }
});

Open your browser and run http://localhost/dinmu. You'll see the select unit field has no values. Let's add some values to the units selectfield. Create an options array with two objects with text Celsius and Fahrenheit, and values c and f. The label GEO in the togglefield makes little sense. Change it to Detect location? As the text of the label now takes up far more space, we'll set the labelWidth to 55%. Set the value of the geo field to value: '1' to enable geolocation by default. Change the labels of all the other fields to lower case and disable the city and country fields by adding disabled:true to both fields. The button text should be Refresh instead of Submit. Change this in the button component. Add a margin with the value 10 5. Add an action property to the button and set it to refresh. This way, we can reference the button later.

{
  xtype: 'button',
  ui: 'confirm',
  margin: '10 5',
  action: 'refresh',
  text: 'Refresh'
}

You might notice the console outputting some warnings. The Ext.Loader, the mechanism that loads all Sencha Touch framework classes in the correct order into memory, needs to load the classes that are used for the form fields. Create a requires array (above the config object), and assign it the following strings:

requires: [
 'Ext.form.FieldSet','Ext.field.Toggle','Ext.field.Select', 'Ext.field.Text','Ext.Button'
],

The interface is finished. But what about the main view? You'll dynamically prefill this page with data; let's start with creating a controller.

The Do I need my Umbrella? interface without the custom style sheet

Building the controller

The controller will be the glue between the settings model (the app data) and the settings view. It will contain references to all view components and dispatch its events. Navigate to the dinmu folder and run the following command:

sencha generate controller Main

This command generates the Main controller. Open app/controller/Main.js: you'll see a controller with empty references (refs) and control objects.

Next let's create references to all the view components: main, settingsview, the titlebar, settings and back buttons and to the settings form fields and refresh button. The selectors are a bit CSS-like. Your code could look like this:

refs: {
  mainView: 'main',
  settingsView: 'settingsview',
  btnSettings: 'main button[action=settings]',
  btnRefresh: 'settingsview button[action=refresh]',
  btnBack: 'main button[action=back]',
  toggleGeo: 'settingsview togglefield',
  fieldCity: 'settingsview textfield[name=city]',
  fieldCountry: 'settingsview textfield[name=country]'
},

Now you can add the controls:

control: {
  'btnRefresh': {
    tap: 'onRefresh'
  },
  'btnSettings': {
    tap: 'onSettingsBtnTap'
  },
  'btnBack': {
    tap: 'onBackBtnTap'
  },
  'toggleGeo': {
    change: 'onToggle'
  },
  'mainView': {
    activeitemchange: 'onCarouselChange'
  }
}

Before browser-testing these events, you must hook the controller to app.js. Open app.js, create a controllers array below the requires array, and pass in the string 'Main', mapping the Main controller to the app/controller/Main.js file:

controllers: [
    'Main'
],

Now we'll add some logics. Go back to Dinmu.controller.Main and add the functions I've written for you in the goodies-tutorial directory on the GitHub repo. You can find them in functions.txt.

Setting up stores and static methods

Stores encapsulate a client-side cache of model objects. Stores can have a proxy too, and also provide functions for sorting, filtering, grouping and querying the model instances (records) contained within it.

Our app needs a store to save all the user settings. Unfortunately, you can't generate stores with Sencha Cmd. Instead let's create a new file in the app/store folder called Settings.js. In it we'll define a new class, Dinmu.store.Settings: this class extends all the methods and properties from the Ext.data.Store class. In the config object, we will create a property called model. It should connect to the Setting model. The settings store also needs to load automatically:

Ext.define('Dinmu.store.Settings', {
  extend: 'Ext.data.Store',
  requires: ['Dinmu.model.Setting'],
  config: {
    model: 'Dinmu.model.Setting',
    autoLoad: true
  }
});

Open app/controller/Main.js. In the config object, create a stores array and add the Dinmu.store.Settings store to it:

stores: 'Dinmu.store.Settings',

Sometimes it's nice to set up your business logics outside the MVC folders. In the app folder, create a folder called utils. In it, create a file named Functions.js, and define a class called Dinmu.utils.Functions. This has a property, singleton, set to true. Now your class is a singleton: you can't create any instances of it, but can run the static methods from everywhere in your code:

Ext.define('Dinmu.utils.Functions', {
  singleton: true,
  //statics methods here
});

Add Dinmu.utils.Functions to the requires array of app.js. Now add some statics functions. Open the functions.txt snippet from the goodies-tutorial directory on the GitHub repo, and copy all the functions into the app/utils/Functions.js file.

The completed app, returning weather data from http://api.worldweatheronline.com

The snippet contains the functions you need for requesting weather data from http://api.worldweatheronline.com. If you'd rather use your own API_KEY, you can edit it on top of Functions.js by changing the string for the API_KEY property. It also contains logics for requesting geolocation on the device and prefilling the template in the main view (I have commented the text for you).

To test the logics, open Chrome DevTools, switch to the Console tab, and type Dinmu.utils.Functions.getWeather('Amsterdam'). The app should return a weather object for Amsterdam, and display some text in the main view. The app is finished! Open your browser and run http://localhost/dinmu again.

Words: Lee Boonstra

This article originally appeared in net magazine issue 247.