Thursday, June 25, 2015

Tips for building AngularJS forms with validation and Bootstrap

Lets get started, in this article we will be using AngularJS 1.3.15, because that's what version the project I'm currently working on uses. Keep in mind, this stuff changes all the time but, for now, this seems to be the "correct" way.

You've probably been using AngularJS for a while now, you also know how forms work, and you probably have forms in you're AngularJS project that are collecting data and everything is working fine. So why do you still need help with forms? That's where I was at a few days ago. I've been building forms with a basic understanding of ng-model and FormController, and they work, but I've been writing a lot of extra JavaScript to do it.

A developer on my team, figured out a better way to do a lot of the validation using AngularJS and it's was so simple. It was right here the whole time: code.angularjs.org/1.3.16/docs/guide/forms.

So, let's put a contact us form together using AngularJS and Bootstrap.
angular.module("mainApp")
    .controller("ContactCtrl", function ($scope, $log) {
        "use strict";
});
<!-- form.tpl.html -->
<form class="form-horizontal" name="contactForm" novalidate>
</form>

Starting with a basic controller and a form template. We already have a ton of stuff setup here thanks to AngularJS. AngularJS adds ng-form code to all <form> and <ng-form> tags that sets up a property on the $scope that matches the name you gave the form. In this example, even though we didn't create it, we can now access $scope.contactForm which is an instance of FormController.

For the HTML template, I'm going to copy the code from the link (code.angularjs.org/1.3.16/docs/guide/forms) to setup a name and email field in our form. Before we use it though, let's update the code to match what Bootstrap has for the basic HTML structure for a horizontal form using their CSS classes.
<!-- form.tpl.html -->
<form class="form-horizontal" name="contactForm">

    <div class="form-group">
        <label for="inputName" class="col-sm-2 control-label">Name:</label>
        <div class="col-sm-10">
            
            <input type="text" id="inputName" name="uName"
                   class="form-control"
                   placeholder="Enter your name"
                   ng-model="user.name"
                   required />
            
            <div ng-show="contactForm.$submitted || contactForm.uName.$touched">
                <span ng-show="contactForm.uName.$error.required">
                    Tell us your name.</span>
            </div>
            
        </div>
    </div>

    <div class="form-group">
        <label for="inputEmail" class="col-sm-2 control-label">E-mail:</label>
        <div class="col-sm-10">
            
            <input type="email" id="inputEmail" name="uEmail"
                   class="form-control"
                   placeholder="E-mail"
                   ng-model="user.email"
                   required />
            
            <div ng-show="contactForm.$submitted || contactForm.uEmail.$touched">
                <span ng-show="contactForm.uEmail.$error.required">
                    Tell us your email.</span>
                <span ng-show="contactForm.uEmail.$error.email">
                    This is not a valid email.</span>
            </div>
            
        </div>
    </div>

    <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
            
            <button type="button" 
                    class="btn btn-default" 
                    ng-click="reset()">Reset</button>
            
            <button type="submit" 
                    class="btn btn-primary" 
                    ng-click="save()" 
                    ng-disabled="contactForm.$invalid">Save</button>
        </div>
    </div>

</form>

In the code above, everything looks pretty standard.

The name attribute
The thing to look at is that on each input we have added a name attribute. This is what I was missing when I first tried to setup forms using validation in this way. Adding the name "uName" will in turn add that property to the parent formController instance "contactForm".

Now, that element's ng-model scope can be accessed via $scope.contactForm.uName. This is big because then we can easily add validation messages like ng-show="contactForm.uEmail.$error.required" in a list under each form input.

$touched
Another thing I overlooked was the wrapping div around the validation messages. With out it, as soon as the form loads, we see the required error message and the input it outlined in red. I thought this was an issue with AngularJS and spent a lot of time getting around it, but actually I just need to use the $touched property. This will cause the input not to be shown as invalid until the user causes a blur event on the field.

$submitted
The last thing I want to point out is the contactForm.$submitted property. Since AngularJS converts all form tags to ng-form, they no longer submit to the server by default when you mark a button as type="submit". What happens is that this property on formController is set to true, which allows us another chance to show any validation errors in the form.

Let's do a little work on our controller now.
angular.module("mainApp")
    .controller("ContactCtrl", function ($scope, $log, userService) {
        "use strict";

    //we need to create a new user to hold our form data. 
    $scope.user = userService.createUser();

    //we'll assume that userService.createUser() returns an object like this:
    //{ name : "", email : "" };

    $scope.reset = function () {
        //here we can just do a simple call to create a new user again
        $scope.user = userService.createUser();
    };

    $scope.save = function () {
        //we can use our formController to do a simple check to see if the form is valid
        if( $scope.contactForm.$valid ) {
            //all fields are valid, so submit to server.
            userService.save( $scope.user );
        }
    };
});

As you can see, we can let AngularJS do most of the work with forms and validation. No more if/else statements like this to check all the custom validation on your form elements.
    $scope.save = function () {
        if($scope.user.name.length === 0) {
             $scope.userNameError = "Name is Required!";
        } else if($scope.user.email.length > 0) {
             $scope.userEmailError = "Email is Required!";
        } else if(! isEmail($scope.user.email) ) {
             $scope.userEmailError = "This is not a valid email.";
        } else {
             //form is valid
             userService.save( $scope.user );
        }
    };

But you never wrote code like that anyway, right?

Wednesday, April 29, 2015

HTML5 Canvas Drawing and Animation 101 (no frameworks!)

The other day I had some downtime and decided to mess around with drawing and animating with the canvas tag. As far as I can tell, unless your building a game or some 3D interactive thing on the web, you probably should opt for svg vector drawing and/or animation since they are way simpler.

For drawing with the canvas element, I recommend one of these great frameworks to ease development: EaselJS and Three.js.

For this experiment, I thought I'd just write vanilla JavaScript since I was just messing around and didn't want to spend a lot of time reading documentation. After a few hours, I end up with this neat little network of dots animation:


Below, I will talk through how I achieved this animation and show the code used.

First off, we need some JavaScript to add the canvas tag to the page. You can always just write the <canvas /> tag in the body of the page and give it an id, so whichever you prefer.
var canvas = document.createElement('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);

Now for the heart of our application, the looper function. This is the function that will repeat over and over again and draw each frame of our animation, just like the frames of a movie. For this, it is recommended that we use the requestAnimationFrame() method in JavaScript instead of setInterval() since it is geared towards animating elements on the screen and will perform better/smoother. Plus, if the tab or window is inactive, the animation stops which is better for the computer's CPU.
var startTime = new Date().getTime(),
    lastTime = 0;

//start looping
requestAnimationFrame( looper );

function looper() {
    requestAnimationFrame( looper );

    //calculate current frame time and delta time
    //delta time - elapsed time since the last call to the looper method
    var now = new Date().getTime() - startTime,
        delta = (now - lastTime)/1000;

    //get the 2d context object
    var ctx = canvas.getContext('2d');

    //clear all graphics from the canvas
    ctx.clearRect(0 , 0 , canvas.width , canvas.height);

    //update game physics (animations)
    update( delta );

    //render everything back to the canvas
    draw( ctx );
    
    //save the current time for next loop
    lastTime = now;
}

//these 2 methods will be finished below
function update(delta) { }

function draw(ctx) { }


In the code above, I ended up not using delta time in my experiment, but if you are building a serious app or game be sure to take a look at some blog post about it: Fix Your Timestep. There is also quite a bit more code you'll need to make sure your game runs at a constant speed no matter what computer or browser it's running on.

Now to talk about what we are drawing/animating. In the picture above we have an array of orange circles (nodes) and a bunch of green lines (links) that are chasing each other around the screen. When one node catches it's target the link is broken and the node stops moving. In the code I represent each orange circle as a node agent. An agent is basically just an object that keeps track of the current values for one of the circles on the screen. This way, our draw function ends up being rather simple. Just loop over all node agents in the array and draw them on the screen.
//create network of nodes
var nodesArray = createNodes(200, canvas);

//This method will create an array of node agents to 
//be drawn on the screen
function createNodes(size, canvas) {
    var nodes = [];

    for(var i = 0; i < size; i++ ){
        //Here we create an agent at a random x, y position
        //with a random radius that is between 5px and 25px
        nodes.push(
            createNodeAgent(
                Math.random() * canvas.width,
                Math.random() * canvas.height,
                5 + (Math.random() * 20)
            )
        );
    }
    return nodes;
}
//This is a factory function that return a new node agent object
function createNodeAgent(x, y, r) {
    return {
        x : x,
        y : y,
        radius : r,
        //Here we give it the orange color and set the alpha
        //based on this agent's size (bigger means more opaque)
        color : "rgba(161,96,9," + r/30 + ")",
        link : -1
    };
}

With our array of nodes we can finish writing the draw method which will fill our canvas with a background color and draw each node agent on the screen.
function draw(ctx) {
    //fill screen with black background
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    //draw all nodes
    nodesArray.forEach(function (agent) {
        drawNode(ctx, agent);
    });
}
function drawNode(ctx, agent) {
    ctx.beginPath();
    ctx.arc(agent.x, agent.y, agent.radius, 0, 2 * Math.PI, false);
    ctx.fillStyle = agent.color;
    ctx.fill();
}

At this point, you should be able to run the app and see all the circles on the screen. Every time you refresh the screen they are placed at random spots on the screen which is pretty cool, but having motion will be way cooler so lets finish the update method.

One thing we need to do first though is figure out how to get the nodes to chase a target node. For this we will use one of the properties we added above to each node called "link". Link is going to be the index of another node in our master array. We could just pick one at random from the master array, but after a few test, it looked much nicer when nodes that are closest to each other are linked together. You end up with more of a networking effect this way.
//connect all nodes to the target node that it is closest to.
linkNodes(nodesArray);

function linkNodes(nodes) {
    nodes.forEach(function (agent, idx) {
        //check how far all other agents are from this agent
        var dists = nodes.map(function (agentToCheck, i) {
            return Math.abs(agent.x - agentToCheck.x) + Math.abs(agent.y - agentToCheck.y);
        });

        //find the next closest agent thats not already linked
        var closest = 0,
            dist = 99999;

        dists.forEach(function (distance, i) {
            if(distance < dist && nodes[i] !== agent && nodes[i].link === -1) {
                dist = distance;
                closest = i;
            }
        });
        agent.link = closest;
    });
}

I'll admit, this method isn't pretty and there is probably a way better way to do this but, whatever, it works so I'm rolling with it.

With this method, we have iterated through each node and gave it a target node to chase. Now we can finish the update method. The update method will slowly step each node towards it's target until it gets within a certain distance. When is distance is reached, the node will clear the link and stop moving.
function update(delta) {

    //nodes move towards the agent its connected to
    nodesArray.forEach(function (agent) {
        //if this agent still has a target, update position
        if(agent.link !== -1) {
            //find target node in master array
            var targetAgent = nodesArray[agent.link];
            
            //step this agent towards the target agent using
            //a simple easing function
            agent.x = chase(agent.x, targetAgent.x, 100);
            agent.y = chase(agent.y, targetAgent.y, 100);
            
            //check to see if we are next the our target node
            var distX = Math.abs(agent.x-targetAgent.x),
                distY = Math.abs(agent.y-targetAgent.y),
                min = (agent.radius + targetAgent.radius);

            //if we are then clear link and stop moving
            if(distX < min && distY < min){
                agent.link = -1;
            }
        }
    });
}
function chase(current, target, constant) {
    var change = (target - current) / constant;
    return current + change;
)

This is getting cooler! So when you run your app, you should see all the dot slowly chasing there targets around the screen. You can add all kinds of cool math here and see what happens, also if you haven't noticed, what we have here is the start of a basic Particle System. From here we can start looking into velocity and force and all kinds of neat things. But, in the spirit of not re-inventing the wheel, we should probably defer to a fully tested framework for more advanced topics.

Before I finish, you might be wondering where all the lines in the image at the top are. Lets revise our update method and add a function to draw the lines between each linked agent.
function draw(ctx) {
    //fill screen with black background
    ctx.fillStyle = 'black';
    ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);

    //first pass, draw all links
    nodesArray.forEach(function (agent) {
        if(agent.link !== -1) {
            var linkedAgent = nodes[agent.link];
            drawLink(ctx, agent, linkedAgent);
        }
    });

    //second pass, draw all nodes
    nodesArray.forEach(function (agent) {
        drawNode(ctx, agent);
    });
}

function drawLink(ctx, agent, linkedAgent) {
   ctx.beginPath();
   ctx.moveTo(agent.x, agent.y);
   ctx.lineTo(linkedAgent.x, linkedAgent.y);

   //line style
   ctx.lineWidth = 5;
   ctx.lineCap = 'round';
   ctx.strokeStyle = 'rgba(161,174,20,0.5)';

   //draw
   ctx.stroke();

   //cleanup
   ctx.lineWidth = 0;
}

Happy Coding!

Thursday, March 5, 2015

Instead of ng-include, try an AngularJS directive

Recently, I came across a lot of code in an AngularJS project I'm working on that uses ng-include. I haven't really used ng-include in Angular very much. Instead, whenever I need to reuse a chunk of HTML, I just use create a custom AngularJS directive. Looking back at how I’ve been using directives, a ng-include might have been a better choice in some places.

With that being said, I still think that in most situations, you are better off splitting up functionality into directives. Many of the places in the current Angular project I’m working on where ng-include is being used, a directive would have been a much better choice. So how to know when to use one or the other? One big red flag is if you are thinking of using the ng-init, ng-controller attributes. These have their uses, but, I believe should be avoided when building your application.

Here is and example of what I mean:
<!-- app/views/properties.html -->
<div class="properties" ng-controller="propertiesCtrl">
    <form class="form-horizontal">
        <div class="form-group" 
             ng-repeat="key in currentElement.props" 
             ng-include="'app/views/properties/main.html'"></div>
    </form>
</div>
<!-- app/views/properties/main.html -->
<label class="col-md-3 control-label">
    {{key | capitalize}} :
</label>
<div class="col-md-7"
     ng-include="'composer/flowDesigner/views/properties/controls/DEFAULT.html'">
</div>

The code above is basically the reason why directives were created in Angular. A directive allows you to create a chuck on code that can be expressed as a tag, a pseudo web component if you will. Directives also allows us to isolate functionality and reduce the amount of code that is in a controller for a view or page.

Here is the example above but written as a directive with some comments about how the code is structured:
angular("mainApp")
    // We define the name of the directive with a prefix. The prefix helps to 
    // distinguish our custom html directive tags from actual html tags
    // example : <com-header> vs <header>
    //
    // NOTE: Angular converts camel case to dash case when rendering directives. 
    //       So in our html page, we will use <com-properties> instead of <comProperies>
    .directive("comProperies", function () {
        "use strict";

        return {
            // restrict how this directive can be used, E means as an html tag only
            restrict: "E",
 
            // replace the tag on the page with the template of this directive
            replace: true, 

            // path to our html template
            templateUrl: "app/views/comProperies.tpl.html",

            // html to JS linking function
            link: properiesLink,

            // this directive's controller function
            // NOTE: Start with a capital letter since angular uses controller like 
            //       pseudo classes
            controller: ProperiesCtrl
        };
  
        function properiesLink($scope, $element, $attrs) {
            // The linking function allows us to add any html manipulation we might need.
            // Basically, if you are using jQuery "$" selector function for anything, 
            // add it here.
            //
            // NOTE: Try to avoid using a linking function. Angular has a lot of directives 
            //       you can add to your template file so you don't need one. 
            //       If you are doing anything with the $attrs object, use $attrs.$observe. 
            //       This will trigger a function if the value of an attribute ever changes.
            //       $element is a jQuery reference to your template's root html element.
        }
  
        function ProperiesCtrl($scope) {
            // A basic controller function for this directive. One thing to remember is that 
            // all instances of this directive use the same controller. So, for example, 
            // "this.currentIndex = 0;" would be a global variable among all 
            // <com-properties> elements.
        }
    });

Not too much has changed in this example except for code structure. The real advantage can be seen as you start to expand functionality of this directive. For example, this directive looks like it creates a form and lists out the properties in currentElement.props so that they can be edited. Now we want to list out the properties of currentPlan.details. By re-using the “ng-model” attribute that is provided by angular. We can update the custom directive html element so it can be written as:
<com-properties ng-model=”currentPlan.details”></com-properties>

To make this update, first inside our template, find and change ng-repeat="key in mainTabKeys" to ng-repeat="key in ngModel". Next, add scope : {ngModel : "=" } to the return object in the directive so now our directive looks like this:
angular("mainApp")
    .directive("comProperies", function () {
        "use strict";

        return {
            restrict: "E",
            replace: true, 
            templateUrl: "app/views/comProperies.tpl.html",
            link: properiesLink,
            controller: ProperiesCtrl,
            scope : { 
                ngModel : "=" 
            }
        };
        
        // ...
    });

With this change, this directive will now list out all the properties of whatever object the attribute ng-model is set to. I encourage you to read more about directive by following the links below, since this article is a very limited overview of how directives are used in AngularJS.

Friday, December 19, 2014

Stop trying to add class structure to JavaScript

Recently, I read an interesting article titled The Two Pillars of Javascript, and one of the points in the article that I found interesting was the part about trying to create classes in JavaScript. Stop trying to create class structure in JavaScript? But, I thought classes and OOP are how we should be programming, plus classes are everywhere in .NET, Java and Ruby.

The article talks about how JavaScript is not like other languages and how we should embrace the "objects without classes" and "anonymous functions (Lambdas)" structure that it provides.

I only bring this up because after reading that article, I realized, that in Angular I'm unconsciously doing this already. With AngularJS you never use the "prototype" or "new" keywords, because your only registering objects or constructor functions.

Ok, lets step back, what are we talking about?
Here is how we create an object using class like functionality as shown in a previous post:
var Car = function(color) {
    this._wheels = 4;
    this._condition = 'good';
    this.initialize(color);
}
Car.prototype.initialize = function(color) {
    this._color = color;
    ...
};
Car.prototype.drive = function(speed) {};

//create a car and drive
var myCar = new Car(#bada55);
myCar.drive(90);

//and then extend Car and create a Truck
var Truck = function(color){
    this._type = 'pickup';
    this.initialize(color);
}
var tmp = Truck.prototype = new Car();
Truck.prototype.Car_initialize = tmp.initialize;
Truck.prototype.initialize = function(color) {
    this.Car_initialize(color);
    ...
};
Truck.prototype.tow = function(weight){};

And this works fine, but how can we do this using just objects and factory methods? The key method you will need is the extend method which we can borrow from jquery $.extend() or any other JavaScript frameworks. The extend method basically copies all the properties and methods from one object to another. You've probably used this to create copies of data objects already.

With this method we can do the same operation as above but with factory methods instead of fake classes. For the Truck object, we can just 'extend' and internal car object using the extend method.
function createCar (color) {
    var _color = color,
        _wheels = 4,
        _condition = 'good';
        
    return {
        getColor : function () { return _color; },
        setColor : function (color) {
            _color = color;
        },
        drive : function (speed) {}
    }
}
//and then extend Car and create a Truck
function createTruck (color) {
    var _car = createCar(color),
        _type = 'pickup';
    
    //here we can use our extend method to add new methods to truck
    return $.extend(_car, {
        getType : function () { return _type; },
        tow : function (weight) {}
    });
}

//now we can create a truck and drive
var myTruck = createTruck(#bada55);
console.log(myTruck.getType());
myTruck.drive(90);


One of the great things about this way is you can now have real private variables. There is no way to access _color and _wheels, unless you create getters and setters which is generally a good practice. We can even have private functions inside our factory methods. Here is a Wiki link to learn more about the Factory Method Pattern. This article only covers a small part of it.

How is this used in Angular? Every time you call a app.controller(), app.directive(), app.factory(), etc. you are supplying a factory method to create this object. Now, thinking this way, I wonder if controllers, directives, or services could be extended? Hmm, not sure, I'll have to do some testing.

Friday, November 7, 2014

AngularJS custom data table example

This is probably the first of many post on AngularJS since the project I'm on uses AngularJS for the front-end with a Java (Play framework) back-end. I don't know anything about Play (sorry) but I have picked up quite a lot of Angular in the last few months.

Feeling like I might be finally getting the hang of AngularJS, I wanted to do a post about the custom data table I just finished. Before You read more, if you want a pre-made component then I'd recommend ngTable. We actually used it for some of the work on this project. In some situation we ended up running into issues and just rolled our own. It seems that, with AngularJS, I often start using a pre-built solution and find myself in a hole with some functionality that the client requested and that wasn't originally part of the component, ugh.

So, this is just a place for you to start from in your project. This table directive will have 2 attributes, "ng-model" which will be the rows of data and "columns" which will be how you configure columns headers and what data to display.

Let start with a simple directive definition:
// customAngularTable.js
mainApp.directive('customAngularTable', function () {
    return {
        restrict : 'E', //this can only be used as a tag 
        replace  : true, //replace the html tag with out template
        require  : 'ngModel', //this directive has to have the attr ng-modal=""
        scope    : {
            ngModel : '=', //variable name will be the same on our scope object
            columns : '='
        },
        templateUrl : 'customAngularTable.tpl.html',
        controller : 'customAngularTableCtrl'
    };
});
The basic template will look something like this:
// customAngularTable.tpl.html
<div>
    <table>
        <thead>
            <tr>
                <th ng-repeat="col in columns" 
                    ng-click="applySort(col)">
                    {{col.label}}
                </th>
            </tr>
            <tr>
                <th ng-repeat="col in columns">
                    <input type="text" 
                           ng-model="filters[col.property]" 
                           ng-change="applyFilters(col)" />
                </th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="row in tableRows">
                <td ng-repeat="col in columns">
                    {{row[col.property]}}
                </td>
            </tr>
        </tbody>
    </table>
    
    <hr />
    <!-- We can just use a pre-built component to page since that 
            is beyond the scope of this tutorial -->
    <pagination ng-model="currentPage" 
                items-per-page="pageSize" 
                total-items="rowsTotal" 
                previous-text="&lsaquo;" 
                next-text="&rsaquo;">
    </pagination>
</div>
Which brings us the the fun part, the controller:
// CustomAngularTableCtrl.js
mainApp.controller('customAngularTableCtrl', function ($scope) {
    'use strict';

    /**
     * View Variables
     */
    $scope.tableRows = [];
    //- sorting
    $scope.currentSortColumn = null;
    $scope.currentSortOrder = '';
    //- filtering
    $scope.filters = {};
    // - paging
    $scope.currentPage = 1;
    $scope.pageSize = 10;
    $scope.rowsTotal = 1;

    /**
     * View Methods
     */
    $scope.applySort = function (columnToSortOn) {
        
        if($scope.currentSortColumn === columnToSortOn) {
            //user has clicked the same column, so we need to change sort order
            //we have 3 sorting states here to step through (asc,desc,'')
            $scope.currentSortOrder = ($scope.currentSortOrder === '') ? 
                'asc' : ($scope.currentSortOrder === 'asc' ? 'desc' : '');
            
            //if the previous operation set currentSortOder to blank, 
            //remove currentSortColumn
            if($scope.currentSortOrder === '') {
                $scope.currentSortColumn = null;
            }
        } else {
            //user has clicked a new column
            $scope.currentSortColumn = columnToSortOn;
            //step to first sort state
            $scope.currentSortOrder = 'asc';
        }
        
        //update view
        updateTableData();
        
    };
    
    $scope.applyFilters = function () {
        //reset current page to 1, se we don't filter out all the rows 
        //and display an invalid page
        $scope.currentPage = 1;
        
        //since, updateTableData will apply all the filters for us, 
        //we just need to re-call that method here.
        updateTableData();
    };

    /**
     * Watches 
     */
    //we'll need to setup a watch for ngModel so if it changes we update the view
    $scope.$watch('ngModel', function (newValue, oldValue) {
        //check if the value is defined and not null
        if(angular.isDefined(newValue) && newValue !== null) {
            //since we have a new array, update view
            updateTableData();
        }
    });
    
    //We need to watch currentPage so we can update the view with the current page
    $scope.$watch("currentPage", function (newValue, oldValue) {
        //check if the page really did change
        if(newValue !== oldValue) {
            updateTableData();
        }
    });

    /**
     * Private Methods
     */   

    function updateTableData () {
        //we will create a new array that we will fill with 
        //all the rows that should still bee in the view.
        var viewArray;

        //step 1: apply filtering on all the rows
        viewArray = $scope.ngModel.filter(applyFilters);
        
        //step 2: if the user has clicked a column, apply sorting
        if($scope.currentSortColumn !== null) {
            // using a getSorter function here allows you to use custom 
            // sorting if you want
            viewArray = viewArray.sort(getSorter());
        }
        
        //step 3: update pagination and apply
        $scope.rowsTotal = viewArray.length;
        // - current page is 1, based but our array is 0 based so subtract 1
        var pageStartIndex = ($scope.currentPage-1) * $scope.pageSize;
        // - page end index is either page size or whatever is left in the array
        var pageEndIndex = pageStartIndex + Math.min(viewArray.length, $scope.pageSize);
        // - splice view array to page start and end index's, and return the 
        //   page we want to view
        viewArray = viewArray.splice(pageStartIndex, pageEndIndex);
        
        //pass the ref to the viewArray to $scope and let angular refresh the html table
        $scope.tableRows = viewArray;
    }
    
    function applyFilters (row) {
        var allowed = true;
        
        //since we set ng-model on each input in the 2nd header row to filters[col.property] 
        //in the view, angular will auto-create a matching property key on the filters object 
        //and set it to whatever the user types into that input.
        
        //So, here we can loop through each property on the $scope.filters property and check
        //if the row should still be displayed.
        Object.keys($scope.filters).forEach(function (key) {
            var rowValue = row[key],
                filterValue = (angular.isDefined($scope.filters[key]) 
                                 ? $scope.filters[key] : "");
            
            //if this value is still allowed by other columns, 
            //test it with this filter value
            if(allowed && filterValue !== null) {
                //here is a good place to add custom filters based on this column. 
                //Ex. var column = lookupColumnFormKey(key);
                //    if(column.type === 'number')  
                //        allowed = numberFilter(rowValue, filterValue); 
    
                allowed = stringSearchFilter(rowValue, filterValue);
            }
        });
        return allowed;
    }
    
    function getSorter () {
        //Here you can return different sort functions based on $scope.currentSortColumn
        //Ex. if($scope.currentSortColumn.type === 'number) 
        //         return numberSorter($scope.currentSortColumn, $scope.currentSortOrder);

        return stringSorter($scope.currentSortColumn, $scope.currentSortOrder);
    }
    
    /**
     * Checks if value contains the chars that are in filterValue.
     */
    function stringSearchFilter (value, filterValue) {
        value = value.toString().toLowerCase(); //toString in case it's a number
        filterValue.toString().trim().toLowerCase();
        return (value.indexOf(filterValue) !== -1);
    }
    
    /**
     * Compares 2 rows as strings based on sortColumn.property.
     */
    function stringSorter (sortColumn, sortOrder) {
        return function (rowA, rowB) {
            var valueA = rowA[sortColumn.property],
                valueB = rowB[sortColumn.property],
                result = valueA.localeCompare(valueB);
            if(sortOrder === 'desc') {
                result *= -1;
            }
            return result;
        };
    }
});
I tried to add as many comments as I could so you can see whats going on here. Next would be how to implement this directive. You'll need to setup the columns array based on the data you want to display in the table.

Here is an example of how this is done:
mainApp.controller('AngularTableTestCtrl', function($scope) {
    $scope.rows = [
        { first : 'Sue', last : 'Davis', title : 'Web Developer', company : 'Infusion' },
        { first : 'David', last : 'Marks', title : 'Sales Rep', company : 'Walmart' },
        { first : 'Jake', last : 'Richards', title : 'Customer Service', company : 'Target' }
    ];
    $scope.columns = [
        { label : 'First Name', property : 'first' },
        { label : 'Last Name', property : 'last' },
        { label : 'Occupation', property : 'title' },
        { label : 'Company', property : 'company' }
    ];
});
And the actual directive html tag would be:
<custom-angular-table columns="columns" ng-model="rows"></custom-angular-table>
This will setup a very basic angular data table directive with filtering, sorting and paging that you can add to as needed. For paging I used Angular UI Bootstrap which works really nice. In the app I'm working on, I had to add custom filters, sorters and input fields into the table body with validation. Basicly, Excel in the browser :).

These files can be found on my GitHub repo https://github.com/jasonsavage/simple-angular-table.

Wednesday, June 4, 2014

Moving to Raleigh to work for Infusion

The time has come for me to pack my bags and move south. I've been in Pittsburgh, PA since I graduated from Edinboro University in 2003, and even a few years before college. Living and working in the burgh, I've gained a lot of experience and had the pleasure working with many really talented people. But, alas, the weather has finally gotten to my wife and me. Plus, my son is starting 2nd grade in the fall! and I fear that if we don't move soon, we'll never leave.

I was offered an Interactive Developer role with a big New York company called Infusion. Infusion is opening an office in Raleigh, NC and I'll be part of the many new hires for that team.

It saddens me to leave my current role with Moxie and move out of the city I've grown to love (and hate sometimes), but this is a great opportunity and I have to give it a try. Raleigh is a really pretty city and the area is booming with a lot of tech jobs, so we'll see how it goes... and hey, you can always move back, right?

Thursday, May 8, 2014

Date Strings from Twitter & Facebook Invaild? (IE & Safari)

Just learned about this yesterday. It seems that if you pass the date string that is returned from Twitter and/or Facebook to the javascript Date() constructor, in IE (and Safari), it shows it as an invalid date.

Twitter returns the "created_time" as something like this: Thu May 01 13:57:04 +0000 2014, which is shown as "Invalid Date" in IE.
    
    //created_time = Thu May 01 13:57:04 +0000 2014
    var date = new Date( jsonData.created_time );
    
    console.log( date.toString() );
    // = IE 9 & 10: 'Invalid Date' 
    // = Chrome:    'Mon May 05 2014 14:50:00 GMT-0400 (Eastern Daylight Time)'
    // = FireFox:   'Mon May 05 2014 14:50:00 GMT-0400 (Eastern Standard Time)'
    // = Safari:    'Mon May 05 2014 14:50:00 GMT-0400 (Eastern Daylight Time)'


For Facebook, the 'created_time' value is something like: 2014-04-17T12:59:04+0000, which is shown as "Invalid Date" in IE and Safari.
    
    //created_time = 2014-04-17T12:59:04+0000
    var date = new Date( jsonData.created_time );
    
    console.log( date.toString() );
    // = IE 9 & 10: 'Invalid Date' 
    // = Chrome:    'Thu Apr 17 2014 08:59:04 GMT-0400 (Eastern Daylight Time)'
    // = FireFox:   'Thu Apr 17 2014 08:59:04 GMT-0400 (Eastern Standard Time)'
    // = Safari:    'Invalid Date'


So, what fixed the problem, for now, was to do a little manipulation on the date string before it is passed it to the Date() constructor.
    
    //twitter = 'Thu May 01 13:57:04 +0000 2014'
    //facebook = '2014-04-17T12:59:04+0000'
    
    var created = facebook.created_time;

    if( isFacebook )
    {
        //this fixes the issue in IE and Safari, and still works in Firefox and Chrome even though they don't need it.
        created = created.replace(/-/g, '/').replace(/T/, ' ').replace(/\+/, ' +');
    }
    else if( isTwitter )
    {
        //this is only an issue in IE, so we can just do a quick test and fix the issue.
        if( navigator.userAgent.match(/MSIE\s([^;]*)/) )
            created = created .replace(/( \+)/, ' UTC$1');
    }

    var date = new Date( created );