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 :)

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 :)

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.