Cordova (aka PhoneGap) 3.x Tutorial

July 2nd 2014: An updated version of this tutorial is available here

cordova

In this tutorial, you’ll create a fully functional employee directory application with Cordova.

What you will learn:

  • How to use different local data storage strategies.
  • How to use several Cordova APIs such as Geolocation, Contacts, and Camera.
  • How to handle specific mobile problems such as touch events, scrolling, styling, page transitions, etc.
  • How to build an application using a single-page architecture and HTML templates.
  • How to build (compile and package) an application locally using the Cordova CLI (Command Line Interface).

Requirements:

To complete this workshop, all you need is a code editor, a modern browser, and a connection to the Internet.

A working knowledge of HTML and JavaScript is assumed, but you don’t need to be a JavaScript guru.

A mobile device or a Mobile SDK is not a requirement for this tutorial. However, if you want to run and test the application on a mobile device or on an emulator for a mobile platform supported by Cordova, you need the Mobile SDK for that platform (iOS SDK, Android SDK, etc.) installed on your system. If you don’t want to install a Mobile SDK, you’ll be able to test your application in a browser on your computer.

Part 1: Creating a Cordova Project


  1. Make sure an up-to-date version of Node.js is installed on your system.
  2. Open Terminal (Mac) or a Command window (Windows), and type the following command to install the Cordova CLI:
    npm install -g cordova

    or on a Mac:

    sudo npm install -g cordova
  3. Navigate to a directory where you store projects on your system. For example:
    cd ~/Projects
  4. Create a project called “workshop”:
    cordova create workshop com.yourname.workshop Workshop
  5. Navigate to the project directory:
    cd workshop
  6. Add the platforms you want to support. For example, to add support for iOS and Android, type:
    cordova platforms add ios
    cordova platforms add android
  7. Make sure you are in the “workshop” directory, and add basic plugins to your projects:
    cordova plugin add org.apache.cordova.device
    cordova plugin add org.apache.cordova.console
  8. Examine the directory structure under workshop.


Part 2: Building a Cordova Project


iOS

You need the iOS SDK installed on your computer to build an iOS version of your application using the steps below.

cordova build ios

The project is built in the workshop/platforms/ios folder. Double-click Workshop.xcodeproj to open the project in XCode, and run it in the emulator or on your device.

You can also run the application in the iOS emulator directly from the command line. First install ios-sim:

sudo npm install -g ios-sim

Then run the application in the iOS emulator:

cordova emulate ios

Android

You need the Android SDK installed on your computer to build an Android version of your application using the steps below.

To build the project in the workshop/platforms/android folder and run it on an Android device connected to your computer using a USB cable, type:

cordova run android

To build the project in the workshop/platforms/android folder and run it in the Android emulator, type:

cordova emulate android

 

Part 3: Setting Up the Workshop Files


  1. Download the assets for the workshop here or clone this repository

    .

  2. Unzip the file anywhere on your file system.
  3. Delete the contents of your project’s workshop/www folder with the exception of the config.xml file.
  4. Copy the contents of cordova-tutorial-master/www into your project’s workshop/www folder.
  5. Build and test your application: If you have a Mobile SDK installed on your system, repeat the steps in Part 2 above. If you don’t, simply open index.html in a browser on your computer.
  6. Type a few characters in the search box to search employees by name. Clicking an employee link doesn’t produce any result at this time.
Step-by-step solutions are available in the cordova-tutorial-master/solutions folder


Part 4: Choosing a Local Storage Option


Step 1: Explore different persistence mechanisms

Open the following files in workshop/js/adapters, and explore the different persistence adapters:

  1. MemoryAdapter (in memory-adapter.js)
  2. JSONPAdapter (in jsonp-adapter.js)
  3. LocalStorageAdapter (in localstorage-adapter.js)
  4. WebSqlAdapter (in websql-adapter.js)
Step 2: Test the application with different persistence mechanisms

The application is initially configured to work with the in-memory datastore. To change the local persistence mechanism for the application:

  1. In index.html: instead of memory-adapter.js, import the .js file for the data adapter of your choice: jsonp-adapter.js, localstorage-adapter.js, or websql-adapter.js.
  2. In js/app.js, replace the instantiation of MemoryAdapter with the instantiation of the data adapter you imported in the previous step: JSONPAdapter, LocalStorageAdapter, or WebSqlAdapter.
  3. Test the application.


Part 5: Using Native Notification


A default JavaScript alert gives away the fact that your application is not native. In this section, we set up the basic infrastructure to display native alerts when the application is running on a device, and fall back to default JavaScript alerts when it is running in the browser.
  1. Add the native dialogs plugin to your project:
    cordova plugin add org.apache.cordova.dialogs
  2. In index.html, add the following script tag (as the first script tag at the bottom of the body):
    <script src="cordova.js"></script>
    
    This instructs the Cordova CLI to inject a platform specific version of cordova.js at build time. In other words, cordova.js doesn’t need to be (and shouldn’t be) present in your project/www folder.
  3. When running on a device with the navigator.notification object available (the dialogs plugin is installed), override the window.alert() function and replace its default implementation with a call to navigator.notification.alert(). Add this code to the “Event Registration” block:
    document.addEventListener('deviceready', function () {
        if (navigator.notification) { // Override default HTML alert with native dialog
            window.alert = function (message) {
                navigator.notification.alert(
                    message,    // message
                    null,       // callback
                    "Workshop", // title
                    'OK'        // buttonName
                );
            };
        }
    }, false);
    
  4. Test the application: click the Help button.

    When you run the application in the browser, you should see a standard browser alert.
    When you run the application on your device, you should see a native alert.


Part 6: Avoid the 300ms Click Delay


  1. Test the application on your iOS device or in the iOS emulator: Tap the Help button, and notice the delay before the dialog appears.
    This delay occurs because the operating system is waiting roughly 300ms to see if the user is going to tap the target again (and therefore perform a double-tap).
  2. In index.html, add the following script tag:
    <script src="lib/fastclick.js"></script>
    

    FastClick is an open source library built by the Financial Times. More information here.

  3. In app.js, register FastClick inside the deviceready event handler.
    FastClick.attach(document.body);
  4. Test the application: Click the Help button. The message should now appear without delay.


Part 7: Setting Up a Single-Page Application


A “Single-Page Application” is a web application that lives within a single HTML page. The “views” of the application are injected into and removed from the DOM as needed as the user navigates through the app. A single-page application architecture is particularly well suited for mobile apps:
  • The absence of page refreshes provides a more fluid and closer to native experience.
  • The UI is entirely created at the client-side with no dependency on a server to create the UI, making it an ideal architecture for applications that work offline.

In this section, we set up the basic infrastructure to turn Employee Directory into a single-page application.

  1. In index.html: remove the HTML markup inside the body tag (with the exception of the script tags).
  2. Inside the immediate function in app.js, define a function named renderHomeView() (right after the findByName function). Implement the function to programmatically add the Home View markup to the body element.
    function renderHomeView() {
        var html =
            "<h1>Directory</h1>" +
            "<input class='search-key' type='search' placeholder='Enter name'/>" +
            "<ul class='employee-list'></ul>";
        $('body').html(html);
        $('.search-key').on('keyup', findByName);
    }
    
  3. Modify the data adapter initialization logic: when the adapter has been successfully initialized, call the renderHomeView() function to programmatically display the Home View.
    var adapter = new MemoryAdapter();
    adapter.initialize().done(function () {
        renderHomeView();
    });
    
  4. Since you moved the registration of the keyup event inside the renderHomeView() function, make sure you remove the original event registration in the Event Registration section.
  5. Since the Help button is no longer there, remove the click event handler for the help button (in the Event Registration section).
  6. Test the application.


Part 8: Using Handlebars Templates


Writing HTML fragments in JavaScript and programmatically inserting them into the DOM is tedious. It makes your application harder to write and harder to maintain. HTML templates address this issue by decoupling the UI definition (HTML markup) from your code. There are a number of great HTML template solutions, including Mustache.js, Handlebars.js, and Underscore.js to name a few.

In this section, we create two templates to streamline the code of the Employee Directory application. We use Handlebars.js but the same result can be achieved using the other HTML template solutions.

Modify index.html as follows:

  1. Add a script tag to include the handlebars.js library:
    <script src="lib/handlebars.js"></script>
    
  2. Create an HTML template to render the Home View. Add this script tag as the first child of the body tag:
    <script id="home-tpl" type="text/x-handlebars-template">
        <div class="topcoat-navigation-bar">
            <div class="topcoat-navigation-bar__item center full">
                <h1 class="topcoat-navigation-bar__title">Employee Directory</h1>
            </div>
        </div>
        <div class="search-bar">
            <input type="search" placeholder="search" class="topcoat-search-input search-key">
        </div>
        <div class="topcoat-list__container">
            <ul class="topcoat-list list employee-list"></ul>
        </div>
    </script>
    
  3. Create an HTML template to render the employee list items. Add this script tag immediately after the previous one:
    <script id="employee-li-tpl" type="text/x-handlebars-template">
        {{#.}}
        <li class="topcoat-list__item">
            <a href="#employees/{{id}}">
                <img src="assets/pics/{{pic}}">
                <p>{{firstName}} {{lastName}}</p>
                <p>{{title}}</p>
                <span class="chevron"></span><span class="count">{{reports}}</span>
            </a>
        </li>
        {{/.}}
    </script>
    
  4. Add topcoat-mobile-light.css and styles.css to the head of index.html
    <link href="assets/topcoat/css/topcoat-mobile-light.css" rel="stylesheet">
    <link href="assets/css/styles.css" rel="stylesheet">
    

Modify the immediate function in app.js as follows:

  1. Immediately before the adapter variable declaration, declare two variables that hold the compiled version of the templates defined above:
    var homeTpl = Handlebars.compile($("#home-tpl").html());
    var employeeLiTpl = Handlebars.compile($("#employee-li-tpl").html());
    
  2. Modify renderHomeView() to use the homeTpl template instead of the inline HTML:
    function renderHomeView() {
        $('body').html(homeTpl());
        $('.search-key').on('keyup', findByName);
    }
    
  3. Modify findByName() to use the employeeLiTpl template instead of the inline HTML:
    function findByName() {
        adapter.findByName($('.search-key').val()).done(function (employees) {
            $('.employee-list').html(employeeLiTpl(employees));
        });
    }
    
  4. Test the application.


Part 9: Creating a View Class


It’s time to provide the application with some structure. If we keep adding all the core functions of the application to the immediate function that bootstraps the app, it will very quickly grow out of control. In this section we create a HomeView object that encapsulates the logic to create and render the Home view.

Step 1: Create the HomeView Class
  1. Create a file named HomeView.js in the js directory, and define a HomeView constructor implemented as follows:
    var HomeView = function (adapter, template, listItemTemplate) {
    
    }
    
    The constructor function takes three arguments: the data adapter, the Home View template, and the employee list item template.
  2. Define an initialize() function inside the HomeView constructor. Define a div wrapper for the view. The div wrapper is used to attach the view-related events. Invoke the initialize() function inside the HomeView constructor function.
    var HomeView = function (adapter, template, listItemTemplate) {
    
        this.initialize = function () {
            // Define a div wrapper for the view. The div wrapper is used to attach events.
            this.el = $('<div/>');
            this.el.on('keyup', '.search-key', this.findByName);
        };
    
        this.initialize();
    
    }
    
  3. Move the renderHomeView() function from app.js to the HomeView class. To keep the view reusable, attach the HTML to the div wrapper (this.el) instead of the document body. Because the function is now encapsulated in HomeView, you can also rename it from renderHomeView() to just render().
    this.render = function() {
        this.el.html(template());
        return this;
    };
    
  4. Move the findByName() function from app.js to HomeView.
    this.findByName = function() {
        adapter.findByName($('.search-key').val()).done(function(employees) {
            $('.employee-list').html(listItemTemplate(employees));
        });
    };
    
Step 2: Using the Home View
  1. In index.html, add a script tag to include HomeView.js (just before the script tag for app.js):
    <script src="js/HomeView.js"></script>
    
  2. In app.js, remove the renderHomeView() function from the immediate function.
  3. Remove the findByName() function from the immediate function.
  4. Modify the adapter initialization logic to display the Home View when the adapter has been successfully initialized. Pass the adapter, the Home View template, and the employee list item template as arguments to the Home View constructor.
    adapter.initialize().done(function () {
        $('body').html(new HomeView(adapter, homeTpl, employeeLiTpl).render().el);
    });
    
  5. Test the application


Part 10: Implementing Native Scrolling


Test the application. Specifically, test the list behavior when the list is bigger than the browser window (or the screen). Notice that the entire view (including the header) is scrolling. To anchor the header at the top of the screen, and scroll the employee list only:

  1. In the Home Page template, add a css class named scroller to the div surrounding the employee list ul.
    <div class="topcoat-list__container scroller">
        <ul class="topcoat-list list employee-list"></ul>
    </div>
    
  2. In assets/css/styles.css define the scroller class as follows:
    .scroller {
        overflow: auto;
        -webkit-overflow-scrolling: touch;
        position: absolute;
        top: 141px;
        bottom: 0px;
        left: 0px;
        right: 0px;
    }
    
  3. Test the application.

On iOS, you can still scroll down the web view (including the header). To disable this behavior, open config.xml in the workshop/www folder and add the following preference as the last line within the widget tag:

<preference name="DisallowOverscroll" value="true" />


If the platforms you target support touch-based scrolling of fixed regions, this approach is all you need. If not, you’ll need to implement a programmatic approach, typically with the help of a library such as iScroll.


Part 11: View Routing


In this section, we add an employee details view. Since the application now has more than one view, we also add a simple view routing mechanism that uses the hash tag to determine whether to display the home view or the details view for a specific employee.

Step 1: Create the employee template

Open index.html and add a template to render a detailed employee view:

<script id="employee-tpl" type="text/x-handlebars-template">
    <div class="topcoat-navigation-bar">
        <div class="topcoat-navigation-bar__item left quarter">
            <a class="topcoat-icon-button--quiet back-button" href="#">
                <span class="topcoat-icon topcoat-icon--back"></span>
            </a>
        </div>
        <div class="topcoat-navigation-bar__item center half">
            <h1 class="topcoat-navigation-bar__title">Employee</h1>
        </div>
    </div>
    <div class='details'>
        <img src="assets/pics/{{pic}}" class="employee-image">
        <h1>{{firstName}} {{lastName}}</h1>
        <h2>{{title}}</h2>
        <h2>{{city}}</h2>
        <div class="topcoat-list__container scroller">
            <ul class="topcoat-list list actions">
                {{#if managerId}}
                <li class="topcoat-list__item"><a href="#employees/{{managerId}}"><p>View Manager</p><p>{{managerName}}</p><div class="action-icon icon-manager"/></a></li>
                {{/if}}
                <li class="topcoat-list__item"><a href="tel:{{officePhone}}"><p>Call Office</p><p>{{officePhone}}</p><div class="action-icon icon-call"/></a></li>
                <li class="topcoat-list__item"><a href="tel:{{cellPhone}}"><p>Call Cell</p><p>{{cellPhone}}</p><div class="action-icon icon-call"/></a></li>
                <li class="topcoat-list__item"><a href="sms:{{cellPhone}}"><p>SMS</p><p>{{cellPhone}}</p><div class="action-icon icon-sms"/></a></li>
                <li class="topcoat-list__item"><a href="mailto:{{email}}"><p>Email</p><p>{{email}}</p><div class="action-icon icon-mail"/></a></li>
            </ul>
        </div>
    </div>
</script>
Step 2: Create the EmployeeView class
  1. Create a file named EmployeeView.js in the js directory, and define an EmployeeView constructor implemented as follows:
    var EmployeeView = function(adapter, template, employee) {
    
    }
    
  2. Define an initialize() function inside the HomeView constructor. Define a div wrapper for the view. The div wrapper is used to attach the view related events. Invoke the initialize() function inside the EmployeeView constructor function.
    var EmployeeView = function(adapter, template, employee) {
    
        this.initialize = function() {
            this.el = $('<div/>');
        };
    
        this.initialize();
    
    }
    
  3. Define a render() function implemented as follows:
    this.render = function() {
        this.el.html(template(employee));
        return this;
    };
    
  4. In index.html, add a script tag to include EmployeeView.js (just before the script tag for app.js):
    <script src="js/EmployeeView.js"></script>
    
Step 3: Implement View Routing
  1. Open app.js. In the Local Variables section, declare a variable named employeeTpl that holds the compiled template for the employee details view:
    var employeeTpl = Handlebars.compile($("#employee-tpl").html());
    
  2. In the Local Variables section, declare a variable named detailsURL that holds a regular expression to match employee details URLs.
    var detailsURL = /^#employees\/(\d{1,})/;
    
  3. In the Event Registration section, add an event listener to listen to URL hash tag changes:
    $(window).on('hashchange', route);
    
  4. In the Local Functions section, define a route() function to route requests to the appropriate view:
    • If there is no hash tag in the URL, display the HomeView
    • If there is a hash tag matching the pattern for an employee details URL, display an EmployeeView for the specified employee.
    function route() {
        var hash = window.location.hash;
        if (!hash) {
            $('body').html(new HomeView(adapter, homeTpl, employeeLiTpl).render().el);
            return;
        }
        var match = hash.match(detailsURL);
        if (match) {
            adapter.findById(Number(match[1])).done(function(employee) {
                $('body').html(new EmployeeView(adapter, employeeTpl, employee).render().el);
            });
        }
    }
    
  5. Modify the adapter initialization logic to call the route() function when the adapter has been successfully initialized:
    adapter.initialize().done(function () {
        route();
    });
    
  6. Test the application.


Part 12: Using the Location API


In this section, we add the ability to tag an employee with his/her location information. In this sample application, we display the raw information (longitude/latitude) in an alert. In a real-life application, we would typically save the location in the database as part of the employee information and show it on a map.

The code below works when running the application as a Cordova app on your device. It should also work in Chrome on the desktop when the page is served with the http:// protocol, and in Firefox, regardless of the protocol (http:// or file://).
  1. Add the geolocaton plugin to your project
    cordova plugin add org.apache.cordova.geolocation
  2. In index.html, add the following list item to the employee-tpl template:
    <li class="topcoat-list__item"><a href="#" class="add-location-btn"><p>Add Location</p></a></li>
    
  3. In the initialize() function of EmployeeView, register an event listener for the click event of the Add Location list item.
    this.el.on('click', '.add-location-btn', this.addLocation);
    

    Make sure you add this line as the last line of the initialize() function (after this.el is assigned).

  4. In EmployeeView, define the addLocation event handler as follows:
    this.addLocation = function(event) {
        event.preventDefault();
        navigator.geolocation.getCurrentPosition(
            function(position) {
                alert(position.coords.latitude + ',' + position.coords.longitude);
            },
            function() {
                alert('Error getting location');
            });
        return false;
    };
    
  5. Test the Application


Part 13: Using the Contacts API


In this section, we use the Cordova Contacts API to provide the user with the ability to add an employee to the device’s contact list.

The code below only works when running the application on your device as a Cordova app. In other words, you can’t test it in a browser on your computer.
  1. Add the contacts plugin to your project
    cordova plugin add org.apache.cordova.contacts
  2. In index.html, add the following list item to the employee template:
    <li class="topcoat-list__item"><a href="#" class="add-contact-btn"><p>Add to Contacts</p></a></li>
    
  3. In the initialize() function of EmployeeView, register an event listener for the click event of the Add to Contacts list item:
    this.el.on('click', '.add-contact-btn', this.addToContacts);
    
  4. In EmployeeView, define the addToContacts event handler as follows:
    this.addToContacts = function(event) {
        event.preventDefault();
        console.log('addToContacts');
        if (!navigator.contacts) {
            alert("Contacts API not supported", "Error");
            return;
        }
        var contact = navigator.contacts.create();
        contact.name = {givenName: employee.firstName, familyName: employee.lastName};
        var phoneNumbers = [];
        phoneNumbers[0] = new ContactField('work', employee.officePhone, false);
        phoneNumbers[1] = new ContactField('mobile', employee.cellPhone, true);
        contact.phoneNumbers = phoneNumbers;
        contact.save();
        return false;
    };
    
  5. Test the Application


Part 14: Using the Camera API


In this section, we use the Cordova Camera API to provide the user with the ability to take a picture of an employee, and use that picture as the employee’s picture in the application. We do not persist that picture in this sample application.

The code below only works when running the application on your device as a Cordova app. In other words, you can’t test it in a browser on the desktop.
  1. Add the camera plugin to your project
    cordova plugin add org.apache.cordova.camera
  2. In index.html, add the following list item to the employee template:
    <li class="topcoat-list__item"><a href="#" class="change-pic-btn"><p>Change Picture</p></a></li>
    
  3. In the initialize() function of EmployeeView, register an event listener for the click event of the Change Picture list item:
    this.el.on('click', '.change-pic-btn', this.changePicture);
    
  4. In EmployeeView, define the changePicture event handler as follows:
    this.changePicture = function(event) {
        event.preventDefault();
        if (!navigator.camera) {
            alert("Camera API not supported", "Error");
            return;
        }
        var options =   {   quality: 50,
                            destinationType: Camera.DestinationType.DATA_URL,
                            sourceType: 1,      // 0:Photo Library, 1=Camera, 2=Saved Album
                            encodingType: 0     // 0=JPG 1=PNG
                        };
    
        navigator.camera.getPicture(
            function(imageData) {
                $('.employee-image', this.el).attr('src', "data:image/jpeg;base64," + imageData);
            },
            function() {
                alert('Error taking picture', 'Error');
            },
            options);
    
        return false;
    };
    
  5. Test the Application


Part 15: Sliding Pages with CSS Transitions


Modify index.html as follows:

  1. Add pageslider.css inside the head tag in index.html:
    <link href="assets/css/pageslider.css" rel="stylesheet">
    
  2. Add a script tag to include the pageslider.js library:
    <script src="lib/pageslider.js"></script>
    
PageSlider is a micro library I host on GitHub here.

Modify app.js as follows:

  1. In the Local Variables section, declare an instance of the PageSlider object as follows:
    var slider = new PageSlider($('body'));
    
  2. In the route() function, replace the calls to $(‘body’).html() with calls to slider.slidePage() passing the same argument to the function.
    slider.slidePage(new HomeView(adapter, homeTpl, employeeLiTpl).render().el);
    

    and

    slider.slidePage(new EmployeeView(adapter, employeeTpl, employee).render().el);
    


Part 16: Explore Other Implementations

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值