Prerequisites

The article assumes the reader knows the basics of AngularJS. The article shows how the cache logic can be written in JavaScript but the UI render is done using AngularJS. A non-angular reader can still choose to continue reading through the article and can get the logic bits from the code. I leave it up to you.

Introduction

In real world, where we have CMS (Content Management System) to assemble our page with modules and each module function independently. The modules are developed by independent developers and the page is assembled by, probably, a different user altogether. AJAX calls are always meant to enhance user experience, but given the fact the each module function independently, there seems to a duplication of AJAX calls made on the page, probably by different modules. This post shows a way, how to cache such AJAX calls with the help of jQuery promise and a JavaScript object.

jQuery Promises

As name suggests, jQuery Promise is a literal promise made by jQuery that a call will be made on the object after its completion. The object is just like an JavaScript object and can be passed around like a ball to any method you want and any number of times you want. For more, read here.

Details

Now that you have an idea of what we are going to do, let me take you through each step of the process.

Constructing an deferred object cache

I will try not to include AngularJS code in the sample but in some places it is unavoidable. Assuming that “command” is the part of the URL and “params” are the parameter key-value map, here is a snapshot of constructing a cache map.

var cacheStorage = {};
 
function getCacheKey(command, params) {
    var paramStr = command + '-';
    if(params) {
        var keys = [];
        for(var key in params) keys.push(key);
        var sortedKeys = keys.sort();
        for(var count=0; count < sortedKeys.length; count++) {
            var sKey = sortedKeys[count];
            paramStr += (sKey + '-' + params[sKey] + (count < sortedKeys.length-1 ? '-' : ''));
        }
    }
    return paramStr;
};
 
var ret = {
    get : function(command, params) {
        var paramKey = getCacheKey(command, params);
        var cachedObj;
        if(paramKey.length > 0) {
            cachedObj = cacheStorage[paramKey];
        }
        if($rootScope.debug) {
            $log.log(paramKey + " => " + (cachedObj ? 'hit' : 'undefined'));
        }
        return cachedObj;
    },
 
    put : function(command, params, deferredObj) {
        var paramKey = getCacheKey(command, params);
        if(paramKey.length > 0) {
            cacheStorage[paramKey] = deferredObj;
        }
    }
};
 
return ret;

Explanation: If you know Angular, you probably knew about $log and $rootScope. If not, just assume that these are variables injected by Angular API. The cache tries to form a key and save the object in the cache map for the key. We sort the params before forming the key because we do not want to duplicate the same object just because user gave params in a different order.

Data Source Client

Now that, our cache is ready, we need to implement a client which uses this cache and can be a interface to all the modules on the page. The requirements of the client is, to provide a generic interface to all the calls to a particular website because we wrote the cache store for a single domain. If multiple domains are involved, it is only a matter of time we edit the cache storage to modify key that includes domain name or the complete url.

var url_defaults = { key: $rootScope.key };
 
function doDSCmd( command, params ) {
    if(!params) { params={};}
    var cachedObj = dsCacheStore.get(command, params);
    if(cachedObj) {
        return {
            'deferredObj' : cachedObj,
            'fromCache' : true
        };
    }
    var url = $rootScope.server + "/" + command + ".json";
    var deferredObj = $http.get(url, { params: angular.extend( {}, url_defaults, params ) } );
 
    deferredObj.success(function (data, status) {
            if($rootScope.debug) {
                $log.log(command + ": " + JSON.stringify(data));
            }
        })
        .error(function (data, status) {
            $log.error("error $http failed with " + status + " for " + url);
        });
 
    dsCacheStore.put(command, params, deferredObj);
    return {
        'deferredObj' : deferredObj,
        'fromCache' : false
    };
};
 
var ret = {
    executeCommand : function(command, params) {
        return doDSCmd(command, params);
    }
};
 
return ret;

Explanation: We are trying to provide a interface with just one public method: executeCommand – which means executing a JSON call. The client is trying to read from cache and if not found, it creates a promise object by var deferredObj = $http.get(url, { params: angular.extend( {}, url_defaults, params ) } );. Consider this, as a jQuery equivalent of $.ajax(). Now, this promise is stored in the cache. Next time, when we get a hit from the cache, we get the promise object. Since, you always get a promise object, your module can always use .success on the promise every time it executes. If the call is already completed, your .success callback is called immediately else it waits. Here is the trick, since you are not creating a new promise, AJAX call is NOT made. Instead, it works on the existing promise object and gets the response from the promise – how many ever times you want.

Sample Module Usage

Here is an example of how to call from a module.

var commandOutput = dsClient.executeCommand(callObj.call, callObj.params);
var fromCache = commandOutput.fromCache;
commandOutput.deferredObj.success(function(response) {
     .....
});

NOTE: The JSON call is used for demo purposes. However, JSONP also works and you just have to change from $http.get to $http.jsonp.

Here is a complete

demo

Conclusion

What did we just do: We learned a bit about jQuery promise, how to implement a cache store that stores jQuery promises for a given url and params, how to implement a client API which makes of cache store and provide a public interface to make AJAX calls and how to write a module that makes use of the client.

Introduction

Yeoman is a very good framework for doing three things 1) scaffolding 2) dependency management and 3) build, preview & test. Yo is the component which does the code scaffolding and we are going to look a bit deeper on how to tune the generators to suit our need and structure.

Installing Yeoman and Git Protocol Problems

When dependencies are pulled via github, the protocol followed is git:// and is blocked by firewall at few places. In order for that to resolve, you need to make sure git uses http(s) instead of git. So, please execute the following line to make sure GIT follows HTTP protocol.

git config --global url."https://".insteadOf git://

After the above steps you should not have problems when using npm install or bower install.

npm install generator-angular generator-karma  # install generators
yo angular                     # scaffold out a AngularJS project
npm install && bower install   # install default dependencies
bower install angular-ui       # install a dependency for your project from Bower
grunt test                     # test your app
grunt server                   # preview your app
grunt                          # build the application for deployment

Yeoman Generators

If you take a look at line 1 on the above snippet, if installs angular generator. So, lets take a look at how to scaffold out a AngularJS directive:

vsankaran-ml2:yeoman3 ksankaran$ yo angular:directive thing1
   create app/scripts/directives/thing1.js
   create test/spec/directives/thing1.js

When you install angular generator using “npm install angular-generator”, it may not installed in the actual yo library for users in Mountain Lion (10.8) or greater. So, if you execute the command “yo angular:directive thing1″, you might get an error saying that generator is not registered. To fix, please execute the following commands.

cd /usr/local/lib/node_modules/yo
npm install generator-angular

As you can see from above, it creates two things: 1) the directive itself and 2) the test case for the directive. This is where Yeoman’s strength is.

yo1

Modify Generator Behavior

Ok, now that we have scaffolded out a directive, how do I make sure that we scaffold out in a proper structure that we wanted to? The answer is not simple and we have to modify generator’s source to achieve that. I know, this is extremely dangerous, but the path and other stuffs is “hardcoded” in the generator code. Before we dig deep into the generator code, please understand that the generator code is located at: /usr/local/lib/node_modules/yo/node_modules/generator-angular.

Now, lets take a look at directive generator code snippet in index.js inside directives (which is inside the generator code) folder:

Generator.prototype.createDirectiveFiles = function createDirectiveFiles() {
  this.appTemplate('directive', 'scripts/directives/' + this.name);
  this.testTemplate('spec/directive', 'directives/' + this.name);
  this.addScriptToIndex('directives/' + this.name);
};

The line this.appTemplate generates the directive file inside script/directives folder. Now we wanted to pass in an extra argument to accomodate module parameter, so that the file gets dropped at scripts/module_name/directives folders (this is just an example and not a final structure). You need to modify the code above like this:

Generator.prototype.createDirectiveFiles = function createDirectiveFiles() {
  if(this.args &amp;&amp; this.args[1]) {
        var module = this.args[1];
        this.appTemplate('directive', 'scripts/'+module+'/directives/' + this.name);
  }
  else {
        this.appTemplate('directive', 'scripts/directives/' + this.name);
  }
  this.testTemplate('spec/directive', 'directives/' + this.name);
  this.addScriptToIndex('directives/' + this.name);
};

Now, on executing the command

vsankaran-ml2:yeoman3 ksankaran$ yo angular:directive thing2 module1
   create app/scripts/module1/directives/thing2.js
   create test/spec/directives/thing2.js

it creates a module1 folder and places the directive file inside it. If you want to change the test script file location, you can change the this.testTemplate command.

yo2

That’s it folks. This is how you modify yo generator code for angular.

Introduction

It’s been sometime I’ve been here. So, I will just get started off. Today, I’m going to write about how to develop tic-tac-toe using AngularJS. The demo is more to understand the possibilities and simplicity of the framework.

What’s needed in the UI

9 boxes arranged in a 3×3 fashion like below:
square-design

Each of these boxes are divs and each div is binded to the data behind as usual

Template:

<div ng-repeat="row in rows">
    <div id="{{column.id}}" ng-repeat="column in row">
        <div ng-click="markUserClick(column)"> </div>
    </div>
</div>

Data:

$scope.rows = [
    [
        {'id' : 'A11','letter': '','class': 'box'},
        {'id' : 'A12','letter': '','class': 'box'},
        {'id' : 'A13','letter': '','class': 'box'}
    ],
    [
        {'id' : 'B11','letter': '','class': 'box'},
        {'id' : 'B12','letter': '','class': 'box'},
        {'id' : 'B13','letter': '','class': 'box'}
    ],
    [
        {'id' : 'C11','letter': '','class': 'box'},
        {'id' : 'C12','letter': '','class': 'box'},
        {'id' : 'C13','letter': '','class': 'box'}
    ]
];

Now, the logic is to make the JS think engine. It’s easy when you follow the strategy mentioned in wiki.

AI Algorithm:

  1. Win: If the AI has two in a row, it will place a third to get three in a row.
  2. Block: If the [opponent] has two in a row, the AI will play the third to block the opponent.
  3. Fork: Creation of an opportunity where the AI has two threats to win (two non-blocked lines of 2).
  4. Blocking an opponent’s fork:
    1. The AI will create two in a row to force the opponent into defending, as long as it doesn’t result in them creating a fork. For example, if “X” has a corner, “O” has the center, and “X” has the opposite corner as well, “O” must not play a corner in order to win. (Playing a corner in this scenario creates a fork for “X” to win.)
    2. If there is a configuration where the opponent can fork, the player should block that fork.
  5. Center: AI marks the center. (If it is the first move of the game, playing on a corner gives “O” more opportunities to make a mistake and may therefore be the better choice; however, it makes no difference between perfect players.)
  6. Opposite corner: If the opponent is in the corner, the AI plays the opposite corner.
  7. Empty corner: The AI plays in a corner square.
  8. Empty side: The AI plays in a middle square on any of the 4 sides.

For TicTacToe DEMO : Click Here.

That’s it folks. Enjoy playing.