<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0"><channel><title>MiamiCoder</title> <link>http://miamicoder.com</link> <description>Sencha Touch, jQuery Mobile, ExtJS tutorials and books.</description> <lastBuildDate>Wed, 08 Feb 2012 13:12:39 +0000</lastBuildDate> <language>en</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <generator>http://wordpress.org/?v=3.3.1</generator> <feedburner:info uri="miamicoder" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/feedburner/MiamiCoder" /><feedburner:info uri="feedburner/miamicoder" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:emailServiceId>feedburner/MiamiCoder</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><item><title>Building a jQuery Mobile Application, Part 4</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/w8V5NkkqFyA/</link> <comments>http://miamicoder.com/2012/building-a-jquery-mobile-application-part-4/#comments</comments> <pubDate>Sun, 15 Jan 2012 16:49:50 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[jQuery Mobile Tutorials]]></category> <category><![CDATA[Tutorials]]></category> <category><![CDATA[JQuery Mobile Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1999</guid> <description><![CDATA[In the fourth part of this jQuery Mobile tutorial, we are going to create the behavior of the app when a user tries to save an invalid note. We are also going to implement the ability to delete notes, and apply a custom theme swatch to change the look of one of the application’s dialogs.]]></description> <content:encoded><![CDATA[<p>In this fourth part of my series on <a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-3/" target="_blank">how to build a jQuery Mobile application</a>, we are going to complete the following tasks:</p><ul><li>Implement the behavior of the app when a user tries to save an invalid note.</li><li>Implement the ability to delete notes.</li><li>Create and apply a custom theme swatch, to change the look of one of the application’s dialogs.</li></ul><p style="text-align: center;"><img class="aligncenter size-full wp-image-1991" style="margin-top: 15px; margin-bottom: 15px;" title="invalid-note-dlg" src="http://miamicoder.com/wp-content/uploads/2012/01/invalid-note-dlg.png" alt="" width="353" height="136" /></p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1992" style="margin-top: 15px; margin-bottom: 15px;" title="delete-note-dlg" src="http://miamicoder.com/wp-content/uploads/2012/01/delete-note-dlg.png" alt="" width="346" height="292" /></p><h3><span id="more-1999"></span></h3><h3>Getting Ready to Validate a Data Model</h3><p>In the previous chapter of the series, we decided that we are going to consider that a note is valid when it has a title, and we are not going to force our users to enter the note’s narrative before they can save the note. We defined this behavior through the isValid() function of the NoteModel module:</p><pre class="brush: jscript; title: ; notranslate">
Notes.NoteModel.prototype.isValid = function () {
    &quot;use strict&quot;;
    if (this.title &amp;&amp; this.title.length &gt; 0) {
        return true;
    }
    return false;
};
</pre><p>We also left an empty branch in the onSaveNoteButtonTapped() function of the Controller module, where we need to add the code that will inform our user that her note is invalid. Here is the function as we originally created:</p><pre class="brush: jscript; title: ; notranslate">
var onSaveNoteButtonTapped = function () {

    // Validate note.
    var titleEditor = $(noteTitleEditorSel);
    var narrativeEditor = $(noteNarrativeEditorSel);
    var tempNote = dataContext.createBlankNote();

    tempNote.title = titleEditor.val();
    tempNote.narrative = narrativeEditor.val();

    if (tempNote.isValid()) {

        if (null !== currentNote) {

            currentNote.title = tempNote.title;
            currentNote.narrative = tempNote.narrative;
        } else {

            currentNote = tempNote;
        }

        dataContext.saveNote(currentNote);

        returnToNotesListPage();

    } else {
        // TODO: Inform the user the note is invalid.
    }
};
</pre><p>Before we complete this function, we need to define what UI elements we will use to inform the user that her note is invalid. We can accomplish this with a jQuery Mobile dialog. In our case, we want to create a very simple dialog, made of a header and a short message to explain our users what is happening:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1991" style="margin-top: 15px; margin-bottom: 15px;" title="invalid-note-dlg" src="http://miamicoder.com/wp-content/uploads/2012/01/invalid-note-dlg.png" alt="" width="353" height="136" /></p><h3>How to Create a Dialog With jQuery Mobile</h3><p>One of the ways you instruct the framework to display a page as a dialog is using the data-role=”dialog” attribute. We will follow this approach, defining an Invalid Note dialog in the index.html file as follows:</p><pre class="brush: xml; title: ; notranslate">
&lt;!--Invalid Note dialog--&gt;
&lt;div id=&quot;invalid-note-dialog&quot; data-role=&quot;dialog&quot; data-title=&quot;Invalid Note&quot; data-theme=&quot;e&quot;&gt;
&lt;div data-role=&quot;header&quot; data-theme=&quot;e&quot;&gt;
&lt;h1&gt;Wait!&lt;/h1&gt;
&lt;/div&gt;
&lt;div data-role=&quot;content&quot;&gt;Enter a title for this note.&lt;/div&gt;
&lt;/div&gt;
</pre><p>We will insert this markup right after the Note Editor page in the index.html file. A detail I don’t want you to miss is how we use the data-theme=”e” attribute to change the appearance of the dialog. Applying the E swatch to the dialog helps give it more of a warning look.<br /> With the dialog in place, we need to go in the controller module and add an identifier for it:</p><pre class="brush: jscript; title: ; notranslate">
var invalidNoteDlgSel = &quot;#invalid-note-dialog&quot;;
</pre><p>This identifier will allow us to activate the dialog in the onSaveNoteButtonTapped() method of the controller like so:</p><pre class="brush: jscript; title: ; notranslate">

var onSaveNoteButtonTapped = function () {

    // Validate note.
    var titleEditor = $(noteTitleEditorSel);
    var narrativeEditor = $(noteNarrativeEditorSel);
    var tempNote = dataContext.createBlankNote();

    tempNote.title = titleEditor.val();
    tempNote.narrative = narrativeEditor.val();

    if (tempNote.isValid()) {

        if (null !== currentNote) {

            currentNote.title = tempNote.title;
            currentNote.narrative = tempNote.narrative;
        } else {

            currentNote = tempNote;
        }

        dataContext.saveNote(currentNote);

        returnToNotesListPage();

    } else {
        $.mobile.changePage(invalidNoteDlgSel, defaultDlgTrsn);
    }
};
</pre><p>Note that we are making the dialog the active page using the $.mobile.changePage() method, which you can use to trigger page changes programmatically. This function takes a reference to the page in question, as well as the transition you want to use when bringing the page into view.</p><p>Instead of passing an inline-defined transition, we are going to define a default transition, which we will use for all the dialogs in the application. We will use the following on-liner to create defaultDlgTrsn right at the beginning of the controller module:</p><pre class="brush: jscript; title: ; notranslate">
var defaultDlgTrsn = { transition: &quot;slideup&quot; };
</pre><p>All right, time to check how the dialog looks. Start your favorite WebKit browser or emulator, and try to save a note with a blank title. You should see the Invalid Note dialog become active:</p><p><img class="aligncenter size-full wp-image-1991" style="margin-top: 15px; margin-bottom: 15px;" title="invalid-note-dlg" src="http://miamicoder.com/wp-content/uploads/2012/01/invalid-note-dlg.png" alt="" width="353" height="136" /></p><h3>The Right Place For The mobileinit Event Handler</h3><p>Our refactoring step for this chapter will consist of moving the mobileinit event handler out of the index.html file. We don’t want to contaminate our html files with JavaScript, so we’re going to move the handler to the Controller.js file. We will place it right after the controller module’s definition:</p><pre class="brush: jscript; title: ; notranslate">

Notes.controller = (function ($, dataContext, document) {

    // Controller’s implementation omitted for brevity.

} (jQuery, Notes.dataContext, document));

$(document).bind(&quot;mobileinit&quot;, function () {
    Notes.controller.init();
});
</pre><p>The index.html file is now virtually free of JavaScript code, which is desirable in order to keep the maintenance costs of the application low.</p><h3>Creating a Confirmation Dialog</h3><p>The second feature we need to address is deleting a note. Users of the application will initiate this workflow by tapping the Delete button on the Edit Note page:</p><p><img class="aligncenter size-full wp-image-1990" style="border-image: initial; margin-top: 15px; margin-bottom: 15px; border-width: 1px; border-color: black; border-style: solid;" title="note-editor-3" src="http://miamicoder.com/wp-content/uploads/2012/01/note-editor-3.png" alt="" width="366" height="398" /></p><p>When the user taps the button, we are going to render a small dialog, asking her for confirmation:</p><p><img class="aligncenter size-full wp-image-1992" style="margin-top: 15px; margin-bottom: 15px;" title="delete-note-dlg" src="http://miamicoder.com/wp-content/uploads/2012/01/delete-note-dlg.png" alt="" width="346" height="292" /></p><p>If the user taps the No button on the confirmation dialog, we will just return to the Note Editor page. However, if she taps the Yes button, we will proceed to delete the note, and then return to the Notes List page.</p><p>Let’s first create the dialog, and then connect it to the Delete button on the Note Editor page.</p><p>We will create the Confirm Delete Note dialog in the index.html file, using the following markup:</p><pre class="brush: xml; title: ; notranslate">
&lt;!-- Confirm Delete Note dialog--&gt;&lt;/pre&gt;
&lt;div id=&quot;confirm-delete-note-dialog&quot; data-role=&quot;dialog&quot; data-title=&quot;Delete Note&quot;&gt;
&lt;div data-role=&quot;header&quot;&gt;
&lt;h1&gt;Delete Note?&lt;/h1&gt;
&lt;/div&gt;
&lt;div data-role=&quot;content&quot;&gt;
&lt;div id=&quot;delete-note-content-placeholder&quot;&gt;&lt;/div&gt;
 &lt;a id=&quot;cancel-delete-note-button&quot; data-role=&quot;button&quot; data-theme=&quot;b&quot; data-rel=&quot;back&quot;&gt;No&lt;/a&gt;
 &lt;a id=&quot;ok-to-delete-note-button&quot; data-role=&quot;button&quot; data-theme=&quot;f&quot;&gt;Yes&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre&gt;
</pre><p>Notice how we use the data-role=”dialog” attribute to have the framework render this page as a dialog. We will use the delete-note-content-placeholder div to render the selected note. The interesting thing about the buttons, which are links decorated with the data-role=”button” attribute, is how we’re using theme swatches to define their colors.</p><p>We’re using the B swatch, a built-in swatch of the default theme, for the No button. The Yes button uses the F swatch to give the button the red color. We will create the F swatch in a few minutes.</p><p>With the dialog created, we need to focus on how we will render it from within the controller module. Evidently, we need to create an identifier for the dialog:</p><pre class="brush: jscript; title: ; notranslate">
var confirmDeleteNoteDlgSel = &quot;#confirm-delete-note-dialog&quot;;
</pre><p>We also need identifiers for the Delete button in the Note Editor page, the Yes button in the Confirm Delete Note dialog, and the div element that will serve as placeholder in the dialog. Let’s add them to the controller module like so:</p><pre class="brush: jscript; title: ; notranslate">
var deleteNoteButtonSel = &quot;#delete-note-button&quot;,
deleteNoteContentPlaceholderSel = &quot;#delete-note-content-placeholder&quot;,
okToDeleteNoteButtonSel = &quot;#ok-to-delete-note-button&quot;;
</pre><p>As the dialog will be activated upon the user tapping the Delete button in the Note Editor page, we will define a tap handler for this button in the init function of the controller. Something like this will do:</p><pre class="brush: jscript; title: ; notranslate">
var init = function () {

    // Rest of the function omitted for brevity.

    d.delegate(deleteNoteButtonSel, &quot;tap&quot;, onDeleteNoteButtonTapped);
};
</pre><p>Here we are saying that the tap event on the button will invoke the onDeleteNoteButtonTapped() function, which we will now add to the controller module:</p><pre class="brush: jscript; title: ; notranslate">
var onDeleteNoteButtonTapped = function () {

    if (currentNote) {
        // Render selected note in confirmation dlg.
        // Deletion will be handled elsewhere, after user confirms it's ok to delete.

        var noteContentPlaceholder = $(deleteNoteContentPlaceholderSel);

        noteContentPlaceholder.empty();
        $(&quot;&lt;/pre&gt;
&lt;h3&gt;&quot; + currentNote.title + &quot;&lt;/h3&gt;
&lt;pre&gt;
&quot; + currentNote.narrative + &quot;

&quot;).appendTo(noteContentPlaceholder);

        $.mobile.changePage(confirmDeleteNoteDlgSel, defaultDlgTrsn);
    }
};
</pre><p>In onDeleteNotebuttonTapped(), we first render the current note’s title and narrative in the content area of the dialog. We can accomplish this by simply appending html nodes to the placeholder we defined within the dialog. Then, we make the dialog the active page, using the $mobile.changePage() function. Note how once again we use the defaultDlgTrsn, previously defined when we were creating the Invalid Note dialog.</p><p>Now we are at a point where we are waiting for the user’s answer to our question &#8211; delete the note, yes or no? The answer will tell us whether to delete the note or cancel the workflow.</p><h3>Deleting a Note</h3><p>Cancellation will occur upon the user tapping the No button. We don’t have to write code for this scenario, as we are using the data-rel=”back” attribute for the No button:</p><pre class="brush: xml; title: ; notranslate">
&lt;a id=&quot;cancel-delete-note-button&quot; data-role=&quot;button&quot; data-theme=&quot;b&quot; data-rel=&quot;back&quot;&gt;No&lt;/a&gt;
</pre><p>This role will cause the button’s tap event to initiate a transition to the previous page, the Note Editor page, which is exactly what we want.</p><p>The Yes button is a little different. When the user taps this button, we need to delete the note and initiate a transition to the Notes List page. The Notes List page should then render the updated notes list.</p><p>We will define the tap handler for the Yes button in the controller module’s init function, binding to the button’s tap event:</p><pre class="brush: jscript; title: ; notranslate">
var init = function () {

    // Rest of the function omitted for brevity.

    d.delegate(okToDeleteNoteButtonSel, &quot;tap&quot;, onOKToDeleteNoteButtonTapped);
};
</pre><p>Then, we will define onOKToDeleteNoteButtonTapped() like so:</p><pre class="brush: jscript; title: ; notranslate">
var onOKToDeleteNoteButtonTapped = function () {

    dataContext.deleteNote(currentNote);
    returnToNotesListPage();
};
</pre><p>Easy, right? We first call the data context module’s deleteNote(), and then transition to the Notes List page by calling returnToNotesListPage(), a function that we created when we were working on the steps required to save a note.</p><p>The problem is that we have not defined a deleteNote() function in the data context module. Before doing so, let’s define a test for this function in the AppSpec.js file:</p><pre class="brush: jscript; title: ; notranslate">
it(&quot;Removes a note from local storage&quot;, function () {

        // Create a note.
        var dateCreated = new Date();
        var id = new String(dateCreated.getTime());
        var noteModel = new Notes.NoteModel({
            id: id,
            dateCreated: dateCreated,
            title: &quot;&quot;,
            narrative: &quot;&quot;
        });

        // Start with an empty notes list.
        var notesList = [];
        // Add note to local storage.
        notesList.push(noteModel);
        $.jStorage.set(notesListStorageKey, notesList);
        notesList = $.jStorage.get(notesListStorageKey);
        expect(notesList.length).toEqual(1);

        // Proceed to delete.
        Notes.dataContext.init(notesListStorageKey);
        Notes.dataContext.deleteNote(noteModel);

        // Should retrieve empty array
        notesList = $.jStorage.get(notesListStorageKey);
        expect(notesList.length).toEqual(0);

        // Clean up
        $.jStorage.deleteKey(notesListStorageKey);

    });
</pre><p>In the test, we first create a note and save it directly into local storage. Then, we use the function being tested, deleteNote(), to remove the note. Of course, the expectation is that we can delete a note using this function.</p><p>The test should fail, as deleteNote() still does not exist:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1995" style="margin-top: 15px; margin-bottom: 15px;" title="it-fails-5" src="http://miamicoder.com/wp-content/uploads/2012/01/it-fails-5.png" alt="" width="385" height="266" /></p><p>With the test in place, let’s head over to the DataContext.js file, and define deleteNote() like so:</p><pre class="brush: jscript; title: ; notranslate">
var deleteNote = function (noteModel) {

    var i;
    for (i = 0; i &lt; notesList.length; i += 1) {
        if (notesList[i].id === noteModel.id) {
            notesList.splice(i, 1);
            i = notesList.length;
        }
    }

    saveNotesToLocalStorage();
};
</pre><p>Again, a very simple function that loops through the array of existing notes, trying to find one with the id of the note we want to delete. If found, the note is removed from the array. The updated array is then serialized to local storage through a call to saveNotesToLocalStorage().</p><p>We also need to add deleteNote() to the public interface of the module so we can invoke it from the controller module:</p><pre class="brush: jscript; title: ; notranslate">
var pub = {
    init: init,
    createBlankNote: createBlankNote,
    getNotesList: getNotesList,
    saveNote: saveNote,
    deleteNote: deleteNote
};
</pre><p>Time to re-run the test, which should pass if we didn’t make any mistakes:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1994" style="margin-top: 15px; margin-bottom: 15px;" title="it-ok-7" src="http://miamicoder.com/wp-content/uploads/2012/01/it-ok-7.png" alt="" width="384" height="323" /></p><p>This completes the code that we needed for the Delete Note feature. The UI elements are in place, and the Controller and Data Context modules are ready to handle this workflow. Now you can fire up your favorite device emulator or WebKit browser, and verify that you can delete notes.</p><h3>Using a Custom Theme Swatch in jQuery Mobile</h3><p>Before we end this chapter, let us take care of a cosmetic issue. How about changing the color of the Yes button in the dialog?</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1996" style="margin-top: 15px; margin-bottom: 15px;" title="delete-note-dlg-2" src="http://miamicoder.com/wp-content/uploads/2012/01/delete-note-dlg-2.png" alt="" width="383" height="288" /></p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1992" style="margin-top: 15px; margin-bottom: 15px;" title="delete-note-dlg" src="http://miamicoder.com/wp-content/uploads/2012/01/delete-note-dlg.png" alt="" width="346" height="292" /></p><p>One approach to accomplish this consists of using a custom jQuery Mobile theme swatch. We will take advantage of the <a href="http://jquerymobile.com/themeroller/" target="_blank">ThemeRoller</a> for jQuery Mobile to change the swatch of the Yes button.</p><p>As you already saw, we assigned the data-theme=”f” attribute to the button when we created the dialog:</p><pre class="brush: xml; title: ; notranslate">
&lt;a id=&quot;ok-to-delete-note-button&quot; data-role=&quot;button&quot; data-theme=&quot;f&quot;&gt;Yes&lt;/a&gt;
</pre><p>With this in mind, we will head over to the <a href="http://jquerymobile.com/themeroller/" target="_blank">ThemeRoller site</a> and define an F swatch. The swatch has several properties, but we’re only interested in those that apply to buttons. Let’s use the following properties for the different buttons states:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1993" style="margin-top: 15px; margin-bottom: 15px;" title="theme-roller-f-swatch" src="http://miamicoder.com/wp-content/uploads/2012/01/theme-roller-f-swatch.png" alt="" width="320" height="600" /></p><p>After entering these values, we can use the Download Theme link to download the theme file.</p><p>The file contains styles for all elements enhanced by the jQuery Mobile framework. We will copy the styles that apply to button elements, or elements decorated with the data-role=”button” attribute, which we will add to our app.css file like so:</p><pre class="brush: css; title: ; notranslate">
.ui-btn-up-f {
	border: 1px solid #c1272d /*{f-bup-border}*/;
	background: #c1272d /*{f-bup-background-color}*/;
	font-weight: bold;
	color: #ffffff /*{f-bup-color}*/;
	text-shadow:  0  /*{f-bup-shadow-x}*/  1px  /*{f-bup-shadow-y}*/  1px  /*{f-bup-shadow-radius}*/ #444444 /*{f-bup-shadow-color}*/;
	background-image: -webkit-gradient(linear, left top, left bottom, from( #D42A31 /*{f-bup-background-start}*/), to( #AD2328 /*{f-bup-background-end}*/)); /* Saf4+, Chrome */
	background-image: -webkit-linear-gradient(top, #D42A31 /*{f-bup-background-start}*/, #AD2328 /*{f-bup-background-end}*/); /* Chrome 10+, Saf5.1+ */
	background-image:    -moz-linear-gradient(top, #D42A31 /*{f-bup-background-start}*/, #AD2328 /*{f-bup-background-end}*/); /* FF3.6 */
	background-image:     -ms-linear-gradient(top, #D42A31 /*{f-bup-background-start}*/, #AD2328 /*{f-bup-background-end}*/); /* IE10 */
	background-image:      -o-linear-gradient(top, #D42A31 /*{f-bup-background-start}*/, #AD2328 /*{f-bup-background-end}*/); /* Opera 11.10+ */
	background-image:         linear-gradient(top, #D42A31 /*{f-bup-background-start}*/, #AD2328 /*{f-bup-background-end}*/);
}
.ui-btn-up-f a.ui-link-inherit {
	color: #ffffff /*{f-bup-color}*/;
}

.ui-btn-hover-f {
	border: 1px solid #DD2C33 /*{f-bhover-border}*/;
	background: #DD2C33 /*{f-bhover-background-color}*/;
	font-weight: bold;
	color: #ffffff /*{f-bhover-color}*/;
	text-shadow:  0  /*{f-bhover-shadow-x}*/  1px  /*{f-bhover-shadow-y}*/  1px  /*{f-bhover-shadow-radius}*/ #444444 /*{f-bhover-shadow-color}*/;
	background-image: -webkit-gradient(linear, left top, left bottom, from( #F33038 /*{f-bhover-background-start}*/), to( #C6272D /*{f-bhover-background-end}*/)); /* Saf4+, Chrome */
	background-image: -webkit-linear-gradient(top, #F33038 /*{f-bhover-background-start}*/, #C6272D /*{f-bhover-background-end}*/); /* Chrome 10+, Saf5.1+ */
	background-image:    -moz-linear-gradient(top, #F33038 /*{f-bhover-background-start}*/, #C6272D /*{f-bhover-background-end}*/); /* FF3.6 */
	background-image:     -ms-linear-gradient(top, #F33038 /*{f-bhover-background-start}*/, #C6272D /*{f-bhover-background-end}*/); /* IE10 */
	background-image:      -o-linear-gradient(top, #F33038 /*{f-bhover-background-start}*/, #C6272D /*{f-bhover-background-end}*/); /* Opera 11.10+ */
	background-image:         linear-gradient(top, #F33038 /*{f-bhover-background-start}*/, #C6272D /*{f-bhover-background-end}*/);
}
.ui-btn-hover-f a.ui-link-inherit {
	color: #ffffff /*{f-bhover-color}*/;
}
.ui-btn-down-f {
	border: 1px solid #DD2C33 /*{f-bdown-border}*/;
	background: #DD2C33 /*{f-bdown-background-color}*/;
	font-weight: bold;
	color: #ffffff /*{f-bdown-color}*/;
	text-shadow:  0  /*{f-bdown-shadow-x}*/  1px  /*{f-bdown-shadow-y}*/  1px  /*{f-bdown-shadow-radius}*/ #444444 /*{f-bdown-shadow-color}*/;
	background-image: -webkit-gradient(linear, left top, left bottom, from( #C6272D /*{f-bdown-background-start}*/), to( #F33038 /*{f-bdown-background-end}*/)); /* Saf4+, Chrome */
	background-image: -webkit-linear-gradient(top, #C6272D /*{f-bdown-background-start}*/, #F33038 /*{f-bdown-background-end}*/); /* Chrome 10+, Saf5.1+ */
	background-image:    -moz-linear-gradient(top, #C6272D /*{f-bdown-background-start}*/, #F33038 /*{f-bdown-background-end}*/); /* FF3.6 */
	background-image:     -ms-linear-gradient(top, #C6272D /*{f-bdown-background-start}*/, #F33038 /*{f-bdown-background-end}*/); /* IE10 */
	background-image:      -o-linear-gradient(top, #C6272D /*{f-bdown-background-start}*/, #F33038 /*{f-bdown-background-end}*/); /* Opera 11.10+ */
	background-image:         linear-gradient(top, #C6272D /*{f-bdown-background-start}*/, #F33038 /*{f-bdown-background-end}*/);
}
.ui-btn-down-f a.ui-link-inherit {
	color: #ffffff /*{f-bdown-color}*/;
}
</pre><p>After adding these classes to the app.css file, we can check the dialog’s look. The Yes button should now render with the new F swatch’s properties:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1992" style="margin-top: 15px; margin-bottom: 15px;" title="delete-note-dlg" src="http://miamicoder.com/wp-content/uploads/2012/01/delete-note-dlg.png" alt="" width="346" height="292" /></p><h3>Summary</h3><p>At this point, we have built a fully functional jQuery Mobile application that people can use to create, edit and delete notes.</p><p>In this series we started with a short introduction to the jQuery Mobile framework, learned how to create a modular application that saves information to the user’s device, created an attractive user interface, and designed behavior tests for the business logic.</p><p>I hope you use the experience gained in this and previous chapters to create great applications. Don’t forget to let me know how it goes! <img src='http://miamicoder.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /></p><h3>Downloads</h3><p>Download the source code for this article: <a href="http://miamicoder.com/wp-content/uploads/2012/01/Building-a-jQuery-Mobile-App-Part-4-Src.zip">Building a jQuery Mobile App Part 4 Sr.c.zip</a></p><h3>The Entire Series</h3><ul><li><a href="http://miamicoder.com/2012/building-a-jquery-mobile-application-part-4/">Building a jQuery Mobile Application, Part 4</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-3/">Building a jQuery Mobile Application, Part 3</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-2/">Building a jQuery Mobile Application, Part 2</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/">Building a jQuery Mobile Application, Part 1</a></li></ul> <div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=IPBiqKRUlhI:wDReE92a5kw:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=IPBiqKRUlhI:wDReE92a5kw:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=IPBiqKRUlhI:wDReE92a5kw:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=IPBiqKRUlhI:wDReE92a5kw:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=IPBiqKRUlhI:wDReE92a5kw:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=IPBiqKRUlhI:wDReE92a5kw:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/IPBiqKRUlhI" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/nKjsQ6AXE4gV7Wh9v-WurlpdnYU/0/da"><img src="http://feedads.g.doubleclick.net/~a/nKjsQ6AXE4gV7Wh9v-WurlpdnYU/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/nKjsQ6AXE4gV7Wh9v-WurlpdnYU/1/da"><img src="http://feedads.g.doubleclick.net/~a/nKjsQ6AXE4gV7Wh9v-WurlpdnYU/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=w8V5NkkqFyA:wDReE92a5kw:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=w8V5NkkqFyA:wDReE92a5kw:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=w8V5NkkqFyA:wDReE92a5kw:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=w8V5NkkqFyA:wDReE92a5kw:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=w8V5NkkqFyA:wDReE92a5kw:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=w8V5NkkqFyA:wDReE92a5kw:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=w8V5NkkqFyA:wDReE92a5kw:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=w8V5NkkqFyA:wDReE92a5kw:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/w8V5NkkqFyA" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2012/building-a-jquery-mobile-application-part-4/feed/</wfw:commentRss> <slash:comments>8</slash:comments> <feedburner:origLink>http://miamicoder.com/2012/building-a-jquery-mobile-application-part-4/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/IPBiqKRUlhI/</feedburner:origLink></item> <item><title>Sencha Touch or jQuery Mobile? – Read This Before You Make a Decision</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/Sk6ot8Qpyy4/</link> <comments>http://miamicoder.com/2011/sencha-touch-or-jquery-mobile-read-this-before-you-make-a-decision/#comments</comments> <pubDate>Tue, 27 Dec 2011 18:28:26 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[jQuery Mobile Tutorials]]></category> <category><![CDATA[Sencha Touch Tutorials]]></category> <category><![CDATA[JQuery Mobile Tutorial]]></category> <category><![CDATA[Sencha Touch Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1927</guid> <description><![CDATA[If you ever need to choose between jQuery Mobile and Sencha Touch, here are some factors you need to consider before making your decision.]]></description> <content:encoded><![CDATA[<p>If you ever need to choose between jQuery Mobile and Sencha Touch, you need to consider these factors before making your decision:</p><h3>About jQuery Mobile</h3><ul><li>It’s a UI-only library, which relies on jQuery and jQuery UI for DOM manipulation, Ajax and other utilities</li><li>To create UI widgets, you generally need to hand-code their html, and the library enhances their look and feel</li><li>As it works by enhancing the html you create, it allows you to re-use or re-purpose existing html</li><li>Some simple applications can be created using only html, without having to write JavaScript code</li><li>It has a relatively small object model, which makes it faster and easier to learn, specially if you are familiar with jQuery</li><li>It does not impose a coding discipline or structure, which gives you flexibility, but can lead to applications that are difficult to maintain</li><li>Easier to integrate with other frameworks</li><li>Targets more devices than Sencha Touch</li><li>Not tied to a particular vendor</li></ul><h3>About Sencha Touch</h3><ul><li>It’s a library that tries to do it all: UI widgets, DOM manipulation, Ajax and other utilities</li><li>Does not depend on other libraries</li><li>Follows a JavaScript-centric approach, where you are required to write little html</li><li>Has a large object model, which provides more features out of the box, but takes longer to learn</li><li>Imposes a coding structure and discipline, which generally results in well-organized code</li><li>Provides built-in server and local storage abstractions, which make it easier to perform CRUD operations on relational data</li><li>Has built-in facilities for creating iOS and Android native packages</li><li>Targets less devices than jQuery Mobile</li></ul><h3>Want To Learn More?</h3><p>I have a selection of Sencha Touch and jQuery Mobile tutorials that will help you build great applications. Check them out using these links:</p><ul><li><a href="http://miamicoder.com/jquery-mobile-tutorials/">jQuery Mobile Tutorials</a></li><li><a href="http://miamicoder.com/sencha-touch-tutorials/">Sencha Touch Tutorials</a></li></ul> <div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=zpFwLihy5v8:vPxW353AyEs:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=zpFwLihy5v8:vPxW353AyEs:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=zpFwLihy5v8:vPxW353AyEs:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=zpFwLihy5v8:vPxW353AyEs:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=zpFwLihy5v8:vPxW353AyEs:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=zpFwLihy5v8:vPxW353AyEs:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/zpFwLihy5v8" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/LTwW-0QKPOM1HpkvqxBqyEGogBQ/0/da"><img src="http://feedads.g.doubleclick.net/~a/LTwW-0QKPOM1HpkvqxBqyEGogBQ/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/LTwW-0QKPOM1HpkvqxBqyEGogBQ/1/da"><img src="http://feedads.g.doubleclick.net/~a/LTwW-0QKPOM1HpkvqxBqyEGogBQ/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=Sk6ot8Qpyy4:vPxW353AyEs:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=Sk6ot8Qpyy4:vPxW353AyEs:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=Sk6ot8Qpyy4:vPxW353AyEs:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=Sk6ot8Qpyy4:vPxW353AyEs:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=Sk6ot8Qpyy4:vPxW353AyEs:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=Sk6ot8Qpyy4:vPxW353AyEs:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=Sk6ot8Qpyy4:vPxW353AyEs:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=Sk6ot8Qpyy4:vPxW353AyEs:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/Sk6ot8Qpyy4" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/sencha-touch-or-jquery-mobile-read-this-before-you-make-a-decision/feed/</wfw:commentRss> <slash:comments>2</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/sencha-touch-or-jquery-mobile-read-this-before-you-make-a-decision/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/zpFwLihy5v8/</feedburner:origLink></item> <item><title>Building a jQuery Mobile Application, Part 3</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/DbRdo2y1gek/</link> <comments>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-3/#comments</comments> <pubDate>Tue, 20 Dec 2011 13:30:56 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[jQuery Mobile Tutorials]]></category> <category><![CDATA[JQuery Mobile Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1857</guid> <description><![CDATA[We continue building a jQuery Mobile application. In this article you will learn how to pass information between the Notes List page and the Note Editor page using URLs and query strings, how to load a note in the Note Editor page, and how to save a note.]]></description> <content:encoded><![CDATA[<p>In this third part of my tutorial on <a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/" target="_blank">how to create a mobile web app using  jQuery Mobile</a> we are going to focus on the following areas:</p><ul><li>How to pass information between the Notes List page and the Note Editor page using URLs and query strings</li><li>How to load a note in the Note Editor page</li><li>How to save a new or edited note</li></ul><div><img class="aligncenter size-full wp-image-1852" style="margin-top: 15px; margin-bottom: 15px; border-width: 1px; border-color: black; border-style: solid;" title="note-editor-1" src="http://miamicoder.com/wp-content/uploads/2011/12/note-editor-1.png" alt="" width="334" height="387" /></div><p>Let’s begin with a few changes that will make the application easier to maintain and test.<br /> <span id="more-1857"></span></p><h3>Refactoring</h3><p>Our data context module directly uses a reference to the jQuery library when calling the jStorage plugin. We will change our code so we pass the jQuery reference as a parameter when we invoke the dataContext module,  like so:</p><pre class="brush: jscript; title: ; notranslate">
Notes.dataContext = (function ($) {

    // Module’s implementation omitted…

} (jQuery));
</pre><p>In addition, we will remove the hard-coded reference to the storage key we use to cache notes in local storage:</p><pre class="brush: jscript; title: ; notranslate">
var notesListStorageKey = &quot;Notes.NotesList&quot;; // No need to hard-code this value.
</pre><p>We will now pass this value to the dataContext module through the init function:</p><pre class="brush: jscript; title: ; notranslate">

var init = function (storageKey) {
    notesListStorageKey = storageKey;
    loadNotesFromLocalStorage();
};
</pre><p>Among other things, this change will allow us to test the dataContext module with a different key than the one we use in the application itself. Let’s modify our AppSpec.js file to support this change:</p><pre class="brush: jscript; title: ; notranslate">

describe(&quot;Data Context tests&quot;, function () {

    var notesListStorageKey = &quot;Notes.NotesListTest&quot;;

    // Other tests omitted…

    it(&quot;Returns dummy notes saved in local storage&quot;, function () {

        Notes.testHelper.createDummyNotes();
        // Load dummy notes from local storage.
        Notes.dataContext.init(notesListStorageKey);

        var notesList = Notes.dataContext.getNotesList();

        expect(notesList.length &gt; 0).toBeTruthy();
    });

});
</pre><h3>Grouping Notes by Date</h3><p>The next change will make it easier for our users to find a particular note in the Notes List page. We are going to group the notes by date:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1854" style="margin-top: 15px; margin-bottom: 15px; border-width: 1px; border-color: black; border-style: solid;" title="notes-list-1" src="http://miamicoder.com/wp-content/uploads/2011/12/notes-list-1.png" alt="" width="334" height="387" /></p><p>We can make this helpful modification in the renderNotes function of the controller module:</p><pre class="brush: jscript; title: ; notranslate">
var renderNotesList = function () {

    var notesList = dataContext.getNotesList();
    var view = $(notesListSelector);

    view.empty();

    if (notesList.length === 0) {

        $(noNotesCachedMsg).appendTo(view);
    } else {

        var liArray = [],
            notesCount = notesList.length,
            note,
            dateGroup,
            noteDate,
            i;

        var ul = $(&quot;&lt;ul id=\&quot;notes-list\&quot; data-role=\&quot;listview\&quot;&gt;&lt;/ul&gt;&quot;).appendTo(view);

        for (i = 0; i &lt; notesCount; i += 1) {

            note = notesList[i];

            noteDate = (new Date(note.dateCreated)).toDateString();

            if (dateGroup !== noteDate) {
                liArray.push(&quot;&lt;li data-role=\&quot;list-divider\&quot;&gt;&quot; + noteDate + &quot;&lt;/li&gt;&quot;);
                dateGroup = noteDate;
            }

            liArray.push(&quot;&lt;li&gt;&quot;
                + &quot;&lt;a data-url=\&quot;index.html#note-editor-page?noteId=&quot; + note.id + &quot;\&quot; href=\&quot;index.html#note-editor-page?noteId=&quot; + note.id + &quot;\&quot;&gt;&quot;
                + &quot;&lt;div  class=\&quot;list-item-title\&quot;&gt;&quot; + note.title + &quot;&lt;/div&gt;&quot;
                + &quot;&lt;div class=\&quot;list-item-narrative\&quot;&gt;&quot; + note.narrative + &quot;&lt;/div&gt;&quot;
                + &quot;&lt;/a&gt;&quot;
                + &quot;&lt;/li&gt;&quot;);

        }

        var listItems = liArray.join(&quot;&quot;);
        $(listItems).appendTo(ul);

        ul.listview();
    }
};
</pre><p>In the loop that creates the list items representing the notes, we keep track of the date the notes were taken, inserting a new list item with the attribute data-role=”list-divider” whenever the date value changes. This gives us a nice list divider: <img class="aligncenter size-full wp-image-1855" style="margin-top: 15px; margin-bottom: 15px; border-width: 1px; border-color: black; border-style: solid;" title="notes-list-dividers" src="http://miamicoder.com/wp-content/uploads/2011/12/notes-list-dividers.png" alt="" width="417" height="251" /> In addition, notice how the list items contain a link whose query string has a reference to the id of the note. We will use this information when we load a selected note into the Note Editor page. Let’s start working on this page now.</p><h3>Creating the Note Editor Page</h3><p>The Note Editor page consists of a div element with the data-role=”page” attribute. We will add this div to the index.html file like so:</p><pre class="brush: xml; title: ; notranslate">
&lt;div data-role=&quot;page&quot; id=&quot;note-editor-page&quot; data-title=&quot;Edit Note&quot;&gt;
    &lt;div data-role=&quot;header&quot; data-position=&quot;fixed&quot;&gt;
        &lt;a href=&quot;#notes-list-page&quot; data-icon=&quot;back&quot; data-rel=&quot;back&quot;&gt;Cancel&lt;/a&gt;
        &lt;h1&gt;
            Edit Note&lt;/h1&gt;
        &lt;a id=&quot;save-note-button&quot; href=&quot;&quot; data-theme=&quot;b&quot; data-icon=&quot;check&quot;&gt;Save&lt;/a&gt;
    &lt;/div&gt;
    &lt;div data-role=&quot;content&quot;&gt;
        &lt;form action=&quot;&quot; method=&quot;post&quot; id=&quot;note-editor-form&quot;&gt;
        &lt;label for=&quot;note-title-editor&quot;&gt;
            Title:&lt;/label&gt;
        &lt;input type=&quot;text&quot; name=&quot;note-title-editor&quot; id=&quot;note-title-editor&quot; value=&quot;&quot; /&gt;
        &lt;label for=&quot;note-narrative-editor&quot;&gt;
            Narrative:&lt;/label&gt;
        &lt;textarea name=&quot;note-narrative-editor&quot; id=&quot;note-narrative-editor&quot;&gt;&lt;/textarea&gt;
        &lt;/form&gt;
    &lt;/div&gt;
    &lt;div data-role=&quot;footer&quot; data-position=&quot;fixed&quot; class=&quot;ui-bar&quot;&gt;
        &lt;a id=&quot;delete-note-button&quot; data-icon=&quot;delete&quot; data-transition=&quot;slideup&quot; data-rel=&quot;dialog&quot;&gt;Delete&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;
</pre><p>This page has a header area with Save and Cancel buttons, a form with the elements that allow us to edit a note, and a footer that hosts the Delete button. <img class="aligncenter size-full wp-image-1853" style="margin-top: 15px; margin-bottom: 15px; border-width: 1px; border-color: black; border-style: solid;" title="note-editor-2" src="http://miamicoder.com/wp-content/uploads/2011/12/note-editor-2.png" alt="" width="417" height="485" /> As you can see, jQuery Mobile has taken care of nicely styling and laying out the form elements.</p><p>We will give the Save and Delete button special treatment, binding to their tap events to trigger the routines that save or delete the note loaded in the Note Editor page.</p><p>The role of the Cancel button is to take us back to the Notes List page. This is why we use a link to the #notes-list-page bookmark.</p><h3>Loading a Note in the Editor</h3><p>Let’s take a moment to think about what needs to happen to load a note into the editor. For existing notes, when the user taps the li element representing a note in the Notes List page, we will perform the following steps:</p><ul><li>Look up the note in the notesList array, based on the note’s id contained in the li element’s link.</li><li>Set the values of the title and narrative form elements in the Note Editor page to those of the selected note’s title and narrative.</li><li>Make the Note Editor page active.</li></ul><p>For new notes, when the user taps the New button in the Notes List page, we will perform these steps:</p><ul><li>Create a new, blank note.</li><li>Set the values of the title and narrative form elements in the Note Editor page to those of the new note’s title and narrative.</li><li>Make the Note Editor page active.</li></ul><p>These routines are similar. The only difference is that in the first case we have to look up and load an existing note, while in the second case we need to create and load a new note.</p><p>Let’s begin with the first set of steps. The first thing we need in our controller is a reference to the Note Editor. We will define a selector for it in the declarations section of the controller module:</p><pre class="brush: jscript; title: ; notranslate">
var noteEditorPageId = &quot;note-editor-page&quot;;
</pre><p>Next, we need to return to the onPageChange() function and handle the case when we are switching to the editor page:</p><pre class="brush: jscript; title: ; notranslate">
var onPageChange = function (event, data) {

    var toPageId = data.toPage.attr(&quot;id&quot;);
    var fromPageId = null;

    if (data.options.fromPage) {
        fromPageId = data.options.fromPage.attr(&quot;id&quot;);
    }

    switch (toPageId) {

        case notesListPageId:
            resetCurrentNote();
            renderNotesList();
            break;

        case noteEditorPageId:

            if (fromPageId === notesListPageId) {
                renderSelectedNote(data);
            }
            break;
    }
};
</pre><p>The handler is a bit more complex now. We added the fromPageId variable, which will store the id of the page we’re navingating from. We will call this page Source page from now on. We will call the page we’re navigating to Target page.</p><p>The value of fromPageId will help us determine if we need to load a note into the editor:</p><pre class="brush: jscript; title: ; notranslate">
if (fromPageId === notesListPageId) {
    renderSelectedNote(data);
}
</pre><p>If the source page is the notes list, we load the note by calling the renderSelectedNote() function, which we will implement in a minute. Note that we added the check for the fromPage parameter because the pagechange event is also triggered after the application launches, when there isn’t a source page yet. It doesn’t make sense to acquire the id of the source page when the source page itself doesn’t exist:</p><pre class="brush: jscript; title: ; notranslate">
if (data.options.fromPage) {
    fromPageId = data.options.fromPage.attr(&quot;id&quot;);
}
</pre><p>Before adding the code for renderSelectedNote(), let’s jump to the top of the controller module and create identifiers for the title and narrative form elements:</p><pre class="brush: jscript; title: ; notranslate">
var noteTitleEditorSel = &quot;[name=note-title-editor]&quot;;
var noteNarrativeEditorSel = &quot;[name=note-narrative-editor]&quot;;
</pre><p>And here&#8217;s the implementation of renderSelectedNote():</p><pre class="brush: jscript; title: ; notranslate">
var renderSelectedNote = function (data) {

    var u = $.mobile.path.parseUrl(data.options.fromPage.context.URL);
    var re = &quot;^#&quot; + noteEditorPageId;

    if (u.hash.search(re) !== -1) {

        var queryStringObj = queryStringToObject(data.options.queryString);

        var titleEditor = $(noteTitleEditorSel);
        var narrativeEditor = $(noteNarrativeEditorSel);

        var noteId = queryStringObj[&quot;noteId&quot;];

        if (typeof noteId !== &quot;undefined&quot;) {

            // We were passed a note id =&gt; We're editing an existing note.
            var notesList = dataContext.getNotesList();
            var notesCount = notesList.length;
            var note;

            for (var i = 0; i &lt; notesCount; i++) {

                note = notesList[i];

                if (noteId === note.id) {

                    titleEditor.val(note.title);
                    narrativeEditor.val(note.narrative);
                    currentNote = note;
                }
            }
        } else {
            // We're creating a note. Reset the fields.
            titleEditor.val(&quot;&quot;);
            narrativeEditor.val(&quot;&quot;);
        }

        titleEditor.focus();
    }
};
</pre><p>The renderSelectedNote() function takes advantage of the fact that we’re passing the selected note’s id in the query string of the list item’s link:</p><pre class="brush: jscript; title: ; notranslate">
liArray.push(&quot;&lt;li&gt;&quot;
    + &quot;&lt;a data-url=\&quot;index.html#note-editor-page?noteId=&quot; + note.id + &quot;\&quot; href=\&quot;index.html#note-editor-page?noteId=&quot; + note.id + &quot;\&quot;&gt;&quot;
    + &quot;&lt;div  class=\&quot;list-item-title\&quot;&gt;&quot; + note.title + &quot;&lt;/div&gt;&quot;
    + &quot;&lt;div class=\&quot;list-item-narrative\&quot;&gt;&quot; + note.narrative + &quot;&lt;/div&gt;&quot;
    + &quot;&lt;/a&gt;&quot;
    + &quot;&lt;/li&gt;&quot;);
</pre><p>Our first goal inside renderSelectedNote() is to inspect the hash of the source page’s URL, to make sure the source page is the Notes List page. We do this using a regular expression search:</p><pre class="brush: jscript; title: ; notranslate">
var u = $.mobile.path.parseUrl(data.options.fromPage.context.URL);
    var re = &quot;^#&quot; + noteEditorPageId;

    if (u.hash.search(re) !== -1) {
	… additional code omitted
    }
</pre><p>To acquire the URL’s hash we use the <a href="http://jquerymobile.com/test/docs/api/methods.html" target="_blank">$.mobile.path.parseUrl()</a> function, which parses a URL into an object that facilitates accessing the URL’s components.</p><p>Once we’re certain the source page is the Notes List, we create an object containing the query string parameters passed from the source page. The queryStringToObject() helper function performs this task:</p><pre class="brush: jscript; title: ; notranslate">
var queryStringToObject = function (queryString) {

    var queryStringObj = {};
    var e;
    var a = /\+/g;  // Replace + symbol with a space
    var r = /([^&amp;;=]+)=?([^&amp;;]*)/g;
    var d = function (s) { return decodeURIComponent(s.replace(a, &quot; &quot;)); };

    e = r.exec(queryString);
    while (e) {
        queryStringObj[d(e[1])] = d(e[2]);
        e = r.exec(queryString);

    }

    return queryStringObj;
};
</pre><p>The queryStringToObject() function takes the value of the data.options.queryString property as a parameter:</p><pre class="brush: jscript; title: ; notranslate">
var queryStringObj = queryStringToObject(data.options.queryString);
</pre><p>The problem is that the data.options object does not have a native queryString property. However, we can create it if we find a place, or rather a time before the page transition occurs, where we can acquire the value of the query string.</p><p>It turns out that this is possible if we define a handler for jQuery Mobile’s pagebeforechange event. Let’s revisit the init() function, and add the following line:</p><pre class="brush: jscript; title: ; notranslate">
d.bind(&quot;pagebeforechange&quot;, onPageBeforeChange);
</pre><p>Now we can define onPageBeforeChange() like so:</p><pre class="brush: jscript; title: ; notranslate">
var onPageBeforeChange = function (event, data) {

    if (typeof data.toPage === &quot;string&quot;) {

        var url = $.mobile.path.parseUrl(data.toPage);

        if ($.mobile.path.isEmbeddedPage(url)) {

            data.options.queryString = $.mobile.path.parseUrl(url.hash.replace(/^#/, &quot;&quot;)).search.replace(&quot;?&quot;, &quot;&quot;);
        }
    }
};
</pre><p>Pay attention to the following line:</p><pre class="brush: jscript; title: ; notranslate">
data.options.queryString = $.mobile.path.parseUrl(url.hash.replace(/^#/, &quot;&quot;)).search.replace(&quot;?&quot;, &quot;&quot;);
</pre><p>Here we use $.mobile.path.parseUrl() to acquire the query string and add it to the data.options object. As the onPageBeforeChange handler is invoked before the onPageChange handler, we’ve found an approach to inject the query string defined in the source page into the events chain, and propagate it to the target pages, where it can be used.</p><p>Back in renderSelectedNote(), we can finally take care of loading the selected note, or resetting the title and narrative fields, like so:</p><pre class="brush: jscript; title: ; notranslate">
if (typeof noteId !== &quot;undefined&quot;) {

    // We were passed a note id =&gt; We're editing an existing note.
    var notesList = dataContext.getNotesList();
    var notesCount = notesList.length;
    var note;

    for (var i = 0; i &lt; notesCount; i++) {

        note = notesList[i];

        if (noteId === note.id) {
            currentNote = note;
            titleEditor.val(currentNote.title);
            narrativeEditor.val(currentNote.narrative);
        }
    }
} else {
    // We're creating a note. Reset the fields.
    titleEditor.val(&quot;&quot;);
    narrativeEditor.val(&quot;&quot;);
}
</pre><p>Observe how we’re keeping a reference to the selected note in the currentNote variable. This will later allow us to save or delete the note without having to perform a lookup on the notesList array.</p><p>This is what it takes to load a note. Let’s make sure things are working as expected. Fire up your favorite browser and confirm that tapping a note in the Notes List page loads the note into the Note Editor page:</p><p style="text-align: center;"><a href="http://miamicoder.com/wp-content/uploads/2011/12/navigation-edit-note-1.png"><img class="aligncenter size-medium wp-image-1876" style="margin-top: 15px; margin-bottom: 15px;" title="navigation-edit-note-1" src="http://miamicoder.com/wp-content/uploads/2011/12/navigation-edit-note-1-300x149.png" alt="" width="300" height="149" /></a></p><p>Similarly, tapping the New button should load a new note into the editor.</p><h3>Saving a Note</h3><p>As the Save Note workflow is initiated when a user taps the Save button, the controller module needs to define a handler for the button’s tap event. We will use this handler to invoke a saveNote() function that we will create in the dataContext module. Let’s work on the tap handler first.</p><p>We need an identifier for the Save button, which we can create at the top of the controller module like so:</p><pre class="brush: jscript; title: ; notranslate">
var saveNoteButtonSel = &quot;#save-note-button&quot;;
</pre><p>Next, we need to bind the tap handler in the controller’s init() function:</p><pre class="brush: jscript; title: ; notranslate">
var init = function () {

    dataContext.init(&quot;Notes.NotesList&quot;);

    var d = $(document);
    d.bind(&quot;pagebeforechange&quot;, onPageBeforeChange);
    d.bind(&quot;pagechange&quot;, onPageChange);
    d.delegate(saveNoteButtonSel, &quot;tap&quot;, onSaveNoteButtonTapped);
};
</pre><p>Now we can define the onSaveButtonTapped function like so:</p><pre class="brush: jscript; title: ; notranslate">
var onSaveNoteButtonTapped = function () {

    // Validate note.
    var titleEditor = $(noteTitleEditorSel);
    var narrativeEditor = $(noteNarrativeEditorSel);
    var tempNote = dataContext.createBlankNote();

    tempNote.title = titleEditor.val();
    tempNote.narrative = narrativeEditor.val();

    if (tempNote.isValid()) {

        if (null !== currentNote) {

            currentNote.title = tempNote.title;
            currentNote.narrative = tempNote.narrative;
        } else {

            currentNote = tempNote;
        }

        dataContext.saveNote(currentNote);

        returnToNotesListPage();

    } else {
        // TODO: Inform the user the note is invalid.
    }
};
</pre><p>The first interesting thing that happens in this function is the creation of a temporary note, and the call to the NoteModel’s isValid method. This is the method that will allow us to validate a note before making it permanent.</p><p>But isValid does not exist yet. We need to create it like so:</p><pre class="brush: jscript; title: ; notranslate">
Notes.NoteModel.prototype.isValid = function () {
    &quot;use strict&quot;;
    if (this.title &amp;&amp; this.title.length &gt; 0) {
        return true;
    }
    return false;
};
</pre><p>A simple check on the note’s title is enough to validate the note. Nothing complicated.</p><p>Back in onSaveButtonTapped, we find out if we’re editing an existing note by observing the value of currentNote. If currentNote points to an existing note, we transfer the title and narrative of the temporary note to it. If currentNote is not poiting to an existing note, we make it point to the temporary note’s reference.</p><p>Then, we save the note by calling the dataContext module’s saveNote function. We haven’t created this function yet. Let’s define a behavior test for it in the AppSpec file:</p><pre class="brush: jscript; title: ; notranslate">
it(&quot;Saves a note to local storage&quot;, function () {

    // Make sure LS is empty before the test.
    $.jStorage.deleteKey(notesListStorageKey);
    var notesList = $.jStorage.get(notesListStorageKey);
    expect(notesList).toBeNull();

     // Create a note.
    var dateCreated = new Date();
    var id = dateCreated.getTime().toString();
    var noteModel = new Notes.NoteModel({
        id: id,
        dateCreated: dateCreated,
        title: &quot;&quot;
    });

    Notes.dataContext.init(notesListStorageKey);
    Notes.dataContext.saveNote(noteModel);

    // Should retrieve the saved note.
    notesList = $.jStorage.get(notesListStorageKey);
    var expectedNote = notesList[0];

    expect(expectedNote instanceof Notes.NoteModel).toBeTruthy();

    // Clean up
    $.jStorage.deleteKey(notesListStorageKey);
});
</pre><p>In this spec we first empty the local storage container we will use. Then, we save a dummy note by calling saveNote on the dataContext module. Last, we retrieve the value from local storage and assert that it is in effect an instance of the NoteModel class.</p><p>As the saveNote function is missing, this test should fail:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1849" style="margin-top: 15px; margin-bottom: 15px;" title="it-fails-4" src="http://miamicoder.com/wp-content/uploads/2011/12/it-fails-4.png" alt="" width="394" height="263" /></p><p>In the dataContext module, let’s create saveNote as follows:</p><pre class="brush: jscript; title: ; notranslate">
var saveNote = function (noteModel) {

    var found = false;
    var i;

    for (i = 0; i &lt; notesList.length; i += 1) {
        if (notesList[i].id === noteModel.id) {
            notesList[i] = noteModel;
            found = true;
            i = notesList.length;
        }
    }

    if (!found) {
        notesList.splice(0, 0, noteModel);
    }

    saveNotesToLocalStorage();
};
</pre><p>This function is pretty straightforward. We start by iterating over the array of existing notes. If we find the id of the edited note, we “edit” the existing note through an in-place replacement with the passed note. If don’t find the id, we simply place the passed note at the beginning of the array.</p><p>Finally, we call the private function saveNotesToLocalStorage, which we also need to add to the dataContext module. This is where we save the modified array to local storage:</p><pre class="brush: jscript; title: ; notranslate">
var saveNotesToLocalStorage = function () {
    $.jStorage.set(notesListStorageKey, notesList);
};
</pre><p>Let’s run the spec again. This time, it should pass:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1850" style="margin-top: 15px; margin-bottom: 15px;" title="it-ok-6" src="http://miamicoder.com/wp-content/uploads/2011/12/it-ok-6.png" alt="" width="394" height="74" /></p><p>Back in the onSaveButtonTapped function, we also wrote a call to the helper function returnToNotesListPage, which we will implement like so:</p><pre class="brush: jscript; title: ; notranslate">
var returnToNotesListPage = function () {

    $.mobile.changePage(&quot;#&quot; + notesListPageId,
        { transition: &quot;slide&quot;, reverse: true });
};
</pre><p>This is a convenience function that we will call every time we need to return to the Notes List page.</p><p>There is one last step we need to take in order for the Edit Note and Save Note workflows to work correctly. We need to make sure we reset the currentNote reference after a note is saved. We can do this from the onPageChange function, within its switch statement:</p><pre class="brush: jscript; title: ; notranslate">
var onPageChange = function (event, data) {
var toPageId = data.toPage.attr(&quot;id&quot;);
var fromPageId = null;
if (data.options.fromPage) {
fromPageId = data.options.fromPage.attr(&quot;id&quot;);
}
switch (toPageId) {
case notesListPageId:
resetCurrentNote(); // Reset reference to the note being edited.
renderNotesList();
break;
case noteEditorPageId:

if (fromPageId === notesListPageId) {
renderSelectedNote(data);
}
break;
}
};
</pre><p>When we’re navigating back to the Notes List page, we’re now invoking the private function resetCurrentNote, which looks like this:</p><pre class="brush: jscript; title: ; notranslate">
var resetCurrentNote = function () {
    currentNote = null;
}
</pre><p>What do you think? Are you ready to test on the emulator? First go back to the mobileinit handler and either remove or comment out the call to createDummyNotes. We don’t need it anymore:</p><pre class="brush: jscript; title: ; notranslate">
$(document).bind(&quot;mobileinit&quot;, function () {

    //Notes.testHelper.createDummyNotes();

    Notes.controller.init();

});
</pre><p>Now, fire up the emulator. You should be able to create and edit notes.</p><p>Nice! We just built the ability to create and edit notes into our application.</p><p>I’m sure you noticed that we still have a loose end in the onSaveNoteButtonTapped function, as we have not handled the invalid note scenario. We will leave this step for the next chapter, along with deleting notes.</p><p>Stay tuned!</p><h3>Downloads</h3><p>Download the source code for this article: <a href="http://miamicoder.com/wp-content/uploads/2011/12/Building-a-jQuery-Mobile-App-Part-3-Src.zip">Building a jQuery Mobile App Part 3 Src.zip</a></p><h3>The Entire Series</h3><ul><li><a href="http://miamicoder.com/2012/building-a-jquery-mobile-application-part-4/">Building a jQuery Mobile Application, Part 4</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-3/">Building a jQuery Mobile Application, Part 3</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-2/">Building a jQuery Mobile Application, Part 2</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/">Building a jQuery Mobile Application, Part 1</a></li></ul> <div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=iUKbyNWoUFs:gHIoygW0PhM:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=iUKbyNWoUFs:gHIoygW0PhM:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=iUKbyNWoUFs:gHIoygW0PhM:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=iUKbyNWoUFs:gHIoygW0PhM:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=iUKbyNWoUFs:gHIoygW0PhM:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=iUKbyNWoUFs:gHIoygW0PhM:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/iUKbyNWoUFs" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/YqdSPIrHaQ9lC2reLX25_958Eo4/0/da"><img src="http://feedads.g.doubleclick.net/~a/YqdSPIrHaQ9lC2reLX25_958Eo4/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/YqdSPIrHaQ9lC2reLX25_958Eo4/1/da"><img src="http://feedads.g.doubleclick.net/~a/YqdSPIrHaQ9lC2reLX25_958Eo4/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=DbRdo2y1gek:gHIoygW0PhM:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=DbRdo2y1gek:gHIoygW0PhM:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=DbRdo2y1gek:gHIoygW0PhM:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=DbRdo2y1gek:gHIoygW0PhM:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=DbRdo2y1gek:gHIoygW0PhM:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=DbRdo2y1gek:gHIoygW0PhM:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=DbRdo2y1gek:gHIoygW0PhM:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=DbRdo2y1gek:gHIoygW0PhM:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/DbRdo2y1gek" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-3/feed/</wfw:commentRss> <slash:comments>0</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-3/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/iUKbyNWoUFs/</feedburner:origLink></item> <item><title>Building a jQuery Mobile Application, Part 2</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/PunTgV3DttM/</link> <comments>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-2/#comments</comments> <pubDate>Wed, 30 Nov 2011 02:04:30 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[jQuery Mobile Tutorials]]></category> <category><![CDATA[Jasmine Tutorial]]></category> <category><![CDATA[JQuery Mobile Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1753</guid> <description><![CDATA[This is the second part of a series of articles of how to create a mobile web app using JQuery Mobile. In this article we will create our first jQuery Mobile page, along with the data access logic to support it.]]></description> <content:encoded><![CDATA[<p>This is the second part of my <a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/">tutorial on how to create a mobile web app using jQuery Mobile</a>. In this tutorial we’re building a mobile web application that offers its users the following features:</p><ul><li>Ability to create, edit and delete notes.</li><li>Ability to store notes on the device running the application, across browser sessions.</li><li>Ability to view the entire collection of notes stored on the device.</li></ul><p>In the first chapter of this series we started building the business logic layer of the app. We are using a simple workflow that consists of the following steps:</p><ul><li>Use the <a href="https://github.com/pivotal/jasmine/wiki" target="_blank">Jasmine framework</a> to define the business logic behavior.</li><li>Implement the behavior in the application.</li><li>Confirm that the implemented behavior adheres to its definition.</li></ul><p>We’re now going to continue working on the business logic features that will allow us to retrieve the list of notes cached on the device. We’re also going to use the jQuery Mobile framework to present the cached notes to the user.</p><p>By the end of this article, we will have completed the first version of the Notes List screen. This screen will render the list of cached notes as depicted below.</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1752" style="margin-top: 15px; margin-bottom: 15px; border-width: 1px; border-color: black; border-style: solid;" title="notes-list-dummy-notes-1" src="http://miamicoder.com/wp-content/uploads/2011/11/notes-list-dummy-notes-1.png" alt="" width="329" height="412" /></p><h3><span id="more-1753"></span>Consolidating Test Suites and Creating a Data Context Module</h3><p>Let’s get started with a bit of refactoring. As you might recall from the first part of this tutorial, we placed the tests into two Jasmine suites. The first suite looks like this:</p><pre class="brush: jscript; title: ; notranslate">
describe(&quot;Public interface exists&quot;, function () {

    it(&quot;Should have public interface to return notes list&quot;, function () {
        expect(Notes.app.getNotesList).toBeDefined();
    });

});
</pre><p>And the second suite looks like this:</p><pre class="brush: jscript; title: ; notranslate">
describe(&quot;Public interface implementation&quot;, function () {

    it(&quot;Should return notes list&quot;, function () {

        var notesList = Notes.app.getNotesList();

        expect(notesList instanceof Array).toBeTruthy();
    });
});
</pre><p>As I was writing the outline for this article, I realized that using two test suites this early in the project, when we have only a few specs, might be overkill. We can simplify things and use only one test suite for now. We can always add more suites if there’s a real need later.</p><p>Let’s replace the suites above with the following:</p><pre class="brush: jscript; title: ; notranslate">
describe(&quot;Data Context tests&quot;, function () {

    it(“Exists in the app”, function () {
        expect(Notes.dataContext).toBeDefined();
    });

    it(“Returns notes Array”, function () {

        var notesList = Notes.dataContext.getNotesList();

        expect(notesList instanceof Array).toBeTruthy();
    });

});
</pre><p>As the first spec indicates, we’re going to rename the app instance variable to dataContext. After all, what this instance will contain is all the functions that have to do with data access &#8211; in our case, access to the notes stored on the device. In the first part of the series, the app variable looked like this:</p><pre class="brush: jscript; title: ; notranslate">
Notes.app = (function () {

    var notesList = [];

    function getNotesList() {
        return notesList;
    }

    return {

        getNotesList: getNotesList

    }

})();
</pre><p>After the renaming, we end up with dataContext, like so:</p><pre class="brush: jscript; title: ; notranslate">
Notes.dataContext = (function () {

    var notesList = [];

    function getNotesList() {
        return notesList;
    }

    return {

        getNotesList: getNotesList

    }

})();
</pre><p>Along with this change, we’re going to rename the App.js file to DataContext.js. After renaming, our files should now look as depicted below.</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1743" style="margin-top: 15px; margin-bottom: 15px;" title="app-folders-4" src="http://miamicoder.com/wp-content/uploads/2011/11/app-folders-4.png" alt="" width="170" height="89" /></p><p>Of course, we need to run our tests to make sure the refactoring did not change the expected behavior. In the specrunner.html, we need to replace the reference to App.js with a reference to DataContext.js:</p><pre class="brush: xml; title: ; notranslate">
&lt;!-- App --&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;app/DataContext.js&quot;&gt;&lt;/script&gt;
</pre><p>Now we can run our Jasmine suite. Let’s open the specrunner.html file in our favorite browser and check the output of the test. If we didn’t make any mistakes during our refactoring, our tests should be green: <img class="aligncenter size-full wp-image-1748" style="margin-top: 15px; margin-bottom: 15px;" title="it-ok-3" src="http://miamicoder.com/wp-content/uploads/2011/11/it-ok-3.png" alt="" width="372" height="136" /> Our refactoring is done. What we did, in short, was rename the application module we created in the first part of the tutorial – it’s now called DataContext – and adjust our tests accordingly.</p><h3>Creating a Blank Note</h3><p>Besides retrieving the cached notes, an important function we need our data context to perform is creating a blank note. This feature is needed when users tap the New button in the UI. Tapping the New button will pass a new, blank note, to the Note Editor view: <img class="aligncenter size-full wp-image-1751" style="margin-top: 15px; margin-bottom: 15px;" title="navigation-new-button-mockup" src="http://miamicoder.com/wp-content/uploads/2011/11/navigation-new-button-mockup.png" alt="" width="564" height="286" />We can describe this “create blank note” behavior in our test suite as follows:</p><pre class="brush: jscript; title: ; notranslate">
it(&quot;Returns a blank note&quot;, function () {

    var blankNote = Notes.dataContext.createBlankNote();
    expect(blankNote.title.length === 0).toBeTruthy();
    expect(blankNote.narrative.length === 0).toBeTruthy();
});
</pre><p>If we run this test, it will not pass. Why? Well, we need to add the createBlankNote() function to the Data Context module. This is the code we need:</p><pre class="brush: jscript; title: ; notranslate">
Notes.dataContext = (function () {

    var notesList = [];

    function createBlankNote() {

        var dateCreated = new Date();
        var id = new String(dateCreated.getTime()) + new String(getRandomInt(0, 100));
        var noteModel = new Notes.NoteModel({
            id: id,
            dateCreated: dateCreated,
            title: &quot;&quot;,
            narrative: &quot;&quot;
        });

        return noteModel;
    }

    function getNotesList() {
        return notesList;
    }

    return {
        createBlankNote: createBlankNote,
        getNotesList: getNotesList
    };

})();
</pre><p>If you look at createBlankNote(), you will notice that it calls a getRandomInt() helper function to create the id of the new note. As we haven’t defined getRandomInt() yet, if we run the Jasmine spec, the results should show that the helper function is missing: <img class="aligncenter size-full wp-image-1747" style="margin-top: 15px; margin-bottom: 15px;" title="it-fails-3" src="http://miamicoder.com/wp-content/uploads/2011/11/it-fails-3.png" alt="" width="374" height="234" />Let’s add getRandomInt() like so:</p><pre class="brush: jscript; title: ; notranslate">
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
</pre><p>Re-running the test again should fail with a different error. The reason for the new error is that we’re trying to create an instance of the NoteModel class, which we haven’t defined yet.</p><p>Let’s create the NoteModel.js file in the application’s folder, and add the following definition for the NoteModel class:</p><pre class="brush: jscript; title: ; notranslate">
Notes.NoteModel = function (config) {
    this.id = config.id;
    this.dateCreated = config.dateCreated;
    this.title = config.title;
    this.narrative = config.narrative;
}
</pre><p>This construct should look familiar to you. The NoteModel is a class that represents a note. Every time we need to move a note’s data around, we will use an instance of this class. And what we will cache on the device is a serialized array of NoteModel instances.</p><p>We also need to include a reference to the NoteModel.js file in specrunner.html:</p><pre class="brush: xml; title: ; notranslate">
&lt;!-- App --&gt;&lt;script type=&quot;text/javascript&quot; src=&quot;app/DataContext.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;app/NoteModel.js&quot;&gt;&lt;/script&gt;
</pre><p>After adding the NoteModel, the tests should pass: <img class="aligncenter size-full wp-image-1749" style="margin-top: 15px; margin-bottom: 15px;" title="it-ok-4" src="http://miamicoder.com/wp-content/uploads/2011/11/it-ok-4.png" alt="" width="369" height="167" /> Are you with me so far? This is getting interesting. Next we will begin working on retrieving cached notes from <a href="http://dev.w3.org/html5/webstorage/">HTML5’s local storage</a>.</p><h3>Retrieving Cached Notes from Local Storage</h3><p>We will use the localStorage API to store notes across browser sessions. Local storage is based on keys and values, where the keys and values are strings. As we will cache an array of NoteModel instances, we will need a serialization mechanism to convert the array of notes to a string that will be saved in local storage. Similarly, we will need a mechanism to convert the serialized notes into an array of NoteModel instances when retrieving the cached notes.</p><p>We could write the serialization/deserialization code we need. However, to simplify this tutorial, we will use an abstraction layer on top of the localStorage API. This layer will take care of the serialization and deserialization services, and it will be provided by the <a href="http://www.jstorage.info/">jStorage</a> plugin.</p><p>The jStorage plugin allows us to cache numbers, strings, and objects in local storage. We are going to place the jStorage plugin in its own folder like so: <img class="aligncenter size-full wp-image-1744" style="margin-top: 15px; margin-bottom: 15px;" title="app-folders-5" src="http://miamicoder.com/wp-content/uploads/2011/11/app-folders-5.png" alt="" width="182" height="135" />In the data context module, we will define a private function which retrieves the cached notes from local storage like so:</p><pre class="brush: jscript; title: ; notranslate">
function loadNotesFromLocalStorage() {

    var storedNotes = $.jStorage.get(notesListStorageKey);

    if (storedNotes !== null) {
        notesList = storedNotes;
    }

}
</pre><p>And let’s also define the notesListStorageKey variable, right after the notesList definition:</p><pre class="brush: jscript; title: ; notranslate">
var notesList = [];
var notesListStorageKey = &quot;Notes.NotesList&quot;;
</pre><p>The loadNotesFromLocalStorage() function simply uses the jStorage plugin to retrieve the list of cached notes from local storage. The plugin takes care of converting the list of notes from a String instance to an Array instance, exactly what we need.</p><p>When the application is run, the cached notes will be presented to the user. This means that the loadNotesFromLocalStorage() is one of the first functions that will be called in the app. To call this function when the application starts, as well as execute any other initialization code needed by the app, we will use a helper method, which we will name init().</p><p>Let’s work on the init() function by first creating a spec for it in the AppSpec.js file:</p><pre class="brush: jscript; title: ; notranslate">
it(&quot;Has init function&quot;, function () {
    expect(Notes.dataContext.init).toBeDefined();
});
</pre><p>Then, we need to add the init() public function to the DataContext.js file:</p><pre class="brush: jscript; title: ; notranslate">
function init() {
    loadNotesFromLocalStorage();
}
.
. // Other functions…
.
return {
    init: init,
    createBlankNote: createBlankNote,
    getNotesList: getNotesList
};
</pre><p>The test for init() should be green at this point: <img class="aligncenter size-full wp-image-1750" style="margin-top: 15px; margin-bottom: 15px;" title="it-ok-5" src="http://miamicoder.com/wp-content/uploads/2011/11/it-ok-5.png" alt="" width="371" height="159" /></p><h3>Testing With Data by Retrieving a List of Dummy Notes</h3><p>At this point we’ve added and tested the features we needed for this part of the tutorial. However, it would be nice if we could also test by saving a few dummy notes to local storage, and having the Data Context module retrieve them for us.</p><p>This is not difficult to do. Let’s first create a helper module, which we will place in a new TestHelper.js file, in the spec folder. This module will allow us to save a few dummy notes into local storage:</p><pre class="brush: jscript; title: ; notranslate">
Notes.testHelper = (function () {

    function createDummyNotes() {

        var notesListStorageKey = &quot;Notes.NotesList&quot;;
        var notesCount = 10;
        var notes = [];

        for (var i = 0; i &lt; notesCount; i++) {

            var note = Notes.dataContext.createBlankNote();
            note.title = &quot;Title &quot; + i;
            note.narrative = &quot;Narrative &quot; + i;
            notes.push(note);
        }

        $.jStorage.set(notesListStorageKey, notes);
    };

    return {
        createDummyNotes: createDummyNotes
    }

})();
</pre><p>Notice that testHelper uses the same notesListStorageKey value used in the data context module, along with the data context’s createBlankNote() function, to create an array of dummy notes. These notes are saved to local storage through the jStorage plugin.</p><p>We also need a reference to TestHelper.js in our specrunner.html file:</p><pre class="brush: xml; title: ; notranslate">
&lt;!-- App --&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;app/DataContext.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;app/NoteModel.js&quot;&gt;&lt;/script&gt;
&lt;!-- Test Helper --&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;spec/TestHelper.js&quot;&gt;&lt;/script&gt;
&lt;!-- Spec --&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;spec/AppSpec.js&quot;&gt;&lt;/script&gt;
</pre><p>Back in the AppSpec.js file, we can define a spec confirming that we can load the dummy notes:</p><pre class="brush: jscript; title: ; notranslate">
it(&quot;Returns dummy notes saved in local storage&quot;, function () {

    Notes.testHelper.createDummyNotes();
    // Load dummy notes from ls.
    Notes.dataContext.init();

    var notesList = Notes.dataContext.getNotesList();

    expect(notesList.length &gt; 0).toBeTruthy();
});
</pre><p>This spec first uses the testHelper’s createDummyNotes() function to place a few notes in local storage. Then, it invokes the data context’s init() function, which in turn calls the loadNotesFromLocalStorage() private function.</p><p>After the call to init(), the spec retrieves the notes list via a call to the data context’s getNotesList() function. The expectation is simply that the notes list is not empty.</p><p>Notice that we could be more thorough and compare the retrieved notes to the ones we saved, making sure that the data did not change. I will leave this exercise as homework for you.</p><p>What do you say we dive into the presentation layer?</p><h3>Rendering Cached Notes</h3><p>Now we’re ready to render the cached notes. If you’ve never read the <a href="http://jquerymobile.com/demos/1.0/docs/about/intro.html" target="_blank">jQuery Mobile overview</a> and <a href="http://jquerymobile.com/demos/1.0/docs/about/getting-started.html" target="_blank">quick start guide</a>, this is a good time to do it. Don’t worry, I’ll wait for you to come back. <img src='http://miamicoder.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /></p><p>The first thing we need to do is use jQuery Mobile’s page template to create our app’s html page. Let’s create the index.html file in our application’s folder, as depicted below: <img class="aligncenter size-full wp-image-1745" style="margin-top: 15px; margin-bottom: 15px;" title="app-folders-6" src="http://miamicoder.com/wp-content/uploads/2011/11/app-folders-6.png" alt="" width="166" height="175" />Then, add the following markup to the index.html file:</p><pre class="brush: xml; title: ; notranslate">
&lt;!--&lt;html&gt;
&lt;head&gt;--&gt;
    &lt;title&gt;&lt;/title&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
    &lt;link href=&quot;../../lib/jqm/jquery.mobile-1.0.min.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; /&gt;
    &lt;link href=&quot;css/app.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; /&gt;
    &lt;script src=&quot;../../lib/jqm/jquery-1.6.4.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;../../lib/jstorage/jstorage.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;app/DataContext.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;app/Controller.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;app/NoteModel.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;spec/TestHelper.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;

    &lt;!—--- Add mobileinit event handler here -----&gt;

    &lt;script src=&quot;../../Lib/jqm/jquery.mobile-1.0.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;!--&lt;/head&gt;
&lt;body&gt;--&gt;
    &lt;div data-role=&quot;page&quot; id=&quot;notes-list-page&quot; data-title=&quot;My Notes&quot;&gt;
        &lt;div data-role=&quot;header&quot; data-position=&quot;fixed&quot;&gt;
            &lt;h1&gt;
                My Notes&lt;/h1&gt;
            &lt;a href=&quot;#note-editor-page&quot; class=&quot;ui-btn-right&quot; data-theme=&quot;b&quot; data-icon=&quot;plus&quot;&gt;New&lt;/a&gt;
        &lt;/div&gt;
        &lt;div data-role=&quot;content&quot; id=&quot;notes-list-content&quot;&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;!--&lt;/body&gt;
&lt;/html&gt;--&gt;
</pre><p>As you can see, the head section of the file has references to the jQuery and jQuery Mobile libraries, the jStorage library, and the application’s files – DataContext.js, Controller.js and NoteModel.js. So far we have created DataContext.js and NoteModel.js. If you’re wondering about Controller.js, we will create it next.</p><p>Besides the application’s JavaScript files, we have included a reference to the TestHelper.js file. This file will help us create the dummy notes that the application will render.</p><p>Notice also, in the head section, a placeholder for the mobileinit event handler, which we will add later. The mobileinit event is fired on the document object when jQuery Mobile starts to execute. We can bind to this event when we want to run initialization code in our application, or when we need to apply overrides to jQuery Mobile&#8217;s defaults.</p><p>Let’s focus on the body section of the file now. Here we’ve created a single jQuery Mobile page with a header and a content section. This will be the Notes List page, the main page of the application. <img class="aligncenter size-full wp-image-1777" style="margin-top: 15px; margin-bottom: 15px;" title="notes-list-mockup-actual-1" src="http://miamicoder.com/wp-content/uploads/2011/11/notes-list-mockup-actual-1.png" alt="" width="487" height="265" />The header of this page already contains the New button, which will allow our users to create a new note. We will work on this button in the next chapter of this series.</p><p>We will use the content section of the Notes List page, adorned with the data-role=”content” attribute, to render the cached notes. This section is empty now, but in a few minutes we will write the code that inserts the list of cached notes into it.</p><p>To finish with the index.html file, let’s create the mobileinit event handler like so:</p><pre class="brush: xml; title: ; notranslate">
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
&lt;link href=&quot;../../lib/jqm/jquery.mobile-1.0.min.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; /&gt;
&lt;link href=&quot;css/app.css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; /&gt;
&lt;script src=&quot;../../lib/jqm/jquery-1.6.4.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;../../lib/jstorage/jstorage.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;app/DataContext.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;app/Controller.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;app/NoteModel.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;spec/TestHelper.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot;&gt;

$(document).bind(&quot;mobileinit&quot;, function () {

//Notes.testHelper.createDummyNotes();

Notes.controller.init();

});

&lt;/script&gt;
&lt;script src=&quot;../../Lib/jqm/jquery.mobile-1.0.min.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
</pre><p>Notice that the event handler is defined right before the reference to the jQuery Mobile library. As the <a href="http://jquerymobile.com/test/docs/api/globalconfig.html" target="_blank">mobileinit event’s documentation</a> explains, this event is triggered immediately upon execution, which means that any handlers for it should be bound before jQuery Mobile is loaded.</p><p>The handler executes a couple of function calls. One is the call to the createDummyNotes() function in the Test Helper module, with which we’re already familiar. The second call is to the init() function of a controller variable that we have not created yet.</p><p>Having observed the modular approach we’re following, as well as the naming conventions for the modules, you can probably see where we’re going with this. In effect, we’ve been building our app in a <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller" target="_blank">Model-View-Controller</a> fashion, where the NoteModel and Data Context modules function as the model, and the jQuery Mobile pages function as the views. We will build the controller next.</p><h3>Creating the Controller</h3><p>Our controller will respond to user input, and will change the models and views accordingly. The application’s controller goes in a new file that we will name Controller.js. Let’s define an empty controller module like so:</p><pre class="brush: jscript; title: ; notranslate">
Notes.controller = (function ($, dataContext) {

})(jQuery, Notes.dataContext);
</pre><p>Observe how we’re passing to the controller a reference to the jQuery object, along with a reference to the app’s data context. This will give the controller the ability to respond to user input, and change the model and views as needed.</p><p>For now, all we need our controller to do is render the list of cached notes. We already established that the controller will have an initialization function, init(), which will be triggered when the document’s mobileinit event is fired. Let’s create the init() function in the controller module like so:</p><pre class="brush: jscript; title: ; notranslate">
Notes.controller = (function ($, dataContext) {

    function init() {

        dataContext.init();
        var d = $(document);
        d.bind(&quot;pagechange&quot;, onPageChange);

    }

    return {

        init: init
    }

})(jQuery, Notes.dataContext);
</pre><p>The init() function in the controller first triggers the data context’s init() function. This is understandable, as we need the data context fully initialized before we can use it.</p><p>You would expect that the next step in the controller’s init() function would be to render the cached notes. That is not wrong, but it would only work for rendering the cached notes upon application initialization. Instead, we’re doing something more useful. We’re binding to jQuery Mobile’s <a href="http://jquerymobile.com/demos/1.0/docs/pages/page-scripting.html" target="_blank">pagechange event</a>. We will use this event to render the cached notes not only when the application starts, but also when the user transitions from the Note Editor page, which we haven’t created yet, to the Notes List page.</p><p>Let’s add the onPageChange() handler function to the controller:</p><pre class="brush: jscript; title: ; notranslate">
Notes.controller = (function ($, dataContext) {

    var notesListSelector = &quot;#notes-list-content&quot;;
var noNotesCachedMsg = &quot;&lt;/pre&gt;
&lt;div&gt;No notes cached&lt;/div&gt;
&lt;pre&gt;&quot;;
    var notesListPageId = &quot;notes-list-page&quot;;
    var currentNote = null;

    function init() {

        dataContext.init();
        var d = $(document);
        d.bind(&quot;pagechange&quot;, onPageChange);

    }

    function onPageChange(event, data) {

        var toPageId = data.toPage.attr(&quot;id&quot;);

        switch (toPageId) {
            case notesListPageId:

                renderNotesList();
                break;
        }
    }

    return {

        init: init
    }

})(jQuery, Notes.dataContext);
</pre><p>This handler is relatively simple. It inspects the id attribute of the page we’re transitioning to, available through the data.toPage property, and takes action based on the value of this property. If the id matches that of the Notes List page, the handler invokes the private function renderNotesList(), which we will define next.</p><p>The renderNotesList() function will ask the app’s data context for the list of cached notes, and it will render it within the Notes List page. Let’s create this function like so:</p><pre class="brush: jscript; title: ; notranslate">
function renderNotesList() {

var notesList = dataContext.getNotesList();
var view = $(notesListSelector);

view.empty();

if (notesList.length === 0) {

$(noNotesCachedMsg).appendTo(view);
} else {

var notesCount = notesList.length;
var note;
var ul = $(&quot;&lt;ul id=\&quot;notes-list\&quot; data-role=\&quot;listview\&quot;&gt;&lt;/ul&gt;&quot;).appendTo(view);
for (var i = 0; i &lt; notesCount; i++) {
note = notesList[i];
$(&quot;&lt;li&gt;&quot;
+ &quot;&lt;a data-url=\&quot;index.html#note-editor-page?noteId=&quot; + note.id + &quot;\&quot; href=\&quot;index.html#note-editor-page?noteId=&quot; + note.id + &quot;\&quot;&gt;&quot;
+ &quot;&lt;div&gt;&quot; + note.title + &quot;&lt;/div&gt;&quot;
+ &quot;&lt;div class=\&quot;list-item-narrative\&quot;&gt;&quot; + note.narrative + &quot;&lt;/div&gt;&quot;
+ &quot;&lt;/a&gt;&quot;
+ &quot;&lt;/li&gt;&quot;).appendTo(ul);
}

ul.listview();
}
};
</pre><p>After obtaining the cached notes from the data context, the renderNotesList() function empties the content section of the Notes List page. It then proceeds to either add a “No notes cached” message to the page if the cached notes array is empty, or create an html list with a list item for each cached note.</p><p>Observe that after adding the html list to the page, we need to call the jQuery Mobile <a href="http://jquerymobile.com/test/docs/lists/docs-lists.html" target="_blank">listview()</a> method on the list. This will apply jQuery Mobile’s list enhancements to the list.</p><p>Notice as well that each list item uses the list-item-narrative css class. We will define this class in the app.cc file, which we will place in the css folder:</p><p><img class="aligncenter size-full wp-image-1746" style="margin-top: 15px; margin-bottom: 15px;" title="app-folders-7" src="http://miamicoder.com/wp-content/uploads/2011/11/app-folders-7.png" alt="" width="118" height="87" /></p><p>This is the class definition:</p><pre class="brush: css; title: ; notranslate">
.list-item-narrative
{
	color: #666666;
	font-weight: normal;
}
</pre><p>With the index.html file and the controller module in place, we are ready to check how we did. Let’s open the index.html file in our favorite browser and see what happens. If everything went well, we should see something like this:</p><p><img class="aligncenter size-full wp-image-1752" style="margin-top: 15px; margin-bottom: 15px; border-width: 1px; border-color: black; border-style: solid;" title="notes-list-dummy-notes-1" src="http://miamicoder.com/wp-content/uploads/2011/11/notes-list-dummy-notes-1.png" alt="" width="329" height="412" /></p><p>What do you think?</p><h3>Next Steps</h3><p>In the next chapter of this series we will work on the features that will allow our users to create, edit and delete notes.</p><h3>Downloads</h3><p>Download the source code for this article: <a href="http://miamicoder.com/wp-content/uploads/2011/11/Building-a-jQuery-Mobile-App-Part-2-Src.zip">Building a jQuery Mobile App Part 2 Src.zip</a></p><h3>The Entire Series</h3><ul><li><a href="http://miamicoder.com/2012/building-a-jquery-mobile-application-part-4/">Building a jQuery Mobile Application, Part 4</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-3/">Building a jQuery Mobile Application, Part 3</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-2/">Building a jQuery Mobile Application, Part 2</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/">Building a jQuery Mobile Application, Part 1</a></li></ul> <div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=tr32OaXTkiY:uVRw0F2t5TE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=tr32OaXTkiY:uVRw0F2t5TE:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=tr32OaXTkiY:uVRw0F2t5TE:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=tr32OaXTkiY:uVRw0F2t5TE:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=tr32OaXTkiY:uVRw0F2t5TE:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=tr32OaXTkiY:uVRw0F2t5TE:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/tr32OaXTkiY" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/WlpQYuqRcTyF32KlRjKOloaM2Qk/0/da"><img src="http://feedads.g.doubleclick.net/~a/WlpQYuqRcTyF32KlRjKOloaM2Qk/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/WlpQYuqRcTyF32KlRjKOloaM2Qk/1/da"><img src="http://feedads.g.doubleclick.net/~a/WlpQYuqRcTyF32KlRjKOloaM2Qk/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=PunTgV3DttM:uVRw0F2t5TE:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=PunTgV3DttM:uVRw0F2t5TE:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=PunTgV3DttM:uVRw0F2t5TE:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=PunTgV3DttM:uVRw0F2t5TE:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=PunTgV3DttM:uVRw0F2t5TE:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=PunTgV3DttM:uVRw0F2t5TE:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=PunTgV3DttM:uVRw0F2t5TE:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=PunTgV3DttM:uVRw0F2t5TE:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/PunTgV3DttM" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-2/feed/</wfw:commentRss> <slash:comments>11</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-2/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/tr32OaXTkiY/</feedburner:origLink></item> <item><title>Building a jQuery Mobile Application, Part 1</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/KJQ8-lPzkzA/</link> <comments>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/#comments</comments> <pubDate>Mon, 07 Nov 2011 15:20:35 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[jQuery Mobile Tutorials]]></category> <category><![CDATA[Jasmine Tutorial]]></category> <category><![CDATA[JQuery Mobile Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1616</guid> <description><![CDATA[This is the first part of a series of articles of how to create a mobile web app using JQuery Mobile. We will write our business logic using JavaScript modules and classes, and we will use the JQuery Mobile framework to help us create our presentation layer. We will build the app following a behavior-driven approach with the help of the Jasmine framework. For each use case, we will create a specification using Jasmine, and then we will implement the specification in the application.]]></description> <content:encoded><![CDATA[<p>This is the first part of a series of articles about how to create a mobile app using jQuery Mobile. As in my <a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">Sencha Touch tutorials</a>, what we will do in this series is create a mobile web application that allows its users to take notes and save them in the device running the app.</p><p>We will engineer our business logic using JavaScript modules and classes, and we will use the jQuery Mobile framework to help us create our presentation layer. We will build the app following a behavior-driven approach with the help of the <a href="https://github.com/pivotal/jasmine/wiki" target="_blank">Jasmine framework</a>. For each use case, we will create a specification using Jasmine, then we will implement the specification in the application.</p><p>In this first part of the tutorial, we are going to talk about the overall design of the application, and we will complete the following tasks:</p><ul><li>Define the features of the application</li><li>Create low fidelity user interface mock-ups</li><li>Begin creating Jasmine specifications for the app’s public interface</li><li>Begin implementing the app’s public interface</li></ul><p>Let’s get started.</p><h3>Application Features</h3><p>The application has a simple feature set. We want to give our customers the following abilities:</p><ul><li>Create notes.</li><li>Edit notes.</li><li>Delete notes.</li><li>Store notes on the device that is running the application, across browser sessions.</li><li>View the entire collection of notes.</li></ul><h3>The Main Views</h3><p>The first thing we need in the app is an interface for our users to create and edit notes. We can do this with a form, which we will call Note Editor. The form will look just like this mock-up, which I created using <a href="http://balsamiq.com/" target="_blank">Balsamiq Mockups</a>:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1633" style="margin-top: 15px; margin-bottom: 15px;" title="note-editor-mockup" src="http://miamicoder.com/wp-content/uploads/2011/11/note-editor-mockup.png" alt="" width="242" height="285" /></p><p>In addition, need a view that renders a list of the existing notes. The Notes List view will be the main view of the application. Our users will first see this view when they launch the app. This is how the Notes List should look:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1635" style="margin-top: 15px; margin-bottom: 15px;" title="notes-list-mockup" src="http://miamicoder.com/wp-content/uploads/2011/11/notes-list-mockup.png" alt="" width="242" height="285" /></p><p>We will connect the Notes List with the Note Editor in such a way that the Notes List will correctly reflect note additions, edits, and deletions.</p><p>OK. We defined the features and look of the app, which brings us right into the realm of specifications and testing.</p><p><span class="Apple-style-span" style="font-size: 15px; font-weight: bold;"><span id="more-1616"></span>Behavior-Driven Development and the Jasmine Framework</span></p><p>As I mentioned earlier, we will build the application in a modular fashion. This will allow us to define and test the business logic independently of the presentation layer. Our work on the business logic will start with a brief introduction to behavior-driven development with the <a href="https://github.com/pivotal/jasmine/wiki" target="_blank">Jasmine framework</a>.</p><p>Jasmine is a framework that allows you to test your JavaScript code in a behavior-driven way. What this means is that you can write tests with Jasmine in a natural language that describes the purpose and benefit of the code under test. As a result, it will be easy for you as developer, and other stakeholders, to focus on the reason and purpose of the code and not the technical details.</p><p>I&#8217;m going to pause here for a quick note on testing frameworks. Some people go to war over what testing framework or method you use. I&#8217;m not one of them. And I happen to think that Jasmine is a good tool for the job at hand, as are many other tools.</p><p>This is an example of a test we will use later in this series that will help you understand how Jasmine works:</p><pre class="brush: jscript; title: ; notranslate">
describe(&quot;Notes functions&quot;, function () {

    it(&quot;Should return a NoteModel instance&quot;, function () {

        var note = Notes.app.createNote();

        expect(note instanceof Notes.model.NoteModel).toBeTruthy();
    });
});
</pre><p>We are using various Jasmine features in this example. One is the function describe(), which we use to create a container for your specifications. In Jasmine, these containers are called &#8220;suites&#8221;.</p><p>The concept of a specification, or “spec”, is implemented with the function it(). When you call it(), you pass a string describing the specification, as well as a function that defines a test for the specification. In the example, we’re expressing that our code should be able to create and return a NoteModel instance.</p><p>The third feature we’re using is the expect() function, which expresses an expectation about the behavior of the application. The call to expect() will include the use of <a href="https://github.com/pivotal/jasmine/wiki/Matchers" target="_blank">expectation matchers</a>. In the example we use the toBeThruthy() matcher to express that our test will pass if the results are true.</p><p>Within the it() function, you will generally write the code needed to set up your test, as well as one or more calls to expect(), which will define your expectations.</p><h3>Getting Ready to Use Jasmine</h3><p>We need to take care of creating our application’s folders before we can use Jasmine. We will create a main folder for the application, which we will name &#8220;NotesApp&#8221;. In this folder, we will create an &#8220;app&#8221; directory where we will place the business logic and presentation files, and a &#8220;spec&#8221; directory, where we will place the Jasmine test suites. In addition, we will create a &#8220;Lib&#8221; directory, which will contain folders for the libraries our application will use.</p><p>The folders should look as depicted below:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1608" style="margin-top: 15px; margin-bottom: 15px;" title="app-folders-1" src="http://miamicoder.com/wp-content/uploads/2011/11/app-folders-1.png" alt="" width="118" height="158" /></p><p>If you’re curious about the &#8220;jqm&#8221; and &#8220;jstorage&#8221; folders, these are the containers where we will place the jQuery Mobile and JStorage frameworks. Don’t worry about them now, we will review them later.</p><p>Now we can <a href="http://pivotal.github.com/jasmine/" target="_blank">download Jasmine</a>, and place its files in the &#8220;jasmine&#8221; directory:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1615" style="margin-top: 15px; margin-bottom: 15px;" title="jasmine-files" src="http://miamicoder.com/wp-content/uploads/2011/11/jasmine-files.png" alt="" width="221" height="178" /></p><p>Let’s go ahead and create our first source files. One for the application, app.js, and one for the Jasmine test suites, AppSpec.js:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1609" style="margin-top: 15px; margin-bottom: 15px;" title="app-folders-2" src="http://miamicoder.com/wp-content/uploads/2011/11/app-folders-2.png" alt="" width="175" height="110" /></p><p>Finally, let’s create the specrunner.html file. This is the html file that will run our Jasmine test suites. The file is included in the Jasmine download, and we will need to change it so it has references to the libraries used by the application under test.</p><p>Here’s the source for the head and body sections of our specrunner.html file:</p><pre class="brush: xml; title: ; notranslate">
   &lt;!-- HEAD --&gt;
   &lt;!-- Jasmine includes --&gt;
    css&quot; rel=&quot;stylesheet&quot; type=&quot;text/css&quot; /&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;../lib/jasmine/jasmine.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;../lib/jasmine/jasmine-html.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;!-- Source files --&gt;
    &lt;script src=&quot;app/App.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;!-- Spec files --&gt;
    &lt;script src=&quot;spec/AppSpec.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
</pre><pre class="brush: xml; title: ; notranslate">
   &lt;!-- BODY --&gt;
       &lt;script type=&quot;text/javascript&quot;&gt;

        jasmine.getEnv().addReporter(new jasmine.TrivialReporter());

        jasmine.getEnv().execute();

    &lt;/script&gt;
</pre><p>Note how the file has references to the Jasmine framework, as well as our application and specifications files.</p><p>Let’s place the specrunner.html file in the &#8220;NotesApp&#8221; directory, as depicted below:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1610" style="margin-top: 15px; margin-bottom: 15px;" title="app-folders-3" src="http://miamicoder.com/wp-content/uploads/2011/11/app-folders-3.png" alt="" width="175" height="134" /></p><p>OK, our main files are in place and it&#8217;s time to start building the application. Let’s do it.</p><h3></h3><h3>Mapping Application Features to Business Logic Specifications</h3><p>The first thing we want is making sure the application’s namespace exists. We can define this specification in the AppSpec.js file:</p><pre class="brush: jscript; title: ; notranslate">
describe(&quot;Public interface exists&quot;, function () {

    it(&quot;Defines the app&quot;, function () {
        expect(Notes.app).toBeDefined();
    });

});
</pre><p>Note that we’ve placed this spec inside a suite named &#8220;Public interface exists&#8221;. This suite will contain all the specs that test that the public functions of the business logic exist. The spec itself is self-explanatory, asserting the expectation that the application’s namespace is defined.</p><p>Let’s run the test and see what happens. If we open the specrunner.html file in our favorite browser and check the output of the test, we should see something like this:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1611" style="margin-top: 15px; margin-bottom: 15px;" title="it-fails-1" src="http://miamicoder.com/wp-content/uploads/2011/11/it-fails-1.png" alt="" width="473" height="292" /></p><p>Our expectation of a defined application namespace just failed. This is OK because we haven’t defined the namespace yet. Let’s define it in the app.js file like so:</p><pre class="brush: jscript; title: ; notranslate">
var Notes = Notes || {}
Notes.app = (function () {

    return {}

})();
</pre><p>If we run the test now, it should pass:</p><p><img class="aligncenter size-full wp-image-1613" title="it-ok-1" src="http://miamicoder.com/wp-content/uploads/2011/11/it-ok-1.png" alt="" width="475" height="87" /></p><p>OK. Simple, but good enough for you to see the rhythm that we want to establish here: Define behavior, test, implement behavior, and test again. Let’s move on to more interesting behaviors.</p><p>Next, we want to define the ability to render the list of notes cached on the device. For this to occur, our business logic should be able to pass a list of notes to the presentation layer. We will define this behavior with a couple of Jasmine specifications.</p><p>The first specification looks like this:</p><pre class="brush: jscript; title: ; notranslate">
describe(&quot;Public interface exists&quot;, function () {
    // App namespace test omitted...
    //
    //
    it(&quot;Should have public interface to return notes list&quot;, function () {
        expect(Notes.app.getNotesList).toBeDefined();
    });

});
</pre><p>This spec is also self-explanatory, asserting the expectation that the business logic defines a getNotesList() function.</p><p>The second specification is a little more detailed:</p><pre class="brush: jscript; title: ; notranslate">
describe(&quot;Public interface implementation&quot;, function () {

    it(&quot;Should return notes list&quot;, function () {

        var notesList = Notes.app.getNotesList();

        expect(notesList instanceof Array).toBeTruthy();
    });
});
</pre><p>This spec goes in a second suite, called &#8220;Public interface implementation&#8221;, where we will place specifications related to the actual implementation of the business logic’s behavior. The spec asserts that the getNotesList() function should return an Array class instance.</p><p>If we refresh specrunner.html in the browser, the tests should fail:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1612" style="margin-top: 15px; margin-bottom: 15px;" title="it-fails-2" src="http://miamicoder.com/wp-content/uploads/2011/11/it-fails-2.png" alt="" width="472" height="517" /></p><p>The tests failed because we’re missing the implementation of the behaviors we’re defining. Let’s go ahead and add the implementation like so:</p><pre class="brush: jscript; title: ; notranslate">
Notes.app = (function () {

    var notesList = [];

    function getNotesList() {
        return notesList;
    }

    return {

        getNotesList: getNotesList

    }

})();
</pre><p>Now the tests should pass:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1614" style="margin-top: 15px; margin-bottom: 15px;" title="it-ok-2" src="http://miamicoder.com/wp-content/uploads/2011/11/it-ok-2.png" alt="" width="472" height="270" /></p><p>Are you with me so far? Don’t tell me you’re not liking this. <img src='http://miamicoder.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /></p><h3>Next Steps</h3><p>In the next chapter of this series we will continue implementing the business logic of the application.</p><p>Did you notice we didn’t do anything with jQuery Mobile in this chapter? That’s OK. We want to have our business logic well implemented and separated from the presentation layer. Later we will have plenty of time to take care of the user interface.</p><p>Stay tuned!</p><h3>The Entire Series</h3><ul><li><a href="http://miamicoder.com/2012/building-a-jquery-mobile-application-part-4/">Building a jQuery Mobile Application, Part 4</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-3/">Building a jQuery Mobile Application, Part 3</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-2/">Building a jQuery Mobile Application, Part 2</a></li><li><a href="http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/">Building a jQuery Mobile Application, Part 1</a></li></ul><h3>Source Code</h3><p>Here&#8217;s the source code for this article:</p><p>AppSpec.js</p><pre class="brush: jscript; title: ; notranslate">
describe(&quot;Notes public interface exists&quot;, function () {

    it(&quot;Defines the app&quot;, function () {
        expect(Notes.app).toBeDefined();
    });

    it(&quot;Has public function: getNotesList&quot;, function () {
        expect(Notes.app.getNotesList).toBeDefined();
    });

});

describe(&quot;Notes functions&quot;, function () {

    it(&quot;Returns a list of notes&quot;, function () {

        var notesList = Notes.app.getNotesList();

        expect(notesList instanceof Array).toBeTruthy();
    });
});
</pre><p>app.js</p><pre class="brush: jscript; title: ; notranslate">
var Notes = Notes || {}
Notes.model = Notes.model || {}

Notes.app = (function () {

    var notesList = [];

    function getNotesList() {
        return notesList;
    }

    return {

        getNotesList: getNotesList

    }

})();
</pre><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=idYtLw0Uj64:l927TrDH2DI:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=idYtLw0Uj64:l927TrDH2DI:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=idYtLw0Uj64:l927TrDH2DI:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=idYtLw0Uj64:l927TrDH2DI:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=idYtLw0Uj64:l927TrDH2DI:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=idYtLw0Uj64:l927TrDH2DI:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/idYtLw0Uj64" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/R8Hq72qBMd7i5LgMWed8O7Vhd48/0/da"><img src="http://feedads.g.doubleclick.net/~a/R8Hq72qBMd7i5LgMWed8O7Vhd48/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/R8Hq72qBMd7i5LgMWed8O7Vhd48/1/da"><img src="http://feedads.g.doubleclick.net/~a/R8Hq72qBMd7i5LgMWed8O7Vhd48/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=KJQ8-lPzkzA:l927TrDH2DI:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=KJQ8-lPzkzA:l927TrDH2DI:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=KJQ8-lPzkzA:l927TrDH2DI:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=KJQ8-lPzkzA:l927TrDH2DI:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=KJQ8-lPzkzA:l927TrDH2DI:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=KJQ8-lPzkzA:l927TrDH2DI:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=KJQ8-lPzkzA:l927TrDH2DI:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=KJQ8-lPzkzA:l927TrDH2DI:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/KJQ8-lPzkzA" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/feed/</wfw:commentRss> <slash:comments>5</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/building-a-jquery-mobile-application-part-1/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/idYtLw0Uj64/</feedburner:origLink></item> <item><title>New eBook – Building a Sencha Touch Application</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/d7IKSImJ1Bw/</link> <comments>http://miamicoder.com/2011/ebook-building-a-sencha-touch-application/#comments</comments> <pubDate>Sat, 22 Oct 2011 16:10:02 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[Books]]></category> <category><![CDATA[Sencha Touch Tutorials]]></category> <category><![CDATA[Sencha Touch Book]]></category> <category><![CDATA[Sencha Touch Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1540</guid> <description><![CDATA[You can build a Sencha Touch application in minutes. My new book "Building a Sencha Touch Application" will teach you how to create a Sencha Touch app from scratch. Check it out!]]></description> <content:encoded><![CDATA[<div style="width: 213px; display: inline; float: left; padding-top: 5px; padding-right: 10px; padding-bottom: 0px; padding-left: 0px;"><p><img class="alignleft size-full wp-image-1488" style="display: inline; border: 1px solid #333333;" title="Book: Building a Sencha Touch Application " src="http://miamicoder.com/wp-content/uploads/2011/10/building-a-sencha-touch-app-ebook-cover-small1.png" alt="Book: Building a Sencha Touch Application" width="200" height="259" /></p></div><p>I just published the <a href="http://miamicoder.com/build-a-sencha-touch-app-in-minutes/">Building a Sencha Touch Application eBook</a>. The book will teach you how to create a Sencha Touch 1.1.1 application that allows its users to take notes and store them on the device running the app.</p><p>You can preview the book using this link: <a href="http://miamicoder.com/wp-content/uploads/2011/10/Preview-Ebook-Building-a-Sencha-Touch-Application.pdf" target="_blank">Preview Ebook &#8211; Building a Sencha Touch Application</a>.</p><p>I would like to thank <a href="http://twitter.com/#!/buffalobillion" target="_blank">Ted Jenkins</a>,&nbsp;<a href="http://twitter.com/#!/AlexBlount" target="_blank">Alex Blount</a>, <a href="http://twitter.com/#!/epiphanydigital" target="_blank">James Brooks</a>&nbsp;and&nbsp;<a href="http://twitter.com/#!/hectoriribarne" target="_blank">Hector Iribarne</a> for their feedback and help creating this book. Thank you, Ted, Alex, James and Hector.</p><p>I would also like to hear what you think about the book and how I can make the next version better. Please leave a comment if you have a few minutes.</p> <div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=7GVGqEHqxN4:_u3Zd_zKYE0:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=7GVGqEHqxN4:_u3Zd_zKYE0:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=7GVGqEHqxN4:_u3Zd_zKYE0:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=7GVGqEHqxN4:_u3Zd_zKYE0:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=7GVGqEHqxN4:_u3Zd_zKYE0:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=7GVGqEHqxN4:_u3Zd_zKYE0:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/7GVGqEHqxN4" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/1VZYxeuvKf8kdG-w6rB1tZDI1gc/0/da"><img src="http://feedads.g.doubleclick.net/~a/1VZYxeuvKf8kdG-w6rB1tZDI1gc/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/1VZYxeuvKf8kdG-w6rB1tZDI1gc/1/da"><img src="http://feedads.g.doubleclick.net/~a/1VZYxeuvKf8kdG-w6rB1tZDI1gc/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=d7IKSImJ1Bw:_u3Zd_zKYE0:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=d7IKSImJ1Bw:_u3Zd_zKYE0:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=d7IKSImJ1Bw:_u3Zd_zKYE0:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=d7IKSImJ1Bw:_u3Zd_zKYE0:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=d7IKSImJ1Bw:_u3Zd_zKYE0:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=d7IKSImJ1Bw:_u3Zd_zKYE0:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=d7IKSImJ1Bw:_u3Zd_zKYE0:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=d7IKSImJ1Bw:_u3Zd_zKYE0:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/d7IKSImJ1Bw" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/ebook-building-a-sencha-touch-application/feed/</wfw:commentRss> <slash:comments>20</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/ebook-building-a-sencha-touch-application/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/7GVGqEHqxN4/</feedburner:origLink></item> <item><title>Writing a Sencha Touch MVC Application</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/bN7J5E_X4sI/</link> <comments>http://miamicoder.com/2011/writing-a-sencha-touch-mvc-application/#comments</comments> <pubDate>Wed, 10 Aug 2011 23:56:33 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[Sencha Touch Tutorials]]></category> <category><![CDATA[Tutorials]]></category> <category><![CDATA[Sencha Touch Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1372</guid> <description><![CDATA[Curious about Sencha Touch MVC? In this article we will explore one of the approaches we can take to create a Sencha Touch application using the MVC pattern.]]></description> <content:encoded><![CDATA[<p>In this article we will explore one of the approaches we can take to create a Sencha Touch application using the MVC pattern. I will base the tutorial on the Notes Application we created in the <a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">Writing a Sencha Touch Application</a> series. Our goal is to build a new version of the Notes Application using Sencha Touch MVC.</p><p>The Notes Application allows its users to take notes and store them on the device running the app. The features we built into the app, which we will reproduce in the MVC version, are the following:</p><ul><li>Ability to create notes</li><li>Ability to edit existing notes</li><li>Ability to delete notes</li><li>Ability to persist notes on the device running the application, across browser sessions</li></ul><p>In terms of user interface, the application revolves around two views, the <strong>Notes List</strong> view and the <strong>Note Editor</strong> view:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1330" style="margin-top: 15px; margin-bottom: 15px;" title="navigation-disclosure-button" src="http://miamicoder.com/wp-content/uploads/2011/06/navigation-disclosure-button.png" alt="" width="500" height="241" /></p><p>Ready? Let’s get started.</p><h3>The model-view-controller pattern in Sencha Touch</h3><p>As of version 1.1.0 of Sencha Touch, the framework’s implementation of the <a href="http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller">model-view-controller</a> pattern is, in my opinion, incomplete and poorly documented. However, we can still take advantage of it to build high-quality applications.</p><p>In general, a Sencha Touch MVC application will consist of one or more views, one or more data models and stores, and one or more controllers. Views have a dual role: they render representations of the data in the models, and capture and transmit user input to the controllers. Controllers will translate the user’s input into changes in the data and/or the behavior of the application. Models represent the application’s data and state.</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1364" style="margin-top: 15px; margin-bottom: 15px;" title="mvc-blocks" src="http://miamicoder.com/wp-content/uploads/2011/08/mvc-blocks.png" alt="" width="373" height="168" /></p><p>In Sencha Touch MVC parlance, the mechanism by which views send messages to the controllers is called <strong>dispatch</strong>, and the controller functions used to handle these messages are called <strong>actions</strong>.</p><p>Let’s apply these concepts to our Notes Application and come up with a tentative design.</p><h3>The Notes App use cases, MVC style</h3><p>Based on our requirements list, we can translate our use cases into the following MVC workflows:</p><ol><li>When the application launches, the user will be presented with a list of the existing notes, or an empty list if there are no notes saved. This will happen by invoking a controller’s <strong>index</strong> action, which in turn will render the <strong>Notes List</strong> view:<img class="aligncenter size-full wp-image-1369" style="margin-top: 0px; margin-bottom: 25px;" title="use-case-index-action" src="http://miamicoder.com/wp-content/uploads/2011/08/use-case-index-action.png" alt="" width="531" height="213" /></li><li>When our user needs to create a new note, she will tap the <strong>New</strong> button in the <strong>Notes List</strong> view. This will invoke the <strong>newnote</strong> action in a controller, where a new note will be created and loaded into the <strong>Note Editor </strong>view:<img class="aligncenter size-full wp-image-1370" style="margin-top: 0px; margin-bottom: 25px;" title="use-case-newnote-action" src="http://miamicoder.com/wp-content/uploads/2011/08/use-case-newnote-action.png" alt="" width="541" height="220" /></li><li>Similarly, when our user needs to edit a note, she will tap the <strong>disclosure</strong> button for the given note. This will invoke the <strong>editnote</strong> action in a controller, causing the note to be loaded into the <strong>Note Editor </strong>view:<img class="aligncenter size-full wp-image-1368" style="margin-top: 0px; margin-bottom: 25px;" title="use-case-editnote-action" src="http://miamicoder.com/wp-content/uploads/2011/08/use-case-editnote-action.png" alt="" width="540" height="226" /></li><li>Tapping the <strong>Save</strong> button in the <strong>Note Editor</strong> view will invoke a controller’s <strong>savenote</strong> action, where the note will be saved in notes cache and the <strong>Notes List</strong> view will be activated:<br /> <img class="aligncenter size-full wp-image-1371" style="margin-top: 0px; margin-bottom: 25px;" title="use-case-savenote-action" src="http://miamicoder.com/wp-content/uploads/2011/08/use-case-savenote-action.png" alt="" width="540" height="208" /></li><li>If our user taps the<strong> Trash</strong> button in the <strong>Note Editor</strong> view, the view will invoke a controller’s <strong>deletenote</strong> action. This action will delete the note loaded in the view and activate the <strong>Notes List</strong> view:<br /> <img class="aligncenter size-full wp-image-1367" style="margin-top: 0px; margin-bottom: 25px;" title="use-case-deletenote-action" src="http://miamicoder.com/wp-content/uploads/2011/08/use-case-deletenote-action.png" alt="" width="540" height="228" /></li><li>Tapping the <strong>Home</strong> button in the <strong>Note Editor</strong> view will invoke a controller’s <strong>canceledit</strong> action, which will discard any changes made to the loaded note, and activate the <strong>Notes List</strong> view:<br /> <img class="aligncenter size-full wp-image-1366" style="margin-top: 0px; margin-bottom: 25px;" title="use-case-canceledit-action" src="http://miamicoder.com/wp-content/uploads/2011/08/use-case-canceledit-action.png" alt="" width="540" height="204" /></li></ol><p>Notice that I have referred to “a controller” in my descriptions of the use cases. This means that we still don’t know if we will use one or many controllers in the application. We will make this decision in a few minutes.</p><p>All right. Now that we have an idea about how we’re going to build the Notes Application, MVC-style, let’s talk about files.</p><h3>Distributing a Sencha Touch application across multiple files</h3><p>You should consider distributing your Sencha Touch application’s source code across multiple files. A single source file might be fine for a very small application, however, as the application and the development team grow, you can cut development and maintenance costs by using a multiple-files model.</p><p>What we will do for this example is, first, create the right folder structure for the application, and then create the different source files that will contain the application’s components. The folder structure reflects the MVC pattern we will use to build the application:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1365" style="margin-top: 15px; margin-bottom: 15px;" title="notes-app-mvc-folders" src="http://miamicoder.com/wp-content/uploads/2011/08/notes-app-mvc-folders.png" alt="" width="159" height="199" /></p><p>We have a root folder called app, and under it, folders for the models, views, controllers and data stores. Besides being self-explanatory, it follows Sencha&#8217;s recommendation to place models, views and controllers in separate folders. Sencha’s build tools are capable of leveraging this structure to create the application’s namespaces when it’s time to build a production-ready version of the source files.</p><p>At this point we have modeled our MVC workflows and created the right folder structure. What do you say about writing some code?</p><h3>The Note data model</h3><p>The Note model is the data model that represents a note. Its source goes in the NoteModel.js file, which we will place in the models folder. This is the source:</p><pre class="brush: jscript; title: ; notranslate">
Ext.regModel('NoteModel', {
    idProperty: 'id',
    fields: [
        { name: 'id', type: 'int' },
        { name: 'date', type: 'date', dateFormat: 'c' },
        { name: 'title', type: 'string' },
        { name: 'narrative', type: 'string' }
    ],
    validations: [
        { type: 'presence', field: 'id' },
        { type: 'presence', field: 'title', message: 'Please enter a title for this note.' }
    ]
});
</pre><h3>The Notes data store</h3><p>The <strong>NotesStore</strong> is our notes cache. It uses the <strong>NoteModel</strong> class as its model and it’s configured to use an instance of the <a href="http://dev.sencha.com/deploy/touch/docs/?class=Ext.data.LocalStorageProxy">Ext.data.LocalStorageProxy</a> class as its proxy. This allows us to save notes across browser sessions. We will place the <strong>NotesStore</strong> class in the NotesStore.js file, which lives in the <strong>stores</strong> folder. This is the store’s source:</p><pre class="brush: jscript; title: ; notranslate">
Ext.regStore('NotesStore', {
    model: 'NoteModel',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-store'
    },
    getGroupString: function (record) {
        if (record &amp;&amp; record.data.date) {
            return record.get('date').toDateString();
        } else {
            return '';
        }
    }
});

NotesApp.stores.notesStore = Ext.StoreMgr.get('NotesStore');
</pre><h3>The Main view</h3><p>As in the <a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">first version of the application</a>, the application’s main view will serve as the viewport, hosting the <strong>Notes List</strong> view and the <strong>Note Editor</strong> view. This view’s class goes in the MainView.js file, which we will place in the <strong>views</strong> folder:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.MainView = Ext.extend(Ext.Panel, {
    fullscreen: true,
    layout: 'card',
    cardSwitchAnimation: 'slide',
    initComponent: function () {

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

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

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

        this.on('render', function () {
            NotesApp.stores.notesStore.load();
        });
    }
});
</pre><h3>The Notes List view</h3><p>We will use an instance of the <strong>NotesListView</strong> class to render the list of cached notes and allow the user to start the “new note” and “edit note” workflows. Its file, NotesListView.js, goes in the <strong>views</strong> folder. This is the source:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.NotesListView = Ext.extend(Ext.Panel, {

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

    layout: 'fit',

    initComponent: function () {

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

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

        this.dockedItems = [this.topToolbar];

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

        });

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

        this.items = [this.notesList];

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

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

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

    refreshList: function () {
        this.notesList.refresh();
    }
});
</pre><p>Let’s pause here and examine a few details.</p><p>While the initComponent() function should be very familiar to you, things become interesting when we look at the helper functions onNewNote() and onEditNote(). These functions will allow us to signal the view’s controller that the user executed either the “new note” or “edit note” commands. The <strong>newnote</strong> or <strong>editnote</strong> controller actions will be invoked in response to these commands.</p><p>How does the view send these signals to the controller? Well, it’s pretty simple. Remember what I said earlier about <strong>dispatch</strong>? Every application gets a default instance of the <a href="http://dev.sencha.com/deploy/touch/docs/?class=Ext.Dispatcher">Dispatcher class</a>, which you can use to send requests to specific actions in a given controller. Invoking Ext.Dispatcher.dispatch() or its shorthand, Ext.dispatch(), will find the desired controller and call the correct controller action.</p><p>You should pass the <strong>controller</strong> and <strong>action</strong> arguments to the dispatch() function. And you can also pass any other parameters that the controller might need to execute the action correctly. For example, in the onEditNote() function we also pass the record corresponding to the note the user needs to edit.</p><p>The third function, refreshList(), will allow this view’s controller to re-render the list of notes in the view after the list has been modified.</p><p>OK, we already dispatched a couple of messages to a controller that does not exist. How about we create it?</p><h3>How many controllers does a Sencha Touch MVC application need?</h3><p>I don’t think they make rules for this. It depends on the application. I know that the Notes App is small: three simple views and six actions. It looks like one controller is enough for this one.</p><p>Let’s create the NotesController.js file in the controllers folder, and add the following code to it:</p><pre class="brush: jscript; title: ; notranslate">
Ext.regController('NotesController',{

    'index': function (options) {

    },

    'newnote': function (options) {

    },

    'editnote': function (options) {

    },

    'savenote': function (options) {

    },

    'deletenote': function (options) {

    },

    'canceledit': function (options) {

    }
});

NotesApp.controllers.notesController = Ext.ControllerManager.get('NotesController');
</pre><p>Starting to make more sense now? As you can see, the controller has the actions we have discussed. This is where we need to implement our use cases. But, before we do it, let’s work on the last view.</p><h3>The Note Editor view</h3><p>We will use an instance of the NoteEditorView class to give our users the ability to edit and delete notes. This class goes in the NoteEditorView.js file, in the <strong>views</strong> folder:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.NoteEditorView = Ext.extend(Ext.form.FormPanel, {

    initComponent: function () {

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

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

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

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

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

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

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

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

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

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

    items: [{
        xtype: 'textfield',
        name: 'title',
        label: 'Title',
        required: true
    }, {
        xtype: 'textareafield',
        name: 'narrative',
        label: 'Narrative'
    }]
});
</pre><p>The helper functions saveButtonTap(), trashButtonTap() and backButtonTab() will allow us to signal the controller that the “save note”, “delete note” or “cancel edit” commands were executed by the user. The application will run the <strong>savenote</strong>, <strong>deletenote</strong> or <strong>canceledit</strong> controller actions in response to these commands.</p><p>Now that our views are finished, let’s implement the controller actions.</p><h3>Implementing controller actions</h3><p>Back in the NotesController class, as we’ve already discussed, the <strong>index</strong> action will create the <strong>Main</strong> view and activate the <strong>Notes List</strong> view:</p><pre class="brush: jscript; title: ; notranslate">
'index': function (options) {

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

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView
    );
}
</pre><p>The <strong>index</strong> action will be invoked when the application launches. We will see how this is done when we implement the application’s launch() function.</p><p>The <strong>newnote</strong> controller action will create a new note, load it into the <strong>Note Editor</strong> view, and activate the view:</p><pre class="brush: jscript; title: ; notranslate">
'newnote': function (options) {

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

    NotesApp.views.noteEditorView.load(note);
    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.noteEditorView,
        { type: 'slide', direction: 'left' }
    );
}
</pre><p>We need the <strong>editnote</strong> action to load the selected note into the <strong>Note Editor</strong> view:</p><pre class="brush: jscript; title: ; notranslate">
'editnote': function (options) {

    NotesApp.views.noteEditorView.load(options.note);
    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.noteEditorView,
        { type: 'slide', direction: 'left' }
    );
}
</pre><p>The <strong>savenote</strong> action will save the note in the cache and activate the <strong>Notes List</strong> view:</p><pre class="brush: jscript; title: ; notranslate">
'savenote': function (options) {

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

    NotesApp.views.noteEditorView.updateRecord(currentNote);

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

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

    NotesApp.stores.notesStore.sync();

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

    NotesApp.views.notesListView.refreshList();

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView,
        { type: 'slide', direction: 'right' }
    );
}
</pre><p>Next comes the <strong>deletenote</strong> action. This action will delete the note loaded in the <strong>Note Editor</strong> and activate the <strong>Notes List</strong> view:</p><pre class="brush: jscript; title: ; notranslate">
'deletenote': function (options) {

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

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

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

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView,
        { type: 'slide', direction: 'right' }
    );
}
</pre><p>Finally, the controller’s <strong>canceledit</strong> action will discard any changes made to the loaded note, and activate the <strong>Notes List</strong> view:</p><pre class="brush: jscript; title: ; notranslate">
'canceledit': function (options) {

    NotesApp.views.mainView.setActiveItem(
        NotesApp.views.notesListView,
        { type: 'slide', direction: 'right' }
    );
}
</pre><p>You with me so far? We’re almost done.</p><p>Compared to the <a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">first version of the app</a>, we’ve moved most of the application’s behavior out of the views, which now deal strictly with UI matters, and into a central location. I can see the MVC version being easier to understand and support.</p><p>Now that we have our data store, model, views and controller in place, we’re just missing the piece that will get this whole mechanism going.</p><h3>Launching the application</h3><p>We will place our app’s entry point in the app.js file, <strong>app</strong> folder, together with the index.html and app.css files. We will use app.js to create our application’s instance like so:</p><pre class="brush: jscript; title: ; notranslate">
var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,

    launch: function () {
        Ext.dispatch({
            controller: NotesApp.controllers.notesController,
            action: 'index'
        });
    }
});
</pre><p>And that’s it. Makes sense?</p><p>As you can imagine, this is not the only approach you can take for incorporating the MVC pattern in a Sencha Touch app. You can go from rolling out your own implementation, to taking full advantage of the framework’s built-in features, which I think are still a work in progress. Either way, you should be able to reach a <a href="http://en.wikipedia.org/wiki/Satisficing">satisficing</a> solution that makes your application easier to develop and more maintainable.</p><p>What do you think?</p><p>Download this tutorial&#8217;s source: <a href="http://miamicoder.com/wp-content/uploads/2011/08/NotesApp-v1.zip">NotesApp-v1.zip</a></p> <div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=UR7DEqh8m48:CPApBxUtwMg:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=UR7DEqh8m48:CPApBxUtwMg:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=UR7DEqh8m48:CPApBxUtwMg:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=UR7DEqh8m48:CPApBxUtwMg:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=UR7DEqh8m48:CPApBxUtwMg:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=UR7DEqh8m48:CPApBxUtwMg:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/UR7DEqh8m48" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/W1moNgTjwbg0fN65sVmefEyeOgE/0/da"><img src="http://feedads.g.doubleclick.net/~a/W1moNgTjwbg0fN65sVmefEyeOgE/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/W1moNgTjwbg0fN65sVmefEyeOgE/1/da"><img src="http://feedads.g.doubleclick.net/~a/W1moNgTjwbg0fN65sVmefEyeOgE/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=bN7J5E_X4sI:CPApBxUtwMg:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=bN7J5E_X4sI:CPApBxUtwMg:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=bN7J5E_X4sI:CPApBxUtwMg:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=bN7J5E_X4sI:CPApBxUtwMg:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=bN7J5E_X4sI:CPApBxUtwMg:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=bN7J5E_X4sI:CPApBxUtwMg:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=bN7J5E_X4sI:CPApBxUtwMg:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=bN7J5E_X4sI:CPApBxUtwMg:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/bN7J5E_X4sI" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/writing-a-sencha-touch-mvc-application/feed/</wfw:commentRss> <slash:comments>61</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/writing-a-sencha-touch-mvc-application/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/UR7DEqh8m48/</feedburner:origLink></item> <item><title>Writing a Sencha Touch Application, Part 4</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/JgqcX8sSONA/</link> <comments>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-4/#comments</comments> <pubDate>Tue, 28 Jun 2011 00:51:09 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[Sencha Touch Tutorials]]></category> <category><![CDATA[Tutorials]]></category> <category><![CDATA[Sencha Touch Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1310</guid> <description><![CDATA[Our app is feature-complete! In this final chapter of the Sencha Touch application tutorial we will learn how to work with Sencha Touch lists and how to remove records from a data store.]]></description> <content:encoded><![CDATA[<p>This is the last of a four-part series on how to write a Sencha Touch application. If you’re new to the series, here are the links to the previous installments:</p><ul><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">Writing a Sencha Touch Application, Part 1</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-2/">Writing a Sencha Touch Application, Part 2</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-3/">Writing a Sencha Touch Application, Part 3</a></li></ul><p>In part 3 of this tutorial we worked on the Note Editor and added the ability to create notes. It is time now to let our users edit and delete notes. Let’s work on the edit note feature first.</p><h3>Disclosure events in a Sencha Touch List</h3><p>When the person using the application touches a note&#8217;s disclosure button in the Notes List view, the selected note should be displayed in the Edit Note view:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1330" style="margin-top: 15px; margin-bottom: 15px;" title="navigation-disclosure-button" src="http://miamicoder.com/wp-content/uploads/2011/06/navigation-disclosure-button.png" alt="" width="500" height="241" /></p><p>We can complete this feature by implementing the onItemDisclosure handler of the notes list. Here&#8217;s the code:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    onItemDisclosure: function (record) {
        var selectedNote = record;
        NotesApp.views.noteEditor.load(selectedNote);
        NotesApp.views.viewport.setActiveItem('noteEditor', { type: 'slide', direction: 'left' });
    },
    itemTpl: '
&lt;div class=&quot;list-item-title&quot;&gt;{title}&lt;/div&gt;' +
        '&lt;div class=&quot;list-item-narrative&quot;&gt;{narrative}&lt;/div&gt;',
    listeners: {
        'render': function (thisComponent) {
            thisComponent.getStore().load();
        }
    }
});
</pre><p>The handler function accepts the selected note as a parameter. What we need to do in the handler is load the note in the editor utilizing the editor&#8217;s load() method, and then, make the Note Editor view active via a call to the viewport’s setActiveItem().</p><p>Very cool. At this point we can switch over to the emulator, where we should be able to edit notes.</p><h3>Removing records from a data store</h3><p>Deleting notes is also very easy. The Trash button on the Note Editor view’s bottom toolbar will trigger this function. We need to change the button’ handler like so:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.noteEditorBottomToolbar = new Ext.Toolbar({
    dock: 'bottom',
    items: [
        { xtype: 'spacer' },
        {
            iconCls: 'trash',
            iconMask: true,
            handler: function () {

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

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

                notesStore.sync();

                notesList.refresh();
                NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });
            }
        }
    ]
});
</pre><p>After acquiring references to the current note, the notes list and the notes data store, we use the store’s findRecord() function to find and remove the note loaded on the editor.</p><p>Next, we call sync() on the store to make the removal permanent, and proceed to re-render the notes list and make the Notes List view active.</p><p>This procedure is similar to the one we followed when we implemented the Save Note feature, although in this case we’re deleting the note, not saving it.</p><p>A quick check on the emulator should confirm that we can now delete notes.</p><h3>Grouping items in a Sencha Touch List</h3><p>One last thing we want to do is, in the Notes List view, render the notes grouped by date. This will make it easier for our users to work with their notes.</p><p>First, we need to tell our notesList Ext.List that it needs to render its items grouped. We can do this using the grouped config option like so:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    grouped: true,
    emptyText: '&lt;div style=&quot;margin: 5px;&quot;&gt;No notes cached.&lt;/div&gt;',
    onItemDisclosure: function (record) {
        var selectedNote = record;
        NotesApp.views.noteEditor.load(selectedNote);
        NotesApp.views.viewport.setActiveItem('noteEditor', { type: 'slide', direction: 'left' });
    },
    itemTpl: '&lt;div class=&quot;list-item-title&quot;&gt;{title}&lt;/div&gt;' +
        '&lt;div class=&quot;list-item-narrative&quot;&gt;{narrative}&lt;/div&gt;',
    listeners: {
        'render': function (thisComponent) {
            thisComponent.getStore().load();
        }
    }
});
</pre><p>Then, we need to override the getGroupString() function of the NotesStore:</p><pre class="brush: jscript; title: ; notranslate">
Ext.regStore('NotesStore', {
    model: 'Note',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-store'
    },
    getGroupString: function (record) {
        if (record &amp;&amp; record.data.date) {
            return record.get('date').toDateString();
        } else {
            return '';
        }
    }
});
</pre><p>You can define the grouping behavior of the store using the groupField property and the getGoupString() function.</p><p>The getGroupString() function returns the string to group on, based on the store’s data model’s properties. By default, getGroupString() returns the value of the groupField property. In our case we want to use the value of the note’s date as the group’s header, but we want to format the value as a date.</p><p>If we check the emulator, the notes taken the same day should render under the same group header:<br /> <img class="aligncenter size-full wp-image-1099" style="margin-top: 15px; margin-bottom: 15px; border: 1px solid black;" title="notes-list-final" src="http://miamicoder.com/wp-content/uploads/2011/06/notes-list-final.png" alt="" width="291" height="370" /></p><h3>We made it!</h3><p>This is pretty much it. A very simple Sencha Touch application and an easy way to learn some of the features of the Sencha Touch framework.</p><p>I hope you take this app to the next level as you learn more about the framework. <img src='http://miamicoder.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /></p><p>And don’t forget to leave a comment letting me know the Sencha Touch topics about which you would like me to write.</p><p><strong>Download</strong> the Notes App: <a href='http://miamicoder.com/wp-content/uploads/2011/06/Notes-App-v1.0.zip'>Notes-App-v1.0.zip</a></p><p>Here’s the rest of the series:</p><ul><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">Writing a Sencha Touch Application, Part 1</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-2/">Writing a Sencha Touch Application, Part 2</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-3/">Writing a Sencha Touch Application, Part 3</a></li><li>Bonus: <a href="http://miamicoder.com/2011/writing-a-sencha-touch-mvc-application/">Writing a Sencha Touch MVC Application</a></li></ul> <div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=od6N4bxDLr8:L0mfYGOROe8:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=od6N4bxDLr8:L0mfYGOROe8:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=od6N4bxDLr8:L0mfYGOROe8:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=od6N4bxDLr8:L0mfYGOROe8:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=od6N4bxDLr8:L0mfYGOROe8:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=od6N4bxDLr8:L0mfYGOROe8:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/od6N4bxDLr8" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/K_DfbzA0qPvpklBoQEZ8g7dV2No/0/da"><img src="http://feedads.g.doubleclick.net/~a/K_DfbzA0qPvpklBoQEZ8g7dV2No/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/K_DfbzA0qPvpklBoQEZ8g7dV2No/1/da"><img src="http://feedads.g.doubleclick.net/~a/K_DfbzA0qPvpklBoQEZ8g7dV2No/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=JgqcX8sSONA:L0mfYGOROe8:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=JgqcX8sSONA:L0mfYGOROe8:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=JgqcX8sSONA:L0mfYGOROe8:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=JgqcX8sSONA:L0mfYGOROe8:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=JgqcX8sSONA:L0mfYGOROe8:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=JgqcX8sSONA:L0mfYGOROe8:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=JgqcX8sSONA:L0mfYGOROe8:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=JgqcX8sSONA:L0mfYGOROe8:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/JgqcX8sSONA" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-4/feed/</wfw:commentRss> <slash:comments>81</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-4/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/od6N4bxDLr8/</feedburner:origLink></item> <item><title>Writing a Sencha Touch Application, Part 3</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/3VfWL4_Pbb0/</link> <comments>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-3/#comments</comments> <pubDate>Tue, 21 Jun 2011 01:54:36 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[Sencha Touch Tutorials]]></category> <category><![CDATA[Tutorials]]></category> <category><![CDATA[Sencha Touch Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1239</guid> <description><![CDATA[This is the third part of a Sencha Touch tutorial where we're building an app that allows its user to take notes and store them on the device. In this article we will learn how to implement navigation with multiple views and validate data models.]]></description> <content:encoded><![CDATA[<p>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&#8217;t checked them out, here are the links to the previous installments:</p><ul><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">Writing a Sencha Touch Application, Part 1</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-2/">Writing a Sencha Touch Application, Part 2</a></li></ul><p>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.&nbsp;In this article we will take the idea in the mock-up and we will create a great-looking widget:</p><div style="text-align: center;"><table style="width: 100%;"><tbody><tr><td><img class="aligncenter size-full wp-image-1104" style="margin-top: 15px; margin-bottom: 15px;" title="note-editor-mockup" src="http://miamicoder.com/wp-content/uploads/2011/06/note-editor-mockup.png" alt="" width="242" height="285" /></td><td><img class="aligncenter size-full wp-image-1257" style="margin-top: 15px; margin-bottom: 15px; border: 1px solid black;" title="note-editor-at-step11" src="http://miamicoder.com/wp-content/uploads/2011/06/note-editor-at-step11.png" alt="" width="236" height="296" /></td></tr></tbody></table></div><h3>Creating a Sencha Touch Form Panel</h3><p>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:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.noteEditor = new Ext.form.FormPanel({
    id: 'noteEditor',
    items: [
        {
            xtype: 'textfield',
            name: 'title',
            label: 'Title',
            required: true
        },
        {
            xtype: 'textareafield',
            name: 'narrative',
            label: 'Narrative'
        }
    ]
});
</pre><p>We are using the <a href="http://dev.sencha.com/deploy/touch/docs/?class=Ext.form.FormPanel">Ext.form.FormPanel</a> class, one of the simplest ways to work with form fields in Sencha Touch, to define a note editor instance.&nbsp;While a TextArea field will facilitate editing the note’s narrative, the view will capture the note’s title with a Text field.</p><p>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.</p><p>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:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [NotesApp.views.noteEditor] // TODO: revert to [NotesApp.views.notesListContainer]
});
</pre><p>This is what we should see using the emulator:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1262" style="margin-top: 15px; margin-bottom: 15px; border: 1px solid black;" title="note-editor-at-step10" src="http://miamicoder.com/wp-content/uploads/2011/06/note-editor-at-step10.png" alt="" width="284" height="285" /></p><h3>Adding top and bottom toolbars to a Sencha Touch Panel</h3><p>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:</p><pre class="brush: jscript; title: ; notranslate">
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.
            }
        }
    ]
});
</pre><p>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.</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1264" style="margin-top: 15px; margin-bottom: 15px;" title="note-editor-top-toolbar" src="http://miamicoder.com/wp-content/uploads/2011/06/note-editor-top-toolbar.png" alt="" width="323" height="47" /></p><p>The bottom toolbar will host the Trash button, which will allow our users to delete notes. This is how we instantiate this toolbar:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.noteEditorBottomToolbar = new Ext.Toolbar({
    dock: 'bottom',
    items: [
        { xtype: 'spacer' },
        {
            iconCls: 'trash',
            iconMask: true,
            handler: function () {
                // TODO: Delete current note.
            }
        }
    ]
});
</pre><p>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:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1265" style="margin-top: 15px; margin-bottom: 15px;" title="note-editor-bottom-toolbar" src="http://miamicoder.com/wp-content/uploads/2011/06/note-editor-bottom-toolbar.png" alt="" width="323" height="45" /></p><p>Now we can add the toolbars:</p><pre class="brush: jscript; title: ; notranslate">
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
        ]
});
</pre><p>Let’s switch over to the emulator to make sure the Note Editor&#8217;s elements are in the right place. Remember that we’ve temporarily modified the viewport so it just renders the Note Editor like so:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [NotesApp.views.noteEditor] // TODO: revert to [NotesApp.views.notesListContainer]
});
</pre><p>This is what we should see on the emulator:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1257" style="margin-top: 15px; margin-bottom: 15px; border: 1px solid black;" title="note-editor-at-step11" src="http://miamicoder.com/wp-content/uploads/2011/06/note-editor-at-step11.png" alt="" width="266" height="333" /></p><p>Very nice, right?</p><p>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.</p><p><img class="aligncenter size-full wp-image-1201" title="notes-list-mockup-nav-aids" src="http://miamicoder.com/wp-content/uploads/2011/06/notes-list-mockup-nav-aids.png" alt="" width="270" height="310" /></p><h3>How to change views in a Sencha Touch application</h3><p>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:</p><pre class="brush: jscript; title: ; notranslate">
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'});
            }
        }
    ]
});
</pre><p>Let’s examine this sequence one stept at a time. First, we create a new note using the ModelMgr’s class create() method:</p><pre class="brush: jscript; title: ; notranslate">
var now = new Date();
var noteId = now.getTime();
var note = Ext.ModelMgr.create(
    { id: noteId, date: now, title: '', narrative: '' },
    'Note'
);
</pre><p>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:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.noteEditor.load(note);
</pre><p>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:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.viewport.setActiveItem('noteEditor', {type: 'slide', direction: 'left'});
</pre><p>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&#8217;s items:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.viewport = new Ext.Panel({
    fullscreen: true,
    layout: 'card',
    cardAnimation: 'slide',
    items: [
        NotesApp.views.notesListContainer,
        NotesApp.views.noteEditor
    ]
});
</pre><p>On the emulator we should see how the Note Editor becomes visible after tapping the New button on the Notes List view:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1267" style="margin-top: 15px; margin-bottom: 15px;" title="navigation-new-button" src="http://miamicoder.com/wp-content/uploads/2011/06/navigation-new-button.png" alt="" width="500" height="275" /></p><p>Everything OK so far?</p><p>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.</p><h3>Validating a data model in Sencha Touch</h3><p>When the Save button is tapped, we need the following things to happen:</p><ol><li>The information in the form fields, the note’s title and narrative, is captured in a Note model instance</li><li>If the title of the note is empty, we will alert the user and abort the Save routine</li><li>If the note is new, we will add it to the notes cache; if it already exists, we will update the cache</li><li>We will then refresh the Notes List and make it the active view</li></ol><p>Sounds complicated? It really isn’t. Let’s see how it’s done.</p><p>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 <img src='http://miamicoder.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /> )&nbsp;when the field is invalid:</p><pre class="brush: jscript; title: ; notranslate">
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.' }
    ]
});
</pre><p>We will use this message when we validate the note in the Note Editor view. Let’s implement the Save button&#8217;s tap handler so we can see how validation takes place:</p><pre class="brush: jscript; title: ; notranslate">
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' });

            }
        }
    ]
});
</pre><p>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.</p><p>Validation takes place via the <a href="http://dev.sencha.com/deploy/touch/docs/?class=Ext.data.Errors" target="_blank">Errors</a> 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(&#8216;title&#8217;)[0].message.</p><p>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:</p><pre class="brush: jscript; title: ; notranslate">
var notesStore = notesList.getStore();
if (notesStore.findRecord('id', currentNote.data.id) === null) {
    notesStore.add(currentNote);
}
</pre><p>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.&nbsp;At this point we’re ready to render the updated notes list:</p><pre class="brush: jscript; title: ; notranslate">
notesList.refresh();

NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });
</pre><p>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’).</p><p>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:</p><pre class="brush: jscript; title: ; notranslate">
{
    text: 'Home',
    ui: 'back',
    handler: function () {
        NotesApp.views.viewport.setActiveItem('notesListContainer', { type: 'slide', direction: 'right' });'s next
    }
}
</pre><p>Can’t wait to see it working? Start your emulator and check it out. You should be able to create and save notes.</p><h3>What&#8217;s next</h3><p>We’ll leave editing and deleting notes for the next part of the tutorial. I you haven&#8217;t checked out the rest of the series, these are the links:</p><ul><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">Writing a Sencha Touch Application, Part 1</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-2/">Writing a Sencha Touch Application, Part 2</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-4/">Writing a Sencha Touch Application, Part 4</a></li><li>Bonus: <a href="http://miamicoder.com/2011/writing-a-sencha-touch-mvc-application/">Writing a Sencha Touch MVC Application</a></li></ul><p>And here’s the application’s js source with the features we just built:</p><pre class="brush: jscript; title: ; notranslate">
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: '
&lt;div class=&quot;list-item-title&quot;&gt;{title}&lt;/div&gt;
' +
                '
&lt;div class=&quot;list-item-narrative&quot;&gt;{narrative}&lt;/div&gt;
',
            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
            ]
        });
    }
});
</pre><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=0BKUQKWP3HU:sqAZg-Kt2mQ:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=0BKUQKWP3HU:sqAZg-Kt2mQ:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=0BKUQKWP3HU:sqAZg-Kt2mQ:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=0BKUQKWP3HU:sqAZg-Kt2mQ:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=0BKUQKWP3HU:sqAZg-Kt2mQ:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=0BKUQKWP3HU:sqAZg-Kt2mQ:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/0BKUQKWP3HU" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/4dUViwlLq24QjWpSfCxRBLa18_c/0/da"><img src="http://feedads.g.doubleclick.net/~a/4dUViwlLq24QjWpSfCxRBLa18_c/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/4dUViwlLq24QjWpSfCxRBLa18_c/1/da"><img src="http://feedads.g.doubleclick.net/~a/4dUViwlLq24QjWpSfCxRBLa18_c/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=3VfWL4_Pbb0:sqAZg-Kt2mQ:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=3VfWL4_Pbb0:sqAZg-Kt2mQ:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=3VfWL4_Pbb0:sqAZg-Kt2mQ:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=3VfWL4_Pbb0:sqAZg-Kt2mQ:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=3VfWL4_Pbb0:sqAZg-Kt2mQ:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=3VfWL4_Pbb0:sqAZg-Kt2mQ:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=3VfWL4_Pbb0:sqAZg-Kt2mQ:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=3VfWL4_Pbb0:sqAZg-Kt2mQ:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/3VfWL4_Pbb0" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-3/feed/</wfw:commentRss> <slash:comments>41</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-3/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/0BKUQKWP3HU/</feedburner:origLink></item> <item><title>Writing a Sencha Touch Application, Part 2</title><link>http://feedproxy.google.com/~r/feedburner/MiamiCoder/~3/t9CApx9AwHs/</link> <comments>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-2/#comments</comments> <pubDate>Mon, 13 Jun 2011 12:33:12 +0000</pubDate> <dc:creator>Jorge</dc:creator> <category><![CDATA[Sencha Touch Tutorials]]></category> <category><![CDATA[Tutorials]]></category> <category><![CDATA[Sencha Touch Tutorial]]></category><guid isPermaLink="false">http://miamicoder.com/?p=1183</guid> <description><![CDATA[This is the second part of a Sencha Touch tutorial where we're building an app that allows its user to take notes and store them on the device. In this article we will learn how to use Data Models and how to use the HTML5 local storage API.]]></description> <content:encoded><![CDATA[<p>In the <a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">first part of this Sencha Touch tutorial</a> we started building an app that allows its user to take notes and store them on the device. Having created the foundation of the application we are in the process of building the Notes List view, which at this point looks like this:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1118" style="margin-top: 15px; margin-bottom: 15px; border: 1px solid black;" title="notes-list-container-at-step4" src="http://miamicoder.com/wp-content/uploads/2011/06/notes-list-container-at-step4.png" alt="" width="258" height="329" /></p><p>This view is missing the Ext.list that will render the notes created by the user and the New button in the toolbar, as depicted in the mock-up we created in the first part of this tutorial:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1105" style="margin-top: 15px; margin-bottom: 10x;" title="notes-list-mockup" src="http://miamicoder.com/wp-content/uploads/2011/06/notes-list-mockup.png" alt="" width="242" height="285" /></p><p style="text-align: center;">&nbsp;</p><p>Let&#8217;s work on the notes list.</p><h3>Creating a Data Model in Sencha Touch</h3><p>Before can create the list, we need to create the data model that will represent a note. We do this using the Ext.regModel() method:</p><pre class="brush: jscript; title: ; notranslate">
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' }
    ]
});
</pre><p>Our Note model, as any <a href="http://dev.sencha.com/deploy/touch/docs/?class=Ext.data.Model" target="_blank">data model in Sencha Touch</a>, is defined as a set of fields and the methods and properties relevant to it. The model has built-in support for validation, which we use to assert, via the validations config option, that the id and title properties require a value. We will see how model validation takes place when we build the note editor.</p><p>Data models also support associations with other models via has-many and belongs-to relationships. Although we will not use associations in this tutorial,  I urge you to check out their documentation and examples. You will likely run into associations when building Sencha Touch applications.</p><h3>Configuring a Sencha Touch Data Store to use HTML5 local storage</h3><p>We also need a mechanism to cache our notes. The Ext.regStore() function allows us to create a data store, our notes cache, like so:</p><pre class="brush: jscript; title: ; notranslate">
Ext.regStore('NotesStore', {
    model: 'Note',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-localstore'
    }
});
</pre><p>Ext.regStore() creates and registers a store with the framework’s <a href="http://dev.sencha.com/deploy/touch/docs/?class=Ext.StoreMgr" target="_blank">Store Manager</a>. Just as other classes in the framework do, you can use the Store Manager to lookup and modify the data stores in your application.</p><p>Our NotesStore’s model config option is the Note model. We also use the sorters option to specify that the notes will be sorted by date in descending order.</p><p>Remember that one of our application’s features is the ability to persist notes between browser sessions? The task of loading and saving model data is delegated to the store’s proxy. We use the proxy config option to define our proxy as an instance of the <a href="http://dev.sencha.com/deploy/touch/docs/?class=Ext.data.LocalStorageProxy" target="_blank">Ext.data.LocalStorageProxy</a> class. This class uses the HTML5 localStorage API to save model data locally on the client browser. I find this abstraction of the local storage API one of the nicest features of the Sencha Touch framework.</p><h3>Creating a Sencha Touch list</h3><p>With data model and store ready, we can create the notes list like so:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    itemTpl: '
&lt;div class=&quot;list-item-title&quot;&gt;{title}&lt;/div&gt;
' +
        '
&lt;div class=&quot;list-item-narrative&quot;&gt;{narrative}&lt;/div&gt;
'
});
</pre><p>Nothing complicated here. The list will use the NotesStore, passed by name through the store config option. We define the markup used to render the notes with the itemTpl config option. The markup uses the list-item-title and list-item-narrative classes, which we will add to the app.css file:</p><pre class="brush: css; title: ; notranslate">
.list-item-title
{
    float:left;
    width:100%;
    font-size:90%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.list-item-narrative
{
    float:left;
    width:100%;
    color:#666666;
    font-size:80%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.x-item-selected .list-item-title
{
    color:#ffffff;
}
.x-item-selected .list-item-narrative
{
    color:#ffffff;
}
</pre><p>Now let’s add the list to the container panel using the panel’s items config option:</p><pre class="brush: jscript; title: ; notranslate">
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]
});
</pre><p>Want to do a quick run on the simulator to see how we are doing? All right, but before we do that, let’s add a dummy note to the NotesStore just so we can see how the list renders a note. We can do this using the data config option:</p><pre class="brush: jscript; title: ; notranslate">
Ext.regStore('NotesStore', {
    model: 'Note',
    sorters: [{
        property: 'date',
        direction: 'DESC'
    }],
    proxy: {
        type: 'localstorage',
        id: 'notes-app-store'
    },
    // TODO: remove this data after testing.
    data: [
        { id: 1, date: new Date(), title: 'Test Note', narrative: 'This is simply a test note' }
    ]
});
</pre><p>On the device emulator the Notes List view should look like this:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1200" style="margin-top: 15px; margin-bottom: 15px; border: 1px solid black;" title="notes-list-container-at-step8" src="http://miamicoder.com/wp-content/uploads/2011/06/notes-list-container-at-step8.png" alt="" width="258" height="329" /></p><p>At this point our Notes List view is almost ready. We’ve added the toolbar, the notes list, the data model and the store that will cache the notes. We’re just missing the two navigation aids that will allow the user to open the Note Editor view: the New button on the toolbar and the disclosure button on each list item.</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1201" style="margin-top: 5px; margin-bottom: 15px;" title="notes-list-mockup-nav-aids" src="http://miamicoder.com/wp-content/uploads/2011/06/notes-list-mockup-nav-aids.png" alt="" width="270" height="310" /></p><h3>Adding buttons to a Sencha Touch toolbar</h3><p>This is how we add the “New” button to the toolbar:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.notesListToolbar = new Ext.Toolbar({
    id: 'notesListToolbar',
    title: 'My Notes',
    layout: 'hbox',
    items: [
        { xtype: 'spacer' },
        {
            id: 'newNoteButton',
            text: 'New',
            ui: 'action',
            handler: function () {
                // TODO: Create a blank note and make the note editor visible.
            }
        }
    ]
});
</pre><p>Using an hbox layout and adding a spacer to the toolbar allows us to push the New button all the way to the right. We will leave the handler function alone for a while. After we create the Note Editor view we’ll add code to open the editor from this handler.</p><h3>Implementing disclosure buttons in a Sencha Touch list</h3><p>The disclosure button is also very easy to implement by means of the onItemDisclosure config option:</p><pre class="brush: jscript; title: ; notranslate">
NotesApp.views.notesList = new Ext.List({
    id: 'notesList',
    store: 'NotesStore',
    itemTpl: '
&lt;div class=&quot;list-item-title&quot;&gt;{title}&lt;/div&gt;
' +
        '
&lt;div class=&quot;list-item-narrative&quot;&gt;{narrative}&lt;/div&gt;
',
    onItemDisclosure: function (record) {
        // TODO: Render the selected note in the note editor.
    }
});
</pre><p>We’re using one of the <a href="http://dev.sencha.com/deploy/touch/docs/?class=Ext.List" target="_blank">onItemDisclosure overrides</a> to bind a listener to the tap event on any list item.&nbsp;All we will need to do inside this listener is grab the record that received the tap and pass it to the Note Editor view. But we will finish this handler later, after we create our Note Editor.</p><h3>Where are we?</h3><p>Let’s stop for another quick check on the simulator. The app should look like this:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1206" style="margin-top: 15px; margin-bottom: 15px; border: 1px solid black;" title="notes-list-container-at-step9" src="http://miamicoder.com/wp-content/uploads/2011/06/notes-list-container-at-step9.png" alt="" width="258" height="329" /></p><p>Cool! We have completed the main view of the application, except the two event handlers that will allow our users to navigate to the Note Editor.</p><p>We are now ready to begin work on the Note Editor:</p><p style="text-align: center;"><img class="aligncenter size-full wp-image-1104" style="margin-top: 15px; margin-bottom: 15px;" title="note-editor-mockup" src="http://miamicoder.com/wp-content/uploads/2011/06/note-editor-mockup.png" alt="" width="242" height="285" /></p><p>But that will happen in the next chapter of this series. For now I will leave you with the source code of the app at this stage:</p><pre class="brush: jscript; title: ; notranslate">
var App = new Ext.Application({
    name: 'NotesApp',
    useLoadMask: true,
    launch: function () {

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

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

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

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

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

        NotesApp.views.viewport = new Ext.Panel({
            fullscreen: true,
            layout: 'card',
            cardAnimation: 'slide',
            items: [NotesApp.views.notesListContainer]
        });
    }
});
</pre><p>Stay tuned! And don’t forget to leave your comments or suggestions. <img src='http://miamicoder.com/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' /></p><p>The rest of the series:</p><ul><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-1/">Writing a Sencha Touch Application, Part 1</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-3/">Writing a Sencha Touch Application, Part 3</a></li><li><a href="http://miamicoder.com/2011/writing-a-sencha-touch-application-part-4/">Writing a Sencha Touch Application, Part 4</a></li><li>Bonus: <a href="http://miamicoder.com/2011/writing-a-sencha-touch-mvc-application/">Writing a Sencha Touch MVC Application</a></li></ul> <div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/Miamicoder?a=PSSVHjjP7yg:-MwArl9entw:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=PSSVHjjP7yg:-MwArl9entw:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/Miamicoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=PSSVHjjP7yg:-MwArl9entw:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=PSSVHjjP7yg:-MwArl9entw:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/Miamicoder?a=PSSVHjjP7yg:-MwArl9entw:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/Miamicoder?i=PSSVHjjP7yg:-MwArl9entw:gIN9vFwOqvQ" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/Miamicoder/~4/PSSVHjjP7yg" height="1" width="1"/>
<p><a href="http://feedads.g.doubleclick.net/~a/adxylu7Lv5znMxO-aNdxf0PK1Cg/0/da"><img src="http://feedads.g.doubleclick.net/~a/adxylu7Lv5znMxO-aNdxf0PK1Cg/0/di" border="0" ismap="true"></img></a><br/>
<a href="http://feedads.g.doubleclick.net/~a/adxylu7Lv5znMxO-aNdxf0PK1Cg/1/da"><img src="http://feedads.g.doubleclick.net/~a/adxylu7Lv5znMxO-aNdxf0PK1Cg/1/di" border="0" ismap="true"></img></a></p><div class="feedflare">
<a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=t9CApx9AwHs:-MwArl9entw:V_sGLiPBpWU"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=t9CApx9AwHs:-MwArl9entw:V_sGLiPBpWU" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=t9CApx9AwHs:-MwArl9entw:gIN9vFwOqvQ"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?i=t9CApx9AwHs:-MwArl9entw:gIN9vFwOqvQ" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=t9CApx9AwHs:-MwArl9entw:7Q72WNTAKBA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=7Q72WNTAKBA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=t9CApx9AwHs:-MwArl9entw:yIl2AUoC8zA"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=yIl2AUoC8zA" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=t9CApx9AwHs:-MwArl9entw:qj6IDK7rITs"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=qj6IDK7rITs" border="0"></img></a> <a href="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?a=t9CApx9AwHs:-MwArl9entw:TzevzKxY174"><img src="http://feeds.feedburner.com/~ff/feedburner/MiamiCoder?d=TzevzKxY174" border="0"></img></a>
</div><img src="http://feeds.feedburner.com/~r/feedburner/MiamiCoder/~4/t9CApx9AwHs" height="1" width="1"/>]]></content:encoded> <wfw:commentRss>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-2/feed/</wfw:commentRss> <slash:comments>35</slash:comments> <feedburner:origLink>http://miamicoder.com/2011/writing-a-sencha-touch-application-part-2/</feedburner:origLink><feedburner:origLink>http://feedproxy.google.com/~r/Miamicoder/~3/PSSVHjjP7yg/</feedburner:origLink></item> </channel> </rss><!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Minified using disk: basic
Page Caching using disk: enhanced
Object Caching 670/740 objects using disk: basic

Served from: miamicoder.com @ 2012-02-08 08:19:04 -->

