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:
- The information in the form fields, the note’s title and narrative, is captured in a Note model instance
- If the title of the note is empty, we will alert the user and abort the Save routine
- If the note is new, we will add it to the notes cache; if it already exists, we will update the cache
- 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:
- Writing a Sencha Touch Application, Part 1
- Writing a Sencha Touch Application, Part 2
- Writing a Sencha Touch Application, Part 4
- Bonus: Writing a Sencha Touch MVC Application
Want To Learn More?
My Sencha Touch books will teach you how to create an application from scratch.
This Article’s Source Code
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
]
});
}
});



Great tutorial! When will part 4 be available?
Thank you Austin. Next part will be available next week.
These tutorials are helping me a lot. Waiting for the next one. Congratulations!
Great tutorials, really helping in learning things….
One thing I did notice working through the above after building the toolbars for the note editor and checking the display, there wasn’t a mention of docking the toolbars to the editor window with
dockedItems: [
NotesApp.views.noteEditorTopToolbar,
NotesApp.views.noteEditorBottomToolbar
]
Thank you, Dave. I added the code to the article.
Thanks, Waiting for next part
Awesome job Jorge, thanks for such tutorials. the overall js is getting long… what about a tutorial in converting this code to MVC?
Thanks, Nick. Converting to MVC is certainly a good idea. First I wanted to show how to build the app without venturing into topics such as patterns, number of files, structure, optimization, etc. I will probably be writing about those next.
Yup exactly, it make sense to go this path!
Great job, Jorge. +1 for showing MVC version!
Really great tutorial this has helped me immensely.THANKS !!!
I’m really enjoying this! Just what I looked for to start with!
Hi.
This is a amazing article for newbie!
However, I think there is some wrong with the following the code snapshot
listeners: {
'render': function (thisComponent) {
thisComponent.getStore().load();
}
}
OK, I know why.
I think that is typo.
'render': function (thisComponent) {should berender: function (thisComponent) {Handy, read the js and json specs.
I comfused that does HTML5 database only used in webserver(tomat) context?
Can app access localstorage in static html page with SenchaTouch framework?
OK, Sorry I found that:
the code “‘render’: function (thisComponent)” and “render: function (thisComponent)” are both fine.
But the code in static html with senchatouch doesn’t work in desktop Safari but iPhone simulator and html page in webserver context are both fine.
And the code is working in chrome with both static html page and webserver context.
So, is it the bug of “component.getStore().load();” ?
Hi jorge, great job!!! I would to start from your example and I would to change something like data encryption, but I’m new with sencha touch. I need to encrypt data (with aes algoritm) before data go to database, but I don’t understand where I need to use my encryption function. Any suggestion?!
Thanks a lot, and I hope to read you soon!
A.
oh, I just find the way!!!
. I love this framework and the full documentation!!! 
hi all . i have tried this example its very good for begineers . in this example i created a new note editor in one js file and edit note in another js file. how can i link these files ? . i want to move from new note page to editor page when i click on the new button in new note js file. pleas help me.
It looks like there is a redundancy on the title and ID check. The ID will always be created right? And the title always must be filled in. Is it true that the presence-check (from tutorial 2) is useless or am I missing something? (Just to be sure)
From tut2, when saving to storage:
validations: [
{ type: 'presence', field: 'id' },
{ type: 'presence', field: 'title' }
]
I’d rather assert that these rules are being followed. I prefer to program defensively, in case that I or the programmer that will maintain the application could make a mistake later on and try to save invalid data.
That’s so true! Thanks for the advice.
Should this save notes permanently and locally on the phone? I am using an iPhone and refreshing the page deletes all the notes. Seems to be working other than that. Thanks.
Matt, What is the fix for the notes disappearing after refresh? I cant seem to work it out..
a few people have mentioned it here but nobody has posted a real solution to it.
thanks
I have not been able to reproduce it. I hope Matt has a chance to comment on his findings.
sorry let me clarify, its after when you delete a note and then refresh your browser (occasionally a few times) all the notes disappear and furthermore crashes the app!
The only way I have found to fix the crash is to delete cookies/history cache etc on browser but this only fixes the app, the notes will have obviously disappeared at this point !
I hope this will help someone find a fix as Im looking forward to use this app!
thanking you.
Max, I have not been able to reproduce this. Did you debug on the JS console? You should be able to see what’s happening.
Scratch that, it is working
Thanks!
For some reason in line 155 NotesApp.views.viewport.setActiveItem(‘noteEditor’, { type: ‘slide’, direction: ‘left’ }); I get the following error. I have replaced the ‘noteEditor’ with the actual object and it works not sure why. The documentation says it takes Number/Object it does not take string parameter according to the document.
Uncaught TypeError: Cannot read property ‘isComponent’ of undefined
Ext.lib.Container.Ext.extend.createComponentsencha-touch.js:6
Ext.lib.Container.Ext.extend.lookupComponentsencha-touch.js:6
Ext.lib.Container.Ext.extend.prepareItemssencha-touch.js:6
Ext.lib.Container.Ext.extend.addsencha-touch.js:6
Ext.layout.CardLayout.Ext.extend.setActiveItemsencha-touch.js:6
Ext.Container.Ext.extend.setActiveItemsencha-touch.js:6
Ext.Button.handlerindex.html:67
Ext.Button.Ext.extend.callHandlersencha-touch.js:6
Great tutorial, thank you.
I thin there is a slight issue in the save note functionality. You update the record and then validate. However, if there is an error in the record and then the user presses the back button, any changes have already been made to the model and then show in the list. The sync command doesn’t seem to be needed in this case. Is there a way to validate the form fields before doing the Update or maybe a way to revert the record back to its original state straight after the error message?
Steve, you might not be using the same Sencha Touch version I use in the tutorial. What version are you using?
Hi Jorge,
Thank you for replying. I am using Sench Touch 1.1.1.
Thank you,
Steve
I have just tried your notes app in 1.1.0.
If you go to edit an existing note and blank out the title then you get an error stating “Please enter a title for this note”.
So, far so good.
If you now press the OK button and then the Home button, you will see that it is showing a blank title. Note. If you close the browser and re-open it reverts back to the original.
So, it isn’t actually saving the change but the borwser is making it look like it has.
I am using Safarai 5.0.4. Do you see the above in your browser? If so, any ideas about how to fix?
Thank you,
Regards,
Steve
Steve, I’m able to reproduce in Chrome. Working on a fix.
Steve, I added a call to reject() on the model after validation fails. Be mindful that there’s a bug in the Ext.util.Stateful implementation still present in ST 1.1.1 GPL. You can find the details in the Sencha Touch 1.X Bugs forum if you search for “stateful”.
Hi Jorge,
Many thanks for investigating the issue and alerting me to the surrent Sencha Touch “stateful” bugs.
In the end, to fix this issue, I called the model.reject() function after the validation failure as you said. However, I also needed to call the model.setDirty() function before the call to model.updateRecord()
Regards,
Steve
Hi Jorge,
Good morning , I’m working on mobile application using MVC and Sencha Touch.
I have created the Tab panel toolbar, I have to retrieve the data from the database when i’m clicking the tab panels in the toolbar.
Please help me out how to get the data.
Thanks in Advance.
awsome Tutorial
Just bought your ebook for support
Thanks for your support. I’m glad that the tutorials helped you.
Thanks for the great tutorial
, it helps me a lot in understanding sencha touch.
But I’m facing a problem when I tried it in tablet (Galaxy tab), when I rotate the screen to portrait (from landscape), the toolbar located on bottom is disappear.
It also happens in all applications I created that use toolbar on bottom, I haven’t found any solution for this.
Could you please help me out of this case?
Thanks in Advance
Hi..
Thanks a lot for great tutorial.
I have some questions.
1. I can not see Label “Title” and “Narrative” there are only textfield and textarea on my browser.
2. There is only one button “Home” on upper toolbar and the width is full of toolbar.
There is no Save button.
3. there is no trash button on bottom toolbar.
I copy and paste sencha touch css and js file in the folder where index.html is and change code as below.
is that problem?
I updated latest google chrome version and copy sencha touch css and js file of 1.1.1 version.
Could you help me out of this case?
Great tutorial. I like it very much.
Hi, Thanks for your great tutorial, Would you mind using Ext Designer 2.0 to redo the tutorial again? I have tried to follow your tutorial but using Ext Designer 2.0, however not success, and problem on how to add handler as well as the script on save button.
Thanks
Fc
Hi Jorge,
Thanks for writing such a tutorial. It is very helpful for beginners like me. In my app that i have build using your tutorial has got some problem as “NEW” button is not working. The code is exactly the same as yours
Thanks