Building an ASP.NET Core Starter App on MacOS (Webpack & NPM)

In the previous post, I quickly covered how to get started in a way that smoothed some future bumps in the road for me.  In this post, we’ll cover replacing the default front-end frameworks and add tooling to support front-end dependency management and static asset assembly.  An example of the configuration mentioned in this post (along with the source code for all future posts) is available at: https://github.com/jrodenbostel/example-web-application.

If you followed the tutorial mentioned in the previous post, chances are you generated an application similar to the result of running `dotnet new mvc …` from the CLI.  If you did, you’re mostly looking at a reasonable shell of an application.  In my opinion, there are some issues with how the front-end tools are made available that are worth remediating.  The two big items are there’s no tool being used to manage front-end dependencies like jQuery and Bootstrap, and most if not all of the files in the wwwroot folder could be considered build artifacts or transient dependencies.  In most cases, build artifacts and dependencies shouldn’t be checked into version control, but rather assembled or retrieved at build time. Here’s a some background information for those unfamiliar with this concept: https://stackoverflow.com/questions/31930370/what-is-npm-and-why-do-i-need-it. I’m not a front-end expert by any means, but I appreciate being able to join a team and quickly get up and running, and I appreciate the consistency and speed of automated build and deployment processes.

Just to change things up for this project, I’ll be using Vue.js (https://vuejs.org/) instead of jQuery and Bulma (https://bulma.io/) instead of Bootstrap.  We’ll change our project to use Sass (https://sass-lang.com/) instead of plain CSS.  We’ll remove the defaults and manage all those things with NPM (https://www.npmjs.com/) and Webpack (https://webpack.js.org/).  We’ll also plug the new tools into the default Layout.

If you’re looking at a freshly generated MVC application, you’ll see the `wwwroot` folder along with subfolders `css`, `js`, and `lib`, and also you’ll also see there are files in each of those folders as well.  These files are referenced in the generated layout (`/Views/Shared/_Layout.cshtml`).

1

I started by deleting everything in the wwwroot folder, including the subfolders.  I created a .gitignore file in the root of my project and added the following entries:

2

If you’ve already made some commits to git, you might have to remove these files from git in addition to deleting them.  I should also note that since this is currently a single project solution, and since I am a Rider user, the following entries are also useful:

3

NPM

In the previous post, we covered installing node.js as a prerequisite to getting the example app up and running.  In this section, we’re going to use the Node Package Manager (NPM) to install all the dependencies we’ll need for this tutorial.  While I hope this helps readers get started quickly, it’s always a good idea to take some time to understand what each of these dependencies does to ensure that you’re not installing anything unnecessary.

First, we’ll create a `package.json` file where all of our dependencies will be recorded.  Soon, we’ll also use this file to set up some automated scripts that leverage our dependencies.  Create `package.json` in the root of your project with the following contents:


{

"name": "example-web-application",

"version": "1.0.0",

"description": "",

}

Next, we’ll install all the tools we need to get a reasonable static asset assembly process setup – specifically, this will install tools needed to incorporate Sass and Webpack into the project.  Run this command from the root of your project:

`npm install css-loader extract-text-webpack-plugin file-loader html-webpack-plugin mini-css-extract-plugin minimist node-sass optimize-css-assets-webpack-plugin sass-loader url-loader webpack webpack-cli webpack-merge –save-dev`

When that is complete, the following path should be valid in your filesystem `<project_root>/node_modules`, and the node_modules folder should have about 83MB  of files in it.  You should also see your package.json file updated to include a listing of the files we just installed.  We’ll add more dependencies later, but this will help us get Webpack configured appropriately.

Webpack

Before we go further, I know for sure I had some success following this tutorial: https://webpack.js.org/guides/getting-started/.  I know for sure that I followed others.  I was not able to find which webpack tutorial I followed in my notes.  With that out of the way, we need a few other things to get started with Webpack.  First we’ll start with the Webpack config.  We’re going to create three files:

`webpack.common.js`: common configuration

`webpack.dev.js`: configuration for ‘development’ environments

`webpack.prod.js`: configuration for ‘production’ environments

webpack.common.js

This file contains file loader plugin configuration (for fonts), and has references to file locations.


var path = require('path');

const webpack = require('webpack');

const HtmlWebpackPlugin = require('html-webpack-plugin');

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

 

module.exports = {

entry: {

app: './Scripts/app.js'

},

output: {

path: path.resolve(__dirname, 'wwwroot'),

filename: 'js/[name].js',

publicPath: '/'

},

plugins: [

new MiniCssExtractPlugin({

filename: 'css/site.css',

chunkFilename: '[name].css'

})

],

module: {

rules: [

{

test: /\.(png|jpg|jpeg|gif|woff|woff2|ttf|eot|svg)(\?.*)?$/,

loader: 'file-loader?name=fonts/[name].[ext]',

},

{

test: /\.s[ac]ss$/i,

use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],

},

]

}

};

 

webpack.dev.js

The contents of this file will be combined with `webpack.common.js` when executed. This file contains a reference to the ‘inline-source-map’ devtool configuration, which is a Webpack option for copying files as-is from location to location ie without minifying or obfuscating them.  This configuration combined with the common configuration should result in files being copied from `/Scripts` to their homes in ‘wwwroot’ with the only transformation being css extracted from the scss/sass files in `/Scripts`.map’ devtool configuration, which is a Webpack option for copying files as-is from location to location ie not   We’ll cover how to run these commands shortly.


const webpack = require('webpack');

const merge = require('webpack-merge');

const common = require('./webpack.common.js');

 

module.exports = merge(common, {

mode: 'development',

devtool: 'inline-source-map'

});

 

webpack.prod.js

The contents of this file will also be combined with `webpack.common.js` when executed.  This file contains references to Webpack plugins used to minify and obfuscate files for optimized loading.  This configuration combined with the common configuration should result in file being copied from `/Scripts` to their homes in `wwwroot`, but this time, they’ll be optimized by the aforementioned plugins.


const webpack = require('webpack');

const merge = require('webpack-merge');

const common = require('./webpack.common.js');

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

const TerserJSPlugin = require('terser-webpack-plugin');

 

module.exports = merge(common, {

mode: 'production',

optimization: {

minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],

}

});

 

Execution Convenience

Now that we have Webpack configured, we need to be able to test it.  Before we test it, we’ll set up an easy way to execute but adding the following entry to the root of our `package.json` file:

“scripts”: {

“dev”: “webpack –config webpack.dev.js –watch”,

“build”: “webpack –config webpack.prod.js”

}

Now, from the root of our project folder (the same folder where `package.json` is located), we can run:

`npm run dev` which runs our dev configuration, and watches the filesystem for changes.  When changes are detected, Webpack is run again with the same configuration. This is useful while in active development as keeping this running in the background will keep you assets building and bundled as you go.

`npm run build` which runs our prod configuration, and is useful when deploying to a shared environment. It executes once and will not run again upon completion.

Next let’s add Vue and Bulma as dependencies to our project and test our Webpack configuration.

From the root of your project, run the following command to install Bulma and Vue as dependencies of our project:


npm install bulma vue --save-dev

Now, let’s setup a JavaScript file and a Sass file for our project and test out our Webpack config.  Note that the file names and paths below are referenced in our Webpack config.

/Scripts/app.js


require('./site.scss');

import Vue from 'vue/dist/vue';

 

/Scripts/site.scss


@charset "utf-8";

 

@import "~bulma/bulma";

 

When we run ‘npm run dev’ or ‘npm run build’ from the root of our project, we should see files appear in wwwroot.

You’ll want to update your `/Views/Shared/_Layout.cshtml` to include references to the new files, and remove references to what was previously there.  You’ll see examples of that in the file below:

/Views/Shared/_Layout.cshtml


<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="utf-8" />

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>@ViewData["Title"] - ExampleWebApplication</title>

<link rel="stylesheet" href="~/css/site.css" />

</head>

<body>

<header>

<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">

<div class="container">

<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">ExampleWebApplication</a>

<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"

aria-expanded="false" aria-label="Toggle navigation">

<span class="navbar-toggler-icon"></span>

</button>

<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">

<ul class="navbar-nav flex-grow-1">

<li class="nav-item">

<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>

</li>

<li class="nav-item">

<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>

</li>

</ul>

</div>

</div>

</nav>

</header>

<div class="container">

<main role="main" class="pb-3">

@RenderBody()

</main>

</div>

 

<footer class="border-top footer text-muted">

<div class="container">

&copy; 2020 - ExampleWebApplication - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>

</div>

</footer>

<script src="~/js/app.js" asp-append-version="true"></script>

</body>

</html>

Last, you’ll probably want to re-style your layout to use Bulma classes, and I’ve added a quick Vue component to handle toggling the navbar styles based on the viewport size.  Here’s an update:


<!DOCTYPE html>

<html lang="en" class="has-navbar-fixed-top">

<head>

<meta charset="utf-8"/>

<meta name="viewport" content="width=device-width, initial-scale=1.0"/>

<title>@ViewData["Title"] - Example Web Application</title>

<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>

</head>

<body>

<header id="navbar-root">

<vue-navbar inline-template>

<nav class="navbar is-fixed-top" role="navigation" aria-label="main navigation">

<div class="navbar-brand">

<a asp-controller="Home" asp-action="Index" class="navbar-item">

<span>Example Web Application</span>

</a>

<a role="button" v-on:click="toggle" v-bind:class="{ 'is-active': burgerActive }" class="navbar-burger burger" data-target="menuBar">

<span></span>

<span></span>

<span></span>

</a>

</div>

<div id="menuBar" v-bind:class="{ 'is-active': burgerActive }" class="navbar-menu">

<div class="navbar-start">

<a class="navbar-item">

Menu 1

</a>

<a class="navbar-item">

Menu 2

</a>

</div>

</div>

</nav>

</vue-navbar>

</header>

<section>

<div class="container">

<div class="columns">

<div class="column is-10 is-offset-1">

<h1 class="title is-1">@ViewData["Title"]</h1>

<h3 class="title is-3 has-text-info">@ViewData["Information"]</h3>

<h3 class="title is-3 has-text-danger">@ViewData["Error"]</h3>

<h3 class="title is-3 has-text-info">@TempData["Information"]</h3>

<h3 class="title is-3 has-text-danger">@TempData["Error"]</h3>

@RenderBody()

</div>

</div>

 

</div>

</section>

 

<footer class="footer">

<div class="content has-text-centered">

&copy; 2020 - yoursitename.com - <a asp-controller="Home" asp-action="Privacy">Privacy</a>

</div>

</footer>

<script src="~/js/layout.js" asp-append-version="true"></script>

</body>

</html>

We’ll also need new Javascript to handle some of the Vue directives referenced in the previous file:

/Scripts/app.js


require('./site.scss');

 

import Vue from 'vue/dist/vue';

 

Vue.component('vue-navbar', {

data() {

return {

width: 0,

height: 0,

burgerActive: false

}

},

methods: {

toggle(e) {

this.burgerActive = !this.burgerActive;

},

handleResize() {

this.width = window.innerWidth;

this.height = window.innerHeight;

this.burgerActive = false;

}

},

created() {

window.addEventListener('resize', this.handleResize);

this.handleResize();

},

destroyed() {

window.removeEventListener('resize', this.handleResize);

}

});

 

new Vue({

el: '#navbar-root'

});

 

At this point, we should have a working Webpack config, Vue and Bulma installed and incorporated into our layout, and a simple Vue component that helps keep the navbar responsive when the viewport size is reduced.

4

In the next post, I’ll focus much more on code, covering adding custom authentication to the project.

Alexa Skills Kit SDK 2.0 with 3rd party APIs

At SPR (the company I work at), once a quarter we host a Solution Day – a day we set aside for people from different teams to spend together in the office “learning by doing”.  At a recent Solution Day, I had the pleasure of working with a few of my colleagues on improving an Alexa skill that lets us interact with an IoT device we built at a previous Solution Day.  The IoT device was built using a Particle Photon (https://www.particle.io/products/hardware/photon-wifi/) and provides us telemetric data about our office Kegerator: what’s on tap, how cold the beer is, and how much beer is left. The original version of the skill in question was ported from another installation that only featured a single faucet and was written using Node.js.  Our office kegerator has two faucets.  The resulting user experience was sub-optimal from both a performance and user experience perspective.  The goal of this Solution Day was to make improvements to both.

During the course of implementing these improvements, we upgraded the Alexa Skills Kit SDK to the latest version (^2.0.0 from ^1.0.0). The purpose of the blog post is to outline a specific problem we faced (and solution we came up with) for integrating a Node.js Alexa skill written with Alexa Skills Kit SDK ^2.0.0 (https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs) and an asynchronous 3rd party REST API call. While this applies to any asynchronous function, in this post we’ll cover an example that used the Particle Cloud API (https://docs.particle.io/reference/api/). Like nearly every other post I’ve written, a lot of this isn’t new information – there’s a lot of “standing on the shoulders of giants” in here – I’m hoping the value is in being able to find this information in one place.

A key change in the latest SDK version is how an AWS Lambda invoked as part of a Skill request terminates and reports results.  In Alexa SDK ^1.0.0, returning results involved emitting an event, like the example below:

  self.emit(':tell', 'The keg is ' + tempVal + ' percent full.');
  lambdaContext.done(null, 'Request for beer quantity successful');

Terminating/reporting results in this manner makes integration with 3rd party REST APIs very easy.  These lines can simply be placed in a callback or a promise.  When the API responds and results are returned, an event can be emitted, and the context is told we’re done.  Here is a more complete example:


'HowMuchBeerIsLeft': function () {
  var tempVal = 0, self = this;
    request(consumptionMonitorUrl).then(function (body) {
      body = JSON.parse(body);
      //other logic
      self.emit(':tell', 'The keg is ' + tempVal + ' percent full.');
      lambdaContext.done(null, 'Request for beer quantity successful');
    });
},

The same is not true in Alexa SDK ^2.0.0 – with the new handler model, terminating a skill request and returning results requires actual ‘return’ statements at the end of the function. You can see there’s not a whole lot of documentation either (https://www.npmjs.com/package/ask-sdk), but there are plenty of example projects (https://github.com/alexa). The new handler model looks like the example below, which is the default ErrorHandler from an example project (https://github.com/alexa/skill-sample-nodejs-berry-bash):


const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    return handlerInput.responseBuilder
      .speak('Sorry, I can\'t understand the command. Please say again.')
      .getResponse();
  },
};

This model is more difficult to use with a skill that requires integrating with a third party API because a ‘return’ requires us to essentially force our code to wait for our asynchronous API call to respond before returning.  Using a ‘return’ statement in a callback or promise won’t work – the function will likely end before the ‘return’ statement is executed.  One of probably a dozen solutions to this problem involves async/await syntax, introduced in Javascript ES7.  A good explanation of async/await can be found here: http://nikgrozev.com/2017/10/01/async-await/, though our examples do not use the request-promise (https://github.com/request/request-promise) library as this site does.

Our example solution will show a handler function waiting for a response from another function that returns a Promise.  Our function returns a Promise because again, we’re not using the request-promise library(though we could).  Our example also includes a ‘bodyTemplateMaker’ function (which is included below), because our skill runs on an Echo Spot, which has a screen. The ‘bodyTemplateMaker’ function (and it’s related functions) is a helper function for building a response that has visual components.  This  function is pulled from an example skill on Github (https://github.com/alexa/skill-sample-nodejs-berry-bash). It is largely untouched.

Libraries we used:


const Alexa = require('ask-sdk-core');
const request = require('request');
const AWS = require('aws-sdk');

Handler (for Alexa skill):


const TempHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'TempIntent';
  },
  async handle(handlerInput) {
    let temperature = await tempLookup();
      return bodyTemplateMaker(
        'BodyTemplate1',
        handlerInput,
        mainImage,
        'How cold is the beer?',
        `Current Temp: ${temperature} F`,
        null,
        null,
        `The beer is being served at ${temperature} degrees Fahrenheit.`,
        null,
        null,
        mainImgBlurBG,
        false
      );
    }
};

 
Async function (for 3rd party API call):


const tempLookup = () => {
  return new Promise(function(resolve, reject) {
    request(servingTempUrl, function(error, response, body){
      body = JSON.parse(body);
      resolve(parseFloat(body.result).toFixed(2));
    });
  });
}

Helper function (bodyTemplateMaker and friends):


function bodyTemplateMaker(pBodyTemplateType, pHandlerInput, pImg, pTitle, pText1, pText2, pText3, pOutputSpeech, pReprompt, pHint, pBackgroundIMG, pEndSession) {
  const response = pHandlerInput.responseBuilder;
  const image = imageMaker("", pImg);
  const richText = richTextMaker(pText1, pText2, pText3);
  const backgroundImage = imageMaker("", pBackgroundIMG);
  const title = pTitle;
    response.addRenderTemplateDirective({
      type: pBodyTemplateType,
      backButton: 'visible',
      image,
      backgroundImage,
      title,
      textContent: richText,
    });
    if (pHint)
      response.addHintDirective(pHint);
    if (pOutputSpeech)
      response.speak(pOutputSpeech);
    if (pReprompt)
      response.reprompt(pReprompt)
    if (pEndSession)
      response.withShouldEndSession(pEndSession);
    return response.getResponse();
}

function imageMaker(pDesc, pSource) {
  const myImage = new Alexa.ImageHelper()
    .withDescription(pDesc)
    .addImageInstance(pSource)
    .getImage();
  return myImage;
}

function richTextMaker(pPrimaryText, pSecondaryText, pTertiaryText) {
  const myTextContent = new Alexa.RichTextContentHelper();
  if (pPrimaryText)
    myTextContent.withPrimaryText(pPrimaryText);
  if (pSecondaryText)
    myTextContent.withSecondaryText(pSecondaryText);
  if (pTertiaryText)
    myTextContent.withTertiaryText(pTertiaryText);
  return myTextContent.getTextContent();
}

Unfortunately, we can’t share the entire codebase for this solution because it contains proprietary and/or private information that we can’t share.  However, one thing we found difficult was navigating various source code repos to cobble our solution together.  We’re hoping this is a succinct, yet complete, solution that others will find helpful.

Enjoy and good luck!

Getting Started with Docker on Azure

Intro & Prereqs
We’re seeing a pretty significant uptick in the use of Azure as the cloud provider of choice among clients. As organizations move to hybrid and/or multi-provider clouds, Docker plays a key role in abstracting underlying platform configuration details away from implementations, allowing developers to build consistently-functioning solutions that can be tested and run in identical configurations, and that can be reliably deployed to disparate environments. In this post we’ll cover running Docker containers on Azure using Docker Machine and using Docker storage volumes for persistent storage.

Since Azure doesn’t yet have a dedicated container service like AWS and GCP, we’ll need to rely on Docker Machine to get the job done. Docker Machine lets us install and control the Docker service on local and remote VMs. We’ll configure a Docker host and use the same Dockerfile we’ve used in previous posts to test our solution. Before we jump in, we’ll need to have the following items installed:

Docker:

Mongo: https://docs.mongodb.com/manual/installation/

  • (the mongod command should be available from your CLI)

MEANjs.org Yeoman Generator: http://meanjs.org/generator.html

  • (the yo command should be available from your CLI, and you should have a generator named

Setting up the project
To get started, navigate to an empty directory and generate a new MEAN-stack app using the MEANjs.org Yeoman generator and the following command and options:

Command:

yo meanjs

Options:

You're using the official MEAN.JS generator.
? What mean.js version would you like to generate? 0.4.2
0.4.2
? In which folder would you like the project to be generated? This can be changed later. mean
Cloning the MEAN repo.......
? What would you like to call your application? mean-test
? How would you describe your application? Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js
? How would you describe your application in comma seperated key words? MongoDB, Express, AngularJS, Node.js
? What is your company/author name? Justin Rodenbostel
? Would you like to generate the article example CRUD module? Yes
? Would you like to generate the chat example module? No
Running npm install for you....
This may take a couple minutes.

------------------------------------------
Your MEAN.js application is ready!

To Get Started, run the following command:

cd mean && grunt

Happy Hacking!
------------------------------------------

Next, we need to create directories to house our Mongo data, and we need to start the Mongo server. Navigate to your project directory (using the names above, it should be in the ‘mean’ directory relative to where you ran the last command) and create directories so that your project contains the following folders:

  • /data
  • /data/db

While in your project directory, start the Mongo server using the following command:

mongod --dbpath data/db

To confirm your database has properly started, you should see output similar to the output below:

2016-08-24T22:03:19.039-0500 I JOURNAL  [initandlisten] journal dir=data/db/journal
2016-08-24T22:03:19.040-0500 I JOURNAL  [initandlisten] recover : no journal files present, no recovery needed
2016-08-24T22:03:19.054-0500 I JOURNAL  [durability] Durability thread started
2016-08-24T22:03:19.054-0500 I JOURNAL  [journal writer] Journal writer thread started
2016-08-24T22:03:19.054-0500 I CONTROL  [initandlisten] MongoDB starting : pid=7300 port=27017 dbpath=data/db 64-bit host=Justins-MacBook-Pro.local
2016-08-24T22:03:19.054-0500 I CONTROL  [initandlisten]
2016-08-24T22:03:19.054-0500 I CONTROL  [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000
2016-08-24T22:03:19.054-0500 I CONTROL  [initandlisten] db version v3.0.7
2016-08-24T22:03:19.054-0500 I CONTROL  [initandlisten] git version: nogitversion
2016-08-24T22:03:19.054-0500 I CONTROL  [initandlisten] build info: Darwin elcapitanvm.local 15.0.0 Darwin Kernel Version 15.0.0: Wed Aug 26 16:57:32 PDT 2015; root:xnu-3247.1.106~1/RELEASE_X86_64 x86_64 BOOST_LIB_VERSION=1_49
2016-08-24T22:03:19.054-0500 I CONTROL  [initandlisten] allocator: system
2016-08-24T22:03:19.054-0500 I CONTROL  [initandlisten] options: { storage: { dbPath: "data/db" } }
2016-08-24T22:03:19.060-0500 I INDEX    [initandlisten] allocating new ns file data/db/local.ns, filling with zeroes...
2016-08-24T22:03:19.093-0500 I STORAGE  [FileAllocator] allocating new datafile data/db/local.0, filling with zeroes...
2016-08-24T22:03:19.093-0500 I STORAGE  [FileAllocator] creating directory data/db/_tmp
2016-08-24T22:03:19.190-0500 I STORAGE  [FileAllocator] done allocating datafile data/db/local.0, size: 64MB,  took 0.096 secs
2016-08-24T22:03:19.215-0500 I NETWORK  [initandlisten] waiting for connections on port 27017

Now that the database server is running we can start our new application using the following command (again from the project directory):

grunt

To confirm the application is up and running, open a browser window and navigate to http://localhost:3000, where you should see something similar to the screenshot below:

Screen Shot 2016-08-24 at 10.06.04 PM

Now that we have a running application, we can start to build out the docker machine we’ll deploy it to.

Provisioning the Azure VM
To start using Azure with Docker, we need to create a Docker Machine configuration that uses a virtual machine in Azure. Using the docker-machine command line tools, we’ll create an Azure Resource Group (complete with a virtual machine, NIC, Firewall, Public IP, Virtual Network, Storage Account and availability set) using the “Azure driver”. It’s pretty simple. Just use the command below:

docker-machine create -d azure \
  --azure-ssh-user ops \
  --azure-subscription-id  \
  --azure-open-port 3000 \
  --azure-image canonical:UbuntuServer:14.04.3-LTS:latest \
  azure-test

This will take a few minutes!

Some items to note in the command above:

  • You must replace in the command below with your own Azure subscription key
  • We’ve chosen to leave port 3000 open for our application’s development mode
  • We’ve chosen to use the long term support version of Ubuntu 14.04 for our host machine
  • We’ve named the Docker Machine ‘azure-test’

When the machine creation is complete use the following command to verify the Docker Machine is available for configuration.

docker-machine ls

Also make sure your newly created machine is the one that’s currently running with the following command:

eval $(docker-machine env azure-test)

Deployment
Before we go further with Docker, let’s a make a few changes to our app’s Dockerfile so we can run Mongo in our container, and start both the MEANjs.org app and Mongo with one command. In your project’s Dockerfile, replace this line:

FROM node:0.12

which indicates which base image to start with, with these lines:

FROM node:0.12.14-wheezy

# Install Mongo
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927
RUN echo "deb http://repo.mongodb.org/apt/debian wheezy/mongodb-org/3.2 main" | tee /etc/apt/sources.list.d/mongodb-org-3.2.list
RUN apt-get update

RUN apt-get install -y mongodb-org

# Install gem sass for  grunt-contrib-sass
RUN apt-get install -y supervisor

In this block, we’re performing the following activities:

  • Adding the apt-get repo and relevant keys for the Mongo binary repository.
  • Installing Mongo DB using apt-get
  • Installing Supervisord (a lightweight process control system that we’ll configure later) using apt-get

Near the middle of the same file, replace this line:

WORKDIR /home/mean

With these:

WORKDIR /home/mean

RUN mkdir -p /var/log/supervisor
RUN mkdir -p /data/db
RUN mkdir ./logs

COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf

In this block, we’re performing the following activities:

  • Creating the log directory for Supervisord
  • Creating the data directory for Mongo DB
  • Creating the logs directory for the MEANjs.org application

Near the bottom of the same file, replace these lines:

# Port 3000 for server
# Port 35729 for livereload
EXPOSE 3000 35729
CMD ["grunt"]

With these:

EXPOSE 3000
CMD ["/usr/bin/supervisord"]

In this block, we’re performing the following activities:

  • Telling Docker to open port 3000 for this image
  • Running Supervisord to “start” the image

You may have noticed that one of the commands we added earlier is copying a file named supervisord.conf from the root of our local project directory to the docker container. Let’s create that file, and add the following content:

[supervisord]
nodaemon=true

[program:mongod]
command=mongod --dbpath /data/db
redirect_stderr=true

[program:grunt]
command=grunt prod --force
redirect_stderr=true

Supervisord is a lightweight process control system commonly used in Linux environments. In this file, we’re instructing supervisord to start mongo and our MEANjs node app in background processes, redirecting their output to the stderr log file.

With a configured docker-machine, we’re ready to build and deploy our containers. Start by using the following command to build your docker image:

docker build -t mean-test .

Next, tag the current build with the following command, replacing with your Docker hub user name. This will assign the image we just assembled the ‘latest’ tag.

docker tag mean-test /mean-test:latest

Next, push the newly tagged image into your repo at Docker hub using the following command:

docker push /mean-test:latest

Now, we can create our machine and storage volume. Use the following command to create a named storage volume using the image you’ve just pushed to Docker Hub, again replacing with your Docker Hub username:

docker create -v /data --name mean-test-storage /mean-test:latest /bin/true

Create the machine, linking to the previously created storage volume and mapping our previously-opened http port (3000), using the command below, again replacing with your Docker Hub username:

docker run -d -p 3000:3000 --volumes-from mean-test-storage --name mean-test /mean-test:latest

Test to see if your app is up and running by navigating to it on the web. If you are not sure what the public IP of your machine you can print the configuration details of your machine in the console by running the following command:

docker-machine env azure-test

Navigate to your site using the public IP and port 3000 and you should see the same screen we saw when you ran the app locally. Pretty easy!

Conclusion
I hope this post has provided a simple overview of how to get started with Docker Machine on Azure, and I hope the use of a full-stack application as an example provides insight beyond the basic tutorials available elsewhere on the web.

Code for this tutorial is available on Github at: https://github.com/jrodenbostel/getting-started-with-docker-on-azure

Time to wrap up some side projects and get back to learning more about using the ELK stack, Elm, and functional programming patterns! Stay tuned.

 

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);
  });
});

Upcoming in 2015

Over the course of the last 3 months, I’ve been involved in a hybrid mobile project using the Ionic framework and Node.js, which has allowed me to apply prior knowledge and learn more about AngularJS, latest-generation javascript features, Yeoman, Bower, Grunt, Crashlytics, and Apache Cordova. I’ve also been spending time learning how to interact with an Arduino using Node.js for a personal project. Stay tuned for posts on those topics in the coming months. Happy New Year!

As a reviewer…

One of the other things I spent time on last year was acting as a reviewer for a book that was recently published by Packt Publishing. I was approached by Packt early in the summer to volunteer to help review a work in progress called Enterprise Application Development with Spring and ExtJs. I was skeptical at first, so I did some research. Easily the majority of blog and message board content on the subject of being an unpaid reviewer, especially for Packt for some reason, was negative. This was an obvious red flag. However, I read a fair number of technical books, and I do enjoy writing, so I thought it might be an interesting thing to participate in, especially if I ever wanted to be an author in the future. In the back of my head I knew that if it became too much, especially given that it was volunteer time, that I could simply back out. I agreed to participate and off we went.

Here’s the basic deal: you get a couple of chapters to go through every 4 days. They give you a questionnaire to fill out with your comments. The publisher encourages feedback on many different levels – sentence structure and grammar, chapter-level organization and content, even the order of the chapters and higher level things. All fairly reasonable.

In all, I didn’t have a problem with the process. It was very interesting to see a book as a work in progress. By the time I was involved, it was clear the tools, table of contents and topics covered were fairly well set. Obviously, getting paid would have been nice, but I knew I was volunteering (well, I was getting a free ebook). I also knew the subject matter, which made things much easier. I was able to bypass the learning part of reading the book and focus on the content, how it was organized, and what type of an experience a reader would have with it.

On the book itself – it’s not bad, it’s just presented in a way that would be hard for me, personally, to digest. For a topic like this, I like to start with a clear goal – working through a project to learn a new technology. Additionally, I prefer consuming new information in small pieces and repeating that consumption, reinforcing foundational concepts while slowly increasing complexity, all the while verifying my progress with test cases. Some examples of books written in this fashion that I enjoyed: Agile Web Development with Rails, Grails in Action. This book is presented in a very waterfall-ish way in the name of Enterprise Application Development, and I think this is where I had the hardest time. Developing apps in the Enterprise does not require big up-front design or a waterfall development methodology. The reader is consuming huge new pieces of knowledge in each chapter, and we’re not doing anything to verify them as we go. The book has the reader waiting for a few layers of the application to be developed before performing integration tests. My concern here is that a reader will be trying to troubleshoot something they wrote two chapters ago, and that they just learned within the last few hours. That sounds frustrating!

Side note: I wasn’t a huge fan of the tools used in the book either. I feel like they are complex enough to be a distraction, that there are more modern/simple choices (Ivy, Eclipse, Spring Data) that would have possibly let the reader focus more on learning Spring and ExtJs.

In short, it was a great experience, I’m glad I did it, and I learned a lot, but if I ever do this again, I’ll make sure my own views and development/learning style are in better alignment with the author’s goals for the book.

On resolutions…

2013 came and went, and I definitely did not get around to learning much of what I planned to learn/write about.  I still have the books, and they’re electronic, so they’re not collecting dust, but instead of continuing to dig into new languages and frameworks for the JVM, I spent much of 2013 learning new technology based on client demand.  Not bad things, just unexpected.  I sharpened my javascript skills, and spent a few months in the .NET world. (pro tip – INSTALL ReSharper ON THE FIRST DAY)

This year I’d like to get through that Play/Scala exploration and check out the Reactor (the asynchronous app toolkit by the Spring team).

When will I stop thinking that putting it in writing will be motivation enough to actually do this stuff?