Getting Around Angular Bootstrap Popover Limitations

Using Bootstrap is nice, it's got some good default behaviors and components set up for quickly building a decent looking web application. However, the Angular Bootstrap tools are a little bit behind.

For instance, I was using the popover in my latest project. There is no way that I could find to bind HTML to the body part of the code, like so:

<div popover="Title" popover-content="<strong>Content</strong>"> ... </div>

So, not wanting to change how the popover looks, but at the same time not liking how it was working as an Angular directive, I rolled my own. For now, I just wanted to get it to work and also include HTML in the body, so I hard coded some things, like the fact that for now, I only expect it to show up on the right side of the thing I'm popping it over.

    module.directive("infobox", function(){
        return {
            restrict: "E",
            transclude: true,
            scope: { title: "=", content: "=" },
            template: "<div ng-transclude class='infobox popover right' style='position: absolute; display: none'></div>",
            controller: function($scope){

            },
            link: function(scope, element, attrs){
                var parentWidth = element.parent().outerWidth();
                var infobox = element.find(".infobox");
                infobox.append("<div class='arrow'></div>")
                element.parent()
                    .on("mouseover", function(){
                        var t = angular.element(this);
                        var offset = t.offset(); offset.left += parentWidth;
                        var h = t.outerHeight() / 2;
                        offset.top = offset.top - (infobox.outerHeight() / 2) + h;
                        t.find("div.infobox").show().offset(offset);
                    })
                    .on("mouseout", function(){
                        var t = angular.element(this);    
                        t.find(".infobox").hide();
                    });             }
        }
    });     module.directive("infoboxTitle", function(){
        return {
            restrict: "E",
            transclude: true,
            require: "^infobox",
            template: "<div ng-transclude class='popover-title'></div>"
        }
    })

    module.directive("infoboxBody", function(){
        return {
            restrict: "E",
            transclude: true,
            require: "^infobox",
            template: "<div ng-transclude class='popover-content'></div>"
        }
    })

Then I use it like this:

         <infobox>
             <infobox-title>{{obj.name}}</infobox-title>
             <infobox-body>
                <p>{{obj.description}}</p>
                <div ng-repeat="(key, val) in obj.attributes">
                    <strong>{{lookupAttributeName(key)}}</strong>: {{val}}
                </div>             
            </infobox-body>
         </infobox>

It generates markup that looks like this, and that works

<infobox class="ng-isolate-scope"><div ng-transclude="" class="infobox popover right" style="position: absolute; display: none; top: -73.5px; left: 241px;">
             <infobox-title class="ng-scope"><div ng-transclude="" class="popover-title"><span class="ng-binding ng-scope">Medium Truck</span></div></infobox-title>
             <infobox-body class="ng-scope"><div ng-transclude="" class="popover-content">
                <p class="ng-binding ng-scope">The medium truck</p>
                <!-- ngRepeat: (key, val) in obj.attributes --><div ng-repeat="(key, val) in obj.attributes" class="ng-binding ng-scope">
                    <strong class="ng-binding">Capacity</strong>: 60
                </div><!-- end ngRepeat: (key, val) in obj.attributes --><div ng-repeat="(key, val) in obj.attributes" class="ng-binding ng-scope">
                    <strong class="ng-binding">Employees Maximum</strong>: 2
                </div><!-- end ngRepeat: (key, val) in obj.attributes --><div ng-repeat="(key, val) in obj.attributes" class="ng-binding ng-scope">
                    <strong class="ng-binding">Employees Minimum</strong>: 1
                </div><!-- end ngRepeat: (key, val) in obj.attributes --><div ng-repeat="(key, val) in obj.attributes" class="ng-binding ng-scope">
                    <strong class="ng-binding">N/A</strong>: 15
                </div><!-- end ngRepeat: (key, val) in obj.attributes --><div ng-repeat="(key, val) in obj.attributes" class="ng-binding ng-scope">
                    <strong class="ng-binding">Miles Per Gallon</strong>: 14
                </div><!-- end ngRepeat: (key, val) in obj.attributes -->             
            </div></infobox-body>
         <div class="arrow"></div></div></infobox>

Don't read too much into what my latest project is :)

Pretty!!

I have made my site a bit prettier.  I brought in bootstrap for some UI elements, like tag list and the year accordions on the right side, as well as the fixed header. I don't know how much more I plan on using Bootstrap for this site though, but I use it a lot in other places.

I was messing around with the fonts and decided I could use big bigger post titles as well as a different overall body text font.

Since I'm not the best with colors, I tend to just pick greyscale and use different combinations of various intensities of grey, black and white to get the job done. The header images are where the color is :)

Enjoy!

Fix for Angular 1.3 $resource Not Stripping $ properties anymore

Version 1:

    module.config(["$httpProvider", function($httpProvider){
        $httpProvider.defaults.transformRequest.unshift( function(data){
            if (typeof data == "object"){
                var remove = [];
                for (var i in data) { if (i.indexOf("$") == 0 && i.indexOf("$$") == -1 && typeof(data[i]) != "function") remove.push(i); }
                remove.forEach(function(k){ delete data[k]; });
            }
            return data;
        });
    }])

Version 2:

    module.config(["$httpProvider", function($httpProvider){
        $httpProvider.defaults.transformRequest.unshift( function(data){
            if (typeof data == "object"){
                var copy = angular.copy(data);
                var remove = [];
                for (var i in copy) { if (i.indexOf("$") == 0) remove.push(i); }
                remove.forEach(function(k){ delete copy[k]; });
                return copy;
            }
            return data;
        });
    }])

Let's examine. There's a bit going on here.

$resource registers its own transformRequest functions before ours is defined, but I want mine to load first.

$httpProvider.defaults.transformRequest.unshift

Unshift puts my method at the beginning. The data at this point is an object, whereas if I were to push it to the end, after the $resource transformRequest function, I get a string. It seems a bit more efficient to work with the object first, than to allow $resource to JSON.stringify the data, then for me to load it up through JSON.parse after.

The main difference between Version 1 and Version 2 is that I copy the object in Version 2 so I can delete any property that begins with $, whereas in Version 1 I skipped deleting functions and properties that Angular needed, but then the reference objects were deleted from my actual object, so the list view got messed up, because it's looking for those properties.  So Version 2 is correct.

I add this to my global module, the one that gets bootstrapped to <html>, so that it only has to be registered once, like so:

;(function(angular){
    var module = angular.module("global.app", [...]);
    module.config(["$httpProvider", function($httpProvider){
        $httpProvider.defaults.transformRequest.unshift( function(data){
            var copy = angular.copy(data);
            if (typeof copy == "object"){
                var remove = [];
                for (var i in copy) { if (i.indexOf("$") == 0) remove.push(i); }
                remove.forEach(function(k){ delete copy[k]; });
            }
            return copy;
        });
    }])
})(angular);

And then bootstrap it 

;(function(angular, document){
    angular.element(document).ready(function(){
        angular.bootstrap(angular.element("html"), ["global.app"]);
    })
})(angular, document);

Simple

 

Chrome 37 Keeps Scroll Position On Page Reload

It's pretty neat, but I mostly refresh a page in order to quickly scroll back to the top without having to use the mouse. My workflow is severely detrimented. Page Up will do I suppose.

No more automatic $promise unwrapping

Like any Philadelphia sports fan says, "BOOOOO!!"

In AngularJS, I liked the cleanliness of the code:

$scope.objects = ObjectAdmin.query();

And as it automatically unwraps the promise, the data updates correctly on the view side, and everything works fine. But I started playing with Angular 1.3 and it no longer does this. Now you have to call:

ObjectAdmin.query().$promise.then(function(data){ $scope.objects = data; })

Which is just ugly. But I guess the team at Angular made the decision for a good reason, but I have to update all of my code. Booooo!!!

This became painfully obvious when attempting to group a multiple select options by a property that is bound as a foreign key, after the promise has resolved.

You can imagine this data structure:

object: {   name: "Test", objectClass: "a_foreign_key" }

objectClass: { key: "a_foreign_key", name: "Object Type" }

Probably easier to think about in a non abstract way (although, in my current project, "Object" is the actual name of the item I'm dealing with).

person: { id: 1, name: "Jason", professionId: 1 }

profession: { id: 1, name: "Software Developer" }

So, when I get the list of "persons", and the list of "profession", I would grab the reference to the profession referenced by the professionId in the person, then assign it to a new property on each person, $professionRef.

so person 1 would like like this:  { id: 1, name: "Jason", professionId: 1, $professionRef: { id: 1, name: "Software Developer" } }

THEN my select list would use the ng-options to this effect:

ng-options="person.name group by person.$professionRef.name for person in persons track by person.id" ng-model="office.bestSmellingDeveloper"

(The use of $professionRef and $resource no longer automatically removing $ properties on POST/PUT is another pain point for me :)

So at the time the $promise resolves, the select list is like F#@% YEAH DATA!!! And it binds itself, but $professionRef isn't updated yet. So it's not grouping by anything and you have a bland list of things. I use the chosen jquery plugin which just makes these look beautiful with bootstrap of course, and I get bummed when it looks fugly.

I realize this wouldn't have worked as is, even with $promise unwrapping, it all depends on when the SELECT chooses to bind its data, and it would typically be immediately after the promise resolves. Really where this affected the code the most was in the chosen plugin I wrote, which looks like this now that I'm not binding promises to it anymore.

    module.directive("ngChosen", function($parse, $timeout){
        return {
            restrict: "A",
            require: "ngModel",
            link: function(scope, element, attrs, ngModel){

                scope.$watch(attrs["ngChosen"], function(){    
                    $timeout(function(){ element.trigger("chosen:updated"); });
                });

                scope.$watch(attrs["ngModel"], function(){
                    $timeout(function(){ element.trigger("chosen:updated"); })
                });
                element.chosen();
            }
        }
    });

But used to look like this:

    module.directive("ngChosen", function($parse, $timeout){
        return {
            restrict: "A",
            require: "ngModel",
            link: function(scope, element, attrs, ngModel){
                var chosen = scope.$eval(attrs["ngChosen"]);

                if (chosen.$promise) {
                    chosen.$promise.then(function(){ $timeout(function(){ element.trigger("chosen:updated"); }); })
                }
                else {
                    scope.$watch(attrs["ngChosen"], function(){    
                        $timeout(function(){ element.trigger("chosen:updated"); });
                     });
                }

                scope.$watch(attrs["ngModel"], function(){
                    $timeout(function(){ element.trigger("chosen:updated"); })
                });
                element.chosen();
            }
        }
    });

But now that Angular is no longer automatically unwrapping promises... well, I guess I could keep the promise unwrapping in the chosen plugin, just in case I have a simple case that I need to bind to it (hardly ever the case), but since I won't be binding $promise objects to select lists most of the time, I can just say I won't bind any even though it's easy. Because I'll be used to unwrapping them manually to handle cases like the aforementioned.

Enjoy!

Doing is the best form of learning

REST.  It's one of those things that most of us developers typically deal with on the client side of things, or if we deal with them on the server side, we're using an already written API which "REST-ifies" our data. It was somewhat of a mystery to me, and a challenge, to know how to implement an abstract RESTful interface. I had written the webserver I use in node.js which supports all kinds of things, but hadn't yet added REST support. Until last night!

It wasn't too much of an undertaking, I guess I implemented the webserver in such a way that could be easily extended with REST. That's good, go "past Jason".

Basically the server gets the request, attempts to rewrite the URL given the site's configuration of URL Rewrites (which I wrote), then it gets the content (server side processing), and writes the content on the response stream, along with doing cookie and header processing, g-zipping, cache headers, etc. Nearly everything a mature web server should do.  It's been around since March 2011 and I always go back to making it better.

I started adding REST to it last night around 10pm, and by 12:30 I was writing code against it using AngularJS.  The REST format is inspired by AngularJS, in that you can provide parameters expected in the format :id   (colon - name).

I always start with how I want to write code.  This, I find, is a very important aspect of how I architect things.  I don't want to write a whole bunch of code that I'll have to write each time I want to use the new feature I am adding. So I START with the code that I'll be writing to use the new feature. If this hasn't yet sunk in as to how important I think this is, let me add this sentence... There. It's very important to me and how I architect.

I know the architecture of my web server, and know that you can't just reference things inside of it that haven't been given a public interface. It really has no public interface. The web applications implement the interface to work within the web server. I am looking at a way to decouple them but for now, this is how it is. The "params" array was added in that way because of this.

this.get = {
    path: "/:id",
    handler: function(db, id, qs, callback){
            engine.getObject(db, id, callback);
        },
    method: "GET",
    params: [
        function(context){ return context.db; },
        "id"
    ]
}

 

So in my site code, I'm registering a "get" method that takes the id of an object, looks in the database, and returns it. A URL for the call may look like this: /resource/objects/53f810db8a8cda084e000001

Here's some node.js code that comes from my "resource" module. Within the resource module, you register resources and later check the URL and http method against the current resources, to see if this is a resource / REST call.

this.registerResource = function(domain, name, resource){
    var r = /:([a-zA-Z0-9]+)/g;    
    var m = null;
    for (var i in resource){
        var res = resource[i];
        var regexString = globalPart + name + res.path;
        while ((m = r.exec(res.path))!=null){
            regexString = regexString.replace(":" + m[1], "(.*?)");
        }
        var local = { domain: domain, name: name, method: res.method, regex: new RegExp(regexString), handler: res.handler, path: res.path, params: res.params };
        resources.push(local);
    }
}

For the resource, for which the above "get" code is just one method on a resource, find all the methods, replace the URL with a regex. Instead of the "path" which would be "objects/:id", it creates "/resource/objects/(.*?)", stores the original path, the method to handle it, the http request method (GET, POST, PUT, DELETE currently supported), and the params array.

When a request is made, find the resource with the following code, if no resource is found, or no resources on the current domain that match the HTTP Method, it's a standard request.

this.getResource = function(domain, url, method){
    var domainResources = resources.filter(function(r){ return r.domain == domain && r.method == method });
    if (domainResources.length == 0) return null;
    var resource = null;
    domainResources.forEach(function(res){
        if (url == globalPart + res.name + res.path) resource = res;  // prefer exact matches first
        else if (resource == null && res.regex.test(url)){
            resource = res;
        }
    })

    return resource;
}

The next methods call the resource handler. For GET / DELETE calls, the requestData is just the querystring, for PUT / POST calls, this will be the form data as parsed by the POST parser in node.

this.extractParamMap = function(url, resourcePath){
    var m = null, map = {};
    var urlParts = url.split("/");
    var resParts = resourcePath.split("/");
    for (var i = 0; i < resParts.length; i++){
        if (resParts[i].indexOf(":") == 0){
            map[resParts[i].substring(1)] = urlParts[i];
        }
    }
    return map;
}

this.handleResource = function(resource, url, context, requestData, callback){
    var params = [];
    var paramMap = this.extractParamMap(url, globalPart + resource.name + resource.path);

    for (var i = 0; i < resource.params.length; i++){
        if (typeof(resource.params[i]) == "function"){
            params.push(resource.params[i](context));
        }
        else if (typeof(resource.params[i]) == "string"){
            params.push(paramMap[resource.params[i]]);
        }
    }

    params.push(requestData);
    params.push(callback);

    resource.handler.apply(resource, params);
}

The code within the webserver which was modified to process resources looks like this.  Determine if it's a resource or standard request. Call accordingly.

        if (req.method == "POST" || req.method == "PUT"){
            post.parseForm(req, function(formData){
                if (loadedResource != null){
                    resource.handleResource(loadedResource, url, site, formData, function(data){
                        var content = {};
                        content.contentType = "application/json";
                        content.content = JSON.stringify(data);
                        finishedCallback(content);
                    });
                }
                else if (handler != null){
                    query.form = formData;
                    handler.handlePost(path, query, site, req, function(content){
                        if (jsonRequest) content.contentType = "application/json";
                        finishedCallback(content);
                    });
                }
            })
        }
        else if (req.method == "GET" || req.method == "DELETE"){
            if (loadedResource != null){
                resource.handleResource(loadedResource, url, site, query.querystring, function(data){
                    var content = {};
                    content.contentType = "application/json";
                    content.content = JSON.stringify(data);
                    finishedCallback(content);
                })
            }
            else if (handler != null){
                handler.handle(path, query, site, null, req, function(content){
                    if (jsonRequest) content.contentType = "application/json";
                    finishedCallback(content);
                });
            }
        }
        else {
            finishedCallback({contentType: "text/html", content: url + " - No handler found", statusCode: 404});
        }

In AngularJS, with the $resource module, this is cake.

        return $resource("/resource/objects/:id", {}, 
            { 
                list: { method: "GET", isArray: true },
                get: { method: "GET" },
                save: { method: "POST" },
                update: { method: "PUT" },
                remove: { method: "DELETE" }
            }
        );

 

That's it!  Later on I might find I need other things, but that's all the code that was required for now for handling resource / REST style methods. I will have a site up in a few weeks / months that will use this heavily. Then you can see it in action!

I really enjoy golf

82 at Linfield

Meta-Bits

C# calls them attributes
java calls them annotations
i'll call mine meta-bits
MBOP
classic Hanson

Fantasy Golf Tracking with Node.js, MongoDB, AngularJS, and Bootstrap - Part 2

I'll address this in parts since the first post really didn't cover anything technical, and was just a bunch of screenshots.

MongoDB Implementation

I previously went over the basic data structure for this app in the previous post.  Teams consist of the team name. Tournaments consist of the key, the name, the start and end date, the course name, the par for the course, and the current round. Also, bools for finished and in progress. Then there is the teams players per tournament which I called teamTournament. It also keeps historical records of their total for that tournament. This lead me to be able to build a leaderboard widget, since the lowest score at the end of the season gets a prize.

Tournament Sample Data

> db.tournaments.find().pretty()
{
        "_id" : ObjectId("53a0964d4fcdf5c39c912acd"),
        "key" : "us-open-2014",
        "name" : "U.S. Open",
        "scoresLocation" : "http://www.pgatour.com/data/r/026/leaderboard-v2.json",
        "startDate" : ISODate("2014-06-12T07:00:00Z"),
        "endDate" : ISODate("2014-06-15T19:00:00Z"),
        "latestRound" : 4,
        "inProgress" : false,
        "isFinished" : true,
        "course" : "Pinehurst No. 2",
        "par" : 70
}

Granted, that inProgress and isFinished can be determined real time, but that's ok.

Team Tournament Sample Data

> db.teamTournament.find().pretty()
{
        "_id" : ObjectId("53a2e61ed42498e823000001"),
        "players" : [
                "28259",
                "28087",
                "21209",
                "08075",
                "31202",
                "28486"
        ],
        "teamId" : ObjectId("53a193fc4fcdf5c39c912af7"),
        "tournamentId" : ObjectId("53a20488d7aee2e01b000001"),
        "tournamentTotal" : 1073
}

I used to use DBRef for referencing other collections, but then I found out that unless you don't know at runtime, you should just use ObjectID.  So my ObjectID of 53a19 etc is in the field of teamId, so I know it's a team reference. If it were called "documentId" and I had collections of "images" and "html snippets" and "swf files", then I could use the DBRef, since it could be one of 3 different collections I want to reference. But since I know, MongoDB says it's much more efficient to just use ObjectID references.

Node.js to Access the Database

As for writing code to access this, I use the Node MongoDB Native library for accessing node, and a helper class that I wrote so I'm not writing lots of code to do things that I do frequently. For instance, my code for getting tournaments looks like this:

this.getTournament = function(db, key, callback){
    dbhelp.findOne(db, "tournaments", { key: key }, function(tournament){
        callback(tournament);
    });
}

this.getCurrentTournament = function(db, callback){
    var upcoming = new Date().addDays(3);
    var past = new Date();

    dbhelp.find(db, "tournaments", { "$or": 
            [ 
                { "startDate": { "$lt": upcoming } },  
                { "endDate": { "$lt": past } }
            ] 
        }, null, { startDate: 1 }, function(tournaments){
        if (tournaments != null && tournaments.length > 0){ return callback(tournaments[0]); }
        else return callback(null);
    });
}

this.getTournaments = function(db, year, callback){
    var start = new Date(year, 0, 1);
    var end = new Date(year, 11, 31);

    dbhelp.find(db, "tournaments", { startDate: { "$gt": start }, endDate: { "$lt": end } }, function(tournaments){
        callback(tournaments);
    });
}

So, you can see, not a lot of code for getting a specific tournament, getting the current tournament, and getting all tournaments this year. (Also I've modified the Date prototype to include a .addDays method).

That is some sample code that I've written for this application. It covers the back end. Next up I'll cover the front end, using AngularJS and Bootstrap to make it work well and look great!

Fantasy Golf Tracking with Node.js, MongoDB, AngularJS, and Bootstrap - Part 1

My family and a bunch of friends are in a fantasy golf league together. The rules are pretty straightforward, although probably not standard.

Rules:
1. Pay $50
2. 10 Tournaments that span the PGA Tour season.
3. Pick 6 golfers before the first tee times on Thursday typically.
4. 4 lowest scores are added and that's your score for each day.
5. If 3 of your players miss the cut, you are assigned the worst score at the end of round 3, pretty much destroying your chance to win.
6. Lowest score wins. $50 payoff for majors (Masters, US Open, British Open, PGA Championship), $25 for the other tournaments.

My brother Pat is the score keeper and chairman of the league. The data collection and reporting was pretty much done in Excel.  This is a fine method for doing such things. The scores would be emailed out along with entertaining commentary.

But then it was my turn to do the data since Pat was going to Boston for the US Open weekend to visit some friends.

Excel was not a viable solution.

I happened to stumble on the PGA Tour Leaderboard website. I noticed that the data is loading in asynchronously, which could only mean AJAX. The question was, which format, and what data is available?

The answer was the start of this application. The data is JSON. And EVERYTHING is available. (I hope I don't get in too much trouble for grabbing it :). Well, everything I needed and some extra stuff.

The first step to building this app was to determine what the Information Architecture was going to look like.  Here's what I came up with:

Teams: Name, Email address of a person in the league.
Tournaments:  Tournament info include URL to the JSON file on pgatour.com, data like start / end date, is it in progress, is it finished.
Team Tournament: Each team's 6 initial golfers for each tournament, and total score recording.

Pulling in the tournament from the pgatour.com JSON file pulls in all of the information required for a tournament in my system, so all that is needed as input is the JSON file URL!

Next you assign teams. There can be 6 max.

Then scores are calculated.

And updated throughout the round, each day, for the whole tournament.

If a player doesn't have 4 players make the cut, they are given a substitute. The worst score from the 3rd round.

That is pretty much everything!  Once again, working with AngularJS is a breeze.

Check out the site at fantasygolf.jasontconnell.com! There you can view the source to get an idea of how it was written. It was very simple that I don't even feel like I can post any difficult code that I wrote, but it's just that it's really cool. Our next tournament is the Congressional next week, I hope everyone in the league uses the site.  I added GA tracking so I'll know for sure :)

Brilliant Business Idea

Dollar Shave Club has this one brilliant business idea as part of their main business model, that is just brilliant and you'd be stupid to not take them up on the offer. I'm talking about the Shave Butter. It's so obvious. Why would anyone get their blades online, shipped automatically every month, but opt to go to the store for shaving cream?  They could offer Dr. Carver's Pungent Shaving Poop Rash Fest Mosquito Urine Cream, and I'd still get it if I didn't have to go to the store.

But really, the Shave Butter is amazing, and I don't want to say that some genius thought of that idea, it's just so obvious that it had to be done, it would just be stupid not to offer it! But that service is great, I've been doing it for almost a year now.

You know you're a code snob when

Today I wrote a method that takes an updated object from the server and merges it with the current user's objects. These objects are in an hierarchy of client -> environment -> credentials.  So I wrote a function that sees that a credential has been updated (deleted, inserted, updated), finds the client and environment that it belongs to, if it doesn't have it, it "pushes" it, otherwise if it's not a delete, it updates it, and if it's a delete it removes it.  The other users can be editing clients or environments as well, so these have to work the same way.

The commit message for this change is as follows:

Update other users' data with data updated from the server.  Need to clean up the compare function, in client controllers line 65 to 118

Yep, 53 lines of non-trivial code and I'm all like "That is WAY too long..."   I will write a generic utility function that will do it for me. I found angular.copy to be very useful, I just need something to tell me what's changed.

I will need that function to keep track of users logging in and out as well, as it updates almost immediately for other users when someone logs out, and it uses way more code than I feel should be necessary as well.... If javascript had a universal "findIndex" method, it would be helpful, but I want to make it a 2-3 liner for these updates without writing my own "findIndex" function. More specialized...

AngularJS

I've started getting into Bootstrap and AngularJS. Look for posts on those topics soon. Bootstrap is fun because even I can make a decent looking app with pretty much no savvy for design, and Angular is fun because it forces you into a new way of thinking, and getting that stuff to work and "getting it" is really what I strive for. The moment something clicks... it's a drug to me.

Saving State in Javascript and AJAX

We're doing something cool in work, to do with resource scheduling in a nice ajax-y, jQuery-UI-ey way.  There are a set number of filters to apply to get the view of the resources you want, which can include Department (for example, Development, Front-End, Client Services, Marketing), the Employee themselves, Employee Roles to show Senior Developers etc, Client and Project. The problem was, these are all filtered via AJAX, and refreshing the page would cause you to lose your filters.

One way that developers solved this was by keeping the state in the "hash" portion of the URL.  The most prevalent method of this was called the "Hashbang" which would add the hash (octothorp) and an excalamation point (! - the "bang").  This is easy but could lead to messy / long URL.

Another method that is pretty convenient is to set a cookie containing the parameters. The downside of this is you can't send the same view that you're looking at to a coworker. That cookie is on your computer only.

I couldn't decide which one to implement so I decided to do both.

    var serializers = {};

    serializers.hash = function(){
        this.serialize = function(str){
            window.location.hash = str;
        };         this.prepDeserialize = function(){
            var hash = window.location.hash;
            return hash == null || hash.indexOf("#") != 0 ? "" : hash.substring(1);
        };
    }     serializers.cookie = function(){
        this.serialize = function(str){
            window.Cookie.set("ajaxState_serialize", str);
        };         this.prepDeserialize = function(){
            return window.Cookie.get("ajaxState_serialize");
        };
    }

I have a cookie helper class which wraps what you can find on quirksmode.org.  If you want to use this code, you can provide your own cookie serializer, your own custom serializers, or whatever, without changing the rest of the code.

The rest of the code is just adding and subtracting values from the serialized data. To call it, it's simply this:

                var data = $.fn.projectFilter.getFilterData($wrap);
                window.AjaxState.serialize(data, true);

So you get the filter object and serialize it. The true on the call to serialize just says to "commit it", which means to write the data to the cookie or the hash on the same call. I've put this code up on this site, you can download it.

Javascript Functional Programming

In work, I was recently tasked with a data comparison project. A bunch of data was getting duplicated. These records include user first and last names, their phone, email, company.  The master file was a list of these same users but they were assigned "card id" because they were already customers.

The method was to find a match for name (first and last) and if the email didn't match, then separate it out. Then find a match for email, and if the name didn't match, separate it out.

You can imagine this logic:

Go through each line... 
var matches = core.filter(function(a){ return a.firstName == line.firstName && a.lastName == line.lastName; }); // match names 
if (matches.length == 1){ var matchingEmail = matches.filter(function(a){ return a.email == line.email; });
    if (matchingEmail.length == 1){ add it to results }
     else separate it out;
}

And do the same, checking email first. The main feature of Object Oriented Programming is to abstract out data and functionality so that different objects can behave the same way in a system. The main idea of functional programming is that the process can be abstracted out.

What I'm basically doing is, match the line to the map based on some values, if there are any matches, find matches within that result which match some other value. If that result is 1, then we've found a good match, and repeat with the comparison methods swapped.

Or, even more abstractly... running a comparison function, getting results, and running another comparison function to filter the results further.

Let's define our comparison functions:

var compareName = function(a) { return a.firstName == line.firstName && a.lastName == line.lastName; };
var compareEmail = function(a) { return a.email == line.email; };

We can create a method to run these comparisons. It will need the line, the master list, and the two comparison functions.

function compare(line, masterList, comparison1, comparison2)

And then we can call it like so:

compare(line, masterList, compareName, compareEmail);
compare(line, masterList, compareEmail, compareName);

Voila!