Building a jQuery Mobile Application, Part 1

This is the first part of a series of articles about how to create a mobile app using jQuery Mobile. As in my Sencha Touch tutorials, 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.

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 Jasmine framework. For each use case, we will create a specification using Jasmine, then we will implement the specification in the application.

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:

  • Define the features of the application
  • Create low fidelity user interface mock-ups
  • Begin creating Jasmine specifications for the app’s public interface
  • Begin implementing the app’s public interface

Let’s get started.

Application Features

The application has a simple feature set. We want to give our customers the following abilities:

  • Create notes.
  • Edit notes.
  • Delete notes.
  • Store notes on the device that is running the application, across browser sessions.
  • View the entire collection of notes.

The Main Views

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 Balsamiq Mockups:

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:

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.

OK. We defined the features and look of the app, which brings us right into the realm of specifications and testing.

Behavior-Driven Development and the Jasmine Framework

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 Jasmine framework.

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.

I’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’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.

This is an example of a test we will use later in this series that will help you understand how Jasmine works:

describe("Notes functions", function () {

    it("Should return a NoteModel instance", function () {

        var note = Notes.app.createNote();

        expect(note instanceof Notes.model.NoteModel).toBeTruthy();
    });
});

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 “suites”.

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.

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 expectation matchers. In the example we use the toBeThruthy() matcher to express that our test will pass if the results are true.

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.

Getting Ready to Use Jasmine

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 “NotesApp”. In this folder, we will create an “app” directory where we will place the business logic and presentation files, and a “spec” directory, where we will place the Jasmine test suites. In addition, we will create a “Lib” directory, which will contain folders for the libraries our application will use.

The folders should look as depicted below:

If you’re curious about the “jqm” and “jstorage” 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.

Now we can download Jasmine, and place its files in the “jasmine” directory:

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:

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.

Here’s the source for the head and body sections of our specrunner.html file:

   <!-- HEAD -->
   <!-- Jasmine includes -->
    <link href="../lib/jasmine/jasmine.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="../lib/jasmine/jasmine.js" type="text/javascript"></script>
<script type="text/javascript" src="../lib/jasmine/jasmine-html.js" type="text/javascript"></script>
    <!-- Source files -->
    <script src="app/App.js" type="text/javascript"></script>
    <!-- Spec files -->
    <script src="spec/AppSpec.js" type="text/javascript"></script>
   <!-- BODY -->
       <script type="text/javascript">

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

        jasmine.getEnv().execute();

    </script>

Note how the file has references to the Jasmine framework, as well as our application and specifications files.

Let’s place the specrunner.html file in the “NotesApp” directory, as depicted below:

OK, our main files are in place and it’s time to start building the application. Let’s do it.

Mapping Application Features to Business Logic Specifications

The first thing we want is making sure the application’s namespace exists. We can define this specification in the AppSpec.js file:

describe("Public interface exists", function () {

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

});

Note that we’ve placed this spec inside a suite named “Public interface exists”. 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.

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:

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:

var Notes = Notes || {}
Notes.app = (function () {

    return {}

})();

If we run the test now, it should pass:

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.

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.

The first specification looks like this:

describe("Public interface exists", function () {
    // App namespace test omitted...
    //
    //
    it("Should have public interface to return notes list", function () {
        expect(Notes.app.getNotesList).toBeDefined();
    });

});

This spec is also self-explanatory, asserting the expectation that the business logic defines a getNotesList() function.

The second specification is a little more detailed:

describe("Public interface implementation", function () {

    it("Should return notes list", function () {

        var notesList = Notes.app.getNotesList();

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

This spec goes in a second suite, called “Public interface implementation”, 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.

If we refresh specrunner.html in the browser, the tests should fail:

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:

Notes.app = (function () {

    var notesList = [];

    function getNotesList() {
        return notesList;
    }

    return {

        getNotesList: getNotesList

    }

})();

Now the tests should pass:

Are you with me so far? Don’t tell me you’re not liking this. :-)

Next Steps

In the next chapter of this series we will continue implementing the business logic of the application.

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.

Stay tuned!

The Entire Series

Want To Learn More?

My How to Build a jQuery Mobile Application EBook takes the Notes App to the next level by adding notes synchronization with a server. Check it out!

Source Code

Here’s the source code for this article:

AppSpec.js

describe("Notes public interface exists", function () {

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

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

});

describe("Notes functions", function () {

    it("Returns a list of notes", function () {

        var notesList = Notes.app.getNotesList();

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

app.js

var Notes = Notes || {}
Notes.model = Notes.model || {}

Notes.app = (function () {

    var notesList = [];

    function getNotesList() {
        return notesList;
    }

    return {

        getNotesList: getNotesList

    }

})();

Comments

  1. says

    Hey Jorge,

    Thanks for the writeup. Really liked what you did with the Sencha Touch Tutorials and am looking forward to see how the jQuery Mobile series goes. I haven’t looked much at jQuery Mobile since investigating it several months back. There seemed to be a lot of flickering and general usability issues that could not be overlooked. Have those improved in your opinion?

  2. says

    The step that describes the specrunner.html has been poorly formatted, therefore it is not “copy & paste”-able.




    css” rel=”stylesheet” type=”text/css” />

  3. says

    Thanks for putting this together; great stuff–especially starting off with BDD. Each person who follows this practice (and sticks with it) will be thanking you for years.

    One suggestion: So that readers can focus on the core concepts you are presenting rather than trivial (but vital) syntax, I suggest you include the essential HTML tags (like , , , etc.) in your examples.

    I realize this is trivial–and that anyone should know these basics–but including these details in your examples takes just seconds, and it would maximize the chances that less experienced readers stick with the content and absorb the important concepts you are illustrating.

    Keep up the great work. Thanks again.

Trackbacks

Leave a Reply

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