Archive

Tag Archives: javascript

Last week I started in on learning AngularJS and putting it to work on the app at my new company, FinderLabs. I have a long post coming up detailing what I’ve learned about setting up an AngularJS page backed by a simple Rails API, but for today I just wanted to jot down some notes about creating an AngularJS directive, because I found it pretty painful figuring it out — some of the AngularJS docs are quite good, and some of them are lacking. Thankfully there are a lot of examples out there, but I had to look over too many of them to get this working. Note that I’m only moderately comfortable with JavaScript coding, so your mileage may vary.

So, what I wanted to was really easy with plain old jQuery, but it turned out to be more complicated when AngularJS entered the picture. Inside a list of items, for each item I had a DIV into which I was rendering a line graph, using Flotr2, and I had to pass it the JSON data needed for the charting. Before converting my page to AngularJS, I simply iterated my list in Ruby and called item.json_chart_data for each one, and called Flotr2. Not any more; now I’m using ng-repeat on a collection of items in my controller scope. What to do? I could in theory stuff the chart data into my items when my API returns them to my controller, but that was overloading the items themselves. So instead, I created a directive that loads the chart data for each item via an AJAX call.

Here’s the relevant markup from the page:

    <div class="item-graph" id="item-graph-{{item.id}}" style="width: 100px; height: 50px; padding-right: 20px">

        <item-graph item="{{item.id}}"></item-graph>

    </div>

This defines the DIV that Flotr2 is going to draw into, with the width and height, and then inside it is my directive, “item-graph”. I pass the item id into the directive, so that it can make the AJAX call to the server to get the graph data. Now let’s look at the directive:

var app = angular.module('my-app', ['restangular', 'ui.bootstrap']).
  config(function(RestangularProvider) {
    RestangularProvider.setBaseUrl('/api/v1');
})
.controller('MyListCtrl', function($scope, Items) {
  $scope.items = Items;
})
.directive('itemGraph', function($http) {
  return {
    restrict: 'E',
    scope:{
        itemId:'@item'
    },
    link: function(scope, element, attrs) {
      scope.$watch('itemId', function(value) {
          if (value) {
            $http.jsonp("/items/" + value + "/item_graph_data.json?callback=JSON_CALLBACK").then(function(response){
              draw_line_graph(document.getElementById('item-graph-' + value), response.data);
            });
          }
        });
    }
  }
});

This is all of the code for the page; I’ll gloss over the top part since that’s pretty standard AngularJS stuff. It defines the app, injecting the dependencies. I use Restangular for most of the server interaction; it does a really nice job of encapsulating things in a clear, RESTful way. The code configures Restangular with the base URL, since my server-side routes are in the ‘/api/v1’ space. Then we define the controller, and use the Items service (see below) to fetch the items. Then we get into the more interesting part: the directive.

First, note the name of the directive: “itemGraph”. But in the page markup, the tag is “item-graph” — it’s an irritating inconsistency, but just remember that directive names are “camel cased” while the name in your markup is “snake cased”. Thus “item-graph” in the page matches up with “itemGraph” in the code. Whatever. So then in the declaration we inject in the $http service so it can be used later.

Directives basically return an object with a couple of important sections. The first is random configuration, of which the restrict is very important, and this caused me more than a few minutes of debugging. I seemed to have everything wired up, but nothing was happening. That’s because restrict defaults to ‘A’, which specifies that the directive is restricted to attributes. I needed ‘E’ for element. This is described in the AngularJS page on directives (here), but it’s a long, long way down the page buried in details about compiling and the “directive definition object”. Major documentation fail, yes. In any case, don’t make my mistake; as soon as I added this, it started invoking my code.

The second piece needed is the scope, which you can think of as what ties the attributes in your page to your directive. In this case, remember that in my page I specified item="{{item.id}}" in order to pass in the item id. In this scope block we specify itemId:'@item', which says that we want a binding between the local property ‘itemId’ and the attribute ‘item’. The ‘@’ indicates a one-way data binding. I highly recommend reading this great blog post that describes the use of attributes, bindings, and functions. You’ll be glad you did.

Whew, okay, so now we have the directive attached to our element, and we have the attribute bound to a local property so we can use it. How do we use it? Well, it’s a little complicated, but not too bad. First: think of the link() function as what’s invoked when the directive is “activated” for an element. So in here, we want to take the item id that was passed in, and do something. However, it turns out that you don’t simply use itemId directly, or (as a normal person might assume) go with attrs.itemId; instead, we have to use our scope, and “watch” the property. In fairness, this is because often what you’re doing is binding your directive to something in the page that can change, such as a form field. Then by watching it, your directive can respond to changes in that attribute. In my case that wasn’t needed, but I still have to play by the rules. So okay, call scope.$watch() on ‘itemId’, and then write a function to deal with it. Apparently when first loaded, the watch function is called, so then we make sure there is a value (perhaps the directive gets loaded before the DOM is ready? I dunno). If there is, let’s finally do something.

What I wanted to do in my case is make a quick call back to the server, specifying the item id — which, remember, is now represented by “value” since that’s the parameter name on the watch function. When it returns, it calls draw_line_graph(), another function that sets up Flotr2 and does the drawing. It draws the graph into the DOM element passed in, with the data also passed in.

And that’s it. Seems like a lot of code to do what could be done in a couple of lines before, to be honest, but it’s packaged and reusable, and easily copied to do more complex things. One last thing; as promised I wanted to include the Items service which is used to get the initial list of items for the page, just in case someone finds it useful. It’s in another small JS file:

var app = angular.module('my-app');

app.factory('Items', function($http, Restangular) {
  var baseItems = Restangular.all('items');
  return baseItems.getList();
});

That’s all there is to it, using Restangular to get the list. This automagically ends up invoking the server URL ‘/api/v1/items’ and returns the JSON response. Restangular is even nicer when you start getting into nested relationships and other more complex needs.

I’ve made some of this generic (“my-app” and “Items”), since I can’t get show full details of the app I’m working on, but hopefully it will also make it easier for this to be used by others. I hope this saves others who are new to AngularJS some of the pain I had in figuring all of this out.

Advertisements

Recently we wanted to add a nice-looking timeline view to some data in a web app, and looked around for good Javascript libraries. Perhaps the coolest one is Timeline JS from Verite, but while gorgeous it’s also super heavyweight, and pretty much demands a page all to itself. We wanted something more economical that could share the page with some other views, and decided on Timeline-Setter, which creates pretty little timelines with just enough view area to provide important information about each event.

However, as-provided, Timeline-Setter wants to exist as a command-line utility. You get a data file ready, run the utility, and it generates a set of HTML (and associated) files that you can drop into an app. That’s dandy if you have a set of data that doesn’t change often, or you want to perhaps run a cron to pre-generate a bunch of timelines. We needed something that could generate a timeline for a dynamic set of data, so we weren’t sure Timeline-Setter would work for us. However, looking it over I thought it seemed potentially usable in a dynamic way. I generated a static example using our data, read through what it had created, and deconstructed what it wanted in order to display the timeline, then wrote some code to dynamically generate the JSON data necessary. It wasn’t too difficult, and fairly shortly we had dynamic timelines going. I wanted to share the info here since it’s a pretty nice library that others should get a lot out of.

We’re using Jammit to handle our static assets, so we simply put “public/javascripts/timeline-setter.js” and “public/stylesheets/timeline-setter.css” into our assets.yml file, but you can use whatever standard approach you take to including JS and CSS into your pages. Once that’s done, you’re ready to go.

Timeline-Setter takes a pretty standard approach to placing itself in the page: it takes the ID of a DIV, and that’s the container which will hold the timeline. One note: we needed to include multiple timelines in a single page, so we had to do a little creative naming of the DIVs that hold the timelines, as you’ll see below.

<div id="timeline-<%= author.id %>" class="timeline_holder"></div>
<pre>
<script type="text/javascript">// <![CDATA[
    $(function () {
      var currentTimeline = TimelineSetter.Timeline.boot([<%= author.make_timeline_json %>], {"container":"#timeline-<%= author.id %>", "interval":"", "colorReset":true});
    });

// ]]></script>

The code here creates the DIV, gives it an ID, and then places a Javascript call to the Timeline-Setter boot() function, which tells it to generate the timeline. The first parameter is the JSON holding the data for the timeline; the second is a set of options, passed in as JSON. “container” of course is the ID of the DIV which will contain the timeline. Other options include “interval”, “formatter”, and “colorReset” among others. See the library’s page as listed at the beginning for details of the API, and in particular see the section headed “Configuring the Timeline JavaScript Embed” for the basics of calling the boot() function.

Now of course we need the make_timeline_json() method, which will take our object’s data and create the JSON needed by Timeline-Setter. As an example here, let’s pretend that we’re showing a timeline of books written by an author over the years.

class Author < ActiveRecord::Base
  def make_timeline_json
    timeline_list = []
    if (!birth_date.nil?)
      timeline_list << "{'html':'','date':'#{birth_date}','description':'Birth Date:','link':'','display_date':'','series':'Events','timestamp':#{(birth_date.to_time.to_i * 1000)}}"
    end
    books.each do |book|
      author_list = book.authors.map { |auth| "#{auth['name']}" }.join('; ')
      timeline_list << "{'html':'Authors: #{author_list}
Publisher: #{book.publisher}','date':'#{book.pub_date}','description':'#{book.title}','link':'','display_date':'','series':'Publications','timestamp':#{(book.pub_date.to_time.to_i * 1000)}}"
    end
    if (!death_date.nil?)
      timeline_list << "{'html':'','date':'#{death_date}','description':'Death Date:','link':'','display_date':'','series':'Events','timestamp':#{(death_date.to_time.to_i * 1000)}}"
    end
    return "#{timeline_list.join(',')}"
  end
end

Essentially, this method creates a JSON string containing a set of entries, each representing an event to place on the timeline. Each entry has several fields: html, date, description, link, display_date, series, and timestamp. Not all of these are used here, but with the basics you can experiment further. The important fields are:

  • html: This will be displayed in the event’s pop-up when clicked on.
  • description: Just what it says.
  • link: an optional URL which will be associated with a link in the event pop-up.
  • series: which “series” this event belongs to; see below for details on this.
  • timestamp: This is the timestamp associated with the event, used to construct the timeline in order.

A note about the “series” parameter: one very nice feature of Timeline-Setter is that you can display more than one set of events in a single timeline. Each set of events is called a “series”. In our example we’re creating two series: “Events” and “Publications”. Each will be shown in a different color, with a title (so the names of the series need to look nice, as they’ll be displayed) and a checkbox so that a viewer can hide and show each series individually. It’s extremely useful.

In the code above, you’ll see that we create the “Birth Date” and “Death Date” events individually, but in the middle we iterate over the books associated with this author. For each book we build a string of authors, semicolon-delimited, just to demonstrate one way to include another list of information in an event’s HTML. I have to admit that I’m not entirely certain why it’s necessary to multiply the timestamps by 1000 to get to the correct time, but it works fine…

And there you are. Hopefully anyone needing an economical, nice-looking timeline with dynamically-generated data can take advantage of this. But certainly, if you can work with static (or infrequently-updated) data, you may be able to use Timeline-Setter out of the box via a cron job or rake task — you could generate the CSV file for its command-line interface, run it, then copy the resulting files into your application. If you need dynamic timelines, though, I hope this post is helpful.

At my last place we got some cool Google Analytics-style charts going using the excellent RaphaelJS library. This week I’ve been playing with the simpler bar and pie charts, and thought I’d share some findings. RaphaelJS is great, but the examples and documentation are still — like so many open source projects — slim. In particular, I found that the example provided for an interactive pie chart is broken, so I had to play around to get that working.

I did the bar chart first, and found after some searching in the Google Group archive that the labels aren’t quite working in the main branch, but thankfully someone else has a forked version of the bar chart file. If you want to get the labels working properly, go here: http://github.com/iamwilhelm/g.raphael and grab g.bar.js.

I set up a simple report page that displays the same data using either a bar chart or a pie chart depending on the parameter “chart”. The data is simply a count of totals for each month of the year, so there are up to 12 bars/segments shown in each chart. The controller gathers up the data from a query and also prepares the labels for the charts:

  def data_by_month
    @chart = params[:chart]
    months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]

    monthly_totals = MyModel.get_monthly_totals()
    @monthly = []
    @month_names = []
    monthly_totals.each_with_index do |month, i|
      @monthly << month['count'].to_i
      month_name = months[i]
      if (@chart == 'pie')  # Need to add counts to the labels for pie charts
        month_name << " (#{month['count']})"
      end
      @month_names << month_name
    end
  end

One of the awkward things you’ll see above is that the code adds the numbers to the labels if it’s going to show a pie chart. That’s because unfortunately I haven’t yet found a way to show the numbers within the pie chart segments. So instead, I display the legend/labels to the side of the pie chart, with the numbers as part of the labels. For example, instead of just showing “January” it shows “January (35)”. Not great, naturally. The bar charts automatically show the numbers when you hover over the bars, so I didn’t need to do that in the bar chart case.

It’s worth quickly pointing out that when the code’s adding the values to the @monthly array, I had to do a .to_i on it. Otherwise the values went in as strings. Oddly enough, that worked fine for the bar chart, but the pie chart didn’t show any results.

The page itself is as follows:

<%= javascript_include_tag "raphael-min.js" -%>
<%= javascript_include_tag "g.raphael-min.js" -%>
<!-- Using g.bar.js from http://github.com/iamwilhelm/g.raphael with horz label fix -->
<%= javascript_include_tag "g.bar.js" -%>
<%= javascript_include_tag "g.pie-min.js" -%>

<script type="text/javascript">
  //<![CDATA[
  window.onload = function () {
    var r = Raphael("holder", 800, 500),
        fin = function () {
            this.flag = r.g.popup(this.bar.x, this.bar.y, this.bar.value || "0").insertBefore(this);
        },
        fout = function () {
            this.flag.animate({opacity: 0}, 300, function () {this.remove();});
        };
    r.g.txtattr.font = "18px 'Fontin Sans', Fontin-Sans, sans-serif";

    r.g.text(160, 10, "Totals by Month for <%= @year -%>");

    <% if (@chart == 'bar') %>
    r.g.barchart(30, 30, 500, 420, [<%= @monthly.to_json -%>]).hover(fin, fout).label([<%= @month_names.to_json -%>]);
    <% else %>
    var pie = r.g.piechart(280, 260, 200, <%= @monthly.to_json -%>, {legend: <%= @month_names.to_json -%>,
        legendpos: "east"});
    pie.hover(function () {
                    this.sector.stop();
                    this.sector.scale(1.1, 1.1, this.cx, this.cy);
                    if (this.label) {
                        this.label[0].stop();
                        this.label[0].scale(1.5);
                        this.label[1].attr({"font-weight": 800});
                    }
                }, function () {
                    this.sector.animate({scale: [1, 1, this.cx, this.cy]}, 500, "bounce");
                    if (this.label) {
                        this.label[0].animate({scale: 1}, 500, "bounce");
                        this.label[1].attr({"font-weight": 400});
                    }
                });
    <% end %>
  };
  //]]>
</script>

<br/><br/>

<h1>Reports : Totals by Month</h1>
<%= link_to('Bar Chart', totals_by_month_path(:chart => 'bar')) %> |
<%= link_to('Pie Chart', totals_by_month_path(:chart => 'pie')) %>

<br/><br/>

<div id="holder"></div>

So, what’s up here? A lot of this code was taken from the RaphaelJS examples, and then munged a bit. After including the files, the onload() function sets up the Raphael object, which is called “holder” to match the id of the <div> down below, which is where the charts will be placed. The functions fin() and fout() are used by the bar chart (so yeah, they should be put inside an if block but I haven’t done that yet). They display/remove the value and do the animation during hover events. The text() call is just to put a title on the chart. In this case it probably makes more sense to leave that in the plain HTML, but it was in the example code so I left it in order to experiment.

If the chart is a bar chart, the line

r.g.barchart(30, 30, 500, 420, [<%= @monthly.to_json -%>]).hover(fin, fout).label([<%= @month_names.to_json -%>]);

is used. Obviously the first bit of code takes the monthly values, which is an array, and drops in the JSON equivalent. Those are the numbers which will be used to create the bar chart. The second bit of code is what places the labels, which in this case are the month names. This threw me off initially, because the function expects an array of arrays, just like the values. This is because it supports “stacked” bar charts — the extra square brackets around the values/labels are important. Without those around the labels, I was getting one character per bar instead of the full string, which was very confusing.

This is what the bar chart looks like (obviously not showing the hover-over which displays the number for each bar):

For the pie chart, the basic call is very similar. The values are passed in the same way, but then the “legend” attribute is used to pass the names of the months (with the totals added in this case). The “legendpos” attribute positions the labels to the north/east/south/west of the chart itself. I started on the west but had trouble with alignment — the chart was getting positioned too close to the labels and would end up partially covering the labels on rollover. Putting the legend on the east worked around that problem for now. The hover() functions were taken verbatim from a pie chart example.

This is what the pie chart looks like (obviously not showing the hover-over, which enlarges the segment and the corresponding legend item):

As you can see, getting these working is very straightforward — the only issues that held me up were due to incomplete documentation or examples not quite working correctly. Hopefully this post can help speed things up for others. RaphaelJS is very powerful library, and the charting plugin that provides the easy charts is a great add-on. Enjoy.