How to Create a Sencha Touch 2 App, Part 2

In this second part of the tutorial on how to build a Sencha Touch 2 application we will continue building a small application that allows people to save notes on the device running the app.

So far, we have been working on the View that renders the list of notes cached on the device:

While building this View, we defined the NotesListContainer Class. We are going to start this article with a modification to this Class. This modification will promote encapsulation, and make the app easier to change and maintain.

We had previously defined the NotesListContainer Class as follows:

Ext.define("NotesApp.view.NotesListContainer", {
    extend: "Ext.Container",
    config: {
        items: [{
            xtype: "toolbar",
            docked: "top",
            title: "My Notes",
            items: [{
                xtype: "spacer"
            }, {
                xtype: "button",
                text: "New",
                ui: "action",
                id:"new-note-btn"
            }]
        }]
    }
});

The changes we will make to this View consist of using the Class’s initialize() function to define its components. We will begin creating a new Class definition like so:

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

    initialize: function () {

        this.callParent(arguments);

    }
});

Notice how we are using the alias config. This config is very helpful, as it effectively defines an xtype for our Class. Thanks to the alias, we can now refer to the NotesListContainer Class with the xtype=”noteslistcontainer” config. We will use this construct to instantiate the Class later in the article,

In Sencha Touch 2, every Class has an initialize() function. Initialize() can be used to perform logic right after the Class is instantiated, it replaces the initComponent() function that exists in Sencha Touch 1, and we can use it to add the toolbar with the New button:

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

        this.add([topToolbar]);
    },
    onNewButtonTap: function () {
        console.log("newNoteCommand");
        this.fireEvent("newNoteCommand", this);
    },
    config: {
        layout: {
            type: 'fit'
        }
    }
});

In the initialize() function, after invoking callParent(), we define the New button variable, along with the Toolbar. As you already know from the previous chapter, the Toolbar’s items are the spacer and the Button.

Our last step within initialize() consists of adding the Toolbar to the View’s items via a call to the add() function.

If you go back to the newButton definition, you will notice that we’ve added a tap handler using the handler config:

var newButton = {
    xtype: "button",
    text: 'New',
    ui: 'action',
    handler: this.onNewButtonTap,
    scope: this
};

This function will capture tap events on the button, and transform them into an event that is more specific and descriptive of the application’s business logic. We will call this event newNoteCommand. Here’s the handler’s implementation:

onNewButtonTap: function () {
    console.log("newNoteCommand");
    this.fireEvent("newNoteCommand", this);
}

This is one of the important changes we are making to the NotesListContainer Class. In the first article of the series, the tap event on the button was captured by the Controller:

Ext.define("NotesApp.controller.Notes", {
    extend: "Ext.app.Controller",
    config: {
        refs: {
            newNoteBtn: "#new-note-btn"
        },
        control: {
            newNoteBtn: {
                tap: "onNewNote"
            }
        }
    },
    onNewNote: function () {
        console.log("onNewNote");
    }

    // Rest of the controller's code omitted for brevity.
});

Now, we’re capturing the event within the View, and broadcasting a new event, newNoteCommand, which will be captured by the Controller:

onNewButtonTap: function () {
    console.log("newNoteCommand");
    this.fireEvent("newNoteCommand", this);
}

Although both approaches are valid, there are important benefits derived from the second approach:

  • The View’s interface is cleaner. It now fires events that are more in line with the business logic of the application.
  • The View is easier to modify and maintain, as the Controller does not need intimate knowledge of the View’s inner workings.

As long as the View’s public events remain the same, the Controller will be able to work with the View. For example, we can change the elements used to trigger the creation of a new note in the View without affecting the Controller. The Controller only needs to listen to the newNoteCommand event fired from the view.

The next step of this refactoring will take place in the app.js file, where we will modify the application() function so we create our NoteListContainer instance using the Class’s alias:

Ext.application({
    name: "NotesApp",

    controllers: ["Notes"],
    views: ["NotesListContainer"],

    launch: function () {

        var notesListContainer = {
            xtype: "noteslistcontainer"
        };

        Ext.Viewport.add(notesListContainer);
    }
});

And finally, we will switch over to the controller and modify the refs section just so we lookup our ref by xtype instead of by id:

Ext.define("NotesApp.controller.Notes", {

    extend: "Ext.app.Controller",
    config: {
        refs: {
            // We're going to lookup our views by xtype.
            notesListContainer: "noteslistcontainer"
        },
        control: {
            notesListContainer: {
                // The commands fired by the notes list container.
                newNoteCommand: "onNewNoteCommand",
                editNoteCommand: "onEditNoteCommand"
            }
        }
    },

    // Commands.
    onNewNoteCommand: function () {

        console.log("onNewNoteCommand");
    },
    onEditNoteCommand: function (list, record) {

        console.log("onEditNoteCommand");
    },
    // Base Class functions.
    launch: function () {
        this.callParent(arguments);
        console.log("launch");
    },
    init: function () {
        this.callParent(arguments);
        console.log("init");
    }
});

Notice that we also added the onEditNoteCommand event and editNoteCommand handler to the controller. We will define the onEditNoteCommand in the next section of the tutorial, when we create the Notes List View.

After these changes, we can open the index.html page in our favorite WebKit browser, to confirm that everything is working as expected. Tapping the New button should produce the console message we added to the onNewButtonTap function:

Creating The Notes List View

The Notes List View is the component that will render the cached notes. Its file is NotesList.js, which we will place in the view folder. To create this component, we will extend the Sencha Touch’s Ext.dataview.List Class:

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

In this definition, we are setting the onItemDisclosure config to true, which means that we want the list to display a disclosure button next to each item:

A tap on the disclosure button will trigger the Note editing feature of the application. We will create the disclosure handler function in a few minutes.

In the NotesList Class, pay attention to the different CSS classes we use in the itemTpl and emptyText configs. They will allow us to nicely format both the list items, and the message the list will show when there are no items to display.

Before creating these styles we need to create the app.css file. We will place the file in the resources/css directory:

Here are the styles we need:

/* Increase height of list item so title and narrative lines fit */
.x-list .x-list-item .x-list-item-label
{
     min-height: 3.5em!important;
}
/* Move up the disclosure button to account for the list item height increase */
.x-list .x-list-disclosure {
position: absolute;
bottom: 0.85em;
right: 0.44em;
}
.list-item-title
{
    float:left;
    width:100%;
    font-size:90%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right:25px;
    line-height:150%;
}
.list-item-narrative
{
    float:left;
    width:95%;
    color:#666666;
    font-size:80%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right:25px;
}
.x-item-selected .list-item-title
{
    color:#ffffff;
}
.x-item-selected .list-item-narrative
{
    color:#ffffff;
}
.notes-list-empty-text
{
    padding:10px;
}

In order to render the NotesList instance, we first need to add the Class name to the views config of the application:

views: ["NotesList", "NotesListContainer"]

Then, we need to add it to the NotesListContainer Class. Back in the NotesListContainer.js file, we will add the notesList variable to the initialize() function:

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",
            listeners: {
                disclose: { fn: this.onNotesListDisclose, scope: this }
            }
        };

        this.add([topToolbar, notesList]);
    },
    onNewButtonTap: function () {
        console.log("newNoteCommand");
        this.fireEvent("newNoteCommand", this);
    },
    config: {
        layout: {
            type: 'fit'
        }
    }
});

Notice how we are setting a listener for the disclose event of the list:

var notesList = {
    xtype: "noteslist",
    listeners: {
        disclose: { fn: this.onNotesListDisclose, scope: this }
    }
};

Now we can define the onNotesListDisclose() function as follows:

onNotesListDisclose: function (list, record, target, index, evt, options) {
    console.log("editNoteCommand");
    this.fireEvent('editNoteCommand', this, record);
}

Here we are taking the approach we followed earlier with the New button. Instead of having the Controller listen to the disclose event of the List, we are hiding this event, and creating the editNoteCommand event, which we will expose to the Controller. This is another step towards making the application more flexible and easier to maintain.

Creating a Sencha Touch Data Model To Represent a Note

The Notes List requires a data store, which will supply the information for its list items. In order to create this store, we first need to define a data model that will represent a note.

Let’s go ahead and define the Note Class. We will place this Class in the Note.js file, which we will save in the model directory:

A note will have four fields: id, date created, title and narrative. We will start with the following definition:

Ext.define("NotesApp.model.Note", {
    extend: "Ext.data.Model",
    config: {
        idProperty: 'id',
        fields: [
            { name: 'id', type: 'int' },
            { name: 'dateCreated', type: 'date', dateFormat: 'c' },
            { name: 'title', type: 'string' },
            { name: 'narrative', type: 'string' }
        ]
    }
});

We will use the idProperty config to establish that the id field is actually the field the framework can use to uniquely identify a note. This seems trivial in our case because we have total control over the names of the fields of the data model. However, you might encounter cases where, for example, the data model’s fields are tightly coupled to column names in an existing database, and the name of the column that uniquely identifies a record is not “id”. This is why the idProperty config is important.

Setting Up Model Validation In Sencha Touch

The id, dateCreated and title fields in our Note Model are mandatory. We will express this requirement using the validations config:

Ext.define("NotesApp.model.Note", {
    extend: "Ext.data.Model",
    config: {
        idProperty: 'id',
        fields: [
            { name: 'id', type: 'int' },
            { name: 'dateCreated', type: 'date', dateFormat: 'c' },
            { name: 'title', type: 'string' },
            { name: 'narrative', type: 'string' }
        ],
        validations: [
            { type: 'presence', field: 'id' },
            { type: 'presence', field: 'dateCreated' },
            { type: 'presence', field: 'title', message: 'Please enter a title for this note.' }
        ]
    }
});

For the title field, we are taking advantage of the message config to define the message the user will see when she tries to save a note without typing in its title.

Before moving on to create the data store, we need to add the model to the models config of the application:

Ext.application({
    name: "NotesApp",

    models: ["Note"],

	// Rest of the app's definition omitted for brevity...
});

Creating a Sencha Touch Data Store

This is all we need for our data model at this point. Now we can focus on creating the data store that will feed the List. The Notes store goes in a new file. We will place this file in the store directory:

For now, the store will simply contain a few hard-coded records:

Ext.define("NotesApp.store.Notes", {
    extend: "Ext.data.Store",
    config: {
        model: "NotesApp.model.Note",
        data: [
            { title: "Note 1", narrative: "narrative 1" },
            { title: "Note 2", narrative: "narrative 2" },
            { title: "Note 3", narrative: "narrative 3" },
            { title: "Note 4", narrative: "narrative 4" },
            { title: "Note 5", narrative: "narrative 5" },
            { title: "Note 6", narrative: "narrative 6" }
        ]
    }
});

Worth highlighting here is the model config, which we use to establish that this store will contain instances of the Note model.

We want the Notes List to render the notes sorted by creation date. This is why we will add the sorters config to the store’s definition:

Ext.define("NotesApp.store.Notes", {
    extend: "Ext.data.Store",
    requires: "Ext.data.proxy.LocalStorage",
    config: {
        model: "NotesApp.model.Note",
        data: [
            { title: "Note 1", narrative: "narrative 1" },
            { title: "Note 2", narrative: "narrative 2" },
            { title: "Note 3", narrative: "narrative 3" },
            { title: "Note 4", narrative: "narrative 4" },
            { title: "Note 5", narrative: "narrative 5" },
            { title: "Note 6", narrative: "narrative 6" }
        ],
        sorters: [{ property: 'dateCreated', direction: 'DESC'}]
    }
});

Now we can jump back to the NotesListContainer.js file, and add the store to the notesList declaration in the NotesListContainer Class:

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

Before we check how we are doing, let’s quickly switch over to the Controller’s definition in the controller/Notes.js file, locate the launch() function, and invoke the store’s load() function as follows:

launch: function () {
    this.callParent(arguments);
    Ext.getStore("Notes").load();
    console.log("launch");
}

We really don’t need this call now – remember that the data is hard-coded – but we are adding in preparation for the next chapter of the tutorial, where we will discontinue the use of hard-coded data, and begin using data stored in the browser’s cache.

What we need to do, though, is add a reference to the store in the app.js file:

Ext.application({
    name: "NotesApp",

    models: ["Note"],
    stores: ["Notes"],
    controllers: ["Notes"],
    views: ["NotesList", "NotesListContainer"],

    launch: function () {

        var notesListContainer = {
            xtype: "noteslistcontainer"
        };       

        Ext.Viewport.add(notesListContainer);
    }
});

Excellent! At this point we should be able to see the hard-coded notes rendered on the screen. Let’s start our emulator, where we should see something like this:

Summary

We began this article making a modification to the main View of the application. One change consisted of adding an initialize() function to the NotesListContainer Class, and using this function to instantiate the Toolbar and Notes List.

The second and more important change is the introduction of two new events, newNoteCommand and editNoteCommand, which will be fired by this View when our users need to create or edit a note. This change increases the maintainability and reliability of the View, and the application in general.

After modifying the NotesListContainer Class, we proceeded to create the NotesList Class. Inheriting from the Ext.dataview.List Class, this is the component that we will use to render the cached notes. This Class needs a data store to keep a cache of the existing notes, therefore we created the Notes store, along with the Note data model, which represents a note.

In the next chapter of this tutorial we will start working on the View that our users will utilize to edit and delete notes. While working on this View, we will become familiar with Sencha Touch forms, model validation, and client side caching using local storage.

Stay tuned!

Downloads

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

The Entire Series

Want To Learn More?

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

Comments

  1. FC says

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

    Ext.define(‘User’, {
    extend: ‘Ext.data.Model’,
    config: {
    fields: [
    {name: 'user_id', type: 'int'},
    {name: 'name', type: 'string'}
    ],
    hasMany: {model: ‘Note’, name: ‘users’},
    }
    });

    Referring to your Model, if we have associated two models as above
    How can I get a list with the template show fields from two models ?

    note.note_id, note.date, note.title
    by : user.name (user.user_id) : note.narrative

    thanks

    FC

    • says

      One solution is to create a ViewModel, as in the Model-View-ViewModel pattern. Your ViewModel will have the properties you’re interested in: noteId, date, title, narrative, authorId and authorName.

      You can bind the List to this ViewModel.

  2. German says

    Firstable, you have made a very good tutorial.
    I only have a question regarding your explanation: inside the controller you say that we should add the line “Ext.getStore(“Notes”).load();” I really dont understand why. I try the code without that line and works perfectly.

    Thanks in advance,
    German.

    • says

      Thank you, German. At this point the call to load is not needed because we are hard-coding the data. The need for the call will become apparent when we stop using hard-coded data and start using the local storage proxy. This will allow the store to load the data cached on the device when the controller is launched.

  3. German says

    Thanks for the answer.

    And regarding your next post, when do you think part 3 will be ready ?

    Regards!
    German.

  4. says

    Jorge,

    I downloaded the sourcecode and when i try to run it there is nothing coming up, just a blank screen with the title of the page saying “My Notes”, any solutions ?

    Environment: XAMPP + Chrome 17.0

    • says

      Make sure you included the sencha touch files, both css and js. Looks like you are missing those. They are not part of the download.

  5. Vicky says

    Wow, best tutorial. But can you help me explanation this line:
    listeners: {
    disclose: { fn: this.onNotesListDisclose, scope: this }
    }

    and:

    listeners: {
    disclose: this.onNotesListDisclose
    }
    I really dont understand that.

    • says

      Thank you, Vicky. The long version of the function allows us to pass the scope of the call to the handler. You can see the difference if you add a breakpoint in the onNotesListDisclose handler, and inspect the “this” reference in the fireEvent call:

      onNotesListDisclose: function (list, record, target, index, evt, options) {
      console.log(“editNoteCommand”);
      this.fireEvent(‘editNoteCommand’, this, record); // <- Break here and check what “this” is pointing to.
      }

      When you use disclose: this.onNotesListDisclose, you will find that “this” points to the list item that received the tap event. This is the default behavior and, in most cases, it is what you need.

      When you use disclose: { fn: this.onNotesListDisclose, scope: this }, you will find that “this” points to the NotesListContainer instance, which is what we need in this particular case, as we are telling the controller that the NotesListContainer fired the editNoteCommand event.

  6. Akash Saikia says

    Hey, fantastic tutorial, finished reading and got it at the right track..

    Eagerly waiting for the next part , i should not ask, but please publish it soon :)

    Thanks.

  7. Aishwarya Sharma says

    Hi,

    I started making the rest of the app on my own but I am facing a strange problem,
    On the New Button click, I open a new view “NotesEditor”….The view is opening but the NotesListContainer toolbar is also visible. Hence, they are overlapping.

    The code I tried is-

    Ext.define(“NotesApp.view.NotesEditor”, {
    extend: “Ext.form.Panel”,
    alias: “widget.noteseditor”,

    initialize: function () {

    this.callParent(arguments);
    var saveButton = {
    xtype: ‘button’,
    text: ‘Save’,
    ui: ‘action’,
    };

    var homeButton = {
    xtype: ‘button’,
    text: ‘Home’,
    ui: ‘back’,
    };

    var trashButton = {
    xtype: ‘button’,
    iconCls: ‘trash’,
    ui: ‘action’,
    iconMask : true
    };

    // var field= {
    // xtype: ‘fieldset’,
    // items : [
    // title,
    // narrative
    // ]
    // };

    var title = {
    xtype: ‘textfield’,
    name: ‘title’,
    label: ‘Title’,
    required: true
    };

    var narrative = {
    xtype: ‘textfield’,
    name: ‘narrative’,
    label: ‘Narrative’
    }
    var topToolbar = {
    xtype: “toolbar”,
    title: ‘Edit Notes’,
    docked: “right”,
    items: [
    homeButton,
    { xtype: 'spacer' },
    saveButton
    ]
    };

    var bottomToolbar = {
    xtype : “toolbar”,
    docked : “bottom”,
    items : [
    { xtype: 'spacer' },
    trashButton
    ]
    };

    this.add([topToolbar, title, narrative, bottomToolbar]);
    },
    config: {
    layout: {
    type: ‘fit’
    }
    }
    });

    Apart from this, when I add a fieldset, it gives an error…

    And why are we using initialize function rather than doing the work in the config itself??

  8. Wayne says

    Thanks for an excellent tutorial Jorge! This is really helping me to get to grips with Sencha.

    I am really looking forward to the next part.

  9. says

    Hi! Very good tutorial! :D And using MVC! :)
    The application works fine. But not quite understand how events work.

    For example in the button:
    handler: this.onNewButtonTap
    how this trigger the “tap” action?
    Because I don’t see that the code say anything about tap.

    onNewButtonTap: function () {
    console.log(“newNoteCommand”);
    this.fireEvent(“newNoteCommand”, this);
    },

    onNewNoteCommand: function () {

    console.log(“onNewNoteCommand”);
    }

    And in the disclousure event :

    listeners: {
    disclose: { fn: this.onNotesListDisclose, scope: this }
    }

    I asume that “disclose” is the word that indicates who fire the event.

    I asume that all of that is the “magic” under the framework, but can you explain a little of this, please?
    Thanks :D

    • says

      Disclose is the name of the event. It’s fired by the list and then captured by the view in the onNotesListDisclose function, which in turn fires the onEditNoteCommand event. This is the event that the controller will listen to.

  10. Chanckjh says

    Hi, I have a map with geolocation. It tracks my current location and draws polylines with autoUpdate=true. Now I want a button to stop tracking my location. So I guess autoUpdate have to be set to false. But I dont know how to do this… :(

    How can I make a button that can interact with the map? What kind of structure does it need?
    My code:

    listeners : {
    maprender: function(comp, map){

    var marker = new google.maps.Marker({
    position : new google.maps.LatLng( ),
    map : map
    }),
    infowindow = new google.maps.InfoWindow({
    content : geo.getLatitude()
    }),

    route = [ ],
    geo = Ext.create(‘Ext.util.Geolocation’, {
    frequency : 3000,
    autoUpdate : true,
    allowHighAccuracy : true,
    listeners : {}
    }
    }
    }

    Please help :(

  11. Symbol12 says

    Hi,

    Read your post, good job.

    Question: is it possible to send push notifications to apps developed in ST2?

    I’m developing an app that uses push notifications, and I’ve read that it’s not possible.

    Sencha is such a comprehensive framework, that I found it hard to believe.

    Even integration with Urban Airship doesn’t seem possible.

    Please help :)

    Thanks

  12. milica says

    Excellent tutorial!
    Can’t wait for the new part :)

    You really explained everything really really good. At first I tried with official documentation but I couldn’t get anything (I’m the beginner, I discovered Sencha Touch 2 days ago), I couldn’t understand the relation between stores and models, how to connect actions from controllers to app elements (alias usage is excellent way to go) and so on…

    Since I’m the beginner, is it possible to do some advance interaction through Sencha Touch like some kind of elements animation or canvas usage and similar?

    Thanks again!

  13. says

    He Jorge, Can u blog some examples of Grids in “Sencha Touch 2.0″..????

    Thanks in Advance…!!!

    Ur Tutorials have helped me a lot leraning “Sencha Touch”.. Great Job..!!!

  14. BrownieBoy says

    I got the following error in the NotesListContainer.js:

    Uncaught Error: [Ext.createByAlias] Cannot create an instance of unrecognized alias: widget.noteslist

    In the Ext.application(), doesn’t “NotesList” need to be included in the views array? I.e:

    views: ["NotesListContainer", "NotesList"],

    rather than:

    views: ["NotesListContainer"],

    There is no such reference in the downloaded source code, but I noticed that it *is* there in part 3 of the tutorial! Whatever, adding the “NotesList” reference stopped the error message for me (although I’m now getting a new one).

    • says

      Thank you. I see the view declared in the download:

      views: ["NotesList", "NotesListContainer"]

      And I added the line to the article, so it is clear that the Notes List’s Class name needs to be listed in the views config.

      • BrownieBoy says

        @Jorge,

        My mistake: it was there in the downloaded code. And now I see it in the article too. Many thanks for that.

        However, I now get the error:

        Uncaught TypeError: Cannot call method ‘load’ of undefined

        This is generated by the line:

        Ext.getStore(“Notes”).load();

        Somebody commented earlier that the code works fine with this line commented out, which is does for me too. Maybe this will become clears as I progress through part 3.

        in the controller. Do you know what is causing this?

          • Ankit says

            Hello Jorge,

            I am having the same error, that “Uncaught TypeError: Cannot call method ‘load’ of undefined”, and if i comment this line then no data is visible.

            Even when I use the -> stores: ["Notes"] in app.js, its shows error: ‘model not found’

            and when I add the model also, it shows error:

            ”GET http://localhost/Notes/sdk/src/app/Model.js?_dc=1334209751465 404 (Not Found)
            sencha-touch.js:8017
            Uncaught Error: [Ext.Loader] Failed loading ‘http://localhost/Notes/sdk/src/app/Model.js’, please verify that the file exists”

            please rectify it and give me proper solutions, I have followed the all steps properly with your tutorial

            Thanks
            Ankit

          • says

            Ankit, you need to add the model to the models config of the application:

            Ext.application({
            name: “NotesApp”,

            models: ["Note"],
            stores: ["Notes"],
            controllers: ["Notes"],
            views: ["NotesList", "NotesListContainer"],

            launch: function () {

            var notesListContainer = {
            xtype: “noteslistcontainer”
            };

            Ext.Viewport.add(notesListContainer);
            }
            });

            I just updated the article to clarify this point.

        • Jay says

          In addition to the ‘stores’ prop, the ‘models’ prop also needs to be declared in app.js:

          stores: ['Notes'],
          models: ['Note'],

          I don’t think this is mentioned in the tutorial — otherwise it’s great! Thanks for sharing your knowledge.

          • Randy Weinstein says

            This is an excellent tutorial. Although the stores: property is added to the application configuration object, you don’t mention adding that in the tutorial itself. I am adding the code iteratively and — like some others — neglected to add the stores parameter. It would be great to add that to the tutorial. I just did a search for ‘stores’ and didn’t find any mention of it in the body of the tutorial. All in all, a very minor omission but can create some confusion for a newbie such as I…..

  15. carls says

    Hi,

    Thanks for nice tutorial!

    I am trying to get data from REST service which is running on my local m/c. I can access and see the data from service using firefox’s rest client and data is in json format. it looks like:

    {“satus”:”OK”,”holdingReports”:[{"m_id":"","m_accountId":"1022-3-10001","m_asOfDate":"1969-12-31","m_issueName":"default","m_marketValue":"35962.5","m_totalMarketValue":"0.028214473272795266","m_assetClassStandards":"Bond","m_assetClassMinor":"Municipal Bond","m_geoRegion":"North America","m_country":"United States","m_sector":"Sector Not Classifiedstore}, ...

    I am trying to display above data uisng following code and facing issue. I can see service was sucessful (server logs) and on Safaris console(in debug mode) data is there but at the same time error message saying:

    "SyntaxError: Unexpected token ':'"
    It looks like it is complaning about colon at port number because i chnage the parameters there is not data return (service is successful) and error is still there. Can you help whats going wrong here:

    Ext.application({
    name: 'AppTest1',

    launch: function() {
    Ext.create("Ext.tab.Panel", {
    fullscreen: true,
    tabBarPosition: 'bottom',

    items: [
    {
    xtype: 'nestedlist',
    title: 'Blog',
    iconCls: 'star',
    displayField: 'title',

    store: {
    type: 'tree',

    fields: [
    'm_accountId', 'm_marketValue',
    {name: 'leaf', defaultValue: true}
    ],

    root: {
    leaf: false
    },

    proxy: {
    type: ‘jsonp’,
    url: ‘http://127.0.0.1:8080/ManagerDashboardWs/rest/hodingReports/ndev01/1022-3-10001/1/2011-12-31′,
    reader: {
    type: ‘json’,
    rootProperty: ‘holdingReports’
    }
    }

    },
    detailCard: {
    xtype: ‘panel’,
    scrollable: true,
    styleHtmlContent: true
    },

    listeners: {
    itemtap: function(nestedList, list, index, element, post) {
    this.getDetailCard().setHtml(post.get(‘content’));
    }
    }
    }

    ]
    });
    }

    });

  16. Rod says

    I do appreciate you putting this out there for us to use. Thanks.

    Wanted to point out that, in the dialogue, you left out the step of adding the stores class to app.js

    • samoth says

      Hi Jorge,

      First a lot of thanks for this great tut! Great job!

      But now some problems came up for me.

      Ext.Viewport.add(notesListContainer); –> this makes problems, because first I start the app with an home screen from where I want to switch to the NotesList … please could you help me?

      Thanks again!

  17. Paul Obeda says

    If the Ext.dataview.List is added directly to the application, all is well.

    But I was struggling if I added the List to another container (e.g. within an Ext.tab.Panel): the data would not show up, but would have a class of “x-item-hidden” applied, and a style of “display: none !important” would prevent it from displaying. (Showing the code in a browser where I could inspect the element and edit its properties allowed me to determine this).

    So … after much weeping and gnashing of teeth, I found that all was well within my environment if I did a show() after the load().

    E.g. in the Ext.dataview.List, add an id: mydatalist, then in the controller, after the load, do a Ext.getCmp(“mydatalist”).show();

    Thank-you for a great tutorial! I don’t know what I would have done without a solid example to build from.

    • Matthew says

      I am having the same problem. The toolbar displays but the notesList does not. I also see that if I remove the “display: none”, the list shows up. However, running Ext.getCmp(“mydatalist”).show(); inside the Notes controller launch function does not display the list for me. Am I running this in the wrong location? Any idea how everyone else is getting this to run without running this show() function? Thanks in advance for the help!

      • Matthew says

        Turns out all you need to do is create the model and store for the list and then it renders properly. You just can’t assume that the list will display properly without these in place.

  18. oldroy says

    I’ve worked through the tut before and learned alot. Now I’m trying to duplicate this with Architect 2. You might be curious to find that you can’t duplicate this while writing/generating the app in Architect 2.

    The most notable problem is the events. I can add an inline handler (architect only seems to allow this inline unless you set up a listener) for the button and fire the custom event. But in architect 2 there doesn’t seem to be any way to find those custom events in the controller, as in “newNoteCommand”.

    I didn’t know if you were experimenting with architect 2 or not. If you are, I’m sure a bunch of people would get good use out of a tutorial to build this same app with architect 2.

    anyway…..thanks…..

    • says

      I know that in Architect this is not straightforward today. I think Architect should fully support this approach, though. I will be posting a version of the application that uses an approach similar to Architect’s.

      • Des says

        I implemented a smal Architect program to illustrate the idea.

        1) Add a Delegate Event Binding to the component (a button in my case)
        2) targetType = Button, name = tap
        3) Inside the Delegate Event Binding, fire the event
        this.fireEvent(“myCustomEvent”, this);

        4) Create a controller ref to the Component containing the button (Panel)
        5) Create a Controller Action
        controlQuery = myPanel (xType of the container)
        targetType = MyPanel (UserClassName)
        name = myCustomEvent (Same name as what was fired from the Button)

        Seems to work

        The final code looks something like this:

        View

        Ext.define(‘MyApp.view.MyPanel’, {
        extend: ‘Ext.Panel’,
        alias: ‘widget.myPanel’,

        config: {
        html: ‘This demonstrates Custom Events. Press the button to send the event.’,
        layout: {
        type: ‘vbox’
        },
        items: [
        {
        xtype: 'button',
        itemId: 'mybutton',
        text: 'Press to Send Business Event'
        }
        ],
        listeners: [
        {
        fn: 'onButtonTap',
        event: 'tap',
        delegate: '#mybutton'
        }
        ]
        },

        onButtonTap: function(button, e, options) {
        console.log(“Firing event 2″);

        this.fireEvent(“myCustomEvent”, this);

        }

        });

        Controller


        Ext.define(‘MyApp.controller.MyController’, {
        extend: ‘Ext.app.Controller’,
        config: {
        refs: {
        myPanel: {
        selector: ‘myPanel’,
        xtype: ‘myPanel’,
        autoCreate: true
        }
        },

        control: {
        “myPanel”: {
        myCustomEvent: ‘onPanelMyCustomEvent’
        }
        }
        },

        onPanelMyCustomEvent: function(panel) {
        console.log(“Event received 2″);

        alert(“Business Event received”);
        }

        });

        HTH
        Des

  19. oldroy says

    I’m also curious why you chose to fire a custom event for the btns. I’m not sure that it really does decouple anything. It just couples the btns to the views and add complexity to the views. Actual total number of lines of code is greater to accomplish this your way I think. Fewer lines are needed just capturing the tap event by reference in the controller – I know – complex controller……

    Also – is there a performance difference by firing and capturing your custom events?

    • says

      It’s up to you. I prefer a Controller that doesn’t know much about the internals of the View. The larger the application, the more important this point becomes for me.

      • Dmitriy says

        Hi Jorge. Thank you for your tutorials. But how about perfomance when you’re firing custom event form another event?

        • says

          Have you measured performance? I have not found a significant difference. I would optimize the case where I found a problem. I would not sacrifice maintainability, which is a real gain, unless I saw a degradation of performance.

  20. stephen says

    Absolutely great tutorial. Well written. Great job :-)

    I do have a question though: in section 1 you defined the toolbar and button in the Config of NotesListContainer.

    But in this section (2) both have been moved from the config to initalize. Although what I see in initialize seems perfectly plausible in itself, I can’t see the logic behind the move. Why first in config and now in initialize? Is it just that in section 1 initialize was not discussed yet?

    Thanks!

    • says

      No particular reason. I wanted to show that initialize is also possible. I will post an additional chapter of the tutorial where I go back to using configs. This way you will have the app using both approaches.

      • stephen says

        Thanks for the quick reply, Jorge. Could I just add one more question: When you are building apps, do you have a preference to place these in either config of initialize?

        • says

          If it is a simple component with few items and events handlers, I tend to use config. If it is lots of items and event handlers, I tend to use initialize.

  21. jgui says

    Hi, Jorge, I think some of the comments are pointing in the right direction when they say your post is missing the line:

    stores: ["Notes"],

    , in the app.js file.

    Thanks for this extremely useful tutorial.

  22. Damon says

    Hello Jorge,

    Thank you for your tutorial. It is extremely informative and helpful. I have encountered a snafu though. I followed along step by step, but when I load the application I am only greeted by a blank page. Do you have any idea how I troubleshoot this?

    Thanks in advance,

    Damon

  23. Harold Carr says

    Great stuff. Much better than sencha docs. Sencha should hire you!

    Questions

    In part 2 you switch to using “initialize” to define NodeListContainer components. What are the PROS/CONS of this compared to what was done in part 1?

    • says

      Thank you. There are no significant advantages to one approach or the other. I wanted to show both. In part 5 I switch back to configs. Now you have two versions of the same app. One using initialize, and one using config.

      • Harold Carr says

        OK. Another question: you also show looking up ref types by ID then later by xtype. Any PROS/CONS to the different approaches.

        Note: one thing you show where you clearly say a benefit : transforming events from the mechanics of the event to an app-specific event = the public interface of a view.

        That is what I am looking to get clearer on: when there is more than one way to do something : what are the PROS/CONS of each.

        THANKS!

  24. JK says

    I had to do a *lot* of googling to learn that
    xtype: “button”,
    handler: this.onNewButtonTap,
    scope: this

    is a synonym for
    xtype: “button”,
    listeners: {
    tap: {fn: this.onNewButtonTap, scope: this}
    }

    it might be good to note in the tutorial that handler is a shortcut property that only applies to button and that listeners is the property to use for events in general

  25. JK says

    Jorge,

    I noticed one thing in the tuturial you may want to correct. In NotesList view config, emptyText and itemTpl keys have opening and closing and tags, in that order–closing tag first. The source code zip file doesn’t have this, and the app works without it. So I’m guessing it’s a typo. At first, I thought it was a strange Sencha requirement.

    • JK says

      that’s opening and closing </pre> and <pre> tags (didn’t realize i needed to use entities to display tags)

  26. Wirtsi says

    Good demo!

    You should note though, that the usage of id for components should best be avoided. Why?

    Because if you are forced to destroy and recreate a panel (e.g. because of a layout change for a different device orientation) you’ll lose the handlers.

    If you use classes or alias you can query for these in the controller instead of going for the id.

    Second caveat for this, .list[cls="myListClass"] will only work if .list only has one class, if it has more then this ComponentQuery will fail …defining alias in the component is the better way to go

    Panel
    {
    xtype: ‘list’,
    alias: ‘myListAlias’
    ..
    }
    Controller
    MyList : .list[alias='"myListAlias"]

  27. Kristian Mandrup says

    I have been trying to follow these examples, but for some reason, the resources/css/app.css file is not applied.
    I can see it in my resources however as:

    And when using the browser and I click on this link, it shows the right CSS code, with this css at the top and some generated (Sencha) css further down… I’m using PhoneGap 1.8 and Sencha Touch 2.0.1.
    So it seems to “be there” but not active somehow!? huh!

    If I copy the style directly into the style tag of index.html it works of course. Strange!

    .list-item-title
    {
    float:left;
    width:100%;
    font-size:90%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding-right:25px;
    line-height:150%;
    }

    Even if I copy into index.html head element, it is still not (really) picked up somehow!

    FriendlyRent

    /**
    * Example of an initial loading indicator.
    * It is recommended to keep this as minimal as possible to provide instant feedback
    * while other resources are still being loaded for the first time
    */


    • Kristian Mandrup says

      Looks like the app.css file is being auto-generated in my setup hence it would be a bad idea to modify it I think. A better approach is to have a separate css file and it works just fine. Would also work better with the sass approach recommended (see SDK tools).

      I noticed that I had accidentally pasted your css code into the middle of the first auto-generated style, which is why it wasn’t picked up. Sorry.

      • Nathan says

        Hi Jorge, thanks for the quick response.

        I changed it store: ‘Notes’, but now I get this error:

        Uncaught Error: [Ext.createByAlias] Cannot create an instance of unrecognized alias: widget.noteslist sencha-touch-debug.js:6295.

        • Nathan says

          I had to make a few changes for this to work:
          1. Add stores: ['Notes'], to app.js
          2. Add a requires property as follows:
          requires: ['NotesApp.view.NotesListContainer',',NotesApp.view.NotesList']
          to app.js.
          3. Remove alias: “widget.noteslistcontainer”, from NotesListContainer.js and replace it with xtype: ‘noteslistcontainer’,

          • says

            I had the same problem and I fixed it as follows:
            1) Specify store in app.js like this: stores: ["Notes"],
            2) In NoteListContainer.js replace store: Ext.getStore(“Notes”), with store: “Notes”, as mentioned by Jorge.

            Actually I mixed up yours and Jorge’s suggestions to fix my problem. Thanks both of you :)

  28. Dharmesh says

    I see that you assign styles to Sencha Touch in-built CSS classes, e.g. x-list-item-label, .x-item-selected, etc. I was looking for a comprehensive list/guide to these inbuilt classes to which we can assign styles. Where can I find it?

  29. Srinivas says

    Jorge,

    I went ahead with the tutorial 2 everything went fine, but our build process was not completing successfully. I have included the build error in the zip file (https://docs.google.com/open?id=0B1h-jczMK2IBeGU4WWJvSjdYR2M) . I had to comment the line “Ext.getStore(“Notes”).load();” in the controller(controller\Notes.js) to build it successfully. Not able to figure it out on why it is not recognized. It has been declared in the “views\NotesListContainer.js”.

    Please help in proceeding further.

    Thanks And Regards
    Srini

  30. Srinivas says

    Never mind I had to add stores: ['Notes'], to app.js.

    Thanks Nathan, your post helped me.

    Regards
    Srinivas

  31. Sel says

    I don’t know how to express my gratitude for these tutorials. You have saved me from so much frustration and agony that I was going through the past few days. Even after just reading part 1 made concepts clear, that I was struggling with for hours. The fact that you have taken the time to explain even the simplest steps is just invaluable! One of the issues I struggled with is that the Sensa Touch 2 docs and code snippets show general concepts and basic how-to’s, but once you want to change something or do something a tad differently you can scratch your head for hours and hope someone on the forums will help you out. By illustrating a function one way and later demonstrating and explaining how to change it to make it work differently and WHY, this is just really an enormous help!!!

    Thank you very much for all your work!

    • says

      Thank you for leaving this comment Sel. I’m very pleased that the articles helped you. Stay tuned for the book, which I will publish this month.

  32. Sel says

    Hello,

    I have a question about assigning a tap handler. In the file “NotesListContainer.js” in the “initialize” function, you have assigned a handler to the “New” button as such:

    handler: this.onNewButtonTap

    So when I tap on the “New” button, the “onNewButtonTap” function will be called. However, only buttons have “handlers” – if I understood correctly.

    So how would I assign a “handler” to a component, when it’s tapped, it would create a new note? For example, instead of using a button provided by the framework, I’d like to use a component displaying an image and when I tap the component, that will create a new note. I purposely DO NOT want to use an Img component, just a simple component because they don’t have “handlers”.

    So how would I go about doing that?

    Just as an example, I tried to re-write this, so that when I tap on the top toolbar (as opposed to the button) it would call the onNewButtonTap function. However, the toolbar doesn’t have “handler” so I tried with a listener as such:

    var topToolbar = {

    listeners: {
    tap: {
    fn: this.onToolBarTap,
    scope: this
    }
    };

    Obviously this doesn’t work. I don’t see any “tap”-like event that is associated with components (eg toolbar). How would I go around this?

    Sorry for the long note, and thank you for your assistance!

    • Sel says

      In case anyone wonders…the solution is:

      listeners: {
      tap: {
      element: ‘element’, // this line needed!
      fn: this.onToolBarTap,
      scope: this
      }
      };

  33. Joseph says

    Nice tutorial. I really appreciate you teaching this. I wanted to point out a couple mistakes though, so you can fix them. First, you added the call to the store before defining it, so when I ran it, I got an error. I figured out that we would be adding the store later, but it should not be in the source code yet at that point. Second, I don’t think you mentioned about adding a reference to the store in the app.js file. I had to refer to your completed tutorial file to figure that out. Oh, and I was getting errors on the lines that have the html in them because it was broken up in to multiple lines – you might want to mention that this needs to be one contiguous line. And please note that the code for this in your download is different than what’s on this page. You used double quotes and no line breaks in the final code…

    • says

      Thanks for the suggestions. I removed the call to the store and added the reference in the app.js file. This should make things clearer.

  34. says

    Hi Jorge – thanks for this … great tutorial series so far and I’m sure the remaining parts will be as useful. As a front end web person I’m enjoying learning something new (Sencha) in a reasonably familiar environment (javascript).

    One thing I am slightly struggling with though is the MVC model that Sencha uses – so I wanted to add these two videos which do a great job of explaining it, just in case anyone else in a similar situation has the same struggles!

    https://vimeo.com/33311074
    https://vimeo.com/33430731

    Thanks again,
    David

  35. Coqmos says

    Hi,

    I Have the following in the index file,

    But still nothing shows but the toolbar with the button.
    The Setup is IIS7 and Chrome

    Thanks

  36. Coqmos says

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

  37. Royo says

    Hi Jorge, Thanks for the awesome tutorial. I came across this after realizing that the Sencha tutorials are pathetic.

    After implementing the code above, the final rendered application doesn’t look the same as the screenshot you have shared. Its almost there in that the themes and colours are the same. But the text is different and off-center. Also, the disclosure buttons look odd. The arrows are not in the centre of the blue circle. I tried fiddling with the app.css to try and increase the size of each list item. But nothing happened. And I couldn’t figure out how to modify the arrows position in the blue circle. Any reason why this would happen? Have I missed out some key Sencha files?

    • says

      See if you are using the same ST version I am using. I’ve had similar problems when I inadvertently forgot to update some of the files to the right version.

  38. Shashi says

    Hi,

    I’m new to Sencha touch. I am trying out some examples using list, but facing problems: On adding the listeners config below, the view is stopped from rendering. If I remove the listeners function, the list is rendered correctly.
    So, it appears due to the addition of the listeners – could someone guide me as the right way to capture the select event.

    code:

    Ext.define(‘MyApp1.view.List1′, {
    extend: ‘Ext.Container’,
    config: {
    layout: {
    type: ‘vbox’
    },
    items: [
    {
    xtype: 'toolbar',
    docked: 'top',
    title: 'Toolbar'
    },
    {
    xtype: 'container',
    flex: 1,
    layout: {
    type: 'vbox'
    },
    items: [
    {
    xtype: 'list',
    flex: 1,
    store: {
    fields: ['title'],
    data: [
    { title: 'Item 1' },
    { title: 'Item 2' },
    { title: 'Item 3' },
    { title: 'Item 4' }
    ]
    },
    itemTpl: ‘{title}’,
    listeners: {
    select: function(view, record, eOpts) {
    Ext.Msg.alert(‘Selected!’, ‘You selected ‘ + record.get(‘title’));
    }
    }
    ]

    }
    ] //items – config
    } //config
    });

  39. says

    Jorge,
    Buenisimo! Excellent tutorial!
    I’ve been following your tutorial from the first one and cannot wait to complete them all.
    I’m curious, your website is called MiamiCoder — do you currently reside in Miami, FL?
    I also live there. I just wanted to know if you had any SenchaTouch/Mobile meetups so that I could go and get to know more devs and learn more.

    Thanks again!
    Camilo.

  40. Ankit says

    Hi,
    My question is bit off topic, but if you could answer it that would be great help.
    I want to use a grid component but Sencha touch 2 doesn’t have any inbuilt grid component in it.
    What are the options do i have ?
    I saw that there is a grid component available in Github, but how can i use it in my project.
    I am using Sencha Architect 2 and i don’t see any direct way to add an external component in it.

Leave a Reply

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