Friday, March 6, 2015

Another Round of Applying a Style Guide (Todd Motto)

At ng-conf 2015, the keynote speakers mentioned another style guide <https://github.com/toddmotto/angularjs-styleguide> by Todd Motto that nicely extends on the previous style guide we used.

Most of the changes are simple, but one (route resolve) requires a little more explanation. See <http://www.johnpapa.net/route-resolve-and-controller-activate-in-angularjs/>.  This also, introduces another style guide by John Papa <https://github.com/johnpapa/angular-styleguide>.

One interesting thing that happens when one goes through the style guides, one learns something basic that for some reason you never thought about before.  One example is Function Hoisting as described in <http://www.w3schools.com/js/js_function_definition.asp>.

The result with these two style guide applied is at <http://jsfiddle.net/sckmkny/r19b5fvy/>.

Wednesday, March 4, 2015

Creating a Customized Directive

In the example, we have repeated (and longish) HTML in the partials (see below). We are going to create a customized directive to streamline the partials.  Again, this is a somewhat manufactured example as the HTML is not that terribly long.

HTML
 
<div class="panel-heading">
     <h3 class="panel-title"><button class="btn btn-default" ng-click="login.navigate('/')"><span class="glyphicon glyphicon-chevron-left"></span> Back</button> Login</h3>                
</div>

A good place to learn about custom directives is through the Code School course on AngularJS <https://www.codeschool.com/courses/shaping-up-with-angular-js>.

The change amounts to creating a directive (and supporting HTML partial) as follows:

JavaScript
// app/directives/screenHeading.js
(function() {
angular.module('myApp').
directive('screenHeading', function() {
 return {
  restrict: 'A',
  templateUrl: 'partials/screenHeading.html',
  scope: {
   centerName: '@',
   leftName: '@',
   leftIcon: '@',
   leftFunction: '&',
   rightName: '@',
   rightIcon: '@',
   rightFunction: '&'
  },
  bindToController: true,
  controllerAs: 'screenHeading',
  controller: function() {
  }
 };
});
})();    
// EOF

HTML
 
    <!-- partials/screenHeading.html (obmit work-around script tag) -->
    <script type="text/ng-template" id="partials/screenHeading.html">    
        <table style="width: 100%">
            <tr>
                <td style="text-align: left; vertical-align: middle; width: 20%;">
                    <button type="button" class="btn btn-default btn-sm" ng-if="screenHeading.leftName" ng-click="screenHeading.leftFunction()"><span class="glyphicon {{screenHeading.leftIcon}}" ng-if="screenHeading.leftIcon"></span> {{screenHeading.leftName}}</button> 
                    <span ng-if="!screenHeading.leftName">&nbsp;</span>
                </td>
                <td style="text-align: center; vertical-align: middle; width: 60%;">
                    <strong>{{screenHeading.centerName}}</strong>
                </td>
                <td style="text-align: right; vertical-align: middle; width: 20%;">
                    <button type="button" class="btn btn-default btn-sm" ng-if="screenHeading.rightName" ng-click="screenHeading.rightFunction()"><span class="glyphicon {{screenHeading.rightIcon}}" ng-if="screenHeading.rightIcon"></span> {{screenHeading.rightName}}</button> 
                    <span ng-if="!screenHeading.rightName">&nbsp;</span>
                </td>
            </tr>
        </table>
    </script>
    <!-- EOF -->

Finally the directive (replacing existing the existing panel-headings)  is used in each of the pages as follows:

On the home page (no back button):

HTML
<div class="panel-heading" 
     ng-attr-screen-heading 
     ng-attr-center-name="Home"
>
</div>

On the other pages, e.g., login (with back button)

HTML
       
<div class="panel-heading" 
     ng-attr-screen-heading 
     ng-attr-center-name="Login"
     ng-attr-left-name="back"
     ng-attr-left-icon="glyphicon-chevron-left"
     ng-attr-left-function="login.navigate('/')"
>
</div>

Tuesday, February 24, 2015

Creating a Customized Service

In the previous posts on promises, we created a function "onceValuePromise" that would have to be repeated across multiple controllers in a more complete app (yuck).  The solution it to move this function into a customized AngularJS service <https://docs.angularjs.org/guide/services> and inject it into the controllers.

This amounts to creating the following customized service:

JavaScript
// app/services/myFirebase.js
(function() {
angular.module('myApp')
.factory('myFirebase', ['$q', function($q) {
    return {
        onceValuePromise: function (ref) {
            var deferred = $q.defer();
            ref.once('value', function(snapshot) {
                deferred.resolve(snapshot);
            }, function(error) {
                deferred.reject(error);
            });
            return deferred.promise;
        }
    };
}]);
})();
// EOF

And then injecting it into the updated controller as follows (also deleting the unused $q injection and "onceValuePromise" function).

JavaScript
// app/controllers/wineriesCtrl.js
(function() {
angular.module('myApp')
.controller('WineriesCntrl', ['$location','$window', 'myFirebase', function($location, $window, myFirebase) {   
    var ctrl = this;
    var myDataRef;
    ctrl.wineries;
    ctrl.navigate = function(path) {
        $location.path(path);        
    };
    myDataRef = new $window.Firebase('https://wineapp.firebaseio.com');
    myFirebase.onceValuePromise(myDataRef.child('wineries'))
    .then(function(snapshot) {
        ctrl.wineries = Object.keys(snapshot.val()).map(function(key) {
            return {key: key, val: snapshot.val()[key]};
        });
    })
    .catch(function() {
    });
}]);
})();
// EOF

Sunday, February 22, 2015

Promises and AngularJS $q Service

Before continuing, one must understand the concept of promises (and deferreds). For some reason, this topic seems to take one awhile to absorb, especially if one has using the traditional JavaScript callback strategy allot.

The following is an excellent introduction to the topic:
<http://www.webdeveasy.com/javascript-promises-and-angularjs-q-service/>

No, really read the tutorial before continuing...

For a real example of why we need promises, one can look at a bit of old code that I did where I had to repeatedly had to reset the indenting as things were getting heavily nested.
<https://github.com/larkintuckerllc/introduce/blob/master/public/app/controllers/meeting.js>

First, let review the call back chain that we are looking to replace in the section "app/controllers/wineriesCtrl.js"

JavaScript
    myDataRef.child('wineries').once('value', function(snapshot) {
        $timeout(function() {
            ctrl.wineries = Object.keys(snapshot.val()).map(function(key) {
                return {key: key, val: snapshot.val()[key]};
            });
        });
    }, function(error) {
    });

Here we two asynchronous functions "once" and "$timeout"; as a reminder, the "$timeout" call is needed as AngularJS does not alerted when the Firebase call returns and thus would not update the DOM.

We are going to first wrap the "once" function to another function that returns a promise.

JavaScript
    function onceValuePromise(ref) {
        var deferred = $q.defer();
        ref.once('value', function(snapshot) {
            deferred.resolve(snapshot);
        }, function(error) {
            deferred.reject(error);
        });
        return deferred.promise;
    };

Then we use the "onceValuePromise" function as follows:

JavaScript
onceValuePromise(myDataRef.child('wineries'))
    .then(function(snapshot) {
        ctrl.wineries = Object.keys(snapshot.val()).map(function(key) {
            return {key: key, val: snapshot.val()[key]};
        });
    })
    .catch(function() {
    });

Ok, this is not a terribly impressive example of having promises simplify the code (mostly because this particular code is fairly simple).  One immediate benefit, however, is that $timeout is no longer needed as using promises with $q automatically alert AngularJS to update the DOM.

Saturday, February 21, 2015

Applying an AngularJS Style Guide

While the AngularJS tutorial suggested a number of conventions, there a many that are not addressed. So rather than having to come up with our own conventions, we are going to follow a style guide <https://github.com/mgechev/angularjs-style-guide>.

The following topics from the style guide are applied to the example we have been following to produce the updated application: <http://jsfiddle.net/sckmkny/j1ah4up5/>.  BTW, there were just too many nitpicky changes to show all the code changes.

note: One topic, the $q (promises/deferred) service is sufficiently complicated that we will discuss it in another post.

Directory Structure

While the JSFIDDLE example is implemented in a single HTML page, comments are interspersed to explain what file the section would belong in.

+ app
   + controllers
      homeCtrl.js
      loginCtrl.js
      wineriesCtrl.js
      winesCtrl.js
index.html
+ partials
      home.html
      login.html
      wineries.html
      wines.html

Markup

Tedious but self-explanatory; basically just moving around things.

Self Executing Anonymous Functions

Actually, this idea was introduced by the Code Schools tutorial.  In this particular application, it is not needed as it does not introduce any global variables.  But as a good practice we wrap the JavaScript in each file with an self executing anonymous function <http://markdalgleish.com/2011/03/self-executing-anonymous-functions/> to prevent the accidental creation of global variables.

ngCloak

This is a simple directive to prevent the browser from showing the template before it is compiled <https://docs.angularjs.org/api/ng/directive/ngCloak>.

Using the Controller As Syntax

Starting off from where the series "So You Want to Build a SPA" <http://buildspa.blogspot.com/> ended, we are going to apply the change in style relating how we bind the view to the controller. This style apparently was introduced with v1.2 (apparently after the original tutorial <https://docs.angularjs.org/tutorial> was written).  This style is used in the newer Code School course (highly recommended): <http://campus.codeschool.com/courses/shaping-up-with-angular-js/intro>.

A fellow (Todd Motto) has an excellent discussion in this change <http://toddmotto.com/digging-into-angulars-controller-as-syntax/>.

Starting from the source code from the So You Want to Build a SPA at <http://jsfiddle.net/sckmkny/y0Lfo7yt/> we apply the following changes.

First we need to update the routes with the "controllerAs" property; in this case I simply use simple names like "home" to reference the controller.

JavaScript
// routes/routes.js
angular.module('myApp').config(['$routeProvider',    
    function($routeProvider) {
        $routeProvider.
            when('/', {
                templateUrl: 'views/home.html',
                controller: 'HomeCntrl',
                controllerAs: 'home'
            }).
            when('/login', {
                templateUrl: 'views/login.html',
                controller: 'LoginCntrl',
                controllerAs: 'login'
            }).
            when('/wineries', {
                 templateUrl: 'views/wineries.html',
                 controller: 'WineriesCntrl',
                 controllerAs: 'wineries'
            }).
            when('/wines', {
                templateUrl: 'views/wines.html',
                controller: 'WinesCntrl',
                controllerAs: 'wines'
            }).
            otherwise({
                redirectTo: '/'
            });
    }
]);
// EOF

Next we update each of the controllers by removing the injection of "$scope" and attach those things that we want to be available to the view to the controller with the keyword "this".  We use the variable "ctrl" to avoid confusion as to what "this" is in nested code.

Below is the changes for just the home page controller; one needs to apply same sorts of changes to the remaining controllers: login, wineries, and wines.

JavaScript
controllers.controller('HomeCntrl', ['$location', '$window', function($location, $window) {
    var ctrl = this;
    var myDataRef;
    ctrl.navigate = function(path) {
        $location.path(path);
    };
    ctrl.authenticated = false;
    myDataRef = new $window.Firebase('https://wineapp.firebaseio.com');
    ctrl.logout = function() {
        ctrl.authenticated = false;
        myDataRef.unauth();
    };
    var authData = myDataRef.getAuth();
 if (authData != null) {
        ctrl.authenticated = true;
    }
}]);

Finally, we have to go back to the view and update all the naked references to now first reference the controller, e.g., "authenticated" is now "home.authenticated". Below is the updates for home; one needs to apply same sort of changes to login, wineries, and wines.

HTML
 

    
    <!-- /views/home.html (obmit work-around script tag)-->
    <script type="text/ng-template" id="views/home.html">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">Home</h3>
            </div>
            <ul class="list-group">
                <li ng-if="! home.authenticated" ng-click="home.navigate('/login');" class="list-group-item">Login</li>
                <li ng-if="home.authenticated" ng-click="home.logout();" class="list-group-item">Logout</li>
                <li ng-click="home.navigate('/wineries');" class="list-group-item">Wineries</li>
                <li ng-click="home.navigate('/wines');" class="list-group-item">Wines</li>
            </ul>
        </div>
    </script>
    <!-- EOF -->