Monday, January 19, 2015

Using Ionic with Windows 8, Windows Phone + Cordova


UPDATE 4/29/2015: See this updated post for Visual Studio 2015 RC! (Below is for CTP3)

Ionic has been gaining in popularity as a free and open source mobile development framework. It leverages AngularJS and SASS to provide a quick start to mobile development for iOS, Android and Windows.

But getting Ionic to work when targeting Windows 8 or Windows Phone apps with CTP3 of Visual Studio Tools for Apache Cordova isn't quite straightforward due to some security context restrictions in Windows 8 apps.

So here is a walk through for getting started with Ionic in VSCordova, along with the "gotchas" for Windows apps:
  1. First note that these steps are for CTP3 of VSCordova! Future releases will likely fix some of the issues presented here.
  2. Create a new Blank App (Apache Cordova) project:

  3. Install Ionic. Here we will use Nuget, but in the future you will likely be able to use Bower for JS packages.
    • Select Tools / NuGet Package Manager / Package Manager Console
    • type in:

      Install-Package ionic
    • be patient, this takes a bit

  4. Install JQuery 2. This is required because it fixes some security context restrictions in Win8 apps.
    • From the NuGet prompt, type in:

      Install-Package jQuery
  5. Let's replace the contents of index.html with a basic ionic template and the required includes:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
        <title>Ionic VSCordova</title>
        <link href="Content/ionic.css" rel="stylesheet" />
        <link href="css/index.css" rel="stylesheet" />
        <script src="cordova.js"></script>
        <script src="scripts/platformOverrides.js"></script>
        <script src="scripts/jquery-2.1.3.js"></script>
        <script src="scripts/ionic.bundle.js"></script> 
        <script src="scripts/index.js"></script>
    </head>
    <body ng-app="ionicApp" ng-csp>

        <ion-nav-bar class="bar-positive">
            <ion-nav-back-button class="button-icon ion-arrow-left-c">
            </ion-nav-back-button>
        </ion-nav-bar>

        <ion-nav-view></ion-nav-view>

        <script id="templates/tabs.html" type="text/ng-template">
            <ion-tabs class="tabs-icon-top tabs-positive">

                <ion-tab title="Home" icon="ion-home" href="#/tab/home">
                    <ion-nav-view name="home-tab"></ion-nav-view>
                </ion-tab>

                <ion-tab title="About" icon="ion-ios7-information" href="#/tab/about">
                    <ion-nav-view name="about-tab"></ion-nav-view>
                </ion-tab>

                <ion-tab title="Contact" icon="ion-ios7-world" ui-sref="tabs.contact">
                    <ion-nav-view name="contact-tab"></ion-nav-view>
                </ion-tab>

            </ion-tabs>
        </script>

    </body>

    </html>

  6. Now let's add in our Angular code to initialize our controller and set up our views. Open index.js and paste the following code right after the:

    "use strict";

     
       var app = angular.module('ionicApp', ['ionic'])

        app.config(function ($stateProvider, $urlRouterProvider) {

            $stateProvider
              .state('tabs', {
                  url: "/tab",
                  abstract: true,
                  templateUrl: "templates/tabs.html"
              })
              .state('tabs.home', {
                  url: "/home",
                  views: {
                      'home-tab': {
                          templateUrl: "partials/home.html",
                          controller: 'HomeTabCtrl'
                      }
                  }
              })
              .state('tabs.personnel', {
                  url: "/personnel",
                  views: {
                      'home-tab': {
                          templateUrl: "partials/personnel.html",
                          controller: 'PersonnelTabCtrl'
                      }
                  }
              })
              .state('tabs.about', {
                  url: "/about",
                  views: {
                      'about-tab': {
                          templateUrl: "partials/about.html"
                      }
                  }
              })
              .state('tabs.contact', {
                  url: "/contact",
                  views: {
                      'contact-tab': {
                          templateUrl: "partials/contact.html"
                      }
                  }
              });

            $urlRouterProvider.otherwise("/tab/home");

        })

        app.controller('HomeTabCtrl', function ($scope) {
            console.log('HomeTabCtrl');
        });

        app.controller('PersonnelTabCtrl', function ($scope) {
            $scope.items = [
                { id: 1, name: "Kirk, James T." },
                { id: 2, name: "McCoy, Leonard" },
                { id: 3, name: "Scott, Montgomery" },
                { id: 4, name: "Uhura, Nyota" },
                { id: 5, name: "Sulu, Hikaru" },
                { id: 6, name: "Chekov, Pavel" },
                { id: 7, name: "Chapel, Christine" },
                { id: 8, name: "Rand, Janice" }
            ];
        });



  7. The code above references four partials: about, contact, home, and personnel. Let's go ahead and create those. Create a new subfolder in the project named "partials" and add the following files:

    about.html
    contact.html
    home.html
    personnel.html


    Inside each of the four partials above, create an ionic view and add some content inside (it doesn't matter what, be creative)... and set the view-title to an appropriate string to display in the header.

  8. <
    ion-view view-title="About">
        <ion-content class="padding">
            <p>ABOUT: this is just a little demo of Ionic working in VSCordova!</p>
        </ion-content>
    </ion-view>
  9. For home.html, let's add a View that links to personnel.

  10. <
    ion-view view-title="Home">
        <ion-content class="padding">
             <a class="button icon icon-right ion-chevron-right" href="#/tab/personnel">Personnel List</a>
        </ion-content>
    </ion-view>
  11. For the personnel.html, let's show an ionic list:

  12. <
    ion-view view-title="Personnel">
        <ion-content class="padding">
            <p>This ion list directive is great, but be sure to also checkout the collection repeat directive when scrolling through large lists.</p>
            <ion-list>
                <ion-item ng-repeat="item in items"
                          item="item">
                    {{ item.id }}: {{ item.name }}
                </ion-item>
            </ion-list>
            <p>
                <a class="button icon ion-home" href="#/tab/home"> Home </a>
            </p>
        </ion-content>
    </ion-view>

  13. Run the project using the Windows or Windows Phone Emulator, and you'll get an ugly blank screen! If you look at the JavaScript Console in Visual Studio, you will see:

    Error: [$compile:tplrt] Template for directive 'ionTabNav' must have exactly one root element. 

    This error in CTP3 is due to an issue in winstore-jscompat.js, which is used by the Cordova tooling to wrap security context issues in Win8 Store apps. To fix this problem, open the following file in the project:

    \merges\windows\scripts\winstore-jscompat.js

    ... and replace it with the fork provided here:

    https://github.com/ClemMakesApps/winstore-jscompat/blob/master/winstore-jscompat.js
  14. Run the project again, and you should be good to go! Here is a screenshot of Ionic running in a Windows Phone 8 Emulator...