How to Create a Sencha Touch 2 App, Part 1

In this series we will create the Sencha Touch 2 version of the Notes Application, an application that allows its users to take notes and store them on the device running the app. Along the way, we will dive into the following areas:

  • The building blocks of a Sencha Touch application.
  • How to implement navigation in an application with multiple views.
  • How to edit data using Sencha Touch form elements.
  • How to render information using lists views.
  • How Sencha Touch stores data with HTML5 local storage.

In the first part of the series, we are going to define the features of the application, its look and feel, and we will start building its main screen.

The Features Of The Notes App

We want our app to its give users the following abilities:

  • Create notes.
  • Edit existing notes.
  • Delete notes.
  • Render a list of the notes currently saved on the device.
  • Persist notes on the device running the application, across browser sessions.

Sencha Touch or jQuery Mobile? – Read This Before You Make a Decision

If you ever need to choose between jQuery Mobile and Sencha Touch, you need to consider these factors before making your decision:

About jQuery Mobile

  • It’s a UI-only library, which relies on jQuery and jQuery UI for DOM manipulation, Ajax and other utilities
  • To create UI widgets, you generally need to hand-code their html, and the library enhances their look and feel
  • As it works by enhancing the html you create, it allows you to re-use or re-purpose existing html
  • Some simple applications can be created using only html, without having to write JavaScript code
  • It has a relatively small object model, which makes it faster and easier to learn, specially if you are familiar with jQuery
  • It does not impose a coding discipline or structure, which gives you flexibility, but can lead to applications that are difficult to maintain
  • Easier to integrate with other frameworks
  • Targets more devices than Sencha Touch
  • Not tied to a particular vendor

About Sencha Touch

  • It’s a library that tries to do it all: UI widgets, DOM manipulation, Ajax and other utilities
  • Does not depend on other libraries
  • Follows a JavaScript-centric approach, where you are required to write little html
  • Has a large object model, which provides more features out of the box, but takes longer to learn
  • Imposes a coding structure and discipline, which generally results in well-organized code
  • Provides built-in server and local storage abstractions, which make it easier to perform CRUD operations on relational data
  • Has built-in facilities for creating iOS and Android native packages
  • Targets less devices than jQuery Mobile

Want To Learn More?

I have a selection of Sencha Touch and jQuery Mobile tutorials that will help you build great applications. Check them out using these links:

New eBook – Building a Sencha Touch Application

Book: Building a Sencha Touch Application

I just published the Building a Sencha Touch Application eBook. The book will teach you how to create a Sencha Touch 1.1.1 application that allows its users to take notes and store them on the device running the app.

You can preview the book using this link: Preview Ebook – Building a Sencha Touch Application.

I would like to thank Ted JenkinsAlex Blount, James Brooks and Hector Iribarne for their feedback and help creating this book. Thank you, Ted, Alex, James and Hector.

I would also like to hear what you think about the book and how I can make the next version better. Please leave a comment if you have a few minutes.

Writing a Sencha Touch MVC Application

In this article we will explore one of the approaches we can take to create a Sencha Touch application using the MVC pattern. I will base the tutorial on the Notes Application we created in the Writing a Sencha Touch Application series. Our goal is to build a new version of the Notes Application using Sencha Touch MVC.

The Notes Application allows its users to take notes and store them on the device running the app. The features we built into the app, which we will reproduce in the MVC version, are the following:

  • Ability to create notes
  • Ability to edit existing notes
  • Ability to delete notes
  • Ability to persist notes on the device running the application, across browser sessions

In terms of user interface, the application revolves around two views, the Notes List view and the Note Editor view:

Ready? Let’s get started.

The model-view-controller pattern in Sencha Touch

As of version 1.1.0 of Sencha Touch, the framework’s implementation of the model-view-controller pattern is, in my opinion, incomplete and poorly documented. However, we can still take advantage of it to build high-quality applications.

In general, a Sencha Touch MVC application will consist of one or more views, one or more data models and stores, and one or more controllers. Views have a dual role: they render representations of the data in the models, and capture and transmit user input to the controllers. Controllers will translate the user’s input into changes in the data and/or the behavior of the application. Models represent the application’s data and state.

In Sencha Touch MVC parlance, the mechanism by which views send messages to the controllers is called dispatch, and the controller functions used to handle these messages are called actions.

Let’s apply these concepts to our Notes Application and come up with a tentative design.

The Notes App use cases, MVC style

Based on our requirements list, we can translate our use cases into the following MVC workflows:

  1. When the application launches, the user will be presented with a list of the existing notes, or an empty list if there are no notes saved. This will happen by invoking a controller’s index action, which in turn will render the Notes List view:
  2. When our user needs to create a new note, she will tap the New button in the Notes List view. This will invoke the newnote action in a controller, where a new note will be created and loaded into the Note Editor view:
  3. Similarly, when our user needs to edit a note, she will tap the disclosure button for the given note. This will invoke the editnote action in a controller, causing the note to be loaded into the Note Editor view:
  4. Tapping the Save button in the Note Editor view will invoke a controller’s savenote action, where the note will be saved in notes cache and the Notes List view will be activated:
  5. If our user taps the Trash button in the Note Editor view, the view will invoke a controller’s deletenote action. This action will delete the note loaded in the view and activate the Notes List view:
  6. Tapping the Home button in the Note Editor view will invoke a controller’s canceledit action, which will discard any changes made to the loaded note, and activate the Notes List view:

Notice that I have referred to “a controller” in my descriptions of the use cases. This means that we still don’t know if we will use one or many controllers in the application. We will make this decision in a few minutes.

All right. Now that we have an idea about how we’re going to build the Notes Application, MVC-style, let’s talk about files.

Distributing a Sencha Touch application across multiple files

You should consider distributing your Sencha Touch application’s source code across multiple files. A single source file might be fine for a very small application, however, as the application and the development team grow, you can cut development and maintenance costs by using a multiple-files model.

What we will do for this example is, first, create the right folder structure for the application, and then create the different source files that will contain the application’s components. The folder structure reflects the MVC pattern we will use to build the application:

We have a root folder called app, and under it, folders for the models, views, controllers and data stores. Besides being self-explanatory, it follows Sencha’s recommendation to place models, views and controllers in separate folders. Sencha’s build tools are capable of leveraging this structure to create the application’s namespaces when it’s time to build a production-ready version of the source files.

At this point we have modeled our MVC workflows and created the right folder structure. What do you say about writing some code?

The Note data model

The Note model is the data model that represents a note. Its source goes in the NoteModel.js file, which we will place in the models folder. This is the source:

Ext.regModel('NoteModel', {
    idProperty: 'id',
    fields: [
        { name: 'id', type: 'int' },
        { name: 'date', type: 'date', dateFormat: 'c' },
        { name: 'title', type: 'string' },
        { name: 'narrative', type: 'string' }
    ],
    validations: [
        { type: 'presence', field: 'id' },
        { type: 'presence', field: 'title', message: 'Please enter a title for this note.' }
    ]
});

The Notes data store

The NotesStore is our notes cache. It uses the NoteModel class as its model and it’s configured to use an instance of the Ext.data.LocalStorageProxy class as its proxy. This allows us to save notes across browser sessions. We will place the NotesStore class in the NotesStore.js file, which lives in the stores folder. This is the store’s source:

Ext.regStore('NotesStore', {
    model: 'NoteModel',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-store'
    },
    getGroupString: function (record) {
        if (record && record.data.date) {
            return record.get('date').toDateString();
        } else {
            return '';
        }
    }
});

NotesApp.stores.notesStore = Ext.StoreMgr.get('NotesStore');

The Main view

As in the first version of the application, the application’s main view will serve as the viewport, hosting the Notes List view and the Note Editor view. This view’s class goes in the MainView.js file, which we will place in the views folder:

NotesApp.views.MainView = Ext.extend(Ext.Panel, {
    fullscreen: true,
    layout: 'card',
    cardSwitchAnimation: 'slide',
    initComponent: function () {

        Ext.apply(NotesApp.views, {
            notesListView: new NotesApp.views.NotesListView({ notesStore: NotesApp.stores.notesStore }),
            noteEditorView: new NotesApp.views.NoteEditorView()
        });

        this.items = [
            NotesApp.views.notesListView,
            NotesApp.views.noteEditorView
        ]

        NotesApp.views.MainView.superclass.initComponent.call(this);

        this.on('render', function () {
            NotesApp.stores.notesStore.load();
        });
    }
});

The Notes List view

We will use an instance of the NotesListView class to render the list of cached notes and allow the user to start the “new note” and “edit note” workflows. Its file, NotesListView.js, goes in the views folder. This is the source:

NotesApp.views.NotesListView = Ext.extend(Ext.Panel, {

    notesStore: Ext.emptyFn,
    notesList: Ext.emptyFn,

    layout: 'fit',

    initComponent: function () {

        this.newButton = new Ext.Button({
            text: 'New',
            ui: 'action',
            handler: this.onNewNote,
            scope: this
        });

        this.topToolbar = new Ext.Toolbar({
            title: 'My Notes',
            items: [
                { xtype: 'spacer' },
                this.newButton
            ]
        });

        this.dockedItems = [this.topToolbar];

        this.notesList = new Ext.List({
            store: this.notesStore,
            grouped: true,
emptyText: '</pre>
<div style="margin: <span class=;">5px;">No notes cached.</div>
<pre>
<pre>',
            onItemDisclosure: true,
itemTpl: '
<div class="list-item-title">{title}</div>
<pre>' +
                            '<div class="list-item-narrative">{narrative}</div>'

        });

        this.notesList.on('disclose', function (record, index, evt) {
            this.onEditNote(record, index);
        }, this),

        this.items = [this.notesList];

        NotesApp.views.NotesListView.superclass.initComponent.call(this);
    },

    onNewNote: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'newnote'
        });
    },

    onEditNote: function (record, index) {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'editnote',
            note: record
        });
    },

    refreshList: function () {
        this.notesList.refresh();
    }
});

Let’s pause here and examine a few details.

While the initComponent() function should be very familiar to you, things become interesting when we look at the helper functions onNewNote() and onEditNote(). These functions will allow us to signal the view’s controller that the user executed either the “new note” or “edit note” commands. The newnote or editnote controller actions will be invoked in response to these commands.

How does the view send these signals to the controller? Well, it’s pretty simple. Remember what I said earlier about dispatch? Every application gets a default instance of the Dispatcher class, which you can use to send requests to specific actions in a given controller. Invoking Ext.Dispatcher.dispatch() or its shorthand, Ext.dispatch(), will find the desired controller and call the correct controller action.

You should pass the controller and action arguments to the dispatch() function. And you can also pass any other parameters that the controller might need to execute the action correctly. For example, in the onEditNote() function we also pass the record corresponding to the note the user needs to edit.

The third function, refreshList(), will allow this view’s controller to re-render the list of notes in the view after the list has been modified.

OK, we already dispatched a couple of messages to a controller that does not exist. How about we create it?

How many controllers does a Sencha Touch MVC application need?

I don’t think they make rules for this. It depends on the application. I know that the Notes App is small: three simple views and six actions. It looks like one controller is enough for this one.

Let’s create the NotesController.js file in the controllers folder, and add the following code to it:

Ext.regController('NotesController',{

    'index': function (options) {

    },

    'newnote': function (options) {

    },

    'editnote': function (options) {

    },

    'savenote': function (options) {

    },

    'deletenote': function (options) {

    },

    'canceledit': function (options) {

    }
});

NotesApp.controllers.notesController = Ext.ControllerManager.get('NotesController');

Starting to make more sense now? As you can see, the controller has the actions we have discussed. This is where we need to implement our use cases. But, before we do it, let’s work on the last view.

The Note Editor view

We will use an instance of the NoteEditorView class to give our users the ability to edit and delete notes. This class goes in the NoteEditorView.js file, in the views folder:

NotesApp.views.NoteEditorView = Ext.extend(Ext.form.FormPanel, {

    initComponent: function () {

        this.backButton = new Ext.Button({
            text: 'Home',
            ui: 'back',
            handler: this.backButtonTap,
            scope: this
        });

        this.saveButton = new Ext.Button({
            text: 'Save',
            ui: 'action',
            handler: this.saveButtonTap,
            scope: this
        });

        this.trashButton = new Ext.Button({
            iconCls: 'trash',
            iconMask: true,
            handler: this.trashButtonTap,
            scope: this
        });

        this.topToolbar = new Ext.Toolbar({
            title: 'Edit Note',
            items: [
                this.backButton,
                { xtype: 'spacer' },
                this.saveButton
            ]
        });

        this.bottomToolbar = new Ext.Toolbar({
            dock: 'bottom',
            items: [
                { xtype: 'spacer' },
                this.trashButton
            ]
        });

        this.dockedItems = [this.topToolbar, this.bottomToolbar];

        NotesApp.views.NoteEditorView.superclass.initComponent.call(this);
    },

    backButtonTap: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'canceledit'
        });
    },

    saveButtonTap: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'savenote'
        });
    },

    trashButtonTap: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'deletenote'
        });
    },

    items: [{
        xtype: 'textfield',
        name: 'title',
        label: 'Title',
        required: true
    }, {
        xtype: 'textareafield',
        name: 'narrative',
        label: 'Narrative'
    }]
});

The helper functions saveButtonTap(), trashButtonTap() and backButtonTab() will allow us to signal the controller that the “save note”, “delete note” or “cancel edit” commands were executed by the user. The application will run the savenote, deletenote or canceledit controller actions in response to these commands.

Now that our views are finished, let’s implement the controller actions.

Implementing controller actions

Back in the NotesController class, as we’ve already discussed, the index action will create the Main view and activate the Notes List view:

'index': function (options) {

    if (!NotesApp.views.mainView) {
        NotesApp.views.mainView = new NotesApp.views.MainView();
    }

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView
    );
}

The index action will be invoked when the application launches. We will see how this is done when we implement the application’s launch() function.

The newnote controller action will create a new note, load it into the Note Editor view, and activate the view:

'newnote': function (options) {

    var now = new Date();
    var noteId = now.getTime();
    var note = Ext.ModelMgr.create({ id: noteId, date: now, title: '', narrative: '' },
        'NoteModel'
    );

    NotesApp.views.noteEditorView.load(note);
    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.noteEditorView,
        { type: 'slide', direction: 'left' }
    );
}

We need the editnote action to load the selected note into the Note Editor view:

'editnote': function (options) {

    NotesApp.views.noteEditorView.load(options.note);
    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.noteEditorView,
        { type: 'slide', direction: 'left' }
    );
}

The savenote action will save the note in the cache and activate the Notes List view:

'savenote': function (options) {

    var currentNote = NotesApp.views.noteEditorView.getRecord();

    NotesApp.views.noteEditorView.updateRecord(currentNote);

    var errors = currentNote.validate();
    if (!errors.isValid()) {
        currentNote.reject();
        Ext.Msg.alert('Wait!', errors.getByField('title')[0].message, Ext.emptyFn);
        return;
    }

    if (null == NotesApp.stores.notesStore.findRecord('id', currentNote.data.id)) {
        NotesApp.stores.notesStore.add(currentNote);
    } else {
        currentNote.setDirty();
    }

    NotesApp.stores.notesStore.sync();

    NotesApp.stores.notesStore.sort([{ property: 'date', direction: 'DESC'}]);

    NotesApp.views.notesListView.refreshList();

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView,
        { type: 'slide', direction: 'right' }
    );
}

Next comes the deletenote action. This action will delete the note loaded in the Note Editor and activate the Notes List view:

'deletenote': function (options) {

    var currentNote = NotesApp.views.noteEditorView.getRecord();

    if (NotesApp.stores.notesStore.findRecord('id', currentNote.data.id)) {
        NotesApp.stores.notesStore.remove(currentNote);
    }

    NotesApp.stores.notesStore.sync();
    NotesApp.views.notesListView.refreshList();

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView,
        { type: 'slide', direction: 'right' }
    );
}

Finally, the controller’s canceledit action will discard any changes made to the loaded note, and activate the Notes List view:

'canceledit': function (options) {

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView,
        { type: 'slide', direction: 'right' }
    );
}

You with me so far? We’re almost done.

Compared to the first version of the app, we’ve moved most of the application’s behavior out of the views, which now deal strictly with UI matters, and into a central location. I can see the MVC version being easier to understand and support.

Now that we have our data store, model, views and controller in place, we’re just missing the piece that will get this whole mechanism going.

Launching the application

We will place our app’s entry point in the app.js file, app folder, together with the index.html and app.css files. We will use app.js to create our application’s instance like so:

var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,

    launch: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'index'
        });
    }
});

And that’s it. Makes sense?

As you can imagine, this is not the only approach you can take for incorporating the MVC pattern in a Sencha Touch app. You can go from rolling out your own implementation, to taking full advantage of the framework’s built-in features, which I think are still a work in progress. Either way, you should be able to reach a satisficing solution that makes your application easier to develop and more maintainable.

What do you think?

Download this tutorial’s source: NotesApp-v1.zip

Writing a Sencha Touch Application, Part 4

This is the last of a four-part series on how to write a Sencha Touch application. If you’re new to the series, here are the links to the previous installments:

In part 3 of this tutorial we worked on the Note Editor and added the ability to create notes. It is time now to let our users edit and delete notes. Let’s work on the edit note feature first.

Disclosure events in a Sencha Touch List

When the person using the application touches a note’s disclosure button in the Notes List view, the selected note should be displayed in the Edit Note view:

We can complete this feature by implementing the onItemDisclosure handler of the notes list. Here’s the code:

NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    onItemDisclosure: function (record) {
        var selectedNote = record;
        NotesApp.views.noteEditor.load(selectedNote);
        NotesApp.views.viewport.setActiveItem('noteEditor', { type: 'slide', direction: 'left' });
    },
    itemTpl: '
<div class="list-item-title">{title}</div>' +
        '<div class="list-item-narrative">{narrative}</div>',
    listeners: {
        'render': function (thisComponent) {
            thisComponent.getStore().load();
        }
    }
});

The handler function accepts the selected note as a parameter. What we need to do in the handler is load the note in the editor utilizing the editor’s load() method, and then, make the Note Editor view active via a call to the viewport’s setActiveItem().

Very cool. At this point we can switch over to the emulator, where we should be able to edit notes.

Removing records from a data store

Deleting notes is also very easy. The Trash button on the Note Editor view’s bottom toolbar will trigger this function. We need to change the button’ handler like so:

NotesApp.views.noteEditorBottomToolbar = new Ext.Toolbar({
    dock: 'bottom',
    items: [
        { xtype: 'spacer' },
        {
            iconCls: 'trash',
            iconMask: true,
            handler: function () {

                var currentNote = NotesApp.views.noteEditor.getRecord();
                var notesList = NotesApp.views.notesList;
                var notesStore = notesList.getStore();

                if (notesStore.findRecord('id', currentNote.data.id)) {
                    notesStore.remove(currentNote);
                }

                notesStore.sync();

                notesList.refresh();
                NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });
            }
        }
    ]
});

After acquiring references to the current note, the notes list and the notes data store, we use the store’s findRecord() function to find and remove the note loaded on the editor.

Next, we call sync() on the store to make the removal permanent, and proceed to re-render the notes list and make the Notes List view active.

This procedure is similar to the one we followed when we implemented the Save Note feature, although in this case we’re deleting the note, not saving it.

A quick check on the emulator should confirm that we can now delete notes.

Grouping items in a Sencha Touch List

One last thing we want to do is, in the Notes List view, render the notes grouped by date. This will make it easier for our users to work with their notes.

First, we need to tell our notesList Ext.List that it needs to render its items grouped. We can do this using the grouped config option like so:

NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    grouped: true,
    emptyText: '<div style="margin: 5px;">No notes cached.</div>',
    onItemDisclosure: function (record) {
        var selectedNote = record;
        NotesApp.views.noteEditor.load(selectedNote);
        NotesApp.views.viewport.setActiveItem('noteEditor', { type: 'slide', direction: 'left' });
    },
    itemTpl: '<div class="list-item-title">{title}</div>' +
        '<div class="list-item-narrative">{narrative}</div>',
    listeners: {
        'render': function (thisComponent) {
            thisComponent.getStore().load();
        }
    }
});

Then, we need to override the getGroupString() function of the NotesStore:

Ext.regStore('NotesStore', {
    model: 'Note',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-store'
    },
    getGroupString: function (record) {
        if (record && record.data.date) {
            return record.get('date').toDateString();
        } else {
            return '';
        }
    }
});

You can define the grouping behavior of the store using the groupField property and the getGoupString() function.

The getGroupString() function returns the string to group on, based on the store’s data model’s properties. By default, getGroupString() returns the value of the groupField property. In our case we want to use the value of the note’s date as the group’s header, but we want to format the value as a date.

If we check the emulator, the notes taken the same day should render under the same group header:

We made it!

This is pretty much it. A very simple Sencha Touch application and an easy way to learn some of the features of the Sencha Touch framework.

I hope you take this app to the next level as you learn more about the framework. :-)

And don’t forget to leave a comment letting me know the Sencha Touch topics about which you would like me to write.

Download the Notes App: Notes-App-v1.0.zip

Here’s the rest of the series:

Writing a Sencha Touch Application, Part 3

This is the third chapter of the Sencha Touch tutorial where we’ve been building a small application that allows its users to create notes and store them on their mobile devices. If you haven’t checked them out, here are the links to the previous installments:

In parts 1 and 2 of the tutorial we built the Notes List view. Now we need to create the Note Editor view; which will allow our users to create, update and delete notes. In this article we will take the idea in the mock-up and we will create a great-looking widget:

Creating a Sencha Touch Form Panel

Based on the Note Editor mockup, our view needs a couple of toolbars and a couple of form fields that will capture the Note’s title and narrative. Let’s create the form fields first:

NotesApp.views.noteEditor = new Ext.form.FormPanel({
    id: 'noteEditor',
    items: [
        {
            xtype: 'textfield',
            name: 'title',
            label: 'Title',
            required: true
        },
        {
            xtype: 'textareafield',
            name: 'narrative',
            label: 'Narrative'
        }
    ]
});

We are using the Ext.form.FormPanel class, one of the simplest ways to work with form fields in Sencha Touch, to define a note editor instance. While a TextArea field will facilitate editing the note’s narrative, the view will capture the note’s title with a Text field.

We’ve added the required: true config option to the title field to signal that we won’t allow notes without a title. When we implement the Save Note feature we will see how we can leverage the required config option of the field, and the validations config option of the Note’s data model, to alert our users that the note’s title is required.

Before we add the toolbars it is a good idea to check how the form looks. Let’s make a quick change in the viewport definition so it renders the note editor, just for testing purposes, instead of the notesListContainer instance we defined previously:

NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [NotesApp.views.noteEditor] // TODO: revert to [NotesApp.views.notesListContainer]
});

This is what we should see using the emulator:

Adding top and bottom toolbars to a Sencha Touch Panel

We are ready to take care of the toolbars. The first toolbar we will create is the one that will host the Home and Save buttons:

NotesApp.views.noteEditorTopToolbar = new Ext.Toolbar({
    title: 'Edit Note',
    items: [
        {
            text: 'Home',
            ui: 'back',
            handler: function () {
                // TODO: Transition to the notes list view.
            }
        },
        { xtype: 'spacer' },
        {
            text: 'Save',
            ui: 'action',
            handler: function () {
                // TODO: Save current note.
            }
        }
    ]
});

When defining this toolbar we’re using the ui config option to give each of our toolbar buttons a distinct look and feel based on their function. We use the ui: ‘back’ value for the Home button because this button will trigger a transition back to the main view of the app. And we use ui: ‘action’ for the Save button. This indicates to the user that the button will trigger the most important feature of the view, which in this case is saving a note.

The bottom toolbar will host the Trash button, which will allow our users to delete notes. This is how we instantiate this toolbar:

NotesApp.views.noteEditorBottomToolbar = new Ext.Toolbar({
    dock: 'bottom',
    items: [
        { xtype: 'spacer' },
        {
            iconCls: 'trash',
            iconMask: true,
            handler: function () {
                // TODO: Delete current note.
            }
        }
    ]
});

There are a couple of details to which you need to pay attention here. First, look at how we use the dock config option to dock the toolbar to the bottom of the view. And last, how we use the iconCls and iconMask config options to render the trash icon within the button:

Now we can add the toolbars:

NotesApp.views.noteEditor = new Ext.form.FormPanel({
    id: 'noteEditor',
    items: [
        {
            xtype: 'textfield',
            name: 'title',
            label: 'Title',
            required: true
        },
        {
            xtype: 'textareafield',
            name: 'narrative',
            label: 'Narrative'
        }
    ],
    dockedItems: [
            NotesApp.views.noteEditorTopToolbar,
            NotesApp.views.noteEditorBottomToolbar
        ]
});

Let’s switch over to the emulator to make sure the Note Editor’s elements are in the right place. Remember that we’ve temporarily modified the viewport so it just renders the Note Editor like so:

NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [NotesApp.views.noteEditor] // TODO: revert to [NotesApp.views.notesListContainer]
});

This is what we should see on the emulator:

Very nice, right?

All right, at this point we have built the Notes List and Note Editor views, and now we need to integrate the Note Editor with the application’s workflow. First, let’s make sure the editor becomes visible when the New button on the Notes List view is tapped.

How to change views in a Sencha Touch application

Let’s go back to the Notes List view and work on the handler for the New button. When the New button is tapped, we want to create a new note, pass it to the Note Editor and make the Note Editor visible:

NotesApp.views.notesListToolbar = new Ext.Toolbar({
    id: 'notesListToolbar',
    title: 'My Notes',
    layout: 'hbox',
    items: [
        { xtype: 'spacer' },
        {
            id: 'newNoteButton',
            text: 'New',
            ui: 'action',
            handler: function () {

                var now = new Date();
                var noteId = now.getTime();
                var note = Ext.ModelMgr.create(
                    { id: noteId, date: now, title: '', narrative: '' },
                    'Note'
                );

                NotesApp.views.noteEditor.load(note);
                NotesApp.views.viewport.setActiveItem('noteEditor', {type: 'slide', direction: 'left'});
            }
        }
    ]
});

Let’s examine this sequence one stept at a time. First, we create a new note using the ModelMgr’s class create() method:

var now = new Date();
var noteId = now.getTime();
var note = Ext.ModelMgr.create(
    { id: noteId, date: now, title: '', narrative: '' },
    'Note'
);

Then we pass the new note to the Note Editor, taking advantage of the FormPanel’s ability to load model instances with its load() method. A call to load() will populate the form’s fields with the values of the model’s fields:

NotesApp.views.noteEditor.load(note);

Finally, as our viewport uses a card layout ,we make the Note Editor visible using the viewport’s setActiveItem() method. When we call setActiveItem() we are passing the id of the card that we want to make active and an object describing the animation we want to use for the transition:

NotesApp.views.viewport.setActiveItem('noteEditor', {type: 'slide', direction: 'left'});

Before we check how the New button works on the emulator we need to fix the viewport’s items array. This is how we should configure the viewport’s items:

NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [
        NotesApp.views.notesListContainer,
        NotesApp.views.noteEditor
    ]
});

On the emulator we should see how the Note Editor becomes visible after tapping the New button on the Notes List view:

Everything OK so far?

With the New button working, we need to make sure the user can save the new note. This function will be triggered by the Save button on the Note Editor.

Validating a data model in Sencha Touch

When the Save button is tapped, we need the following things to happen:

  1. The information in the form fields, the note’s title and narrative, is captured in a Note model instance
  2. If the title of the note is empty, we will alert the user and abort the Save routine
  3. If the note is new, we will add it to the notes cache; if it already exists, we will update the cache
  4. We will then refresh the Notes List and make it the active view

Sounds complicated? It really isn’t. Let’s see how it’s done.

Before we implement the Save button’s tap handler, let’s modify the Note data model so it works better at the time of validation. What we will do is override the “message” property used by the validation function of the model’s title field. The goal here is to display a friendlier message (friendlier in my opinion :-) ) when the field is invalid:

Ext.regModel('Note', {
    idProperty: 'id',
    fields: [
        { name: 'id', type: 'int' },
        { name: 'date', type: 'date', dateFormat: 'c' },
        { name: 'title', type: 'string' },
        { name: 'narrative', type: 'string' }
    ],
    validations: [
        { type: 'presence', field: 'id' },
        { type: 'presence', field: 'title', message: 'Please enter a title for this note.' }
    ]
});

We will use this message when we validate the note in the Note Editor view. Let’s implement the Save button’s tap handler so we can see how validation takes place:

NotesApp.views.noteEditorTopToolbar = new Ext.Toolbar({
    title: 'Edit Note',
    items: [
        {
            text: 'Home',
            ui: 'back',
            handler: function () {
                NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });
            }
        },
        { xtype: 'spacer' },
        {
            text: 'Save',
            ui: 'action',
            handler: function () {

                var noteEditor = NotesApp.views.noteEditor;

                var currentNote = noteEditor.getRecord();
                // Update the note with the values in the form fields.
                noteEditor.updateRecord(currentNote);

                var errors = currentNote.validate();
                if (!errors.isValid()) {
                    currentNote.reject();
                    Ext.Msg.alert('Wait!', errors.getByField('title')[0].message, Ext.emptyFn);
                    return;
                }

                var notesList = NotesApp.views.notesList;
                var notesStore = notesList.getStore();

                if (notesStore.findRecord('id', currentNote.data.id) === null) {
                    notesStore.add(currentNote);
                } else {
                    currentNote.setDirty();
                }

                notesStore.sync();
  notesStore.sort([{ property: 'date', direction: 'DESC'}]);

                notesList.refresh();

                NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });

            }
        }
    ]
});

In the handler function we first use the form panel’s getRecord() method to acquire a reference to the Note model loaded into the form. We then use the updateRecord() method to transfer the values from the form fields to the acquired model reference.

Validation takes place via the Errors object returned by the call to validate() on the Note data model. The call to isValid() tells us if there were errors. As only the note’s title is a required field, we can look up the message for this error, which we defined via the model’s validations config option, with the call to errors.getByField(‘title’)[0].message.

Having passed validation, we need to obtain a reference to the notes cache and add the note to the cache if it doesn’t already exist. The framework’s Store class makes this a breeze with its findRecord() method:

var notesStore = notesList.getStore();
if (notesStore.findRecord('id', currentNote.data.id) === null) {
    notesStore.add(currentNote);
}

After updating the notes data store, we use the sync() method to make the changes permanent. Then we use sort() to sort the notes by date. At this point we’re ready to render the updated notes list:

notesList.refresh();

NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });

Two interesting things about this step. Notice that this is one of the areas where the application can be optimized, as we’re re-rendering the notes list even if the list did not change. Also, as we’re navigating back to the main view, we configured the animation to use a slide transition from right to left (direction: ‘right’).

Before we finish this part of the tutorial, let’s implement the handler for the Home button. The Home button on the Note Editor will simply take us back to the Notes List. This is the code that does the trick:

{
    text: 'Home',
    ui: 'back',
    handler: function () {
        NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });'s next
    }
}

Can’t wait to see it working? Start your emulator and check it out. You should be able to create and save notes.

What’s next

We’ll leave editing and deleting notes for the next part of the tutorial. I you haven’t checked out the rest of the series, these are the links:

And here’s the application’s js source with the features we just built:

var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,
    launch: function () {

        Ext.regModel('Note', {
            idProperty: 'id',
            fields: [
                { name: 'id', type: 'int' },
                { name: 'date', type: 'date', dateFormat: 'c' },
                { name: 'title', type: 'string' },
                { name: 'narrative', type: 'string' }
            ],
            validations: [
                { type: 'presence', field: 'id' },
                { type: 'presence', field: 'title', message: 'Please enter a title for this note.' }
            ]
        });

        Ext.regStore('NotesStore', {
            model: 'Note',
            sorters: [{
                property: 'date',
                direction: 'DESC'
            }],
            proxy: {
                type: 'localstorage',
                id: 'notes-app-store'
            }
        });

        NotesApp.views.noteEditorTopToolbar = new Ext.Toolbar({
            title: 'Edit Note',
            items: [
                {
                    text: 'Home',
                    ui: 'back',
                    handler: function () {
                        NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });
                    }
                },
                { xtype: 'spacer' },
                {
                    text: 'Save',
                    ui: 'action',
                    handler: function () {

                        var noteEditor = NotesApp.views.noteEditor;

                        var currentNote = noteEditor.getRecord();
                        // Update the note with the values in the form fields.
                        noteEditor.updateRecord(currentNote);

                        var errors = currentNote.validate();
                        if (!errors.isValid()) {
                            currentNote.reject();
                            Ext.Msg.alert('Wait!', errors.getByField('title')[0].message, Ext.emptyFn);
                            return;
                        }

                        var notesList = NotesApp.views.notesList;
                        var notesStore = notesList.getStore();

                        if (notesStore.findRecord('id', currentNote.data.id) === null) {
                            notesStore.add(currentNote);
                        } else {
                           currentNote.setDirty();
                        }

                        notesStore.sync();
                        notesStore.sort([{ property: 'date', direction: 'DESC'}]);

                        notesList.refresh();

                        NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });

                    }
                }
            ]
        });

        NotesApp.views.noteEditorBottomToolbar = new Ext.Toolbar({
            dock: 'bottom',
            items: [
                { xtype: 'spacer' },
                {
                    iconCls: 'trash',
                    iconMask: true,
                    handler: function () {
                        // TODO: Delete current note.
                    }
                }
            ]
        });

        NotesApp.views.noteEditor = new Ext.form.FormPanel({
            id: 'noteEditor',
            items: [
                {
                    xtype: 'textfield',
                    name: 'title',
                    label: 'Title',
                    required: true
                },
                {
                    xtype: 'textareafield',
                    name: 'narrative',
                    label: 'Narrative'
                }
            ],
            dockedItems: [
                    NotesApp.views.noteEditorTopToolbar,
                    NotesApp.views.noteEditorBottomToolbar
                ]
        });

        NotesApp.views.notesList = new Ext.List({
            id: 'notesList',
            store: 'NotesStore',
            itemTpl: '
<div class="list-item-title">{title}</div>
' +
                '
<div class="list-item-narrative">{narrative}</div>
',
            onItemDisclosure: function (record) {
                // TODO: Render the selected note in the note editor.
            },
            listeners: {
                'render': function (thisComponent) {
                    thisComponent.getStore().load();
                }
            }
        });

        NotesApp.views.notesListToolbar = new Ext.Toolbar({
            id: 'notesListToolbar',
            title: 'My Notes',
            layout: 'hbox',
            items: [
                { xtype: 'spacer' },
                {
                    id: 'newNoteButton',
                    text: 'New',
                    ui: 'action',
                    handler: function () {

                        var now = new Date();
                        var noteId = now.getTime();
                        var note = Ext.ModelMgr.create(
                            { id: noteId, date: now, title: '', narrative: '' },
                            'Note'
                        );

                        NotesApp.views.noteEditor.load(note);
                        NotesApp.views.viewport.setActiveItem('noteEditor', { type: 'slide', direction: 'left' });
                    }
                }
            ]
        });

        NotesApp.views.notesListContainer = new Ext.Panel({
            id: 'notesListContainer',
            layout: 'fit',
            html: 'This is the notes list container',
            dockedItems: [NotesApp.views.notesListToolbar],
            items: [NotesApp.views.notesList]
        });

        NotesApp.views.viewport = new Ext.Panel({
            fullscreen: true,
            layout: 'card',
            cardAnimation: 'slide',
            items: [
                NotesApp.views.notesListContainer,
                NotesApp.views.noteEditor
            ]
        });
    }
});

Writing a Sencha Touch Application, Part 2

In the first part of this Sencha Touch tutorial we started building an app that allows its user to take notes and store them on the device. Having created the foundation of the application we are in the process of building the Notes List view, which at this point looks like this:

This view is missing the Ext.list that will render the notes created by the user and the New button in the toolbar, as depicted in the mock-up we created in the first part of this tutorial:

 

Let’s work on the notes list.

Creating a Data Model in Sencha Touch

Before can create the list, we need to create the data model that will represent a note. We do this using the Ext.regModel() method:

Ext.regModel('Note', {
    idProperty: 'id',
    fields: [
        { name: 'id', type: 'int' },
        { name: 'date', type: 'date', dateFormat: 'c' },
        { name: 'title', type: 'string' },
        { name: 'narrative', type: 'string' }
    ],
    validations: [
        { type: 'presence', field: 'id' },
        { type: 'presence', field: 'title' }
    ]
});

Our Note model, as any data model in Sencha Touch, is defined as a set of fields and the methods and properties relevant to it. The model has built-in support for validation, which we use to assert, via the validations config option, that the id and title properties require a value. We will see how model validation takes place when we build the note editor.

Data models also support associations with other models via has-many and belongs-to relationships. Although we will not use associations in this tutorial, I urge you to check out their documentation and examples. You will likely run into associations when building Sencha Touch applications.

Configuring a Sencha Touch Data Store to use HTML5 local storage

We also need a mechanism to cache our notes. The Ext.regStore() function allows us to create a data store, our notes cache, like so:

Ext.regStore('NotesStore', {
    model: 'Note',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-localstore'
    }
});

Ext.regStore() creates and registers a store with the framework’s Store Manager. Just as other classes in the framework do, you can use the Store Manager to lookup and modify the data stores in your application.

Our NotesStore’s model config option is the Note model. We also use the sorters option to specify that the notes will be sorted by date in descending order.

Remember that one of our application’s features is the ability to persist notes between browser sessions? The task of loading and saving model data is delegated to the store’s proxy. We use the proxy config option to define our proxy as an instance of the Ext.data.LocalStorageProxy class. This class uses the HTML5 localStorage API to save model data locally on the client browser. I find this abstraction of the local storage API one of the nicest features of the Sencha Touch framework.

Creating a Sencha Touch list

With data model and store ready, we can create the notes list like so:

NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    itemTpl: '
<div class="list-item-title">{title}</div>
' +
        '
<div class="list-item-narrative">{narrative}</div>
'
});

Nothing complicated here. The list will use the NotesStore, passed by name through the store config option. We define the markup used to render the notes with the itemTpl config option. The markup uses the list-item-title and list-item-narrative classes, which we will add to the app.css file:

.list-item-title
{
    float:left;
    width:100%;
    font-size:90%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.list-item-narrative
{
    float:left;
    width:100%;
    color:#666666;
    font-size:80%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.x-item-selected .list-item-title
{
    color:#ffffff;
}
.x-item-selected .list-item-narrative
{
    color:#ffffff;
}

Now let’s add the list to the container panel using the panel’s items config option:

NotesApp.views.notesListContainer = new Ext.Panel({
    id: 'notesListContainer',
    layout: 'fit',
    html: 'This is the notes list container',
    dockedItems: [NotesApp.views.notesListToolbar],
    items: [NotesApp.views.notesList]
});

Want to do a quick run on the simulator to see how we are doing? All right, but before we do that, let’s add a dummy note to the NotesStore just so we can see how the list renders a note. We can do this using the data config option:

Ext.regStore('NotesStore', {
    model: 'Note',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-store'
    },
    // TODO: remove this data after testing.
    data: [
        { id: 1, date: new Date(), title: 'Test Note', narrative: 'This is simply a test note' }
    ]
});

On the device emulator the Notes List view should look like this:

At this point our Notes List view is almost ready. We’ve added the toolbar, the notes list, the data model and the store that will cache the notes. We’re just missing the two navigation aids that will allow the user to open the Note Editor view: the New button on the toolbar and the disclosure button on each list item.

Adding buttons to a Sencha Touch toolbar

This is how we add the “New” button to the toolbar:

NotesApp.views.notesListToolbar = new Ext.Toolbar({
    id: 'notesListToolbar',
    title: 'My Notes',
    layout: 'hbox',
    items: [
        { xtype: 'spacer' },
        {
            id: 'newNoteButton',
            text: 'New',
            ui: 'action',
            handler: function () {
                // TODO: Create a blank note and make the note editor visible.
            }
        }
    ]
});

Using an hbox layout and adding a spacer to the toolbar allows us to push the New button all the way to the right. We will leave the handler function alone for a while. After we create the Note Editor view we’ll add code to open the editor from this handler.

Implementing disclosure buttons in a Sencha Touch list

The disclosure button is also very easy to implement by means of the onItemDisclosure config option:

NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    itemTpl: '
<div class="list-item-title">{title}</div>
' +
        '
<div class="list-item-narrative">{narrative}</div>
',
    onItemDisclosure: function (record) {
        // TODO: Render the selected note in the note editor.
    }
});

We’re using one of the onItemDisclosure overrides to bind a listener to the tap event on any list item. All we will need to do inside this listener is grab the record that received the tap and pass it to the Note Editor view. But we will finish this handler later, after we create our Note Editor.

Where are we?

Let’s stop for another quick check on the simulator. The app should look like this:

Cool! We have completed the main view of the application, except the two event handlers that will allow our users to navigate to the Note Editor.

We are now ready to begin work on the Note Editor:

But that will happen in the next chapter of this series. For now I will leave you with the source code of the app at this stage:

var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,
    launch: function () {

        Ext.regModel('Note', {
            idProperty: 'id',
            fields: [
                { name: 'id', type: 'int' },
                { name: 'date', type: 'date', dateFormat: 'c' },
                { name: 'title', type: 'string' },
                { name: 'narrative', type: 'string' }
            ],
            validations: [
                { type: 'presence', field: 'id' },
                { type: 'presence', field: 'title' }
            ]
        });

        Ext.regStore('NotesStore', {
            model: 'Note',
            sorters: [{
                property: 'date',
                direction: 'DESC'
            }],
            proxy: {
                type: 'localstorage',
                id: 'notes-app-localstore'
            },
            // TODO: remove this data after testing.
            data: [
                { id: 1, date: new Date(), title: 'Test Note', narrative: 'This is simply a test note' }
            ]
        });

        NotesApp.views.notesList = new Ext.List({
            id: 'notesList',
            store: 'NotesStore',
            itemTpl: '
<div class="list-item-title">{title}</div>
' +
                '
<div class="list-item-narrative">{narrative}</div>
',
            onItemDisclosure: function (record) {
                // TODO: Render the selected note in the note editor.
            }
        });

        NotesApp.views.notesListToolbar = new Ext.Toolbar({
            id: 'notesListToolbar',
            title: 'My Notes',
            layout: 'hbox',
            items: [
                { xtype: 'spacer' },
                {
                    id: 'newNoteButton',
                    text: 'New',
                    ui: 'action',
                    handler: function () {
                        // TODO: Create a blank note and make the note editor visible.
                    }
                }
            ]
        });

        NotesApp.views.notesListContainer = new Ext.Panel({
            id: 'notesListContainer',
            layout: 'fit',
            html: 'This is the notes list container',
            dockedItems: [NotesApp.views.notesListToolbar],
            items: [NotesApp.views.notesList]
        });

        NotesApp.views.viewport = new Ext.Panel({
            fullscreen: true,
            layout: 'card',
            cardAnimation: 'slide',
            items: [NotesApp.views.notesListContainer]
        });
    }
});

Stay tuned! And don’t forget to leave your comments or suggestions. :-)

The rest of the series:

Writing a Sencha Touch Application, Part 1

In this series we will create a Sencha Touch application that allows its users to take notes and store them on the device running the app. Along the way, we will dive into the following areas:

  • Building blocks of a Sencha Touch application
  • Rendering information using a list view
  • Editing information using form elements
  • Client-side data persistence across browser sessions
  • Navigation in a multi-view application

Sencha Touch Application

Application features

We’re after this simple feature set:

  • Ability to create notes
  • Ability to edit existing notes
  • Ability to delete notes
  • Ability to persist notes on the device running the application, across browser sessions

With these features in mind, let’s talk visual design.

Designing the main views

The first thing we need is a way for our users to create and edit notes. We can do this with a form, which we will call Note Editor, that will look just like this mock-up:

How would you build the Note Editor using Sencha Touch components? Take a look at this:

We also need a view that renders a list of the existing notes. This Notes List will be the main view of the application, and it will need to be connected with the Note Editor. This is how the Notes List should look:

And these are the Sencha Touch components we will use:

There’s one additional component that, although not visible, is vital to the application. This component will function as the viewport of the app, and it will take care of rendering and managing the navigation between the Notes List and the Note Editor. To play this role, we will choose a Panel configured with a card layout, where the Notes List and the Note Editor will be the layout’s cards:

Building blocks of a Sencha Touch application

Three different files will host the pieces of our application: index.html, app.js and app.cs:

Index.html is the file used to launch the app. In this file we need to include references to the Sencha Touch framework, as well as the app.js and app.cs files:

<script src="../../../senchatouch/1.1.0/sencha-touch-debug.js" type="text/javascript"></script>
<link href="../../../senchatouch/1.1.0/sencha-touch.css" rel="stylesheet" type="text/css" />
<link href="app.css" rel="stylesheet" type="text/css" />
<script src="app.js" type="text/javascript"></script>

App.js and app.css will in turn contain the javascript source and the styles used by the app.

In the app.js file, the very first thing we will do is instantiate an Application:

var App = new Ext.Application({
    name : 'NotesApp',
    useLoadMask : true,
    launch : function () {

    }
})

The Ext.Application class represents a Sencha Touch application. As described in the documentation for the Ext.Application class, instantiating a new application automatically creates a global NotesApp variable, along with the following namespaces:

  • NotesApp
  • NotesApp.views
  • NotesApp.controllers
  • NotesApp.models
  • NotesApp.stores

The launch() function is only run once. This is where we will create our application’s viewport:

launch: function () {

	NotesApp.views.viewport = new Ext.Panel({
		fullscreen: true,
		html:'This is the viewport'
	});
}

Our app’s viewport is an Ext.Panel. This panel will host the Notes List and Note Editor. When we set its fullscreen config option to true, we are instructing the panel to take up all the width and height available. This will also set the monitorOrientation config to true, enabling the panel to listen to orientation change events.

Monitoring orientation changes is a desirable feature for our viewport, as we want it to correctly render the Notes List and the Note Editor when the device’s orientation changes.

If you navigate to the index.html page using a device or simulator, our app will look like this:

Creating the Notes List container

The Notes List is the view that will be presented to our users when they launch the application. As defined in the mock-up, this view will consist of a toolbar and a list of notes. Let’s build it one step at a time.

First, we need the panel that will contain both the toolbar and the list of notes:

We will call this panel notesListContainer, and this is how we will instantiate it:

NotesApp.views.notesListContainer = new Ext.Panel({
    id : 'notesListContainer',
    layout : 'fit',
    html: 'This is the notes list container'
});

Before we add the toolbar and notes list to this panel, let’s render it within the viewport like so:

var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,
    launch: function () {

        NotesApp.views.notesListContainer = new Ext.Panel({
            id : 'notesListContainer',
            layout : 'fit',
            html: 'This is the notes list container'
        });

        NotesApp.views.viewport = new Ext.Panel({
            fullscreen : true,
            layout : 'card',
            cardAnimation : 'slide',
            items: [NotesApp.views.notesListContainer]
        });
    }
})

Note how we’ve added the layout and cardAnimation config options to the viewport. This is because we will use a card layout, where the Notes List container and the Note Editor are the cards. As in many popular mobile apps, these cards will be displayed using a slide animation.

A quick check on the simulator or device should confirm that the Notes List container is present:

What are we missing in this view? The toolbar and the notes list. We will take care of the toolbar next.

Adding a Sencha Touch toolbar to a panel

We can define the toolbar as follows. This is a plain toolbar, we won’t be adding buttons to it for now:

NotesApp.views.notesListToolbar = new Ext.Toolbar({
    id: 'notesListToolbar',
    title: 'My Notes'
});

Adding the toolbar to our notesListContainer is a simple step. We just need to use the dockedItems config option:

NotesApp.views.notesListContainer = new Ext.Panel({
    id: 'notesListContainer',
    layout: 'fit',
    html: 'This is the notes list container',
    dockedItems: [NotesApp.views.notesListToolbar]
});

Another quick check on the simulator or device should confirm that the toolbar is correctly rendered:

What’s next

In the next article of this series we will add the notes list to the view we just created, and we will move on to adding the ability to create and edit notes.

Here’s the source code for the application as it stands at this point:

var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,
    launch: function () {

        NotesApp.views.notesListToolbar = new Ext.Toolbar({
            id: 'notesListToolbar',
            title: 'My Notes'
        });

        NotesApp.views.notesListContainer = new Ext.Panel({
            id: 'notesListContainer',
            layout: 'fit',
            html: 'This is the notes list container',
            dockedItems: [NotesApp.views.notesListToolbar]
        });

        NotesApp.views.viewport = new Ext.Panel({
            fullscreen: true,
            layout: 'card',
            cardAnimation: 'slide',
            items: [NotesApp.views.notesListContainer]
        });
    }
})

Makes sense? What do you think?

The rest of the series: