Unit Testing a Simple Service in AngularJs

Monday, July 14, 2014

Introduction

When testing in AngularJs, I have found that once you have everything setup correctly, unit testing with Jasmine is not all that complicated. However, when I first started testing in AngularJs, getting things setup right was a bit hit or miss for me. At the time, the documentation for testing wasn't all that definitive so it took me a little longer to grasp the concepts than probably should have been needed.

So in this post, I am going to show how I test a simple service that takes an array of directory paths as a string and parses it out into an array of arrays. Its a simple test for a service that has no dependencies and only one function.

I am basically building on my previous post where I showed how to setup a unit testing environment using NodeJs, GruntJs, KarmaJs, and Jasmine.


The Service

So in this example, I am taking an AngularJs factory that returns one function. This function should take an array of paths and convert them into an array of array of directories.

The whole point of encapsulating this code is that not only is it testable, but is also mockable, and it is reusable.


The Unit Test

So here is the test:

The way I setup a unit test for the module, is I divide the test module into three different sections.

  1. The first section I include any variables that will be used by all the tests. This includes things like the scope, mocks, services, controllers etc. On a side note, if my mock data becomes too big, I separate them out into a different file and place the file in a folder called mocks and then reference them from there. My Karma.conf.js file then will need a reference to this folder.
  2. The next section is the most critical section and that is the section that sets everything up before each test runs.
  3. The last section is the section that handles all the tests.

Let me focus on the second section as that for me has always been the trickiest to get right.

BeforeEach

Jasmine comes with two global variables (beforeEach and afterEach), and as the names describe, the beforeEach runs before each test, while the afterEach runs after each test.

Here I am only using the beforeEach to set up the tests.

Module

The module command is used to specify what AngularJs module you are going to test. You need not worry about any other dependent modules, at this point, the module routine is only specific to the module you currently want to test, even if you have other dependent modules.

Inject

The inject function wraps objects into an injectable function and is where I am initializing the service. In this case I am only referencing my service that I want to test since I don't have any other dependencies.

Notice the underscores in the parameters.These underscores are actually ignored by the injector but allow us differentiate between what is being injected and the module level variable that we want to use for all out tests so they can be assigned accordingly.

The Actual Tests

Finally, the last part of the file contains the actual tests themselves.

If I am using a TDD approach, the first thing I do is make sure the service exists. This is represented by the test, "should be defined as a service". Once this test passes, I am then ready to move on to my next test.

I then test to make sure the method I am actually testing in the service exists. This test is represented by the test, "should have a function called getReportList". Again, once this test passes, I can then move to me next tests.

Then my last two tests, test that the function is actually returning what I am expecting it to return.

Conclusion

Hope that helps. In later posts I will show some different unit tests, such as testing a controller, testing a service that has a $HttpBackend dependency, and testing a controller that has a service dependency. In those posts I hope to cover things like mocks, spies, and the $HttpBackend service as well. 

Angular Unit Testing - Getting setup with Jasmine, Karma, and Grunt

Tuesday, June 24, 2014

As a developer coming from an ASP.Net, C# background, I had come to appreciate the ability to be able to unit test my code. I find that I am not all that great a programmer, and often times the unit tests I write have brought to surface a bug that I would have introduced had I not had a unit test in place to discover the bug in the first place.

In the past, I had not given much thought to do JavaScript unit tests. I had just assumed that it wasn't really possible to do them without a lot of jumping through hoops. However, with AngularJs, unit testing isn't all that difficult. I have found that once I have gotten over the initial hurdle of getting everything setup, unit testing JavaScript code has become quite useful.

I find the feedback loop is much quicker with the unit tests I write, because I can see write away that the code is working or not without having to do much of anything.

So with being said, lets look and see how to setup the unit testing framework.

NodeJs

For my unit tests, I use Jasmine, Karma, Grunt which all sit on top of the NodeJs runtime, so for that you will first need to install NodeJS.

NodeJs comes with package installer called NPM. Similar to Gems in Ruby, or Nuget in Visual Studio, NPM will install all the packages needed to start unit testing.

Note, if you are starting a project from scratch, I would recommend installing Yoeman. Yoeman is a scaffolding package that will install a starter project with everything already configured.  For example, when I have a new AngularJs project, I use the Yoeman Angular Fullstack generator, and then go back in and finegle the generated code to my own preferences.

For this post I am assuming that the project and tests already exists. So I am going to only go though all the steps to to get up and running with unit testing on an existing project.

Also, I am running Windows 8.1, so if you are on a different OS like Linux or Apple OS, or even a different version of windows, these steps might differ slightly. I'm not really sure, but I had noticed that other posts were slightly different than what I had to do to get started.

Creating the package.json File

The first thing to do is create a package.json file. NPM uses this file to keep track of what you have installed and what versions of the packages you are using. The easiest way to create this file is to initialize NPM from the command line.

npm init

Doing this generates a file that will look like this.

 

You could also just manually create the file yourself. Either way, this file is required.

Installing the packages

You can install each package separately from the command line but an easier way to get everything installed is to get a copy of the packages.json file with all the packages you need and just type the following from the command line:

npm install

This command will take what ever you have in the package.json file and install it in your project if it isn't already installed.

 

My package.json file looks like this:

The packages under the "devDependencies" node tells NPM that these are packages are only for use in the development environment and are not to be copied to production.

You might also want to install Grunt, Karma and maybe even Yoeman globally so you don't have to keep installing it for every project. You can do that with the following commands.

npm install -g grunt-cli

npm install -g karma-cli

npm install -g yoeman

The "cli" suffix is the package that allows grunt and karma to called directly from the command line in Windows. The -g parameter tells NPM you want to install the package globally.

Initializing the karma.config.js File

Karma is a test runner developed by the AngularJs team. It's basically the engine that goes through all the unit test and runs them, and then provides the results of the executed unit tests. Once karma is installed, you will need a karma.config.js file to so karma knows things where the JavaScript files are located, what testing framework you want to use, etc.

You can generate a karma.config.js file by running the following command:

karma init

When you run this you will be asked a bunch of questions:

  • Which unit testing framework do you use?
  • Do you want to use Require.js?
  • Do you want to capture any browsers automatically?
  • What is the location of your source and test files?
  • Should any of the files included by the previous patterns be excluded?
  • Do you want karma to watch all the files and run the tests on change?

I typically accept all the defaults, and, if needed, go back and change the configuration later. My current file looks like this:

If you have separated your JavaScript framework files from your own custom JavaScript files, you will need to add what ever framework files you are using that are included in the tests. For example, I have included angular and angular-mocks for angular specific testing and I also have a reference to lodash.js.

Configuring Grunt

Gruntjs is a task runner that lets you run several tasks at once. For this demo, I am only running karma, but I could add other tasks like JsHint to check my JavaScript syntax, or Uglify to minimize files. Grunt needs a gruntfile.js file installed which tells grunt what tasks to run and how to run them.

My grunt file is pretty simple since I am only running one task. It looks like this:

   

Now I'm ready

Now that I have setup my Grunt file, and I have told Karma to watch my JavaScript files. Every time I make a change to JavaScript code and change it, Karma will run the test and provide me the results in the command window. I simply run "grunt" from the command line.

Running "karma:unit" (karma) task
[2014-06-24 11:14:00.421] [INFO] karma - Karma v0.12.16 server started at http://localhost:9876/
[2014-06-24 11:14:00.569] [INFO] launcher - Starting browser Chrome
[2014-06-24 11:14:04.015] [INFO] Chrome 35.0.1916 (Windows) - Connected on socket aX-CPdsznQq6Oi75cNhf with id 3608593
Chrome 35.0.1916 (Windows): Executed 19 of 19 SUCCESS (0.047 secs / 0.043 secs)


Conclusion

I think in future posts, I will discuss a little how I go about writing unit tests, but for now I'll leave you with this in the hope that at least gets you started in writing AngularJs unit tests.

If you have any questions or feedback, shoot me a comment I would glad to hear what you think?

 

JavaScript, Hoisting, Firefox, If/then Statements

Friday, March 21, 2014

Quick intro about JavaScript and Scoping

In most C and Java type languages a block (that is any curly brace) defines the scope of a variable or a function. However in JavaScript, scoping is at the function level. So when I define a variable inside a block (if/then statement for example) it can be used throughout the entire function it is in, including outside its defined block.

JavaScript will also take that function that is defined anywhere in a function and set its definition at the top of the function behind the scenes. This is called hoisting, and basically it allows you to call a function that is defined below the code you are calling it from.

 

So for example:

 

This works in all browsers because JavaScript hoisted the inner function to the top of the outer function behind the scenes.

If/Then statements and Firefox

The problem is in Firefox if you define a function inside an if/then statement the function no longer gets hoisted. This only happens in Firefox.

 

So in the following example, the following code works in IE and Chrome but is broken in Firefox.

 

In Firefox I get an error because it did not hoist the callHoistedFunction therefore it was not defined at the point it was being used.

ReferenceError: callHoistedFunction is not defined
var hello = callHoistedFunction();
 

So the moral of the story is never define a function inside a “if/then” or “try/catch” statement. As a matter of practice, you should probably just define all variables and functions at the top of the outer function as that is where it gets hoisted to anyways.

 

This is from Douglas Crockford’s book the “JavaScript, The Good Parts”. Pg 102.

 

In most languages, it is generally best to declare variables at the site of the first use. That turns out to be a bad practice in JavaScript, because it does not have block scope. It is better to declare all variables at the top of each function.

 

Here is his video on his book in case you were also interested.

http://www.youtube.com/watch?v=hQVTIJBZook

 

Hope this helps,

AngularJS - Using a Service to Communicate Between Two Different Controllers

Monday, January 6, 2014

Introduction

AngularJS, being an MVC type of framework allows you to have a controller that is responsible for marshaling data to and from a view on your web page.

Well, what if you have a multiple controllers on a page that are completely independent from one another, but in certain cases you want those controllers to be able to communicate back and forth. I say completely independent, because you could nest the controllers, making a parent-child relationship, and then you could use the $rootScope object to communicate with each other.

How $rootScope Works

Every AngularJS application has exactly one $rootScope. All other scopes inside the application are children of the $rootScope. So in the case of nested controllers, the child controller's scope object is a descendent of the parent scope which is ultimately a descendent of the application $rootScope. So, for example, if you try to evaluate {{message.text}} on a child scope. and If that property does not exist on the child scope, AngularJS will then evaluate the parent scope for that property, and then ultimately it will evaluate the $rootScope.

So, in the case of controllers that have a parent-child relationship, you can use the existing inheritance between the controller to facilitate the communication.

Using Factories and Services to Facilitate Communication Across Independent Controllers

In the case your controllers are independent from each other, then you will need a service that will link the two controllers. In my example, I am using a factory which essentially a service that returns an object.

My example is just a web page with two text boxes that chat with each other. Each text box is in a different controller, but each controller has the list of message from both text boxes.

Here is the HTML.

    <div ng-controller="Controller1">
      <h1>Controller 1</h1>
      <ul ng-repeat="msg1 in messages1 track by msg1.id">
        <li>{{msg1.text}}</li>
      </ul>
      <p>
        Controller 1 Message: <input type="text" ng-model="text">
        <button ng-click="postMessage()">Post</button>
      </p>
    </div>
    <p></p>
    <div ng-controller="Controller2">
      <h1>Controller 2</h1>
      <ul ng-repeat="msg2 in messages2 track by msg2.id">
        <li>{{msg2.text}}</li>
      </ul>
      <p>
        Controller 1 Message: <input type="text" ng-model="text">
        <button ng-click="postMessage()">Post</button>
      </p>
    </div>

Each ng-controller section above has a copy of the same messages that is sent out by the service that manages the message list.

So to link the two controllers above, I have added a Angular Factory that will be injected into each controller. Here is the code.

app.factory('messageService', function ($rootScope) {
    var messenger = {
        messages: [],
        identity: 0,
        addMessage: function (text, caller) {
 
            this.identity += 1;
            var id = this.identity,
                message = {
                    text: caller + text,
                    id: id
                };
 
            this.messages.push(message);
            $rootScope.$broadcast('messageAdded');
        }
    };
 
    return messenger;
});

In this service, I am injecting the $rootScope, which as available throughout the Angular application. I have a function called addMessage which, when called by a controller, adds a message to the collection of messages and then broadcasts that the message was added using the $broadcast function. Also, keep in mind that a factory in Angular is a singleton object, so there is only one occurrence of its state in the current Angular application, and any other object that access its state will get what ever values it hold at that current time.

Looking at the code for the controllers, you will see that each of the controllers injects the service and calls the addMessage function when a user enters a text message and clicks the Post button.

app.controller('Controller1', function ($scope, messageService) {
    $scope.messages1 = messageService.messages;
    $scope.post = {
        text: ''
    };
 
    $scope.postMessage = function () {
        console.log($scope.post);
        messageService.addMessage($scope.text, "controller 1");
    };
 
    $scope.$on('messageAdded', function () {
        $scope.messages1 = messageService.messages;
    });
});

Each controller also has $on listener which is listening for the messageAdded broadcast. When this broadcast is detected by the controller, it fires the function to update the message collection retrieving the updated message list from the service.

I am finding this approach useful when the application you are working on gets a bit more complex and you have a lot of controllers on a page responsible for different things. In certain cases you may want to update a portion of the page that is managed by a controller, when a user clicks on another part of page managed by a different controller.

Here is the Plunker example

http://plnkr.co/edit/UA28cXfF0I8r3M54DyYa?p=preview

 

Old School MVP for ASP.Net Web Froms

Saturday, November 23, 2013

Introduction

Sometimes you have to work with a legacy site because, let's face it, it works and it's just not worth the effort to rewrite the whole thing via the newer technologies.

But still, if you need to add a web page and you still want to be able to separate the presentation logic from the domain logic, the Model View Presenter Pattern is the way to go.

I am not going to go into much detail about the pattern itself, there is a great article about it up on MSDN, but I would like to show you a great implementation of it that reduces the amount of code you will need to write to get it going.

The Base View

The first thing to do, is to set up the views, which will be interfaces the web page will use to represent the data on the page.

public interface IView
{
     
}

Yeah, not much here, just an empty interface, but this interface will be inherited from all the other views, making them all recognizable.

The Base Presenter

The base presenter class gets the base view injected into it. By doing this, the presenter will have a way to map the model to the web page.

public abstract class Presenter<TView>
{
    public TView View { getset; }
 
    public abstract void OnViewInitialized();
 
    public abstract void OnViewLoaded();
 
}

The Base Page class all Web Pages Will Inherit From

Now that we have the presenter and the view, we can create a base web page class that will inherit from the System.Web.UI.Page class. Using this class as the base, the web page will automatically inject the presenter and the view onto the page (you can also do this for a UserControl base class).

public abstract class MvpBasePage<TPresenterTView> : Page where TPresenter : Presenter<TView>
    {
 
        protected Presenter<TView> Presenter { getset; }
 
        protected MvpBasePage()
        {
            if (!(this is TView))
                throw new InvalidOperationException("Must impliment the generic type TView");
            try
            {
                Presenter = ObjectFactory.GetInstance<TPresenter>();
                Presenter.View = (TView)((object)this);
            }
            catch
            {
                Trace.Write(ObjectFactory.WhatDoIHave());
                throw;
            }
 
            Debug.WriteLine(ObjectFactory.WhatDoIHave());
        }
    }

I am using StructureMap to register the presenter classes, but any Inversion of Controll container should work here as well.

The Default Page Presenter and View

Now that I have all my base classes ready I can then start using them to create web pages that will conform to the Model View Presenter pattern. So for my default page, I am going to impliment a small version of my trusty sample application, which is a project tracker. You can see an almost full version that uses AngularJs on GutHub, but in this case I am just going mock a client list, which is the first page.

So my client list view is just going to have two properties on it: a content title, and a list of clients (Note: you can also have functions and events on the view for more complicated scenarios).

public interface IClientsView : IView
{
    string ContentTitle { getset; }
    List<Client> Clients {  set; }
}

My presenter for the clients just calls a service that is mocking a list of clients.

public class ClientsPresenter : Presenter<IClientsView>
  {
      private readonly IClientsService _clientsService;
 
      public ClientsPresenter(IClientsService clientsService)
      {
          _clientsService = clientsService;
      }
 
      public override void OnViewInitialized()
      {
          View.ContentTitle = "Client List";
          List<Client> clients = _clientsService.GetAll();
          View.Clients = clients;
      }
 
      public override void OnViewLoaded()
      {
      }
  }

Look Ma! Hardly any code behind!

Now with all this in place my Code Behind file only has the responsibility of marshaling data to and from the view, making it very clean.

public partial class _Default : MvpBasePage<ClientsPresenterIClientsView>IClientsView
    {
        private List<Client> _clients;
 
        public string ContentTitle { getset; }
 
        public List<Client> Clients
        {
            set
            {
                clientsRepeater.DataSource = value;
                clientsRepeater.DataBind();
            }
        }
 
        protected void Page_Load(object senderEventArgs e)
        {
            Presenter.OnViewInitialized();
            if (Page.IsCallback == false)
                Presenter.OnViewLoaded();
        }
    }

And lastly, here is the actual web form

<asp:Content runat="server" ID="FeaturedContent" ContentPlaceHolderID="FeaturedContent">
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h1><%: ContentTitle %></h1>
            </hgroup>
            <section>
                <asp:Repeater runat="server" ID="clientsRepeater">
                   <HeaderTemplate>
                        <table>
                        <thead>
                            <tr>
                                <th>Client Name</th>
                                <th>Contact Name</th>
                            </tr>
                        </thead>
                        <tbody>
                            
                        
                   </HeaderTemplate>
                    <ItemTemplate>
                        <tr>
                            <td><%#Eval("ClientName"%></td>
                            <td><%#Eval("ContactName"%></td>
                        </tr>
                    </ItemTemplate>
                    <FooterTemplate>
                        </tbody>
                    </table>
                    </FooterTemplate>
                </asp:Repeater>
            </section>
        </div>
    </section>
</asp:Content>

That's pretty much it.

The great thing here is if you need to work in legacy applications, you can start this pattern at any point. I have added new pages with this pattern into legacy application that do not follow any pattern without much effort.

I put all the code up on GutHub, in case you would like to take a look.

 

Validation Gotchas to Watch For When Using Angular

Thursday, July 11, 2013

Introduction

Validating a form in Angular is rather straight forward and it is pretty unobtrusive when it comes to the amount of code you have to right, but I thought I would quickly share some gotchas that I experienced when validating a form.

<form name="taskForm" ng-submit="save()" class='form-horizontal' novalidate>

The Form Tag and Controller

First thing to note is that if you want your submit function to fire so you can validate the form in your controller, you need to add the novalidate attribute to your form tag. Otherwise, the browser's built in validating handler will kick and prevent the form submission from firing.

if ($scope.taskForm.$valid === falsereturn;

Notice the form name is automatically attached to scope so it can be validated.  Angular has some built evaluation function you can use to validate the form.

To keep the errors from showing until the user has submitted the form: I created an isSubmitted variable on the $scope object and setting it true when the user clicks the submit button.

$scope.isSubmitted = true;

The Input tag.

Another thing to note is that if you want to use the other custom attributes like email, min, max, etc, then you need to make sure your use the correct input type in your tag. If you don’t, those validation errors will not fire.

Here is a number example:

<div class="control-group" ng-class="{error: isSubmitted && (timesheetForm.rate.$error.required || timesheetForm.rate.$error.number || timesheetForm.rate.$error.min || timesheetForm.rate.$error.max)}">
                <label class="control-label" for="timesheet-rate">Actual Rate:</label>
                <div class="controls">
                    <input type="number" ng-model="timesheet.rate" class="input-mini" 
id="timesheet-rate" name="rate" required min="1" max="999"/>
                    <span class="help-inline" 
ng-show="isSubmitted && timesheetForm.rate.$error.required">Actual rate is required.</span>
                    <span class="help-inline" 
ng-show="isSubmitted && timesheetForm.rate.$error.number">Actual rate is not a valid number.</span>
                    <span class="help-inline" 
ng-show="isSubmitted && timesheetForm.rate.$error.min">Actual rate must be greater than 0.</span>
                    <span class="help-inline" 
ng-show="isSubmitted && timesheetForm.rate.$error.max">Actual rate must be less than 999.</span>
                </div>
            </div>

Here is an email example:

            <div class="control-group" ng-class="{error: isSubmitted && (clientForm.email.$error.required || clientForm.email.$error.email)}">
                <label class="control-label" for="email-address">Email Address:</label>
                <div class="controls">
                    <input type="email" ng-model="client.contactEmail" class="input-xlarge" id="email-address" name="email" required/>
                    <span class="help-inline" ng-show="isSubmitted  && clientForm.email.$error.required">Email is required.</span>
                    <span class="help-inline" ng-show="isSubmitted  && clientForm.email.$error.email">Not a valid email address.</span>
                </div>
            </div>

You Need a Name:

You need to include the name attribute in your input tag and the name of the tag cannot have a "-" in the name. Same thing here; the validation will work with the dash.

Styling Using ng-class

For styling errors in Twitter Bootstrap you have to provide an error class in the control-group div tag which is the parent of the input tag that is being validated. To do that with an input element that several validations you can evaluate several conditions at once.

Here I am using the ng-class directive to validate several different scenarios.

<div class="control-group" ng-class="{error: isSubmitted && (timesheetForm.hours.$error.required || timesheetForm.hours.$error.number || timesheetForm.hours.$error.min || timesheetForm.hours.$error.max)}">

Here is the entire form.

        <form name="timesheetForm" ng-submit="save()" class='form-horizontal' novalidate>
            
            <div class="control-group" ng-class="{error: isSubmitted && timesheetForm.timesheetName.$error.required}">
                <label class="control-label" for="timesheet-name">Timesheet Name:</label>
                <div class="controls">
                    <input type="text" ng-model="timesheet.timesheetName" 
class="input-xlarge" id="timesheet-name" name="timesheetName" ng-focus required/>
                    <span class="help-inline" 
ng-show="isSubmitted && timesheetForm.timesheetName.$error.required">Timesheet name is required.</span>
                </div>
            </div>
            <div class="control-group">
                <label class="control-label" for="timesheet-description">Description:</label>
                <div class="controls">
                    <textarea ng-model="timesheet.timesheetDescription" 
class="input-xlarge" id="timesheet-description"  cols="20" rows="10"></textarea>
                </div>
            </div>
            <div class="control-group" ng-class="{error: isSubmitted && timesheetForm.startDate.$error.required}">
                <label class="control-label" for="timesheet-start-date">Start Date:</label>
                <div class="controls">
                    <input type="text" jq-datepicker ng-model="$parent.timesheet.startDate" 
class="input-small" id="timesheet-start-date" name="startDate" required/>
                    <span class="help-inline" 
ng-show="isSubmitted && timesheetForm.startDate.$error.required">Start date is required.</span>
                </div>
            </div>
            <div class="control-group" ng-class="{error: isSubmitted && (timesheetForm.hours.$error.required || timesheetForm.hours.$error.number || timesheetForm.hours.$error.min || timesheetForm.hours.$error.max)}">
                <label class="control-label" for="timesheet-hours">Actual Hours:</label>
                <div class="controls">
                    <input type="number" ng-model="timesheet.actualHours" class="input-mini" id="timesheet-hours" name="hours" required min="1" max="999"/>
                    <span class="help-inline" ng-show="isSubmitted && timesheetForm.hours.$error.required">Actual hours are required.</span>
                    <span class="help-inline" ng-show="isSubmitted && timesheetForm.hours.$error.number">Actual hours is not a valid number.</span>
                    <span class="help-inline" ng-show="isSubmitted && timesheetForm.hours.$error.min">Actual hours must be greater than 0.</span>
                    <span class="help-inline" ng-show="isSubmitted && timesheetForm.hours.$error.max">Actual hours must be less than 999.</span>
                </div>
            </div>
            <div class="control-group" ng-class="{error: isSubmitted && (timesheetForm.rate.$error.required || timesheetForm.rate.$error.number || timesheetForm.rate.$error.min || timesheetForm.rate.$error.max)}">
                <label class="control-label" for="timesheet-rate">Actual Rate:</label>
                <div class="controls">
                    <input type="number" ng-model="timesheet.rate" class="input-mini" id="timesheet-rate" name="rate" required min="1" max="999"/>
                    <span class="help-inline" ng-show="isSubmitted && timesheetForm.rate.$error.required">Actual rate is required.</span>
                    <span class="help-inline" ng-show="isSubmitted && timesheetForm.rate.$error.number">Actual rate is not a valid number.</span>
                    <span class="help-inline" ng-show="isSubmitted && timesheetForm.rate.$error.min">Actual rate must be greater than 0.</span>
                    <span class="help-inline" ng-show="isSubmitted && timesheetForm.rate.$error.max">Actual rate must be less than 999.</span>
                </div>
            </div>
            <div class="control-group">
                <label class="control-label" for="timesheet-revision-number">Revision Number:</label>
                <div class="controls">
                    <input type="text" ng-model="timesheet.revisionNumber" class="input-small" id="timesheet-revision-number"/>
                </div>
            </div>
            <div class="control-group" ng-class="{error: isSubmitted && timesheetForm.status.$error.required}">
                <label class="control-label" for="timesheet-status">Status:</label>
                <select ng-model="timesheet.statusId" 
data-ng-options="t.statusId as t.statusName for t in statuses" id="timesheet-status" name="status" required></select>
                <span class="help-inline" ng-show="isSubmitted && timesheetForm.stats.$error.required">Status selection is required.</span>
            </div>
            <div class="control-group" ng-class="{error: isSubmitted && timesheetForm.user.$error.required}">
                <label class="control-label" for="timesheet-user">User:</label>
                <select ng-model="timesheet.userId" data-ng-options="m.userId as m.userName for m in members" id="timesheet-user" name="user" required></select>
                <span class="help-inline" ng-show="isSubmitted && timesheetForm.user.$error.required">Member selection is required.</span>
            </div>
            <div class="form-actions">
                <button class="btn btn-primary" >Save</button>
                <button class="btn" ng-click="cancel()" >Remove</button>
            </div>
        </form>

Hope that helps.

Multiple Column Sorting of a Table Using Angular

Thursday, June 20, 2013

This year I have been focusing a lot on Single Page Application (SPA) and the various tools that can be used to build these applications efficiently.
 
So far, I have checked out knockout.js, backbone.js and angular.js, but have been mainly focused on getting my head around angular.js. I have been hearing great things about ember.js too, so perhaps I'll tackle that next. For now, though, I am focusing on angular.js. 
 
So in this post, I thought I would share how to take advantage of angular's filtering functionality. Specifically: how to have a table with a sort button on each of the table columns. It's actually pretty easy.

Binding

One of the core features of angular.js is its ability to do "two-way binging". That is, once the page loads, Angular, binds any models on the its scope to the designated fields on the page. These fields can be a single record or a collection of records. This means properties I change on the scope will immediately change on the screen. Angular handles all that magic.

Filter

Angular has a filter function that allows you to do things like query your collection filtering the result on page. The filter function can also sort your collection instantaneously on the page. In my example of sorting, I can also change the sort the sort column dynamically and I can also reverse the sort dynamically by storing the sort values in the $scope. Note that the $scope is sort of like a ViewBag if you think of it in terms of ASP.Net ASP. It is holds properties that the page has access to and that you can change at anytime.

Template

Here is what my template looks like
<table class="table table-striped table-condensed">
        <thead>
            <tr>
                <th></th>
                <th>Client Name <button class="btn btn-mini btn-link" ng-click="sort('clientName')"><i  class="icon-sort"></i></button> </th>
                <th>Contact Name<button class="btn btn-mini btn-link" ng-click="sort('contactName')"><i  class="icon-sort"></i></button></th>
                <th>Email Address<button class="btn btn-mini btn-link" ng-click="sort('contactEmail')"><i  class="icon-sort"></i></button></th>
                <th></th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="client in clients | orderBy:orderProp:direction">
                <td><a ng-href="/#/clients/edit/{{client.clientId}}"><i class="icon-edit"></i></a></td>
                <td><a ng-href="/#/clients/view/{{client.clientId}}"> {{client.clientName}}</a></td>
                <td>{{client.contactName}}</td>
                <td>{{client.contactEmail}}</td>
                <td></td>
                <td><remove-button text="Remove Client" action="removeClient()"></remove-button></td>
            </tr>
        </tbody>
    </table>

I tell Angular to loop through the collection by using the ng-repeat command and then I can set the sorting options by using the filter command orderBy. I am then telling it what property in the scope to use for the sorting (orderProp) and which property will be used for ascending and descending direction (direction).

Also notice my heading column links. They have a ng-click command to call the function sort. The sort function takes a parameter for which column to sort and will either change the column sorting or the direction depending on what column was clicked.

Then in the controller class, I initialize those properties and create the sort function which checks to see if it was the same column that is set in the scope and if it is it simply changes the direction of the sort. If the column is different than what is in scope, then it resort based on the new column and resets the direction to ascending.

                    $scope.direction = true;
		    $scope.orderProp = "clientName";
		    $scope.sort = function(column) {
		        if ($scope.orderProp === column) {
		            $scope.direction = !$scope.direction;
		        } else {
		            $scope.orderProp = column;
		            $scope.direction = true;
		        }
		    };

Here is my Plunker Example.

 

My Daughter's Flute Evaluation

Saturday, May 4, 2013

My oldest daughter had her flute evaluation today. I can't recall doing anything that stressful when I was her age. Any how, as a proud pop, I couldn't resist and posted this on my website. She did really great.

They're many schools represented at this event, and as a matter of fact, my daughter's particular judge had traveled 5 hours to be at the competition. When we arrived at the particular school holding the competion, we settled in the cafeteria where kids from all over the region were practicing before their evaluation.  All I can say is, I am glad my daughter plays the flute and not trumpet like I did growing up, as trumpets can be quite loud when a bunch of them are together in one room plaing different music at the same time.

Free DotNetCurry Subscription

Thursday, May 2, 2013

If you are looking for some good programming related content on the latest on the Microsoft development stack, DotNetCurry has a free digital magazine that you can subscribe to.

http://www.dotnetcurry.com/magazine/

One of the things I like about this mazazine is that it has a lot of walk through examples where you can go through step by step and recreated what the article recreates. I find that I grasp new concepts a lot better when I can go through and code things up myself. For me it sinks in better that way.

Any how, check it out.

Building a Blog Redux - Creating a Lucene.Net Index (Part 8)

Thursday, October 18, 2012

This is the eighth post in a series of posts about how I went about building my blogging application.

  1. Building a Blog Redux - Why Torture Myself (Part 1)
  2. Building a Blog Redux - The tools for the trade (Part 2)
  3. Building a Blog Redux - Entity Framework Code First (Part 3)
  4. Building a Blog Redux - Web fonts with @font-face and CSS3 (Part 4)
  5. Building a Blog Redux - Goodreads feed using Backbone.js (Part 5)
  6. Building a Blog Redux - Mapping View Models to Entities Using AutoMapper (Part 6)
  7. Building a Blog Redux - Setting Up Lucene.Net For Search (Part 7)

In the previous post in the series I talked about how I went about Lucene.Net in my blogging application using StructureMap. In this post, I am going continue with the searching feature and write about how the indexing is set up.

What Initiates Writing an Index

Researching other implementations of creating an index, I saw several different options of when an index was created and refreshed. I saw examples of applications reindexing when the application starts up, when the content is updated or on demand on an admin screen. For my first implementation, I chose to keep it simple, so I just create a button on my admin site that kicks off the reindexing when the button is clicked.

Here's the view. Pretty simple.

<h2>Manage Search Index</h2>
<div id="manage-index-panel">
    
    <input id="reindex" type="button" value="Re-index Posts" />
    <div id="index-waiting-panel" style="displaynone;">
        <img src="/Content/images/sb_wait.gif" alt="Waiting"/>
    </div>
    <div id="reindex-status-panel">
        <ul></ul>
    </div>
</div>

The event kicked off via a AJAX call. I was thinking the reindexing would be taking a long time, but actually it is pretty fast so I am not so sure I needed to do this but hey what the heck.

var indexAdmin = function ($) {
 
    var $waitPanel = $('#index-waiting-panel'),
        $reindex = $('#reindex'),
        $status = $('#reindex-status-panel ul'),
        postUrl = '/manage/index/reindex',
        displayIndexCompletionStatus = function (items) {
            console.log('data returned', items);
            $waitPanel.css({ display: 'none' });
            $.each(items, function () {
                $status.append('<li>' + this + '</li>');
            });
        },
        init = function () {
 
            $reindex.on('click'function () {
                $waitPanel.css({ display: 'inline' });
                $.ajax({
                    url: postUrl,
                    cache: false,
                    contentType: 'application/json; charset=utf-8',
                    dataType: "json",
                    type: "POST",
                    success: function (data) {
                        displayIndexCompletionStatus(data);
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        console.log(jqXHR);
                        console.log(textStatus);
                        console.log(errorThrown);
                        displayIndexCompletionStatus(errorThrown);
                    }
                });
            });
        };
 
 
    return { init: init };
 
}(jQuery);
 
jQuery(document).ready(function() {
    indexAdmin.init();
});

Here is the MVC action that receives the API call.

public JsonResult Reindex()
        {
            var errors = _searchIndexService.RebuildIndex();
            if (errors != null && errors.Count > 0)
            {
                var errorList = errors.Select(error => error.Exception.Message).ToList();
                return Json(new { Status = errorList.ToArray() });
            }
            var status = new { Status = new[] { "Success" } };
            return Json(status);
        }

Pretty clean action. Just calling my Reindexing Service and if there were any errors then return a list of the errors. I am then sending a status message back in the response.

Creating the Index Document

Before I talk about the code that actually writes to the index let me first talk about the main Lucene.Net classes that are going to be involved in process.

  • The Directory is a flat list of files that may only be writing to once. What this means is once a file is created. It cannot be modified; rather, it can only be read from or deleted.
  • Documents are the primary units which stored within a directory. A document will have a collection of fields with will specify what content to index, store and compare with other content.
  • The IndexWriter is the class that actually writes out the index. It also has the ability to remove items from the index as well.
  • The Analyzer are the objects that tokenizes the content so it can be searched on later. There are various derivations of the analyzers that very in there complexity in searching. I am using the SnowballAnalyzer which does things like matches on root word on longer words. So for example is someone searching on "programming" would get match on the word "program". (Note: The SnowballAnalyzer is in a different Nuget package called Lucene.Contrib). 

The IndexWriter is the first thing that I setup. I really like how elegantly this object was set up in SubText so I am pretty much using the same implementation for my IndexWriter. You can see their version of the implementation by downloading their source code from their Google Code Repository. Essentially every time you need to call a IndexWriter function, you need to ensure the current thread is the only instance that is using object during the transaction. So rather then repeating the same code for every transaction, SubText creates a function that locks the object first and passes back a delegate where you can then call an action to executed.

Here is the locking function:

        private void EnsureWriterInstance()
        {
            if (writer != nullreturn;
 
            if (IndexWriter.IsLocked(_directory)) IndexWriter.Unlock(_directory);
 
            writer = new IndexWriter(_directory, _analyzer, IndexWriter.MaxFieldLength.UNLIMITED);
            writer.SetMergePolicy(new LogDocMergePolicy(writer));
            writer.SetMergeFactor(5);
        }

Then every time an action needs to be excuted the DoWriterAction is called.

        private void DoWriterAction(Action<IndexWriter> action)
        {
            lock (WriterLock)
            {
                EnsureWriterInstance();
            }
            action(writer);
        }

Be sure the writer is static:

private static IndexWriter writer;

The code is returning the current IndexWriter if one already exists and if it does not exists, it makes sure the document object and setting the merging policy. The merge factor is a setting that determines how often the segment indices are merged. According to the specs, "With smaller values, less RAM is used while indexing, and searches on unoptimzed values are faster, but indexing speed is slower. With larger, values, searches on unoptimized indices are slower, indexing is faster."

For my blog site, I would like the user to be able to search on the title, content, tags, and meta-description, but I would like to store other things in the document that I can use in the displaying the search result like the publishing date, and the slug for the link. Conversely, some field I just want to index, but I do not want to store like the post content.

//create the fields
            var postId = new Field(
                PostId,
                NumericUtils.IntToPrefixCoded(post.Id),
                Field.Store.YES,
                Field.Index.NOT_ANALYZED,
                Field.TermVector.NO);
 
            var title = new Field(
                Title,
                post.Title,
                Field.Store.YES,
                Field.Index.ANALYZED,
                Field.TermVector.YES);
 
            var body = new Field(
                Body,
                post.PostContent,
                Field.Store.NO,
                Field.Index.ANALYZED,
                Field.TermVector.YES);
 
            var tags = new Field(
                Tags,
                tagDelimited.ToString(),
                Field.Store.NO,
                Field.Index.ANALYZED,
                Field.TermVector.YES);
 
            var metaDescirption = new Field(
                MetaDescription,
                post.Description ?? string.Empty,
                Field.Store.NO,
                Field.Index.ANALYZED,
                Field.TermVector.YES
                );
 
            var published = new Field(
                IsPublished,
                post.IsPublished.ToString(),
                Field.Store.NO,
                Field.Index.NOT_ANALYZED,
                Field.TermVector.NO);
 
            var slug = new Field(
                Slug,
                post.Slug,
                Field.Store.YES,
                Field.Index.NO,
                Field.TermVector.NO);
 
            var pubdate = new Field(
                DatePublished,
                pubDate,
                Field.Store.YES,
                Field.Index.NOT_ANALYZED,
                Field.TermVector.NO);

So each document has a collection of fields and each field I specify the following:

  • The key (name) of the field.
  • The value of the field.
  • Whether or not I want to store the raw value in the field for later use,
  • Whether or not I want to index the value of this field to be used in searching.
  • Whether or not and/or how I want the field to use term vectors.

I can also weight some fields so that I get a higher match on a word if for example it is located in the title verses being located in the body. I can do this by calling the SetBoost function and giving it a weighting factor.

So then once my fields are created and weighted, I can then add them to a document.

            //boost some of the entries
            title.SetBoost(4);
            tags.SetBoost(2);
            body.SetBoost(1);
            slug.SetBoost(2);
            metaDescirption.SetBoost(1);
 
            //add the fields to the docuement
            doc.Add(postId);
            doc.Add(title);
            doc.Add(body);
            doc.Add(tags);
            doc.Add(published);
            doc.Add(pubdate);
            doc.Add(slug);
            doc.Add(metaDescirption);

Once the document is prepared then you add this current document to the index writer where the collection of documents are contained.

 DoWriterAction(indexWriter => indexWriter.AddDocument(CreateDocument(currentPost)));

Once all the documents are added then the commit function where all the pending changes.

DoWriterAction(
                indexWriter =>
                    {
                        indexWriter.Commit();
                        if (optimize) indexWriter.Optimize();
                    });

The Optimize function on the indexWriter reorganizes the indexes so that they can be queried faster and provide better performance.

Conclusion:

So now I have functionality that when I click a button, the current index is refreshed with the latest blog content from the database. In my next post in this series I will show you how I can now query this index.

As always you can check out the code at my GitHub repository.

Resources: