jQuery Mobile + CouchDB: Part 4 – Editing Documents

2011 January 4th by todd anderson

In the previous post, I addressed using templates to generate the jQuery Mobile external pages. Though it addressed a fundamental (imo) part of serving up HTML from the CouchDB instance and had a few nice tricks for jQuery Mobile, it essentially was organization and cleanup so I could move forward in developing the application without it getting to cluttered for my development and workflow.

In this article, I am going to take the templating structure established in the previous post and add another external page to the jQuery Mobile application – one in which I will be able to edit the document served up from the CouchDB database. Along with this, I’ll discuss assigning event handlers to DOM elements using jQuery, adding buttons to footers and headers, and clearing external jQuery Mobile pages from cache to ensure a user is looking at the most recent changes to a document.

Edit Template

In the previous post, we moved from returning HTML from CouchDB using a show function and string manipulation to moving mark-up over to a template and utilizing Mustache to render the served up jQuery Mobile external page. We are going to use the same technique to deliver another page that will allow a user to edit a target CouchDB document, with the option to either cancel or submit their changes.

Open up your favorite text editor, add the following snippet and save the file as albumedit.html in the /templates directory of your albums couchapp (eg. /Documents/workspace/custardbelly/couchdb/albums/templates/albumedit.html):

/templates/albumedit.html

<div data-role="page" id="albumedit" data-nobackbtn="true">

    <div data-role="header" id="albumheader">

        <a id="cancelBackButton" href="#" data-icon="delete">Close</a>

        <h1 class="albumtitle">{{title}}</h1>

    </div>

    <div data-role="content" data-theme="c" style="padding:0em;">

        <form id="albumform" action="#" method="get" data-identity="{{document}}">

            <div data-role="fieldcontain">

                <label for="artist">Artist:</label>

                <input id="artistField" name="artist" type="text" value="{{artist}}" />

            </div>

            <div data-role="fieldcontain">

                <label for="title">Title:</label>

                <input id="titleField" name="title" type="text" value="{{title}}" />

            </div>

            <div data-role="fieldcontain">

                <label for="description">Description:</label>

                <textarea id="descriptionField" name="description" cols="40" rows="8">{{description}}</textarea>

            </div>

            <div class="ui-body ui-body-b">

                <fieldset class="ui-grid-a">

                    <div class="ui-block-a">

                        <p id="cancelButton" data-role="button" data-theme="d">Cancel</p>

                    </div>

                    <div class="ui-block-b">

                        <p id="submitButton" data-role="button" data-theme="a">Submit</p>

                    </div>

                </fieldset>

            </div>

        </form>

    </div>

    <div data-role="footer" />

</div>

{{>scripts}}

That is some pretty hefty mark-up in relation to the relatively small (element count-wise) pages we have previously created. What should be familiar are the {{mustache}} tokens which will be dynamically filled with content using mustache.js in our soon-to-be created show function for the album edit page; but there are a few jQuery Mobile tidbits in here that we should take a little deeper of a look at.

/templates/albumedit.html

<div data-role="page" id="albumedit" data-nobackbtn="true">

    <div data-role="header" id="albumheader">

        <a id="cancelBackButton" href="#" data-icon="delete">Close</a>

        <h1 class="albumtitle">{{title}}</h1>

    </div>

A data-role of page is set as usual to declare a jQuery Mobile page and an id attribute is assigned for a reason that will be discussed shortly. What is of note in the page div is the data-nobackbtn attribute. If set the a value of true, this declaration will remove the default Back button from the header and allow us to customize the options. For this case, we have replaced the Back button with a Close button:

/templates/albumedit.html

<a id="cancelBackButton" href="#" data-icon="delete">Close</a>

Without specifying a style class, the Close button will be positioned to the left in the header. The href is set to an empty anchor. Essentially this is just a placeholder for an action defaulted to a link in the header. When we create the controller script for the album edit page later in this article, we will prevent the default behaviour and handle the close manually.

Content Form

jQuery Mobile has some great organization and styling when it comes to form elements. Positioning and aligning fields is all handled by the framework and, unless you want to get fancy, you basically just have to declare the desired element or assign the desired role/layout in order to achieve a fairly simple form. What we have done for our album edit page template is pretty basic:

/templates/albumedit.html

<form id="albumform" action="#" method="get" data-identity="{{document}}">

    <div data-role="fieldcontain">

        <label for="artist">Artist:</label>

        <input id="artistField" name="artist" type="text" value="{{artist}}" />

    </div>

    <div data-role="fieldcontain">

        <label for="title">Title:</label>

        <input id="titleField" name="title" type="text" value="{{title}}" />

    </div>

    <div data-role="fieldcontain">

        <label for="description">Description:</label>

        <textarea id="descriptionField" name="description" cols="40" rows="8">{{description}}</textarea>

    </div>

    <div class="ui-body ui-body-b">

        <fieldset class="ui-grid-a">

            <div class="ui-block-a">

                <p id="cancelButton" data-role="button" data-theme="d">Cancel</p>

            </div>

            <div class="ui-block-b">

                <p id="submitButton" data-role="button" data-theme="a">Submit</p>

            </div>

        </fieldset>

    </div>

</form>

The input fields are populated with the dynamic values from our show function (discussed later) and two buttons are presented to either Cancel or Submit any changes to those values. By marking each div with a data-role of fieldcontain, the label/input pair will be styled and aligned with each other div with a horizontal bar to delimit them vertically. The Cancel and Submit buttons are held in a fieldset with a grid layout assigned:

/templates/albumedit.html

<fieldset class="ui-grid-a">

    <div class="ui-block-a">

        <p id="cancelButton" data-role="button" data-theme="d">Cancel</p>

    </div>

    <div class="ui-block-b">

        <p id="submitButton" data-role="button" data-theme="a">Submit</p>

    </div>

</fieldset>

By assigning a ui-block value to each div in the fieldset, they will essentially be sized at 50% within the grid and positioned accordingly – Cancel to the left of Submit. You may notice that the “buttons” are not a link elements nor button elements. The reason being that both of those within a context of a form element were somehow being overridden to always perform a submit action. We will want to trap the user interaction with these elements in order to either a) reset the field values or b) submit our changes to the CouchDB database. So, they have been declared as p elements with a data-role of button so as to have the proper styling, but not the default action role of submittal of form without being captured and prevented first.

We will get into the submission of an edited document in a bit when we discuss the associated script with the album edit page, but until then take notice of the Partial inclusion in our albumedit template:

/templates/albumedit.html

{{>scripts}}

… and let’s dive into creating our show function for editable album pages.

Edit Show Function

If you have been following along with the previous post, you will remember that we removed the string-manipulated mark-up from the show function for the album view page and replaced it with Mustache templating. The show function we will create for the album edit page will largely look the same as the one created for the album view page. In fact, the only difference will be the target template and partial include.

Open up your favorite text editor, create a new file, add the following snippet and save the file as album-edit.js in the /shows directory of your albums couchapp (eg. /Documents/workspace/custardbelly/couchdb/albums/shows/album-edit.js):

/shows/album-edit.js

function(doc, req) {

    var Mustache = require("vendor/couchapp/lib/mustache");

    var stash = {

            artist: doc.artist,

            title : doc.title,

            description: doc.description,

            document: doc._id

    };

    return Mustache.to_html(this.templates.albumedit, stash, this.templates.partials.albumedit);

}

Again, if you have been following along with previous posts, this would look relatively familiar. Essentially, we are using the mustache.js template plugin to deliver HTML mark-up of the previously created albumedit.html jQuery Mobile page. The associated document is retrieved from the CouchDB database by passing the _id of the document in the request (eg. http://127.0.0.1:5984/albums/_design/albums/_show/
album-edit/db04eb7e5c845ee0aa791ae1ed001e4d
).

The previously created /templates/albumedit.html will be used as the template and is supplied as the first argument in Mustache._tohtml(). The stash object (second argument) is used to dynamically filled the values of the fields upon load and the specified properties of the album document are available for editing: artist name, album title and personal description. The third argument is the Partial includes directory that will be substituted in the {{>}} declarations of the /templates/albumedit.html document. There is only one Partial defined in albumedit.html{{>scripts}} – which will essentially declare a JavaScript file with actions associated with the album edit page.

Scripts Partial

Open up your favorite text editor and save the following snippet as scripts.html in /templates/partials/albumedit:

/templates/partials/albumedit/scripts.html

<script src="../../script/album-edit-page.js"></script>

If you have followed along with create the show function, template and partial for the album view page, this again will look similar. We are essentially loading an associated JavaScript file for the album edit page. As explained in the previous post, the script could be included here and not redirected to a js file, but for my own organizational habits I create a separate JavaScript file and have it residing in the _/attachments/script/ directory of my albums couchapp (eg. _/Documents/workspace/custardbelly/couchdb/albums/
attachments/script/album-edit-page.js).

album-edit-page.js

If we think about the role of our album edit page in the scheme of the application, we want to present the user with the ability to edit the album document from the CouchDB albums database. The form will initially be filled with the latest editable values and the user has the ability to either submit changes or cancel any pending changes. A Cancel action will return the fields back to the original values and return the user to the album view page. To ensure that the user is presented with the latest changes to the document from the database, we will also need to clear the page from the page cache just as we have done with the album view page. With these requirements in mind, let’s start creating the associated script for the album edit jQuery Mobile page served up from the album-edit show function.

AlbumEditPageController

We’ll start by creating a quasi-view-controller for the album edit page in the similar fashion as the one used for the album view page (created in the previous post). Initially, it will handle recognizing once the jQuery Mobile page div is shown and remove it from the page cache once it is hidden (by navigating away from the page).

Open your favorite text editor and save the following snippet as album-edit-page.js in the _/attachments/script/ directory:

_/attachments/script/album-edit-page.js

var AlbumEditPageController = function() {



    function handleEditPageViewHide()

    {

        var docId = $("#albumform").data("identity");

        var pageCache =  $(document.getElementById("_show/album-edit/" + docId));

        pageCache.unbind( "pagehide", handleEditPageViewHide );

        pageCache.empty();

        pageCache.remove();

    }



    function handleEditView()

    {

        // Watch for bound hide of page to clear from cache.

        var docId = $("#albumform").data("identity");

        var albumPage = $(document.getElementById("_show/album-edit/" + docId));

        albumPage.bind( "pagehide", handleEditPageViewHide );

    }



    return {

        initialize: function() {

            $("div[data-role='page']").live( "pageshow", function() {

                $("div[data-role='page']").die( "pageshow" );

                handleEditView();

            });

        }

    };

}();



function handleEditPageReady()

{

    AlbumEditPageController.initialize();

}

$().ready( handleEditPageReady )

Once the page is loaded, the AlbumEditPageController is initialized an a pageshow event handler is assigned to recognize when the albumedit jQuery Mobile page is shown. We access the page in the DOM using the standard data-role attribute value for a jQuery Mobile page. I stumbled upon this solution from these two forum posts – http://forum.jquery.com/topic/force-page-update and http://forum.jquery.com/topic/binding-events-to-buttons-in-a-dialog – and seem to be the current agreed upon solution for accessing a loading jQuery Mobile page. In the handleEditView() handler, the page in the DOM is accessed using document.getElementById() and assigned an event handler for pagehide to clear it from the page (ie. DOM) cache. Aside from the external page id used to access the element, this is pretty much identical to the solution from the previous post.

One of the requirements for the album edit page is the ability to cancel and clear any edited fields by the user. In order to do so, we will stored a local object with the provided name/value pairs so the fields can be easily reverted.

With album-edit-page.js open in your favorite text editor, make the following modifications and save the file:

_/attachments/script/album-edit-page.js

var AlbumEditPageController = function() {



    var editableAlbum;



    function handleEditPageViewHide()

    {

        editableAlbum = null;



        var docId = $("#albumform").data("identity");

        var pageCache =  $(document.getElementById("_show/album-edit/" + docId));

        pageCache.unbind( "pagehide", handleEditPageViewHide );

        pageCache.empty();

        pageCache.remove();

    }



    function handleEditView()

    {

        // Watch for bound hide of page to clear from cache.

        var docId = $("#albumform").data("identity");

        var albumPage = $(document.getElementById("_show/album-edit/" + docId));

        albumPage.bind( "pagehide", handleEditPageViewHide );



        storeUneditedDocument();

    }



    function storeUneditedDocument()

    {

        var artist = $("input#artistField").val();

        var album = $("input#titleField").val();

        var description = $("textarea#descriptionField").val();

        editableAlbum = {artist:artist, album:album, description:description};

    }



    return {

        initialize: function() {

           $("div[data-role='page']").live( "pageshow", function() {

                $("div[data-role='page']").die( "pageshow" );

                handleEditView();

            });

        }

    };

}();



function handleEditPageReady()

{

    AlbumEditPageController.initialize();

}

$().ready( handleEditPageReady )

An editableAlbum member is declared and updated upon pageshow in the storeUneditedDocument() method. With the original values stored in this externally-immutable we can ensure that whenever a user decides to cancel the editing of the album document we can revert the field values to the original document values. Let’s hook up the cancelBackButton and cancelButton elements declared in the albumedit.html template with click event handlers in order to handle the revert action back to the original values.

Save the following modifications to album-edit-page.js:

_/attachments/script/album-edit-page.js

var AlbumEditPageController = function() {



    var editableAlbum;



    function handleEditPageViewHide()

    {

        $("#cancelButton").die( "click", handleCancelEdit );

        $("#cancelBackButton").die( "click" );

        editableAlbum = null;



        var docId = $("#albumform").data("identity");

        var pageCache =  $(document.getElementById("_show/album-edit/" + docId));

        pageCache.unbind( "pagehide", handleEditPageViewHide );

        pageCache.empty();

        pageCache.remove();

    }



    function handleEditView()

    {

        // Watch for bound hide of page to clear from cache.

        var docId = $("#albumform").data("identity");

        var albumPage = $(document.getElementById("_show/album-edit/" + docId));

        albumPage.bind( "pagehide", handleEditPageViewHide );



        storeUneditedDocument();

    }



    function storeUneditedDocument()

    {

        var artist = $("input#artistField").val();

        var album = $("input#titleField").val();

        var description = $("textarea#descriptionField").val();

        editableAlbum = {artist:artist, album:album, description:description};

    }



    function revertEdits()

    {

        $("input#artistField").val( editableAlbum.artist );

        $("input#titleField").val( editableAlbum.album );

        $("textarea#descriptionField").val( editableAlbum.description );

    }



    function handleCancelEdit()

    {

        revertEdits();

    }



    return {

        initialize: function() {

            $("#cancelButton").live( "click", handleCancelEdit );

            $("#cancelBackButton").live( "click", function( event ) {

                event.preventDefault();

                handleCancelEdit();

                return false;

            });

            $("div[data-role='page']").live( "pageshow", function() {

                $("div[data-role='page']").die( "pageshow" );

                handleEditView();

            });

        }

    };

}();



function handleEditPageReady()

{

    AlbumEditPageController.initialize();

}

$().ready( handleEditPageReady )

In this snippet, we have wired the two cancelable button elements up to invoke the handleCancelEdit() method upon a click event. All revertEdits() does is reset the field values based on the stored values in editableAlbum. We also take care to kill those event listeners when the page is hidden. Now that we have one requirement fulfilled (cancel-ability) for the album edit page, let’s move on to original intent of the page – submitting document changes.

Submit

To submit changes to the target album document form the album edit page we will use the database API provided in the jquery.couchdb library plugin. That library is automatically loaded from the loader script created by couchapp if remember back to the first post where we accessed the list of available album documents from the albums database in our CouchDB instance. We resolved the $db member in the index.html to the albums database when the page is loaded. When submitting from the album edit page, we will just use that instance to load, modify and save the target document back to the database.

Make the following modifications to album-edit-page.js and save:

_/attachments/script/album-edit-page.js

var AlbumEditPageController = function() {



    var editableAlbum;



    function handleEditPageViewHide()

    {

        $("#cancelButton").die( "click", handleCancelEdit );

        $("#cancelBackButton").die( "click" );

        $("#submitButton").die( "click" );

        editableAlbum = null;



        var docId = $("#albumform").data("identity");

        var pageCache =  $(document.getElementById("_show/album-edit/" + docId));

        pageCache.unbind( "pagehide", handleEditPageViewHide );

        pageCache.empty();

        pageCache.remove();

    }



    function handleEditView()

    {

        // Watch for bound hide of page to clear from cache.

        var docId = $("#albumform").data("identity");

        var albumPage = $(document.getElementById("_show/album-edit/" + docId));

        albumPage.bind( "pagehide", handleEditPageViewHide );



        storeUneditedDocument();

    }



    function storeUneditedDocument()

    {

        var artist = $("input#artistField").val();

        var album = $("input#titleField").val();

        var description = $("textarea#descriptionField").val();

        editableAlbum = {artist:artist, album:album, description:description};

    }



    function saveDocument( document )

    {

        $db.saveDoc( document, {

            success: function( response )  {

                updateEditableAlbum( document );

            },

            error: function() {

                alert( "Cannot save document: " + document._id );

            }

        });

    }



    function updateEditableAlbum( document )

    {

        editableAlbum.artist = document.artist;

        editableAlbum.album = document.album;

        editableAlbum.description = document.description;

    }



    function revertEdits()

    {

        $("input#artistField").val( editableAlbum.artist );

        $("input#titleField").val( editableAlbum.album );

        $("textarea#descriptionField").val( editableAlbum.description );

    }



    function handleCancelEdit()

    {

        revertEdits();

    }



    return {

        initialize: function() {

            $("#cancelButton").live( "click", handleCancelEdit );

            $("#cancelBackButton").live( "click", function( event ) {

                event.preventDefault();

                handleCancelEdit();

                return false;

            });

            $("#submitButton").live( "click", function( event ) {

                var docId = $("#albumform").data("identity");

                $db.openDoc( docId, {

                    success: function( document ) {

                        document.artist = $("input#artistField").val();

                        document.album = $("input#titleField").val();

                        document.description = $("textarea#descriptionField").val();

                        saveDocument( document );

                    },

                    error: function() {

                        alert( "Cannot open document: " + docId );

                    }

                });

            });

            $("div[data-role='page']").live( "pageshow", function() {

                $("div[data-role='page']").die( "pageshow" );

                handleEditView();

            });

        }

    };

}();



function handleEditPageReady()

{

    AlbumEditPageController.initialize();

}

$().ready( handleEditPageReady )

A click event handler is assigned to the submitButton element, from which the document is loaded and assigned the associated field values. Upon update to the new values, $db.saveDoc() is invoked with a success handler that updates the privately held editableAlbum object. This ensures that the values are properly restored back to any saved changes if the user decides to cancel at any time after the successful save.
Almost there. We will just finish off our controller by navigating the user to the album view page upon Cancel or Submit.

The following is the full album-edit-page.js file with the last modifications highlighted. Save the following changes to album-edit-page.js:

_/attachments/script/album-edit-page.js

var AlbumEditPageController = function() {



    var editableAlbum;



    function handleEditPageViewHide()

    {

        $("#cancelButton").die( "click", handleCancelEdit );

        $("#cancelBackButton").die( "click" );

        $("#submitButton").die( "click" );

        editableAlbum = null;



        var docId = $("#albumform").data("identity");

        var pageCache =  $(document.getElementById("_show/album-edit/" + docId));

        pageCache.unbind( "pagehide", handleEditPageViewHide );

        pageCache.empty();

        pageCache.remove();

    }



    function handleEditView()

    {

        // Watch for bound hide of page to clear from cache.

        var docId = $("#albumform").data("identity");

        var albumPage = $(document.getElementById("_show/album-edit/" + docId));

        albumPage.bind( "pagehide", handleEditPageViewHide );



        storeUneditedDocument();

    }



    function navigateToAlbumPage( docId )

    {

        $.mobile.changePage( "_show/album/" + docId, "slide", true, true );

    }



    function storeUneditedDocument()

    {

        var artist = $("input#artistField").val();

        var album = $("input#titleField").val();

        var description = $("textarea#descriptionField").val();

        editableAlbum = {artist:artist, album:album, description:description};

    }



    function saveDocument( document )

    {

        $db.saveDoc( document, {

            success: function( response )  {

                updateEditableAlbum( document );

                navigateToAlbumPage( document._id );

            },

            error: function() {

                alert( "Cannot save document: " + document._id );

            }

        });

    }



    function updateEditableAlbum( document )

    {

        editableAlbum.artist = document.artist;

        editableAlbum.album = document.album;

        editableAlbum.description = document.description;

    }



    function revertEdits()

    {

        $("input#artistField").val( editableAlbum.artist );

        $("input#titleField").val( editableAlbum.album );

        $("textarea#descriptionField").val( editableAlbum.description );

    }



    function handleCancelEdit()

    {

        revertEdits();

        var docId = $("#albumform").data("identity");

        navigateToAlbumPage( docId );

    }



    return {

        initialize: function() {

            $("#cancelButton").live( "click", handleCancelEdit );

            $("#cancelBackButton").live( "click", function( event ) {

                event.preventDefault();

                handleCancelEdit();

                return false;

            });

            $("#submitButton").live( "click", function( event ) {

                var docId = $("#albumform").data("identity");

                $db.openDoc( docId, {

                    success: function( document ) {

                        document.artist = $("input#artistField").val();

                        document.album = $("input#titleField").val();

                        document.description = $("textarea#descriptionField").val();

                        saveDocument( document );

                    },

                    error: function() {

                        alert( "Cannot open document: " + docId );

                    }

                });

            });

            $("div[data-role='page']").live( "pageshow", function() {

                $("div[data-role='page']").die( "pageshow" );

                handleEditView();

            });

        }

    };

}();



function handleEditPageReady()

{

    AlbumEditPageController.initialize();

}

$().ready( handleEditPageReady )

In navigateToAlbumPage() we use $.mobile.changePage() to navigate the user to the external album view page using the show function of our CouchDB application based on the _id of the target document that is loaded in the album edit page.

That should be it… aside from one thing. Although (once we push our changes) we can access the page using the #_show/album-edit/${id} location, as the application currently stands, there is no way to access the album edit page from any other page in the application. To remedy that, we will make some modifications to the album.html template and the album-page.js script to navigate the user to the album edit page.

Accessing the Album Edit Page

I am going to assume that you have been following along with previous posts here and that you already have the /templates/album.html and _/attachments/script/album-page.js files. We are going to make modifications to them to include an edit button in the album view page and assign a click handler to navigate to the album edit page.

album.html

Open up the /templates/album.html file in your favorite text editor and save the following modifications:

/templates/album.html

<div data-role="page" id="albumview" data-position="inline" data-back="true">

    <div data-role="header" id="albumheader">

        <h1 class="albumtitle">{{title}}</h1>

        <a href="#home" data-icon="grid" class="ui-btn-right">Home</a>

    </div>

    <div data-role="content" id="albumcontent" data-identity="{{document}}">

      <h2 class="artist">{{artist}}</h2>

      <p class="title">{{title}}</p>

      <p class="description">{{description}}</p>

      <p id="editbutton" data-role="button" data-theme="a">Edit</p>

    </div>

    <div data-role="footer" />

</div>

{{>scripts}}

The only modification to the album.html template e have made is the inclusion of an Edit button which is a p element attributed with the data-role of button. The jQuery Mobile framework will handle styling the Edit button to have consistency in look-and-feel with the other buttons throughout the application. Now let’s hook up a click event listener for that button element to take the user to the album edit page.

album-page.js

Open up _/attachments/script/album-page.js in your favorite text editor and save the following modifications:

_/attachments/script/album-page.js

var AlbumPageController = function() {



    function handleView()

    {

        $("#editbutton").live( "click", handleEdit );



        // Watch for bound hide of page to clear from cache.

        var docId = $("#albumcontent").data("identity");

        var albumPage = $(document.getElementById("_show/album/" + docId));

        albumPage.bind( "pagehide", handlePageViewHide );

    }



    function handleEdit( event )

    {

        // Prevent default link event.

        event.preventDefault();

        // Access document id from data-identity.

        var docId = $("#albumcontent").data("identity");

        // Change page.

        $.mobile.changePage( "_show/album-edit/" + docId, "slide", false, true );

        return false;

    }



    function handlePageViewHide()

    {

        $("#editbutton").die( "click", handleEdit );



        var docId = $("#albumcontent").data("identity");

        var albumPageCache =  $(document.getElementById("_show/album/" + docId));

        albumPageCache.unbind( "pagehide", handlePageViewHide );

        albumPageCache.empty();

        albumPageCache.remove();

    }



    return {

        initialize : function() {

            $("div[data-role='page']").live( "pageshow", function() {

                $("div[data-role='page']").die( "pageshow" );

                handleView();

            });

        }

    };

}();



function handlePageViewReady()

{

    AlbumPageController.initialize();

}

$().ready( handlePageViewReady );

The handleEdit() handler navigates to the album edit page using the $.mobile.changePage() method pointing to the page created by the show function using the target document id that is used to show the album view page.

Now that we have implemented a way to navigate to the album edit page from the application through the album view page, we can deploy our changes to the CouchDB instance for our albums application.

Deployment

Our Albums application has been modified to allow a user to modify properties of an album document from an album edit page. That page can be accessed by clicking an Edit button from the album view page accessible from a selection of an album from the list presented on the landing index.html. With these modifications saved, we can now push to the CouchDB database using the couchapp utility. Open a terminal and navigate to the directory where you create your CouchApp applications (for me that is /Documents/workspace/custardbelly/couchdb and in there i have a folder named albums which is the CouchApp application directory for these examples). Enter the following command to push the changes to the CouchDB instance:

couchapp push albums http://127.0.0.1:5984/albums

If all was successful and you now go to http://127.0.0.1:5984/albums/_design/albums/index.html, click on an album from the list and select to edit that document. From the album edit page you are able to either cancel any edits or submit the changes to the target album document. What a thing of beauty :) Let’s take a look at the current state of the application:

index.html (aka #home):
index.html

album view page:
album view page

album edit page:
album edit page

Conclusion

This post got a lot longer than I had anticipated… which just goes to show what a wind-bag i am :) All joking aside, I think we covered some good ground, especially about saving modifications to a document back to the CouchDB database. We also covered, again, how to monitor pages so as to remove them from cache and ensure that the user is always presented with the latest document information. We also delved into assigning event listeners to button elements. All good fun… well, hopefully, it was to you.

[Note] This post was written against the following software versions:
CouchDB – 1.0.1
CouchApp – 0.7.2
jQuery – 1.4.4
jQuery Mobile – 1.0a2
If you have found this post and any piece has moved forward, hopefully the examples are still viable/useful. I will not be updating the examples in this post in parellel with updates to any of the previously mentioned software, unless explicitly noted.

Articles in this series:

  1. Getting Started
  2. Displaying a page detail of a single album.
  3. Templates and Mustache
  4. Displaying an editable page of an album.
  5. Creating and Adding an album document.
  6. Deleting an album document
  7. Authorization and Validation – Part 1
  8. Authorization and Validation – Part 2

Full source for albums couchapp here.

Posted in CouchDB, jquery, jquery-mobile.