How to Create a Sencha Touch 2 App, Part 4

This is the fourth part of my tutorial on how to create a Sencha Touch 2 application. In this article, we are going to complete the following tasks:

  • Add the delete note feature to the Note Editor View.
  • Implement the navigation back to the Notes List Container View when the Home button in the Note Editor View is tapped.
  • Modify the Notes List Container View so it renders the notes grouped by date.

Deleting Records from a Sencha Touch Data Store

The Delete Note workflow begins when a user taps the Delete Button on the Note Editor View:

This Button needs a tap handler, which we will add to the NoteEditor Class, in the NoteEditor.js file:

var deleteButton = {
    xtype: "button",
    iconCls: "trash",
    iconMask: true,
    handler: this.onDeleteButtonTap,
    scope: this
};

As we did with the Save Button, we are using the handler and scope configs to map the function that will handle tap events on the Button, as well as to pass the View as the scope for the handler function.

Of course, we need to add the onDeleteButtonTap() function to the NoteEditor Class:

onDeleteButtonTap: function () {
    console.log("deleteNoteCommand");
    this.fireEvent("deleteNoteCommand", this);
}

This is the same pattern we’ve used to emit events from the Views throughout the application. We capture the event triggered from a control in the View, and create a View event that is in turn captured by the Controller.

Over in the Notes Controller, we are going to map a handler function to the deleteNoteCommand event fired by the Note Editor View. We will do this in the control config, under the noteEditor key:

control: {
    notesListContainer: {
        // The commands fired by the notes list container.
        newNoteCommand: "onNewNoteCommand",
        editNoteCommand: "onEditNoteCommand"
    },
    noteEditor: {
        // The commands fired by the note editor.
        saveNoteCommand: "onSaveNoteCommand",
        deleteNoteCommand: "onDeleteNoteCommand"
    }
}

Now we can implement the onDeleteNoteCommand() function like so:

onDeleteNoteCommand: function () {

    console.log("onDeleteNoteCommand");

    var noteEditor = this.getNoteEditor();
    var currentNote = noteEditor.getRecord();
    var notesStore = Ext.getStore("Notes");

    notesStore.remove(currentNote);
    notesStore.sync();

    this.activateNotesList();
}

Here, we acquire references to the Note Editor, the note loaded into the editor, and the Notes Store. Remember that the getNoteEditor() function is a routine the framework created for us when we declared the editor in the refs config:

refs: {
    // We're going to lookup our views by xtype.
    notesListContainer: "noteslistcontainer",
    noteEditor: "noteeditor"
}

Our next steps in onDeleteNoteCommand() are to remove the current note from the store and make the changes permanent:

notesStore.remove(currentNote);
notesStore.sync();

Finally, we activate the Notes List Container View:

this.activateNotesList();

Another quick check on the emulator should confirm that at this point we are able to delete notes.

Navigating Back To the Main View

In order to navigate from the Note Editor View back to the Notes List Container View without making any changes to a note, we need to add a tap handler for the Home Button in the Note Editor Class:

var backButton = {
    xtype: "button",
    ui: "back",
    text: "Home",
    handler: this.onBackButtonTap,
    scope: this
};

We will define the onBackButtonTap() function as follows:

onBackButtonTap: function () {
    console.log("backToHomeCommand");
    this.fireEvent("backToHomeCommand", this);
}

In the Controller, we will map this event to the onBackToHomeCommand() handler function:

control: {
    notesListContainer: {
        // The commands fired by the notes list container.
        newNoteCommand: "onNewNoteCommand",
        editNoteCommand: "onEditNoteCommand"
    },
    noteEditor: {
        // The commands fired by the note editor.
        saveNoteCommand: "onSaveNoteCommand",
        deleteNoteCommand: "onDeleteNoteCommand",
        backToHomeCommand: "onBackToHomeCommand"
    }
}

And the onBackToHomeCommand() function will look like this:

onBackToHomeCommand: function () {

console.log("onBackToHomeCommand");
this.activateNotesList();
}

At this point, we can use the emulator to check that a tap on the Home Button activates the Notes List Container View.

Setting Up Grouping in a Sencha Touch List

One important usability detail we are missing is the ability to render the cached notes grouped by date. It’s amazing how easily we can accomplish this in Sencha Touch. Let’s first define a grouper config for the Notes Store:

Ext.define("NotesApp.store.Notes", {
    extend: "Ext.data.Store",
    requires:"Ext.data.proxy.LocalStorage",
    config: {
        model: "NotesApp.model.Note",
        proxy: {
            type: 'localstorage',
            id: 'notes-app-store'
        },
        sorters: [{ property: 'dateCreated', direction: 'DESC'}],
        grouper: {
            sortProperty: "dateCreated",
            direction: "DESC",
            groupFn: function (record) {

                if (record && record.data.dateCreated) {
                    return record.data.dateCreated.toDateString();
                } else {
                    return '';
                }
            }
        }
    }
});

As of this writing, groupers are not explained very well in Sencha Touch’s documentation. However, it is not difficult to make sense of this config’s properties. The groupFn config is the function used to generate the label for the group. In our case, the label will be the date the notes were taken:

The sortProperty config defines the value that will be used to sort the groups. If you do not include this config, the fields will be sorted based on the value returned by the function defined with the groupFn config. The direction config specifies the direction to sort the groups.

The last change needed to implement grouping consists of adding the grouped config to the NotesList Class:

Ext.define("NotesApp.view.NotesList", {
    extend: "Ext.dataview.List",
    alias: "widget.noteslist",
    config:{
        scrollable:'vertical'
    },
    config: {
        loadingText: "Loading Notes...",
        emptyText: "</pre>
<div class="\&quot;notes-list-empty-text\&quot;">No notes found.</div>
<pre>
",
        onItemDisclosure: true,
        grouped: true,
        itemTpl: "</pre>
<div class="\&quot;list-item-title\&quot;">{title}</div>
<div class="\&quot;list-item-narrative\&quot;">{narrative}</div>
<pre>
"
    }
});

When we set the grouped config to true, the List will use the groups defined in its store, through the grouper config, to render its items appropriately.

Let’s check how the list looks after we turned on grouping. Start the emulator and confirm that the notes list has date groups similar to the following screenshot:

Summary

We just finished building all the features of the Note Editor View by adding both, the ability to delete notes, and the ability to navigate back to the Notes List Container View when the Home Button is tapped.

We also modified the Notes List so the notes are rendered in groups based on the date the notes were saved. This makes it easier for our users to find their notes.

In the next chapter of this series we are going to create a version of the app where the Notes List and Notes Editor Views are defined using config objects, instead of initialize functions.

Stay tuned! :-)

Downloads

Download the source code for this article: NotesApp-ST2-Part4.zip

The Entire Series

Want To Learn More?

My Sencha Touch books will teach you how to create an application from scratch.

Comments

  1. Aishwarya Sharma says

    Hi Jorge,

    This has been an exceptional help in learning Sencha Touch MVC applications.
    Hope you keep posting many more topics in Sencha Touch, specially Custom Styling using SASS and COMPASS…

  2. Lin says

    This tutorial did help a lot
    What I am struggling with in st2 is how to communicate with sever side data.
    Could you use this example to store notes on sever side (Jason file or database) instead of this localstorages? Thanks

  3. Lin says

    I changed the store’s proxy as following: (uncommend your type&id), but it can’t save physically.
    proxy: {
    //type: ‘localstorage’,
    //id: ‘notes-app-store’
    type: ‘ajax’,
    url: ‘data/notes.json’,
    reader: {
    type: ‘json’
    }
    },

    • says

      The store is not able to save directly to the file. Remember, the store is on the client side, and your json file is on the server side. You will need to create a server-side handler that (1) sends the contents of the json file to the client, and (2) receives data sent by the store and updates the json file.

      • Lin says

        Hi Jorge, your explanation gets rid of my confusions. I need to create server scripts like php to do this.
        it’s nice of you. I hope your future posts cover server-side topics as there are quite few references out there in ST documentations

      • Janusz says

        Jorge,
        Is there any way to update that notes data via proxy ‘jsonp’ (cross domain)?
        I can fact that data from data base via php file, which generate the json file, but I have a problem with updating the changed note. I mean the note is updated in the store, but I’d like to be able to update the data in database.

  4. Lance says

    Thanks again Jorge, huge huge help.

    How can I reference the fields in the editor view?

    I’m using Ext.ComponentQuery.query(‘togglefield’)[].setValue(0); which works but it would be better to get by name.

  5. Aishwarya Sharma says

    Hi Jorge,

    When we are on the NotesListContainer page, if I pull the NotesList with my mouse in the downwards direction there comes a void in between the toolbar and the NotesList. Can this be avoided??

    Can we change the look and feel of the tabpanels etc according to our needs, I mean is it possible at least??

    Thanks and Regards

  6. Aishwarya Sharma says

    Hi,

    I wanted to ask if it is possible to make an accordion in Sencha Touch 2 .
    I yes then could you just hint how to proceed with it.

    Thanks & Regards

  7. José says

    Hi Jorge,

    How setting different handlers for the “disclosure” and “itemtap” events on a list ?

    var servidoresList = {
    xtype: “servidoreslist”,
    store: Ext.getStore(“Servidores”),
    listeners: {
    disclose: {
    fn: this.onServidoresListDisclose,
    scope: this
    },
    itemtap: {
    fn: this.onItemTap,
    scope: this
    }
    };

    Thanks and Regards

  8. reza says

    Hi,
    First of all thank you for your great tutorial. I have faced a wired issue.
    When I add a few notes, then I remove them and refresh the browser. I get the following error. I would appreciate it, if you could help me on the issue.

    ‘undefined’ is not an object (evaluating ‘record.isModel’)

    Thanks
    Reza

  9. José says

    Hi Jorge,

    In your example i changed the title toolbar in file NoteEditor.js as follows
    var topToolbar = {
    xtype: ‘toolbar’
    docked: “top”,
    title: this.record.get (‘title’),
    items: [
    backButton
    ]
    };
    and I get the following error:
    Uncaught TypeError: Can not call method ‘get’ of undefined

    can you help me?

    Thanks and Regards

  10. Aishwarya Sharma says

    Hey Jorge,

    I have made a view with a toolbar(which has a button firing a tap event) and a panel.
    When I try to set the height of the panel in the controller through the following code:

    onToggleList: function()
    {
    console.log(“In controller”);
    this.getAccordionPanel().getComponent(‘p’).setHeight(’0′);

    }

    it gives me an error “Uncaught TypeError: Cannot call method ‘setHeight’ of undefined”
    where p is the id of the panel.
    Can you tell how I can access the height when getComponent() is not working??

    Thanks $ Regards

  11. Himanshu Rathore says

    Hey Jorge,
    Brilliant tutorial sir helped me lot.Right now i am wondering how can we integrate sms gateway to my sencha touch 2.0 app.I have gone through the following links and dont find it helpful because the link provided phonegap’s SMS class link but now its deprecated.

    http://www.sencha.com/forum/showthread.php?131445-It-is-possible-to-send-SMS-Messages-from-a-Sencha-Touch-App

    http://stackoverflow.com/questions/3193997/send-sms-through-phonegap-on-android

    So can you show us by a tutorial or code snippet to achieve this.

  12. Unyime Daniel says

    This is by far the most documented tutorial I have come across the internet.
    I need a book that teacher sencha designer or architect or sencha 2 itself.
    George please start writing one for us. If you know any good one, pls recommend.
    We dont want to start building and fine out that another version of sencha3 is out.

  13. James says

    Firstly many thanks for this excellent tutorial. Once I delete a note I can still add new notes and edit them afterwards, but if I refresh the browser it hangs on loading and I get the following error:

    Store.js:904TypeError: ‘undefined’ is not an object (evaluating ‘record.isModel’)

    Looks like removing a record breaks to the store? This happens in both Safari and Chrome. Any help would be appreciated.

    Also, for further articles, it would be great to see how to customise the default themes and work with Compass/Sass. I’d also like to see how that’s done using Sencha Architect 2.

    Thanks again.

  14. Lin says

    hi, jorge
    can you answer me a question unrelevant to this notes app issue. How can i do in ST2 to tap a image to fullscreen and tap again to restore it to original size? (just like we operate in iPhone image rol)l

  15. Juan Carlos Morales Mora says

    Thanks a lot for the tutorial. It was really helpfull and well explained. A very nice start for the Sencha Touch technology.

    Really nice job, thanks a lot!!!!!!

  16. epine says

    This give me a whole ST’s technoloy. It was really wonderful. Very Very nice job and thanks a lot.

  17. Ram says

    Hi Jorge,

    on click of list view item the disclose is not working i am using sench touch 2
    onNotesListDisclose: function (list, record, target, index, evt, options) {
    console.log(“editNoteCommand”);
    this.fireEvent(‘editNoteCommand’, this, record);
    },

    This function is not at all calling please help with this issue

    Thanks

      • Ram says

        Ext.define(“NotesApp.view.NotesListContainer”, {
        extend: “Ext.Container”,
        alias: “widget.noteslistcontainer”,

        initialize: function () {

        this.callParent(arguments);

        var newButton = {
        xtype: “button”,
        text: ‘New’,
        ui: ‘action’,
        handler: this.onNewButtonTap,
        scope: this
        };

        var topToolbar = {
        xtype: “toolbar”,
        title: ‘My Notes’,
        docked: “top”,
        items: [
        { xtype: 'spacer' },
        newButton
        ]
        };

        var notesList = {
        xtype: “noteslist”,
        store: Ext.getStore(“Notes”),
        listeners: {
        disclose: { fn: this.onNotesListDisclose, scope: this }
        }
        };

        this.add([topToolbar, notesList]);
        },
        onNewButtonTap: function () {
        console.log(“newNoteCommand”);
        this.fireEvent(“newNoteCommand”, this);
        },
        onNotesListDisclose: function (list, record, target, index, evt, options) {
        console.log(“editNoteCommand”);
        this.fireEvent(‘editNoteCommand’, this, record);
        },

        config: {
        layout: {
        type: ‘fit’
        }
        }
        });

        here is the NotesListContainer am i missing something on click of list view the disclose is not calling the function onNotesListDisclose

  18. Han says

    And launches the delete confirmation window, select yes, you want to make the deletion.

    onDeleteNoteCommand: function() {
    console.debug(“onDeleteNoteCommand”);

    Ext.Msg.confirm(“Warning”, “Are you sure Delete?”, function(btn, text) {
    if(btn == ‘yes’) {
    var noteEditor = this.getNoteEditorView(); // this error how??
    var currentNote = noteEditor.getRecord();
    var notesStore = Ext.getStore(“Notes”);

    notesStore.remove(currentNote);
    notesStore.sync();
    this.activateNotesList(); // this error ??
    }
    });
    },

  19. Ram says

    Hi jorge,

    You have written every function inside initialize function which will call the controller js file
    can you please give some example how to call a function of controller written inside
    config
    {

    xtype:’button’,
    text:’test Call me’,
    flex:1,
    listeners: {
    tap: function () {
    console.log(“test callmeTapped”);
    this.fireEvent(“backToHomeCommand”, this);

    }

    }

    },

    This function is not calling the controller js file

  20. Terence Tian says

    It is really a nice article, did help me a lot. can you make a sample about routes of controller

    thanks a lot.

  21. Michael McClenaghan says

    I am receiving the same error as a few other people. “Uncaught TypeError: Cannot read property ‘isModel’ of undefined “. This error occurs when at least 2 notes are added, then one is deleted, and the page is refreshed. The only way to rectify this is to clear the browser cache, which removes all other notes too. Any idea why this is happening? I assume it is something to do with an entry not being deleted in the local-storage.

    • Michael McClenaghan says

      After looking into it, it seems that the store.remove(record) method is removing the model from the localstore, but is not removing the Id from the proxy itself, so when the Store is loaded on the page refresh, it is trying to lookup an Id that does not exist.

  22. says

    Hi.,
    i wish to connecting mysql database using sencha touch tutorial in android native application..please explain step by step example..because am a beginer for android and sencha-touch.

  23. Gangadhar says

    Hi, Great thanks to your tutorial…. I have built my application based on your Part5….

    But, my problem is i want to load two views . First view contains list of values coming from url dynamically.

    second view contains the details of the particular item clicked which loads it from other url.
    Now the first list is coming perfectly but, second view is not getting dynamic data of particular item.
    I have declared a global variable , and i am reassigning the global variable value in first container with value of a whatever the clicked item. now the global variable contains the clicked item value. when i am giving this global variable in second url value this not displaying the clicked item value rather it is displaying the starting static global variable value. now how to pass the reassigned global variable value to second url…..

    my url pattern is…
    first url: http://localhost/……collection?&format=xml
    second url: http://localhost/……collection(value=’123′)?&format=xml

    Any help will be highly appreciated….

      • Gangadhar says

        Hi Jorge,

        Thanks for your reply…

        As per my understanding ,I think that there is one problem with this type of approach for dynamic data .

        Since all the containers are loading initially in app.js, the dynamic URLs in next containers are not taking the clicked item value of the first list for displaying the details.

        Can we do something so that Containers are loaded one after other and not initially…

        Kindly reply me with suggestive answers… Thanks in advance…

          • Gangadhar says

            Hi Sir,

            My store is containing URL.
            According to my requirement i want to reload the Store at certain place.
            Is there any way to reload the Store so that i can load dynamic value to the store in Controller file.

            Reply me soon sir…its urgent.Thanks in advance…

            Regards,
            Gangadhar

  24. Gangadhar says

    Hi Sir,

    Anyway I tried in my own way and the problem got solved.

    the other problem is i want to load Google map into application .
    The map should load according to store’s location.
    As there are different locations, the map should load dynamically when i click on Map button from Employee details container.

    Can you please help me with some code how to load Map when i click on Map button. The Control goes to Controller when i click on Map button. So, the actual logic will be in Controller file.

    Thanks in advance…..

  25. patrick says

    Jorge man am so greatfull for all your posts they really helped me alot….But i wish you can just help with using the same app but using a mysql database….i’ve done all your examples and they are are the best sencha touch 2 examples google can find..please help me with the mysql part..thank you

    • says

      Hang in there Patrick. I will be writing about connecting the application to a server soon. Thank you for being a loyal reader.

  26. Alexis says

    Mr. Jorge, let me thank you for all your work you don’t know the important that have this tutorial for me know. Thanks for your time and sharing your skills! ^^

  27. Alexis says

    Hi, Mr. Jorge, Well this type I write you because I have a real big problem, When I build the app seems like the models and stores not exist, on the list of notes I can’t see anything, and into New Note view any field show, but this happens just after build. Please can help me?

    I don’t wan’t bother you, but please I need to know what I suppose to do.

  28. Girish says

    Hi Jorge
    The tutorial is good and everything is working fine,but when i type in narrative field and try to save it a alert msg should fire but its showing the following error,please tell me how to solve this
    Uncaught Error: [ERROR][NotesApp.controller.Notes#onSaveNoteCommand] Using Ext.Msg without requiring Ext.MessageBox

    Regards,
    Girish

    • says

      Add the requires to the NoteEditor. It should en up looking like this:

      requires: ['Ext.Toolbar', 'Ext.form.FieldSet', 'Ext.form.Text', 'Ext.field.TextArea', 'Ext.MessageBox'],

  29. Girish says

    Hi Jorge,

    Thanks jorge now its working fine..Please share if u have some more tutorials..once again thanks for this tutorial.

    Regards,
    Girish

  30. GUIISJ says

    And launches the delete confirmation window, select yes, you want to make the deletion.

    onDeleteNoteCommand: function() {
    console.debug(“onDeleteNoteCommand”);

    Ext.Msg.confirm(“Warning”, “Are you sure Delete?”, function(btn, text) {
    if(btn == ‘yes’) {
    var noteEditor = this.getNoteEditorView(); // this error how??
    var currentNote = noteEditor.getRecord();
    var notesStore = Ext.getStore(“Notes”);

    notesStore.remove(currentNote);
    notesStore.sync();
    this.activateNotesList(); // this error ??
    }
    });
    },

        • says

          onDeleteNoteCommand: function() {
          console.debug(“onDeleteNoteCommand”);

          var me = this;

          Ext.Msg.confirm(“Warning”, “Are you sure Delete?”, function(btn, text) {
          if(btn == ‘yes’) {
          var noteEditor = me.getNoteEditorView();
          var currentNote = noteEditor.getRecord();
          var notesStore = Ext.getStore(“Notes”);

          notesStore.remove(currentNote);
          notesStore.sync();
          me.activateNotesList();
          }
          });
          },

          • GUIISJ says

            OMG !!! Thank you so much Jorge.
            Next issue, I want create Log-in page and check name,password
            Code me

            Ext.application({
            name: “NotesApp”,

            models: ["Note"],
            stores: ["Notes"],
            launch: function() {
            Ext.create(‘Ext.form.Panel’, {
            fullscreen: true,
            items: [{
            xtype: 'fieldset',
            title: 'Register',
            items: [{
            xtype: 'textfield',
            label: 'Name',
            name: 'name',
            type: 'string'
            }, {
            xtype: 'passwordfield',
            label: 'Password',
            name: 'password'
            },
            {
            xtype: 'button',
            text: 'OK',
            ui: 'confirm',
            handler: function() {
            if(name == 'name'){

            Ext.Msg.alert('', 'Login Successful!');}
            else {Ext.Msg.alert('Error', 'Both username and password are required.');}

            }
            }]
            }]
            });
            }
            });

            Help me again!! Please

  31. Jihane says

    Hello there, thak’s a lot for this tutorial, it did clarify the sencha touch framework to me.

    I have one question which is : can i use sencha touch effects such as animateActiveItem in other framework like bootstrap twitter or zurb foundation?

    Thank’s a lot, knowing this is hugely crucial to me :)
    Best regards

  32. Trung Nguyen says

    Hello Jorge,

    I have a problem after build app native with this example. From browser I can save the record, close browser and then rerun browser, record still there. But with Android app after using command line. sencha app build native. The app running inside my device. I can save record, but after I stop the app, rerun the app again record has been gone.

    Do you have any ideas?

    Thanks a lot,
    Trung.

  33. Trung Nguyen says

    Hi Jorge,

    I works fine after I drop folders into an Android www and build the application with Eclipse, not use command line from sencha. Is it bug from sencha?

    Thanks,
    Trung.

  34. Harshita Singh says

    Hi,

    I have a requirement wherein i want to generate controls dynamically reading the values from json.My json would have values for UI components.Please help me out with this if you have some demos tutorials for same.

    Thanks

  35. Welder Jobs says

    Howdy I am so excited I found your webpage, I really found you by accident, while I was researching
    on Yahoo for something else, Anyways I am here now and would just like to say thanks a lot for a remarkable post and
    a all round enjoyable blog (I also love the theme/design), I don’t have time to read through it all at the moment but I have saved it and also added in your RSS feeds, so when I have time I will be back to read much more, Please do keep up the excellent work.

  36. says

    Hi Jorge,

    Thank you very much for this nice tutorials series :)
    I learned more with these than I did with the Sencha doc (which is a bit confusing and poor to my sense..).

    I have a question about “sorters”, how to dynamically change the sorter property (currently “dateCreated”) ? With a “filters/criteria” box for example ?

    Thank you :)

  37. Net Mat says

    Hi Jorge,

    I admire and appreciate your effort in creating such a beautifully explained tutorial. Thanks a lot for the effort. Now could not resist asking one question here on store and grouper config.

    I think grouping the notes is view related stuff. So the grouper config in “view” (here NoteList) should be sufficient. Why there is a “grouper” config for “store” (here Notes) and a “grouped” config in view?

    Thanks!

  38. Shubham Srivastava says

    Hi Jeorge
    I really appreciate your tutorials , they are really helpful. I have one question, …..how can we get different views or different event occurs when we tap on a disclosure item. Example: like in this note app if we are clicking on disclosure we are gettin to the edit note section, what if we like to get a different view if we tap on a different disclosure on notes list section.

    Looking forward for your favourable reply…..

    Shubham

Leave a Reply

Your email address will not be published. Required fields are marked *