Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Writing a Sencha Touch Application, Part 3

DZone's Guide to

Writing a Sencha Touch Application, Part 3

· Mobile Zone
Free Resource

Discover how to focus on operators for Reactive Programming and how they are essential to react to data in your application.  Brought to you in partnership with Wakanda

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:

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 New 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()) {
                    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);
                }

                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()) {
                            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);
                        }

                        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
            ]
        });
    }
});

 

 

From http://miamicoder.com/2011/writing-a-sencha-touch-application-part-3/

Learn how divergent branches can appear in your repository and how to better understand why they are called “branches".  Brought to you in partnership with Wakanda

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}