Getting Started With Ionic & NgCordova

In my most recent engagement, I’ve been working on a hybrid mobile app built using Ionic and ngCordova. Functionality-wise, the app itself is fairly straightforward, but since this is my first project that directly targets mobile devices (as opposed to responsive web), I’ve learned a few things that I think are worth sharing. Like most posts, the information contained has been cobbled together from many different sources during my time on this project. The purpose of this post is to walk through how to configure a new, fully testable project using Ionic and ngCordova. Like always, the code for this post is in Github at (https://github.com/jrodenbostel/getting-started-with-ionic-and-ngcordova).

Ionic (http://ionicframework.com) Hybrid mobile app development frameworks have been around for quite some time now. The Ionic Framework is one of the better entries I’ve seen to date. Based on current web technologies and frameworks (HTML5, CSS3, AngularJs (https://angularjs.org)), and leveraging a tried and true native container that runs on many devices (http://cordova.apache.org), Ionic provides a mostly-familiar starting point for folks new to mobile development. On top of that, Ionic is also packaged with a nice set of UI components and icons that help applications look nice as well as function smoothly.

ngCordova (http://ngcordova.com) The ngCordova project basically wraps the Cordova API to make it more Angular-friendly by giving the developer the ability to inject Cordova components as dependencies in your Angular controllers, services, etc. This project is still new and changing rapidly, but simplifies development greatly, and makes code that calls Cordova from within Ionic more readable and more easily testable.

Others (Yeoman, Grunt, Bower, Karma, Protractor, Mocha, Chai) These are the tools we’ll use to build our app. They are used for a variety of things, all are introduced from the same source – Yeoman (http://yeoman.io). Remember how revolutionary the scaffolding features of Rails were when they first surfaced? Yeoman provides scaffolding like that, and anyone can write a generator. There happens to be a generator for Ionic, and in my opinion, it’s all but necessary to use. Out of the box, you get a working app shell, a robust Grunt (http://gruntjs.com) script for app assembly, packaging, emulation, etc, dependency injection via Bower (http://bower.io), and example Mocha (http://mochajs.org) tests running via Karma (http://karma-runner.github.io/0.12/index.html). The only item we’ll add is support for end to end integration tests with Protractor (https://github.com/angular/protractor).

Prerequisites Before we start, you’ll need to have node.js (http://nodejs.org) and npm (https://www.npmjs.org) installed on your machine. Installation instructions can be found here (http://nodejs.org/download/) and here (http://blog.npmjs.org/post/85484771375/how-to-install-npm).

Step 1 – Scaffold Install Yeoman using the following command:

npm install -g yo

Install the Ionic Generator for Yeoman using the following command:

npm install -g generator-ionic

Create a folder for your project and use the newly install generator to build the shell of an Ionic app. Be sure you’re executing these commands in the root of your project folder. You can play around and answer the questions however you’d like. If you’re interested in following along, I’ve included the answers I’ve used and the relevant output below:

Justins-MacBook-Pro:getting-started-with-ionic-and-ngcordova justin$ yo ionic
    _             _
   (_)           (_)
    _  ___  _ __  _  ___
   | |/ _ \| '_ \| |/ __|
   | | (_) | | | | | (__
   |_|\___/|_| |_|_|\___|

[?] Would you like to use Sass with Compass (requires Ruby)? Yes
Created a new Cordova project with name "GettingStartedWithIonicAndNgcordova" and id "com.example.GettingStartedWithIonicAndNgcordova"
[?] Which Cordova plugins would you like to include? org.apache.cordova.console, org.apache.cordova.device
[?] Which starter template [T] or example app [A] would you like to use? [T] Tabs

Install plugins registered at plugins.cordova.io: grunt plugin:add:org.apache.cordova.globalization
Or install plugins direct from source: grunt plugin:add:https://github.com/apache/cordova-plugin-console.git

Installing selected Cordova plugins, please wait.
Installing starter template. Please wait

     info ... Fetching http://github.com/diegonetto/ionic-starter-tabs/archive/master.tar.gz ...
     info This might take a few moments

Step 2 – Run! Validate there weren’t any issues running the generator by starting the app. The Yeoman generator we’ve used includes a full-featured build script that includes a variety of ways to start up our app. We’ll use more features of the script later, but for a complete list of available commands visit the generator’s Github page (https://github.com/diegonetto/generator-ionic).For now, we’ll serve the app with the simple http server included as part of our sample app (courtesy of the Yeoman generator) using the following command (from the root of your project folder:

grunt serve

This should have started the server and opened your default browser. In the browser, you should see something similar to the screenshot below:

Screen Shot 2015-02-04 at 2.39.34 PM

Step 3 – ngCordova We’re off to a nice start – a fully functional app, running in the browser, with automated chai tests (using Karma (http://karma-runner.github.io/0.12/index.html) via grunt test) and some static code analysis (using jshint (http://jshint.com/) via grunt jshint) in place, all as a result of our Yeoman generator. If we explore the generated code, we notice that the app itself is very simple. As soon as we start writing code that depends on device APIs (checking for a network connection, identifying the current device, etc), we run into a problem: there’s only a global reference to Cordova, and there isn’t a nice way to inject Cordova into our Angular controllers, especially for testing. This is where ngCordova comes into play. Here, we’ll write some simple code that checks the device platform the app is currently running on, and display it on the opening screen. Let’s start by writing a test* that looks for an object in scope of the DashCtrl called ‘devicePlatform’. First, there are a few different ways to run the tests. One enables watching, but doesn’t run the tests immediately (you have to leave this on, and it runs tests as/when files in your project change), and the other just runs the tests on demand. With watching:

grunt test

On demand:

grunt karma

At the bottom of ‘/test/spec/controllers.js’, add a test for the DashCtrl with the following code:

describe('Controller: DashCtrl', function () {

  var should = chai.should();

  // load the controller's module
  beforeEach(module('GettingStartedWithIonicAndNgcordova'));

  var DashCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    DashCtrl = $controller('DashCtrl', {
      $scope: scope
    });
  }));

  it('should inspect the current devicePlatform', function () {
    scope.devicePlatform.should.equal('ios');
  });

});

Immediately after adding that code to our test file (if you’re using ‘grunt test’) or running the tests on demand (using ‘grunt karma’), we should see results in our terminal window, and we should see that this test has failed because ‘devicePlatform’ is undefined in the DashCtrl’s scope.

PhantomJS 1.9.8 (Mac OS X) Controller: DashCtrl should inspect the current devicePlatform FAILED
TypeError: 'undefined' is not an object (evaluating 'scope.devicePlatform.should')

Next, we’ll install ngCordova and implement the logic this test is exercising. Detailed instructions on installing ngCordova can be found here (http://ngcordova.com/docs/install/).The simplest install is via Bower using the following command:

bower install ngCordova

Add a reference to the newly installed ngCordova to your app/index.html file, above the reference to cordova, such that:

    <script src="lib/ngCordova/dist/ng-cordova.js"></script>
    <!-- cordova script (this will be a 404 during development) -->
    <script src="cordova.js"></script>

To get the device OS, we’ll need to use Cordova’s Device plugin. If you haven’t already, we’ll need to make sure that it’s installed. Use the following command to install it:

cordova plugin add org.apache.cordova.device

Next, we’ll add ngCordova to our project as a module. In app/scripts/app.js, change this line:

angular.module('GettingStartedWithIonicAndNgcordova', ['ionic', 'config', 'GettingStartedWithIonicAndNgcordova.controllers', ‘GettingStartedWithIonicAndNgcordova.services’])

to:

angular.module('GettingStartedWithIonicAndNgcordova', ['ionic', 'config', 'GettingStartedWithIonicAndNgcordova.controllers', 'GettingStartedWithIonicAndNgcordova.services', 'ngCordova'])

Next, let’s write the code that adds the device platform to the DashCtrl scope. Start by injecting the device plugin into the DashCtrl using the code below:

.controller('DashCtrl', function($scope, $cordovaDevice) {
})

Then create the devicePlatform scope variable and set it’s value to the device’s actual platform using the following code:

.controller('DashCtrl', function($scope, $cordovaDevice) {
     $scope.devicePlatform = $cordovaDevice.getPlatform();
})

Finally, add a reference to the device plugin to templates/tab-dash.html:

<ion-view title="Dashboard">
  <ion-content class="has-header padding">
    <h1>Dash</h1>
     <h2>{{devicePlatform}}</h2>
  </ion-content>
</ion-view>

You’ll notice that when we run our test again, they still fail. This is because karma tests run in the browser – the browser doesn’t interact with Cordova plugins – there’s no platform for the browser, there’s no ‘model’, there’s no device for Cordova to plug in to. We’ll need to add a few more things to get this working. At this point, if you’re interested in continuing on under the assumption that you’ll only be unit testing and never testing in the browser (which includes automated end to end testing) prior to testing on the device, you can simply mock any calls to Cordova using spies/doubles. I think there’s value in automated end to end testing and manual browser testing prior to testing on devices. I think it’s an easy and efficient way to troubleshoot your code in an environment isolated from platform dependencies. In that case, we’ll use ngCordovaMocks (and some grunt scripting) to make our unit tests pass in our development environment, we’ll add Protractor so we can test our app end-to-end prior to running on the device, and finally, we’ll run the app in the iOS emulator to complete our validation.

ngCordovaMocks You might notice in the ngCordova Bower package that there are an additional set of files named ‘ng-cordova-mocks’. These compliment ngCordova by providing empty implementations of the services that ngCordova wraps, which can be injected in place of the standard ngCordova implementations for testing purposes. First, we’ll need to add references in two places – our application config, and our test config. For the application configuration, update our app’s module definition in /scripts/app.js:

angular.module('GettingStartedWithIonicAndNgcordova', ['ionic', 'config', 'GettingStartedWithIonicAndNgcordova.controllers', 'GettingStartedWithIonicAndNgcordova.services', 'ngCordovaMocks'])

The test config can be found in our Grunt script. In /Gruntfile.js, find the karma task. In the karma task, you should see a configuration option named ‘files’. Add a line to update it to the following:

        files: [
          '<%= yeoman.app %>/lib/angular/angular.js',
          '<%= yeoman.app %>/lib/angular-animate/angular-animate.js',
          '<%= yeoman.app %>/lib/angular-sanitize/angular-sanitize.js',
          '<%= yeoman.app %>/lib/angular-ui-router/release/angular-ui-router.js',
          '<%= yeoman.app %>/lib/ionic/release/js/ionic.js',
          '<%= yeoman.app %>/lib/ionic/release/js/ionic-angular.js',
          '<%= yeoman.app %>/lib/angular-mocks/angular-mocks.js',
          '<%= yeoman.app %>/lib/ngCordova/dist/ng-cordova-mocks.js',
          '<%= yeoman.app %>/<%= yeoman.scripts %>/**/*.js',
          'test/mock/**/*.js',
          'test/spec/**/*.js'
        ],

Now, we’ll update our test to use the new ngCordovaMocks library. We’ll add a reference to the ngCordovaMocks module, we’ll inject a decorated version of our $cordovaDevice plugin into our DashCtrl, and we’ll update our test condition accordingly.

describe('Controller: DashCtrl', function () {

  var should = chai.should(), $cordovaDevice = null, $httpBackend, DashCtrl, scope;

     beforeEach(module('GettingStartedWithIonicAndNgcordova'));
     beforeEach(module('ngCordovaMocks'));

     beforeEach(inject(function (_$cordovaDevice_) {
          $cordovaDevice = _$cordovaDevice_;
     }));

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope, _$httpBackend_) {
          $httpBackend = _$httpBackend_;
          $httpBackend.when('GET', /templates\S/).respond("");
          $cordovaDevice.platform = 'TEST VALUE';
    scope = $rootScope.$new();
    DashCtrl = $controller('DashCtrl', {
      $scope: scope
    });
          $httpBackend.flush();
  }));

  it('should inspect the current deviceType', function () {
    scope.devicePlatform.should.equal('TEST VALUE');
  });

});

You can see now we’re decorating $cordovaDevice, supplying it with a value for it’s platform property, and we’re asserting that the $cordovaDevice.getPlatform() method is returning the correct value via our $scope.devicePlatform variable. We’ve also added a mock $httpBackend (and subsequent flush) that will listen to and ignore any page requests triggered by our controller initializing. In this way, we can simulate a specific platform and exercise our code in unit tests, AND our app still runs in the browser. At this point, running in the browser without ngCordovaMocks would cause failures. To really see the value of ngCordovaMocks, we’ll add support for Protractor tests.

Protractor (https://github.com/angular/protractor) First, we’ll need to install two node modules that give us new grunt tasks: one to control a Selenium Webdriver (http://www.seleniumhq.org), and one to run our protractor tests.

npm install grunt-protractor-webdriver --save-dev
npm install grunt-protractor-runner --save-dev

While grunt-protractor-runner installs a controller for Selenium Webdriver, we still need a Selenium server. We can install a standalone Selenium server by running the following the root of our project:

node_modules/protractor/bin/webdriver-manager update

Next, we’ll update our grunt script to include configurations for the new tasks, and add a new task of our own. Include these new tasks somewhere in your grunt.initConfig object:

    protractor_webdriver: {
      all: {
        command: 'webdriver-manager start'
      }
    },
    protractor: {
      options: {
        keepAlive: true, // If false, the grunt process stops when the test fails.
        noColor: false // If true, protractor will not use colors in its output.
      },
      all: {
        options: {
          configFile: 'test/protractor-conf.js'
        }
      }
    },

Then register our custom task somewhere after grunt.initConfig:

  grunt.registerTask('test_e2e', [
    'protractor_webdriver',
    'protractor'
  ]);

We’re not doing anything special in this config – we’re basically using a grunt task to control the Selenium server we could otherwise control from the CLI, and we’re offloading much of our protractor config to a properties file. Next, create the properties file at the path listed above (test/protractor-conf.js):

exports.config = {
    seleniumAddress: 'http://localhost:4444/wd/hub',

    specs: [
        'e2e/**/*.js'
    ],

    framework: 'mocha',

    capabilities: {
        'browserName': 'chrome',
        'chromeOptions': {
          args: ['--args','--disable-web-security']
        }
    },

    /**
     * This should point to your running app instance, for relative path resolution in tests.
     */
    baseUrl: 'http://localhost:8100',
};

Last, we’ll write a new end to end test case and execute it. Create a file at /test/e2e (which is the directory we included in our protractor configuration above). I named mine ‘tabs.js’. Add the content below to the file:

var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');

chai.use(chaiAsPromised);
var expect = chai.expect;

describe('Ionic Dash Tab', function() {

  var decoratedModule = function() {
          var ngCordovaMocks = angular.module('ngCordovaMocks');
          var injector = angular.injector(['ngCordovaMocks', 'ng']);
    ngCordovaMocks.service('$cordovaDevice', function() {
               var cordovaDevice = injector.get('$cordovaDevice');
               cordovaDevice.platform = 'ios';
      return cordovaDevice;
    });
  };

  it('should have the correct heading', function() {
          browser.addMockModule('ngCordovaMocks', decoratedModule);
    browser.get('http://localhost:8100');

          var heading = element(by.css('h2'));
          expect(heading.getText()).to.eventually.equal('ios');
  });
});

In the code above, we’re decorating the $cordovaDevice service (much like we were in the unit tests), by first getting a reference to the ngCordovaMocks module, then getting a handle on the injector instance from the ngCordovaMocks module, then getting the $cordovaDevice service itself, and finally decorating the service by setting the platform to our desired value. In the test itself, we’re adding our newly decorated ngCordovaMocks module to Protractor’s browser instance. At this point, running the tests should yield positive results. You can run them using the custom task we registered (assuming your server is already running), by using the following command (be sure your server is running with ‘grunt serve’:

grunt test_e2e

Dynamic Configuration Since we’ve updated our app to only use ngCordovaMocks instead of ngCordova, we need the ability to switch between using the two seamlessly. Inspiration from this portion of the post comes from this post (http://www.ecofic.com/about/blog/getting-started-with-ng-cordova-mocks). To do this, we’ll use the grunt-text-replace grunt task. Install the grunt-text-replace node package using the following statement:

npm install grunt-text-replace --save-dev

Next, add the following config to Gruntfile.js somewhere in your grunt.initConfig object:

 replace: {
            production: {
              src: [
                '<%= yeoman.app %>/index.html',
                '<%= yeoman.app %>/<%= yeoman.scripts %>/app.js'
              ],
              overwrite: true,
              replacements:[
                { from: 'lib/ngCordova/dist/ng-cordova-mocks.js', to: 'lib/ngCordova/dist/ng-cordova.js' },
                { from: '\'ngCordovaMocks\'', to: '\'ngCordova\'' }
              ]
            },
            development: {
              src: [
                '<%= yeoman.app %>/index.html',
                '<%= yeoman.app %>/<%= yeoman.scripts %>/app.js'
              ],
              overwrite: true,
              replacements:[
                { from: 'lib/ngCordova/dist/ng-cordova.js', to: 'lib/ngCordova/dist/ng-cordova-mocks.js' },
                { from: '\'ngCordova\'', to: '\'ngCordovaMocks\'' }
              ]
            }
          },

Now we’ll add calls to these tasks to our existing Grunt tasks, as well as create a new init-development task, as seen below:

  grunt.registerTask('test', [
    'replace:development',
    'clean',
    'concurrent:test',
    'autoprefixer',
          'karma',
    'karma:unit:start',
    'watch:karma'
  ]);

  grunt.registerTask('serve', function (target) {
    if (target === 'compress') {
      return grunt.task.run(['compress', 'ionic:serve']);
    }

    grunt.config('concurrent.ionic.tasks', ['ionic:serve', 'watch']);
    grunt.task.run(['init-development', 'concurrent:ionic']);
  });

  grunt.registerTask('init', [
    'replace:production',
    'clean',
    'wiredep',
    'concurrent:server',
    'autoprefixer',
    'newer:copy:app',
    'newer:copy:tmp'
  ]);

  grunt.registerTask('init-development', [
    'replace:development',
    'clean',
    'wiredep',
    'concurrent:server',
    'autoprefixer',
    'newer:copy:app',
    'newer:copy:tmp'
  ]);

  grunt.registerTask('test_e2e', [
     'replace:development',
    'protractor_webdriver',
    'protractor'
  ]);

When we run ‘grunt serve’, the application will start in the web server, and will be running with ngCordovaMocks. From here, we can run our automated end to end tests using ‘grunt test_e2e’. We can also simply run the unit tests standalone using ‘grunt test’. You can see the way the tasks above changed to make that possible – calls to ‘replace:development’ prior to the tasks executing. In the case of the ‘test’ task, it was altered slightly to also include the ‘karma’ task to run the tests through after initial invocation, then followed by a test watcher. At this point, we can also run in our emulator without issue. To do that, we’ll quickly add the iOS platform to our project, and kick off the emulator to see the ‘real’ platform displayed on the screen.

grunt platform:add:ios

…followed by:

grunt emulate:ios

At this point, we’ll start to see a slight divergence from the way the app is functioning on the web versus in our emulator. In some cases, the device is available before all of the Cordova plugins are loaded. Furthermore, the way the screen refreshes as a result of this is also slightly different. To counter this, we’ll have to add a bit of logic to our controller to wait for the device to be ready. Update the DashCtrl with the following code:

.controller('DashCtrl', function($scope, $cordovaDevice, $ionicPlatform) {
     $ionicPlatform.ready(function() {
          $scope.devicePlatform = $cordovaDevice.getPlatform();
     });
})

Run the emulator again, and we should see the app functioning properly:

Screen Shot 2015-02-04 at 1.51.16 PM

That’s it! We should now be able to run in the browser, in the emulator, and through our automated tests with consistency. This setup has paid efficiency dividends for me on my current project, and I hope it helps folks get started on the right foot. It was a lot longer than I thought it would be. *As previously stated, I ran the Yeoman generator with the ‘Tabs’ example project option. Turns out it came with a broken test. I added a question to an open issue on this at the ngCordova project’s Github (https://github.com/driftyco/ng-cordova) page You can find the fixed test below:

'use strict';

describe('Controller: FriendsCtrl', function () {

  var should = chai.should();

  // load the controller's module
  beforeEach(module('GettingStartedWithIonicAndNgcordova'));

  var FriendsCtrl,
    scope;

  // Initialize the controller and a mock scope
  beforeEach(inject(function ($controller, $rootScope) {
    scope = $rootScope.$new();
    FriendsCtrl = $controller('FriendsCtrl', {
      $scope: scope
    });
  }));

  it('should attach a list of pets to the scope', function () {
    scope.friends.should.have.length(4);
  });
});

Part 1: Getting Started (again): Build A Web App With Spring Boot

As the latest in a long line of enhancement to the Spring framework aimed at eliminating boilerplate code and decreasing the time spent by developers setting up a new project, Spring Boot serves its purpose well. In this installment, I’ll walk through getting a project off the ground, rehashing much of what is covered in the first Spring-Boot starter guide on the Spring.io site. This will serve as the basis for upcoming installments. In this series, I’ll be using Java 1.7, Gradle 1.11, and Spring Boot 1.0.1.

Setting Up

There are Spring Boot starter versions of several different projects: web projects, data projects, integration projects, etc. Here we’ll be starting with the Spring Boot web project.

The first step is configuring your project’s directory structure. Spring Boot, whether you’re using Gradle or Maven, both follow the classic convention for directory naming and source structure. Create an example project using the structure below, substituting values in square brackets with your own:

[project-name]/src/main/java/[package-name]

In the root of our project, we’ll create a new build.gradle file:

[project-name]/build.gradle:

buildscript {
    repositories {
        maven { url "http://repo.spring.io/libs-snapshot" }
        mavenLocal()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.0.1.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'

jar {
    baseName = 'getting-started'
    version =  '0.1.0'
}

repositories {
    mavenCentral()
    maven { url "http://repo.spring.io/libs-snapshot" }
}

dependencies {
    compile("org.springframework.boot:spring-boot-starter-web")

    testCompile("junit:junit")
}


task wrapper(type: Wrapper) {
    gradleVersion = '1.11'
}

Several items to mention here:

The ‘buildscript’ closure sets up the build itself. This is where the plugins used by the build are declared, along with the repositories in which they can be found.

Following that, there are several plugin applications.

  • The java plugin provides the basic tasks related to a simple java project – compiling and packaging, among others.
  • The eclipse and idea plugins allow project files to be created for eclipse and Intellij, respectively.
  • The spring-boot plugin contains tasks that build executable jars and execute them using embedded tomcat using tasks
    like ‘bootRun’, which we’ll use often in these tutorials.

Next the ‘repositories’ closure is used to declare where the project’s dependencies will be found.

After that, there is a ‘dependencies’ closure that contains the jars required by the project

Last, there is a standard gradle pattern – the task wrapper – which is used to enforce the version of gradle being used on the project, as well as allow easy execution of the build by users who do not have gradle installed. More information on that found here.

Application Configuration

In the source root of our project ([project-name]/src/main/java/[package-name]), create a file called ‘Application.java’. This file mirrors the one found in the first Spring-Boot tutorial, but since it serves as the foundation for upcoming tutorial sections, I’ll call out a few things already explained on the Spring.io site:

[project-name]/src/main/java/[package-name]/Application.java:

package com.rodenbostel.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

You’ll notice two major components of this file: the annotations, and the main method.

The annotations are marker interfaces used to alert the framework that this is a Spring config file (@Configuration), that you’d like to scan for beans to load in the current and child packages (@ComponentScan) and that you’d like to use auto configuration settings (@EnableAutoConfiguration). Auto configuration in Spring Boot takes the concept of convention over configuration to an almost Rails or Grails-like level. It provides basic configuration of an application – where to find properties files, how properties files are named when using Spring Profiles, configuration a DispatcherServlet (note the lack of web.xml), and much more. It is worth it to look further into what auto configuration is providing to insure your application does not conflict with it, especially if you’re planning on deploying to containers that also provide libraries on the class path by default (I’m looking at you, Websphere).

The main method simply allows the application to be executed from the command line. This includes the startup of your embedded container (in our case, Tomcat). It’s worth mentioning here that the return type of SpringApplication.run is an ApplicationContext object which can be further manipulated.

Creating a Controller

Again using the similar source to that of the Spring Boot demos on the Spring.io site, we’ll begin developing our app using a sample controller configuration as a RestController to verify our app is functioning correctly. In our source root ([project-name]/src/main/java/[package-name]), create a file called ‘SampleController.java’.

[project-name]/src/main/java/[package-name]/SampleController.java:

package com.rodenbostel.sample;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    @RequestMapping("/")
    public String index() {
        return "Greetings from Spring Boot!";
    }

}

Here we are using constructs familiar since Spring MVC 3.5-ish. The RestController interface declares just that – a RESTful controller. This controller will return strings instead of views by default. The RequestMapping annotation on the only method in this class should be familiar, too. This calls for the controller to respond to requests at “/“ by executing the index() method. In this case, we’re responding with a simple String.

Execution

For this introduction, and the tutorials that follow, I’ll be using ‘gradle bootRun’ to execute the app. Upon executing ‘gradle bootRun’, in a browser, visit ‘http://localhost:8080/’, and admire you’re work. You should be looking at a simple string: ‘Greetings from Spring Boot!’.
Screen Shot 2014-04-07 at 9.21.09 PM

Preparing for Part 2

Again, this post re-hashed most of what is found on the Spring.io site on the same topic. I hope this has included some additional insight that will help get you started. Since this will be the foundation for Parts 2 and beyond, the code up to this point can be found on Github. The next installment will start where this post left off. Check back soon for the next installment: Adding views using Thymeleaf.

Beyond The Examples

In the last several years, rapid web application frameworks have been most developers first choice when starting a new project. Rails and Grails have taken the concept of convention of configuration to extremes, eliminating boilerplate code, and allowing developers to create web applications more quickly than ever. The latest incarnation of Spring and its related tools go far in emulating this kind of productivity. Spring Boot and it’s related starter projects make creating a Spring application possible with just a few lines of code. With Spring Boot recently hitting it’s 1.0 release, and Spring 4 having just been released in December, it’s an exciting time to get reacquainted with an old stand-by framework and tool set. The Getting Started projects at Spring.io serve their purpose extremely well, but as they are only intended to get a project up and running, they leave much to be discovered. The individual tutorials are great, but I had a hard time finding something that put it all together into an app similar to what would be required in the real world. I’ve recently started a new project using Spring Boot and Spring 4 and hope to share some of my experiences to date in a series of posts.

Here are the topics I hope to cover in each part:

Part 1: Getting Started (again): Build A Web App With Spring Boot
Part 2: Adding Views Using Thymeleaf
Part 3: Form Binding
Part 4: Locale Support
Part 5: Spring Security

Tips For The ExtJS Beginner

If you haven’t already, learn more about Javascript before you start
I wrote a decent amount of Javascript before I started using ExtJS. I’ve used Prototype, jQuery, & Dojo, but I’ve never written as much Javascript as I have on this project, and I’ve never unit tested any Javascript I’ve written until now. The learning curve for ExtJS is not steep, but first learning the basics of Javascript – best practices, common mistakes, design patterns – will drastically improve the quality of your code. You can write a fully-functioning and beautiful ExtJS app in messy and error-prone Javascript. I recommend Javascript: The Good Parts (http://shop.oreilly.com/product/9780596517748.do) and Javascript Patterns (http://shop.oreilly.com/product/9780596806767.do) for reading. I also use a JSHint plugin in my IDE, and I use Jasmine and Phantom for unit testing. I also recommend rolling the execution of each of these into your project’s CI process.

Read the License Agreement
The license agreement changed a few times early in the life of ExtJS, and can be restrictive depending on how you intend to use the product. The Sencha licensing FAQ site does a good job explaining the specifics. https://www.sencha.com/store/licensing-faq/

Use Sencha CMD
Several tools that are packaged with ExtJS require the app to be organized in a specific way. It is possible to use a custom directory structure for your application, but it can be a maintenance concern in the long term as the framework matures and is upgraded. For instance, in the last two upgrades, 4.0->4.1 & 4.1->4.2, the expected structure of the app changed significantly as new tools and concepts are introduced. The example that comes to mind in the last upgrade was the introduction of packages. Starting your project by creating an app using the Sencha CMD tool, and then using it to upgrade the app (which then is responsible for changing the app’s directory structure, if necessary) can be a major time saver.

Use Refs
Not only do they give you conveniently named accessors for view elements to use in place of lengthy dom queries, but they give you a level of abstraction – limiting references to a potentially changing UI/DOM to one place, and saving the price of continual DOM queries by holding references to DOM elements.

Use the framework to build your UI, don’t build a UI and then try to use the framework
Don’t fight the framework. If you’re working with a UI/UX person, be sure they are at least familiar with ExtJS and have read through the examples. Building interfaces using ExtJS constructs as the building blocks instead of building an interface and forcing the framework behind it can save a lot of time and go far in managing the expectations of users.

Be sure you know which browsers you’re supporting and test in all of them
ExtJS is cross-platform, but like almost anything, there are performance differences and gotchas – mostly in IE8 and IE9 – that you need to watch for. The performance issues are expected, but there are some functional differences as well. The two that come to mind are animation issues and issues detecting native Javascript functionality. Test in all of your supported browsers early and often.

Extend base classes, do not edit them
Occasionally, there will be the need to tweak how base classes function to suit your needs or to make up for default functionality that changed between versions of the framework. To save yourself from upgrade pain in the future, use Ext.override (http://docs.sencha.com/extjs/4.2.1/#!/api/Ext-method-override) and change only what you need to change and only where need it. This will prevent any undesirable behavior in places where the out of the box functionality does suit your needs, and/or where it is used internally by the framework.

Do not be afraid to dig into the source
You will see cryptic error messages, and sometimes they’ll originate from Ext files rather than those of your app, especially if they happen while the app is starting up. Learning how to navigate the source while debugging and also from the API docs is a great help. The source is obviously the truth for what is happening in the framework, but also serves as a great location to find implementation examples that you can use in your app.

Grid performance
In our application, we have only one source of consistent performance issues in ExtJS – data grids. The main thing to keep in mind is the amount of time it takes to render the full width and height of the grid. If you need to load a lot of data in your grid, limit the amount of data that is rendered by using the buffered renderer plugin (http://docs.sencha.com/extjs/4.2.1/#!/api/Ext.grid.plugin.BufferedRenderer). Also, try to limit the number of columns in your grid either by not displaying them at all, or by allowing the user to configure what is displayed. Both of these will help prevent unnecessary rendering of off-screen elements when the grid is rendered and re-rendered (for instance, when the window is resized).

Learn to read the API docs
The ExtJS API is quite rich, and learning how to read the API will help you be productive every day. The docs are presented in a very slick way, and allow you to quickly search for classes, filter elements within a class, view the source from any listing in the API doc, and they include working examples.