Skip to Content

This is part 3 in a series about building SAP mobile apps with Sencha Touch. Part 1 provides an overview of the Sencha family of framework and tools. In part 2 we started coding and introduced Models, Stores and Views.

Controllers

Now let’s have a look at Controllers. Controllers respond to view events and handle the data through Models and Stores.

We are going to expand our sample app so that the user can select an agency from the Travel Agency list and its details are shown in a details view. The details view contains a back button to return to the list.

NavigationView.png

ST offers a UI component, called NavigationView which makes it very easy to build this type of user interface. Conceptually, the NavigationVIew manages a stack of pages and displays the one on top of the stack. You can simply push a new page on the stack. The NavigationView will automatically take care of the toolbar title, back buttons and page transitions.

Here’s the code for the TravelAgencyList view, which will be the first page in the stack:

// define a List to display Travel Agencies
Ext.define('App.view.TravelAgencyList', {
    extend: 'Ext.dataview.List',
    alias: 'widget.travelagencylist',
    config: {
        grouped: true,
        title: 'Travel Agencies',
        itemTpl: '{Name}'
    }
});

Please note the alias. This offers a shorthand to include this list in other components, such as the NavigationView:

Ext.define('App.view.MainNavigationView', {
    extend: 'Ext.navigation.View',
    config: {
        fullscreen: true,
        // we would like a navigation bar on top for title and back button
        navigationBar: {
            docked: 'top'
        },
        // initially the navigation view will only contain the travel agency list
        items: [{
            xtype: 'travelagencylist'
        }]
    }
});

Now, let’s define a view to display the details of a Travel Agency. We’ll use a FormPanel which can contain Fieldsets and Fields.

// define a FormPanel to display Travel Agency details
Ext.define('App.view.TravelAgencyDetails', {
    extend: 'Ext.form.Panel',
    alias: 'widget.travelagencydetails',
    config: {
        title: 'Travel Agency',
        items:  [{
            xtype: 'fieldset',
            title: 'Address', // title of fieldset
            // define defaults that apply to all items in this container
            defaults: {
                xtype: 'textfield',
                readOnly: true,
                labelWidth: '30%'
            },
            items: [
                {
                    name: 'Name',
                    label: 'Name'
                }, {
                    name: 'Street',
                    label: 'Street'
                }, {
                    name: 'City',
                    label: 'City'
                }, {
                    name: 'Country',
                    label: 'Country'
                }
            ]
        }]
    }
});

The next step is to actually display the details view when a users selects (taps on) an Agency from the list. We could easily create an event listener in the list view to open a details view. But we want to maintain a clear separation and loose coupling, so we are going to handle the list item tap event in a controller:

Ext.define('App.controller.TravelAgencies', {
    extend: 'Ext.app.Controller',
    config: {
        views: ['TravelAgencyDetails'],
        // define a reference to the main nav view
        refs: {
            navView: 'mainnavigationview'
        },
        // define which event handlers should be invoked
        control: {
            'travelagencylist': {
                itemtap: 'onTravelAgencyListItemTap'
            }
        }
    },
    onTravelAgencyListItemTap: function (list, index, target, record) {
       
        var navView = this.getNavView(),  
            name = record.get('Name');
        navView.push({
            xtype: 'travelagencydetails',
            title: name,
            record: record // pass the selected record
        });
    }
});

Let’s point out some details:

  • The refs config object creates references using component queries. This works similarly to CSS selector, but now operating on ST component. ST will automatically create getters on the controller. E.g. this.getNavView() will return the MainNavigationView instance
  • The control config object is used to connect component events to event handlers. You can refer to handler functions by their name.

Finally, you need to tell the Ext.application which controller to include:

Ext.application({
    name: 'App',
    // define required stores, views and controllers
    stores: ['TravelAgencies'],
    views: ['MainNavigationView', 'TravelAgencyList'],
    controllers: ['TravelAgencies'],
    launch: function () {
        // create a store and navigation view
        var store = Ext.create('App.store.TravelAgencies'),
            view = Ext.create('App.view.MainNavigationView');
        // push a travel agency list into the Navigation view
        view.push({
            xtype: 'travelagencylist',
            store: store
        });
    }
});

Code organization

You are now familiar with all the key ingredients of a ST application: models, stores, proxies, views and controllers. It is best practices to organize your code into separate source files, one file for each class, in a folder structure that mirrors the namespace. The root folder is by default called ‘app’.

Here’s how your file structure should look like:

Code Organization.png

Instead of manually including all the different js files in your application html file, you can let Sencha Touch dynamically load all the source files. But you have to inform ST about the dependencies in your code through the ‘requires’ config of a class.

Let’s look at the TravelAgency Store class. It requires the TravelAgency Model class, because it is used in the model config:

// define a store to load a collection TravelAgency records
Ext.define('App.store.TravelAgencies', {
    extend: 'Ext.data.Store',
    // the class depends on the TravelAgency model
    requires: 'App.model.TravelAgency',
    config: {
        // in the model config we use the TravelAgency model,
        // that's why we need to define it as a dependency.
        model: 'App.model.TravelAgency',
        // automatically load the records when store is created
        autoLoad: true,
        // perform client-side sorting on field Name
        sorters: 'Name',
        // configure grouping
        grouper: {
            // group by the first letter of Name field
            groupFn: function (record) {
                return record.get('Name')[0];
            }
        }
    }
});

The Sencha Command tools will use the same dependency information to include only the source files required from your app and the framework when building the production version of your app.

Create, update and delete bookings

So far, we have used the SAP OData connector to fetch data from the SAP NW Gateway server. However, we can modify server data just as well!
As an example we will create, modify and delete a Booking for a Flight in the SAP NW Gateway Flight service.

We’ll first define a Booking model:

Ext.define('App.model.Booking', {
    extend: 'Ext.data.Model',
    config: {
        fields: [{
            name: 'AirLineID',
            type: 'string'
        }, {
            name: 'FlightConnectionID',
            type: 'string'
        }, {
            name: 'FlightDate',
            type: 'string'
        },
    {
        name: 'BookingID',
        type: 'string',
        defaultValue: ''
    },
    {
        name: 'CustomerID',
        type: 'string'
    }, {
        name: 'TravelAgencyID',
        type: 'string'
    }, {
        name: 'PassengerName',
        type: 'string'
    }, {
        name: 'CustomerType',
        type: 'string'
    }, {
        name: 'Smoker',
        type: 'boolean'
    }, {
        name: 'LuggageWeight',
        type: 'float'
    }, {
        name: 'WeightUnit',
        type: 'string'
    }, {
        name: 'Invoice',
        type: 'boolean'
    }, {
        name: 'FlightClass',
        type: 'string'
    }, {
        name: 'PriceInForeignCurrency',
        type: 'float'
    }, {
        name: 'ForeignCurrencyCode',
        type: 'string'
    }, {
        name: 'PriceInLocalCurrency',
        type: 'float'
    }, {
        name: 'LocalCurrencyCode',
        type: 'string'
    }, {
        name: 'SalesOfficeID',
        type: 'string'
    }, {
        name: 'BookingDate',
        type: 'string'
    }, {
        name: 'TravelAgencyID',
        type: 'string'
    }, {
        name: 'Cancelled',
        type: 'boolean'
    }
    , {
        name: 'Reserved',
        type: 'boolean'
    }
    , {
        name: 'PassengerName',
        type: 'string'
    }, {
        name: 'Title',
        type: 'string'
    }, {
        name: 'PassengerDateOfBirth',
        type: 'string'
    }],
        proxy: {
            type: 'odata',
            url: 'http://gw.esworkplace.sap.com/sap/opu/odata/IWBEP/RMTSAMPLEFLIGHT_2'+
                        '/BookingCollection',
            withCredentials: true,
            username: 'GW@ESW',
            password: 'ESW4GW'
        }
    }
});

Now, let’s create a booking. Please note that there are some required fields that we need to provide:

var booking;
booking = Ext.create('App.model.Booking', {
    AirLineID: flight.get('AirLineID'),
    FlightConnectionID: flight.get('FlightConnectionID'),
    FlightDate: flight.get('FlightDate'),
    CustomerID: '00004274',
    TravelAgencyID: '00000087',
    PassengerName: 'Joe Picard'
});
booking.save(function (record, operation) {
    if (operation.wasSuccessful()) {
        console.log('Booking created. Id:' + record.get('BookingID'));
    } else {
        console.log('Create booking failed');
    }
});

Once we a Booking, we may update its properties:

booking.set('PassengerName', 'Tom Picard');
booking.save(function (record) {
    console.log('Booking updated')
});

Finally, to delete a booking:

booking.erase(function () {
    console.log('Booking cancelled');
})

Please note that the server determines how to handle the delete request. In the Flights service, the Booking record is not removed from the database. Instead, the Booking is marked as ‘Cancelled’.

Have a look at Developer Tools in Chrome, while developing your app. Here you can sse the Create (POST), Update (PUT) and Delete (DELETE) requests on HTTP level:

http requests.png

Typically you’ll place code to fetch and modify data in the controller, in response to view events.

It’s time to wrap up! In this post we have introduced controllers to handle UI events and access the Models. We  organized our code in seperate source files and let Sencha Touch dynamically load the source files. Finally, we performed create, update and delete actions on the server data. 

I hope this helps you get started with building SAP mobile apps with Sencha Touch.

Thanks for reading!

To report this post you need to login first.

6 Comments

You must be Logged on to comment or reply to a post.

  1. Former Member

    Hi Luc Stakenborg,

    I followed all the steps as above. But I got an error during tapping(click event).

    Uncaught TypeError: Cannot call method ‘push’ of undefined.

    Can you please help to solve this error. Thanks.

    (0) 
    1. Former Member

      Hi, I had the same problem.

      In MainNavigationView I removed the items, because no store was defined.
      And it is important to add the alias here!

      Ext.define(‘App.view.MainNavigationView’, { 

          extend: ‘Ext.navigation.View’, 

                alias: ‘widget.mainnavigationview’, 

          config: { 

              fullscreen: true,   

              navigationBar: { 

                  docked: ‘top’ 

              }

          } 

      });

      In the app.js this is my launch-function:

      var store = Ext.create(‘App.store.TravelAgencies’);

      var view = Ext.create(‘App.view.MainNavigationView’, {

                                              items: [{

                                              xtype: ‘travelagencylist’, 

                                                store: store

                                    }]

                          });

      My TravelAgencies Controller looks this:

      Ext.define(‘App.controller.TravelAgencies’, { 

          extend: ‘Ext.app.Controller’, 

          config: {        

              refs: { 

                  navView: ‘mainnavigationview’

       

              },                                 

              control: { 

                  ‘travelagencylist’: { 

                      itemtap: ‘onTravelAgencyListItemTap’ 

                  } 

              } 

          }, 

          onTravelAgencyListItemTap: function (list, index, target, record) { 

              var navView = this.getNavView();   

              var name = record.get(‘Name’);   

              navView.push({ 

                  xtype: ‘travelagencydetails’, 

                  title: name, 

                  record: record

              }); 

          } 

      });

      And the head of my TravelAgencyDetails looks like this:

      Ext.define(‘App.view.TravelAgencyDetails’, { 

          extend: ‘Ext.form.Panel’, 

          alias: ‘widget.travelagencydetails’,

      Hope this helps!

      (0) 
  2. Former Member

    Thank you very much for the great Tutorial!

    It would be great if you could provide the whole and full working example (including Flight/Bookings/BookingsCollection Models) -> and provide it in Code-Exchange -> there is “just” an OData Example with Sencha Touch of a self created C# Webservice, the possibility here with the Netweaver gateway is much more simpler!

    The last example for Bookings-Model will fail because there is no flight.

    (0) 
  3. Former Member

    Hi Luke,

    How can we handle deep entity(Header & multiple items) set to read, create & update data using the Sencha Touch OData connector?

    This is a real world scenario but unable to get the OData connector to handle this.

    Any ideas?

    regards

    Nitesh

    (0) 

Leave a Reply