Archive

Tag Archives: angularjs

I needed to put in a nice Date and Time selector into a form that’s using AngularJS, and wondered what might work. Searches came up with the Bootstrap Datetimepicker (on Github here) and a couple of related Stackoverflow posts, but nothing that laid it out simply. I ended up doing it mostly-but-not-quite by-the-book AngularJS, so I thought I’d write up a quick post about it in case it helps someone else.

Grab the download of the Datetimepicker from the site, and do the usual if you’re working in Rails like I am: copy the JS file into vendor/assets/javascripts, and the datetimepicker.css into vendor/assets/stylesheets, and add them to your application JS and SCSS files. From there, I made an AngularJS directive, mostly based on the one in this Stackoverflow post, though I had to make some tweaks to it. First, the HTML, which is simple enough; just put the directive into your page wherever you want the input field:

<date-time-picker recipient="recipient"></date-time-picker>

In my case, I have an ng-model called “recipient”, representing the person for whom the user is scheduling an event, so I passed in the recipient to the directive. The directive is as follows:

.directive('dateTimePicker', function() {
  return {
    restrict: 'E',
    replace: true,
    scope: {
      recipient: '='
    },
    template:
      '<div>' +
      '<input type="text" readonly data-date-format="yyyy-mm-dd hh:ii" name="recipientDateTime" data-date-time required>'+
      '</div>',
    link: function(scope, element, attrs, ngModel) {
      var input = element.find('input');

      input.datetimepicker({
        format: "mm/dd/yyyy hh:ii",
        showMeridian: true,
        autoclose: true,
        todayBtn: true,
        todayHighlight: true
      });

      element.bind('blur keyup change', function(){
        scope.recipient.datetime = input.val();
      });
    }
  }
});

The setup specifies the recipient object that’s passed in, so that the directive can set the datetime property of it. The template is straight-forward, of course, being a normal text input with the datetimepicker options — I realized while writing this that there’s not really any need to specify the format there since it’s also specified down below in the JavaScript initializer. The initializer will take precedence in this case.

The link function is the first important piece here. It finds the input field within the directive’s element, and then initializes the datetimepicker widget on it. You can specify any of the datetimepicker options here, of course. The next piece is the one that really makes things work: the bind() call. This is called when the input field is changed/blurred, and it sets the datetime property of the recipient object to the value of the input field. Essentially this works because the user will select the date and the time using the widget, then move out of the input field to submit the form. As they leave the field, this sets the recipient.datetime property, so when the form is submitted, the AngularJS object has the desired value.

A nice side-effect of the way this works is that it supports multiple datetime fields for multiple recipients. I have my recipient list in an ng-repeat block with an add button, and as additional recipients are added with different dates/times, everything works as it should. Each recipient has their own datetime field and value.

Quick little tip to hopefully save others the hassle of tracking this down… I just added some pjax to an app in order to quick-load some content into a DIV in the page. However, the content includes a small AngularJS app, and when the content got loaded, the app wasn’t getting initialized, so instead of nice content I had mustache tags {{all over the place}}. Not so nice. After some searching and testing, I found that the following works:

    $(document).pjax('a[data-pjax]', '#content-panel');
    $(document).on('pjax:complete', function() {
        angular.bootstrap($("#content-panel")[0], ["my-app"]);
    })

That waits for the pjax request to complete, then boots the AngularJS app, and everything is good again.

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.