Mar 17 2016
Mar 17

f1_chrr_hero
This week, we were proud to once again help launch County Health Rankings, a project we have been fortunate to support over seven annual releases.

A collaboration between the Robert Wood Johnson Foundation and the University of Wisconsin Population Health Institute, the Rankings compare counties within each state on more than 30 factors that impact health, including such social determinants as education, jobs, housing, exercise, commuting times, and more.

In honor of the seventh release, and in honor of St. Patrick’s Day – a day upon which Americans are prone to misfortune due to less-than-healthy behaviors – we provide seven lucky reasons we love this year’s Rankings:

1. You Can Compare Counties Across State Lines

Rankings fans have long desired to compare counties in different states. While it would never make sense to compare state-by-state ranks, you can now create head-to-head comparisons on specific measures between any county of your choosing. For example, here we’ve compared several counties named “Orange” including these counties in Florida and California that are home to Disney World and Disneyland. (Might this help answer the age-old question: Are the happiest counties on Earth also the healthiest?)

image04

2. Easy-to-Use Additional Measures

The Rankings provide county-level information on a variety of interesting additional measures, such as residential segregation and health care costs, that do not factor into ranking calculations yet are helpful to gaining a better portrait of a county’s health. These measures can now be directly accessed within any county snapshot. Just browse to your favorite county (here’s mine) and click the plus (+) signs to reveal additional measures.

image01

3. Improved Details About Measures that Affect Health

This year, we’ve created new pages for each ranked and additional measure to help audiences understand more about how factors such as adult obesity, drug overdose deaths, and insufficient sleep affect our health. Previously, these were only described in the context of a given state when viewing data in the application. In practical terms, this means you can now quickly find these measures via the site’s main search function. We also revamped the focus area pages that each measure is related to. For example, here is the overview of tobacco use, which includes a clear description, measurement strategy, references, and a list of relevant policies and programs that can lead to improvements.  

image06

4. Key Findings Report

A beautiful, new Key Findings report takes a broader, national perspective on the Rankings. It explains that rural counties have had the highest rates of premature death rates, lagging behind more urbanized counties.

The Drupal crowd might be interested in knowing that this was built using the Paragraphs module, which allowed our site editors a fair bit of flexibility in creating longform content on the fly, since their content wasn’t approved/ finalized until very close to our release date. They did this by adding any number of pre-built components (Paragraph types) like downloadable images, text fields, section headers, and callout boxes to the report, then rearranging as needed via drag and drop. And because we created this as its own content type, it’s also now very easy for editors to go back and create reports from the PDFs of key findings from previous years.

image05

5. Health Gaps Reports

Revealing state-by-state Health Gaps reports explore the factors that cause different health outcomes across each state, and what can be done to address them.

image03

6. Areas of Strength:

When viewing any one of the over 3,000 county snapshots – say, Wayne County, MI, for example – you can now highlight specific areas in which a county is performing well by clicking the checkboxes in the upper right. This compliments the “Areas to Explore” toggle we introduced a few years ago.

image00

7. Boozing Discouraged

Before you imbibe on St. Patrick’s Day, you should check out your county’s performance on Alcohol-impaired Driving Deaths and Excessive Drinking.

image02
Seriously, nearly 90,000 deaths are attributed annually to excessive drinking. So wherever you live, take care this St. Patrick’s Day!

Previous Post

Luck-Changing Web Dev Limericks

Dec 27 2015
Dec 27

Getting URL Alias For Referenced Entity With RESTful API in Drupal

As documented in a previous post, with RESTful, you can create a separate API endpoint to look up the base node ID from a URL alias for a specific entity. However, there are also URL aliases for other entities that are referenced from within your base entity, such as users and taxonomy terms. We don't want to have to do a separate lookup for each of these entities outside of our base entity, so the ideal situation would be to just have a field returned within our base entity lookup that contains these URL aliases.

As an example, let's say that in our Blog entity, instead of using the built in node creator for the author, we have an entity reference field called Author (field_author) that references users (this gives us flexibility in specifying the content author in allowing us to easily select other names, as well as allowing us to have multiple authors). And, as is typical in Drupal 7, we use term reference fields for taxonomy terms, and since in a decoupled front end we want to display those terms as links, we need that path. We want to be able to get these aliases as fields when we query the Blog node.

The solution to this problem in RESTful is process callbacks (see this video for examples). These work very similar to process callbacks in the Form APi in Drupal; they are simply custom callbacks that can be used to do custom manipulating of field data once it has been retrieved and before it is returned.

The callbacks are added in the field definition in the publicFields function using the process_callbacks key. Here is the definition for a user alias within the Users class:

class Users__1_1 extends Users__1_0 {

  /**
   * {@inheritdoc}
   */
  protected function publicFields() {
    $public_fields = parent::publicFields();
...
    $public_fields['alias'] = array(
      'property' => 'uid',
      'process_callbacks' => array(
        array($this, 'getAlias')
      )
    );

    return $public_fields;
  }

  public function getAlias($value) {
    return drupal_get_path_alias('user/' . $value);
  }
}

Things to note in this code:

  • The property is the data that you need to have to get the alias, and in this case it is the user id (uid).
  • We define the callback name as an array with the $this reference as the first item because we are making the callback a member function in this class. If the function were placed elsewhere (such as a .module or .inc file) or is a regular PHP function, we would just pass the function name.
  • The $value argument that is passed to the callback is the value of the field defined in the property key; in this case, the uid.

We just call drupal_get_path_alias() and pass it the uid to get the URL alias. This alias is then returned as part of the author member in the JSON object:

{
  "type": "users",
  "id": "123",
  "attributes": {
    "id": "123",
    "label": "Steve Edwards",
    "self": "http://mysite.com/api/v1.1/users/123",
    "mail": "[email protected]",
    "name": "Steve Edwards",
    "alias": "author/steve-edwards"
}

A similar case is a taxonomy term from a term reference field. Here is an example for a Journalists vocabulary:

class Journalists__1_0 extends ResourceEntity implements ResourceInterface {

  /**
   * {@inheritdoc}
   */
  protected function publicFields() {
    $public_fields = parent::publicFields();

    $public_fields['alias'] = array(
      'property' => 'tid',
      'process_callbacks' => array(
        array($this, 'getAlias')
      )
    );

    return $public_fields;
  }

  public function getAlias($value) {
    return drupal_get_path_alias('taxonomy/term/' . $value);
  }
}

This is very similar to the Author example, with the only difference being that we are passing a term id (tid) instead of a user id. The resulting output is the same:

{       
  "type": "journalists",
  "id": "1938",
  "attributes": {
    "id": "1938",
    "label": "Jake Tapper",
    "self": "http://mysite.com/api/v1.0/journalists/1938",
    "alias": "journalists/jake-tapper"
}, 

As mentioned above, if you are familiar with process callbacks in Form API, then this will look very familiar.

Also, see this wiki page I created in the GitHub repo for RESTful for another example of using process callbacks to add the path for an image style derivative.

Dec 06 2015
Dec 06

Creating a RESTful API URL Alias Lookup in Drupal

Over the past few months (since Drupalcon LA), I have been actively diving in to the world of decoupled Drupal, in particular using AngularJS as a front end. After going through many tutorials and looking at the multiple options for providing an API from Drupal to be consumed by Angular, I settled on using the RESTful module. One thing I like in particular about it is that you have to explicitly define what you want available in your API, as compared to everything being automatically being made available when the module is enabled.

In addition to the documention in the GitHub wiki and on drupal.org, there is also a really great YouTube video series on how to write your custom classes and perform the tasks you need to do to comsume the API, such as sorting, filtering, and displaying related content.

Clean URL Mapping

In Drupal, the most common method of creating URLs is to use clean URLs to access data, instead of using the base /node/$nid URL. But, since Drupal uses the base URL for loading the node data, there has to be mapping and lookup functionality for linking the clean URL with the base URL. This is handled with the core Path module, with the mappings being stored in the url_alias table. When a page is accessed with a clean URL, the url_alias table is queried to get the base URL, the node is loaded, and the beat goes on.

However, in looking through all of the different API options and tutorials, I didn't see this clean URL lookup functionality (although I didn't look incredibly hard, so I might have missed something, too). Everything I saw was based on passing an entity ID in a GET URL. Even if I'm using a decoupled front end, I still want to use a clean URL, so I need to be able to take that clean URL, query my API to get the entity id using the clean URL as the search criteria, and then use that value to load the whole entity.

In the process of my searching, I discovered a wiki page that showed that RESTful has a built in class (a data provider in RESTful lingo) that allows you to query a database table directly. This was exactly what I needed.

The Code

Creating a RESTful plugin for querying a table is very similar to creating a plugin for getting entity information; you have to first define your plugin, and then define your public fields (the fields you want available as part of the API).

Here's the final code for my plugin:

/**
 * Contains \Drupal\restful_tutorial\Plugin\resource\url_alias
 */

namespace Drupal\restful_tutorial\Plugin\resource\url_alias;

use Drupal\restful\Plugin\resource\ResourceDbQuery;
use Drupal\restful\Plugin\resource\ResourceInterface;

/**
 * Class URLAlias__1_0
 * @package Drupal\restful_tutorial\Plugin\resource\url_alias
 *
 * @Resource(
 *   name = "urlalias:1.0",
 *   resource = "urlalias",
 *   label = "URL Alias",
 *   description = "Gets the entity id from a URL alias.",
 *   authenticationTypes = TRUE,
 *   authenticationOptional = TRUE,
 *   dataProvider = {
 *     "tableName": "url_alias",
 *     "idColumn": "alias",
 *     "primary": "pid",
 *     "idField": "alias",
 *   },
 *   majorVersion = 1,
 *   minorVersion = 0,
 *   class = "URLAlias__1_0"
 * )
 */

class URLAlias__1_0 extends ResourceDbQuery implements ResourceInterface {

  /**
   * {@inheritdoc}
   */
  protected function publicFields() {
    $public_fields['pid'] = array(
      'property' => 'pid'
    );

    $public_fields['source'] = array(
      'property' => 'source'
    );

    $public_fields['alias'] = array(
      'property' => 'alias'
    );

    return $public_fields;
  }
}

Here are the key points from the code:

  • The annotations are what provides the details about the table source.
  • The idColumn value is the database field to which the value passed in the URL will be compared.

I then query the api endpoint with the following URL:

http://mysite/api/urlalias/steve-edwards/2015/12/05/my-blog-title

and the resulting JSON output looks like this:

{
  "data": {
    "type": "urlalias",
    "id": "154177",
    "attributes": {
      "pid": "154177",
      "source": "node/97783",
      "alias": "steve-edwards/2015/12/05/my-blog-title"
    },
    "links": {
      "self": "http://mysite/api/v1.0/urlalias/154177"
    }
  },
  "links": {
    "self": "http://mysite/api/v1.0/urlalias/steve-edwards/2015/12/05/my-blog-title"
  }
}

From there, you can get the id value, and then call your appropriate endpoint to get the full entity.

This is a pretty basic use of the DataProviderDbQuery class, but it's a very powerful tool that allows you to create and expose custom queries as a RESTful API endpoint as needed for a decoupled setup.

May 14 2015
May 14

My colleague Adam Juran and I just finished with our session, Zero to MVP in 40 minutes: Coder and Themer Get Rich Quick in Silicon Valley, at DrupalCon LA. This one was a real journey to prepare, and through it we learned a lot of dirty truths about Drupal 8, Javascript frameworks, and the use cases where the two make sense together.

The live coding challenge in our session proposal seemed simple: create a web application that ingests content from an external API, performs content management tasks (data modelling, relationships, etc.) through the Drupal 8 interface, and deliver it all to an AngularJS front-end. This is exactly the “headless Drupal” stuff that everyone has been so excited about for the last year, so doing it in a 40 minute head-to-head code battle seemed like an entertaining session.

Ingesting content from an external API

The first hard truth we discovered was the limitations of the still-nascent Drupal 8. Every monthly release of a new Drupal 8 beta includes a series of “change records,” defining all the system-wide changes that will have to be accounted for everywhere else. For example, one change record notes that a variable we often use in Drupal forms is now a different kind of object. This change breaks every single form, everywhere in Drupal.

The frequency of this kind of change record is a problem for anyone who tries to maintain a contributed module. No one can keep up with their code breaking every month, so most don’t. The module works when they publish it as “stable”, but two or three months later, it’s fundamentally broken. changes like this currently happen 10-15 times every month. Any module we were hoping to use as a part of this requirement – Twitter, Oauth, Facebook – were broken when we started testing.

We finally settled on using Drupal’s robust Migrate module to bring in external content. After all, Drupal 7 Migrate can import content from almost any format! Turns out that this isn’t the case with Drupal 8 core’s Migrate module. It’s limited to the basic framework you need for all migrations. Importers for various file types and sources simply haven’t been written yet.

No matter which direction we turned, we were faced with the fact that Drupal 8 needed work to perform the first requirement in our challenge. We chose to create a CSV Source plugin ourselves (with much help from mikeryan and chx) just to be able to meet this requirement. This was not something we could show in the presentation; it was only a prerequisite. Phew!

Displaying it All in Angular

Building an AngularJS based front end for this presentation involved making decisions about architecture, which ended up as the critical focus of our talk. AngularJS is a complete framework, which normally handles the entire application: data ingestion, manipulation, and display. Why would you stick Drupal in there? And what would an Angular application look like architecturally, with Drupal 8 inside?

You always have a choice of what to do and where to do it. Either system can ingest data, and either system can do data manipulation. Your decision should be based on which tool does each job the best, in your particular use case: a catch-all phrase that includes factors like scalability and depth of functionality, but also subtler elements like the expertise of your team. If you have a shop full of AngularJS people and a simple use case, you should probably build the entire app in Angular!

Given that perspective, Drupal really stands out as a data ingestion and processing engine. Even when you have to write a new Migration source plugin, the Entity model, Drupal’s “plug-ability”, and Views make data crunching extremely easy. This is a strong contrast to data work in Angular, where you have to write everything from scratch.

We feel that the best combination of Drupal and Angular is with Drupal ingesting content, manipulating it, and spitting it out in a ready-to-go format for AngularJS to consume. This limits the Angular application to its strengths: layout, with data from a REST back-end, and only simple logic.

The Session

[embedded content]

In the session, we talked a bit about the larger concepts involved, and moved fairly quickly into the technical demonstration. First, Adam demonstrated the flexibility of the decoupled front-end, using bower libraries to build an attractive layout without writing a single line of custom CSS.  Then I demonstrated importing data from CSV sources into Drupal 8, along with the simplicity of configuring Drupal Views to output JSON. Taken together, the videos are 37 minutes long – not bad for a totally custom RESTful endpoint and a nice looking front-end!

Here is Adam’s screencast, showing off the power of the bootstrap-material-design library to build a good looking site without any custom CSS at all:

Here is my screencast, demonstrating how easy it is to create Migrate module importers and REST exports in Drupal 8.

And here is the final screencast, quickly showing the changes we made in AngularJS to have it call the two Drupal Services.

Want to learn of Forum One’s Drupal development secrets? Check out our other Drupalcon blog posts, or visit our booth (#107) and talk with our tech wizards in person!

Previous Post

DrupalCon LA Day 1!

Next Post

Hacking the Feds: Forum One Among the Winners at GSA Hack-a-Thon

Dec 15 2014
Dec 15

Angular.js is the hot new thing right now for designing applications in the client. Well, it’s not so new anymore but is sure as hell still hot, especially now that it’s being used and backed by Google. It takes the idea of a JavaScript framework to a whole new level and provides a great basis for developing rich and dynamic apps that can run in the browser or as hybrid mobile apps.

logo_drupal

In this article I am going to show you a neat little way of using some of its magic within a Drupal 7 site. A simple piece of functionality but one that is enough to demonstrate how powerful Angular.js is and the potential use cases even within heavy server-side PHP frameworks such as Drupal. So what are we doing?

We are going to create a block that lists some node titles. Big whoop. However, these node titles are going to be loaded asynchronously using Angular.js and there will be a textfield above them to filter/search for nodes (also done asyncronously). As a bonus, we will also use a small open source Angular.js module that will allow us to view some of the node info in a dialog when we click on the titles.

So let’s get started. As usual, all the code we write in the tutorial can be found in this repository.

Ingredients

In order to mock this up, we will need the following:

  • A custom Drupal module
  • A Drupal hook_menu() implementation to create an endpoint for querying nodes
  • A Drupal theme function that uses a template file to render our markup
  • A custom Drupal block to call the theme function and place the markup where we want
  • A small Angular.js app
  • For the bonus part, the ngDialog Angular module

The module

Let us get started with creating a custom module called Ang. As usual, inside the modules/custom folder create an ang.info file:

name = Ang
description = Angular.js example on a Drupal 7 site.
core = 7.x

…and an ang.module file that will contain most of our Drupal related code. Inside this file (don’t forget the opening <?php tag), we can start with the hook_menu() implementation:

/**
 * Implements hook_menu().
 */
function ang_menu() {
  $items = array();

  $items['api/node'] = array(
    'access arguments' => array('access content'),
    'page callback'     => 'ang_node_api',
    'page arguments' => array(2),
    'delivery callback' => 'drupal_json_output'
  );

  return $items;
}
/**
 * API callback to return nodes in JSON format
 *
 * @param $param
 * @return array
 */
function ang_node_api($param) {

  // If passed param is node id
  if ($param && is_numeric($param)) {
    $node = node_load($param);
    return array(
      'nid' => $param,
      'uid' => $node->uid,
      'title' => check_plain($node->title),
      'body' => $node->body[LANGUAGE_NONE][0]['value'],
    );
  }
  // If passed param is text value
  elseif ($param && !is_numeric($param)) {
    $nodes = db_query("SELECT nid, uid, title FROM {node} n JOIN {field_data_body} b ON n.nid = b.entity_id WHERE n.title LIKE :pattern ORDER BY n.created DESC LIMIT 5", array(':pattern' => '%' . db_like($param) . '%'))->fetchAll();
    return $nodes;
  }
  // If there is no passed param
  else {
    $nodes = db_query("SELECT nid, uid, title FROM {node} n JOIN {field_data_body} b ON n.nid = b.entity_id ORDER BY n.created DESC LIMIT 10")->fetchAll();
    return $nodes;
  }
}

In hook_menu() we declare a path (api/node) which can be accessed by anyone with permissions to view content and which will return JSON output created in the callback function ang_node_api(). The latter gets passed one argument, that is whatever is found in the URL after the path we declared: api/node/[some-extra-param]. We need this argument because of we want to achieve 3 things with this endpoint:

  1. return a list of 10 most recent nodes
  2. return a node with a certain id (api/node/5 for example)
  3. return all the nodes which have the passed parameter in their title (api/node/chocolate for example, where chocolate is part of one or more node titles)

And this is what happens in the second function. The parameter is being checked against three cases:

  • If it exists and it’s numeric, we load the respective node and return an array with some basic info form that node (remember, this will be in JSON format)
  • If it exists but it is not numeric, we perform a database query and return all the nodes whose titles contain that value
  • In any other case (which essentially means the lack of a parameter), we query the db and return the latest 10 nodes (just as an example)

Obviously this callback can be further improved and consolidated (error handling, etc), but for demonstration purposes, it will work just fine. Let’s now create a theme that uses a template file and a custom block that will render it:

/**
 * Implements hook_theme().
 */
function ang_theme($existing, $type, $theme, $path) {
  return array(
    'angular_listing' => array(
      'template' => 'angular-listing',
      'variables' => array()
    ),
  );
}

/**
 * Implements hook_block_info().
 */
function ang_block_info() {

  $blocks['angular_nodes'] = array(
    'info' => t('Node listing'),
  );

  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function ang_block_view($delta = '') {

  $block = array();

  switch ($delta) {
    case 'angular_nodes':
      $block['subject'] = t('Latest nodes');
      $block['content'] = array(
        '#theme' => 'angular_listing',
        '#attached' => array(
          'js' => array(
            'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js',
            'https://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular-resource.js',
            drupal_get_path('module', 'ang') . '/lib/ngDialog/ngDialog.min.js',
            drupal_get_path('module', 'ang') . '/ang.js',
          ),
          'css' => array(
            drupal_get_path('module', 'ang') . '/lib/ngDialog/ngDialog.min.css',
            drupal_get_path('module', 'ang') . '/lib/ngDialog/ngDialog-theme-default.min.css',
          ),
        ),
      );
      break;
  }

  return $block;
}

/**
 * Implements template_preprocess_angular_listing().
 */
function ang_preprocess_angular_listing(&$vars) {
  // Can stay empty for now.
}

There are four simple functions here:

  1. Using hook_theme() we create our angular_listing theme that uses the angular-listing.tpl.php template file we will create soon.
  2. Inside the hook_block_info() we define our new block, the display of which is being controlled inside the next function.
  3. Using hook_block_view() we define the output of our block: a renderable array using the angular_listing theme and which has the respective javascript and css files attached. From the Google CDN we load the Angular.js library files, inside ang.js we will write our JavaScript logic and in the /lib/ngDialog folder we have the library for creating dialogs. It’s up to you to download the latter and place it in the module following the described structure. You can find the files either in the repository or on the library website.
  4. The last function is a template preprocessor for our template in order to make sure the variables are getting passed to it (even if we are actually not using any).

As you can see, this is standard boilerplate Drupal 7 code. Before enabling the module or trying out this code, let’s quickly create the template file so Drupal doesn’t error out. Inside a file called angular-listing.tpl.php, add the following:

        <div ng-app="nodeListing">
        
           <div ng-controller="ListController">
        
             <h3>Filter</h3>
             <input ng-model="search" ng-change="doSearch()">
        
              <ul>
                <li ng-repeat="node in nodes"><button ng-click="open(node.nid)">Open</button> {{ node.title }}</li>
              </ul>
        
             <script type="text/ng-template" id="loadedNodeTemplate">
             <h3>{{ loadedNode.title }}</h3>
             {{ loadedNode.body }}
             </script>
        
            </div>
        
        </div>

Here we have some simple HTML pimped up with Angular.js directives and expressions. Additionally, we have a <script> tag used by the ngDialog module as the template for the dialog. Before trying to explain this, let’s create also our ang.js file and add our javascript to it (since the two are so connected):

angular.module('nodeListing', ['ngResource', 'ngDialog'])

  // Factory for the ngResource service.
  .factory('Node', function($resource) {
    return $resource(Drupal.settings.basePath + 'api/node/:param', {}, {
      'search' : {method : 'GET', isArray : true}
    });
  })

  .controller('ListController', ['$scope', 'Node', 'ngDialog', function($scope, Node, ngDialog) {
    // Initial list of nodes.
    $scope.nodes = Node.query();

    // Callback for performing the search using a param from the textfield.
    $scope.doSearch = function() {
      $scope.nodes = Node.search({param: $scope.search});
    };

    // Callback to load the node info in the modal
    $scope.open = function(nid) {
      $scope.loadedNode = Node.get({param: nid});
      ngDialog.open({
        template: 'loadedNodeTemplate',
        scope: $scope
      });
    };

}]);

Alright. Now we have everything (make sure you also add the ngDialog files as requested in the #attached key of the renderable array we wrote above). You can enable the module and place the block somewhere prominent where you can see it. If all went well, you should get 10 node titles (if you have so many) and a search box above. Searching will make AJAX calls to the server to our endpoint and return other node titles. And clicking on them will open up a dialog with the node title and body on it. Sweet.

But let me explain what happens on the Angular.js side of things as well. First of all, we define an Angular.js app called nodeListing with the ngResource (the Angular.js service in charge communicating with the server) and ngDialog as its dependencies. This module is also declared in our template file as the main app, using the ng-app directive.

Inside this module, we create a factory for a new service called Node which returns a $resource. The latter is in fact a connection to our data on the server (the Drupal backend accessed through our endpoint). In addition to the default methods on it, we define another one called .search() that will make a GET request and return an array of results (we need a new one because the default .get() does not accept an array of results).

Below this factory, we define a controller called ListController (also declared in the template file using the ng-controller directive). This is our only controller and it’s scope will apply over all the template. There are a few things we do inside the controller:

  1. We load nodes from our resource using the query() method. We pass no parameters so we will get the latest 10 nodes on the site (if you remember our endpoint callback, the request will be made to /api/node). We attach the results to the scope in a variable called nodes. In our template, we loop through this array using the ng-repeat directive and list the node titles. Additionally, we create a button for each with an ng-click directive that triggers the callback open(node.nid) (more on this at point 3).
  2. Looking still at the template, above this listing, we have an input element whose value will be bound to the scope using the ng-model directive. But using the ng-change directive we call a function on the scope (doSearch()) every time a user types or removes something in that textfield. This function is defined inside the controller and is responsible for performing a search on our endpoint with the param the user has been typing in the textfield (the search variable). As the search is being performed, the results populate the template automatically.
  3. Lastly, for the the bonus part, we define the open() method which takes a node id as argument and requests the node from our endpoint. Pressing the button, this callback function opens the dialog that uses a template defined inside of the <script> tag with the id of loadedNodeTemplate and passes to it the current scope of the controller. And if we turn to the template file, we see that the dialog template simply outputs the title and the body of the node.

Conclusion

You can see for yourself the amount of code we wrote to accomplish this neat functionality. Most of it is actually boilerplate. A very fast node query block that delivers results asynchronously with all of its benefits. And if you know Angular.js, you can imagine the possibility of enhancing the Drupal experience further.

Now, are you interested to learn more about the love between Angular.js and Drupal? Would you have done anything differently? Let us know in the comments below!

Nov 10 2014
Nov 10

atrium-logo (1) The recent 2.23 version of Open Atrium contains a cool new interactive site builder and navigator application (see it in action). This application was written using the AngularJS framework.  The combination of Drupal, jQuery, and AngularJS proved to be powerful, but wasn’t without some pitfalls.

Using AngularJS in Drupal

AngularJS-large The basics of using Angular within Drupal is pretty straight-forward.  Simply reference the external AngularJS scripts using the drupal_add_js() function, then add your custom javascript app code, then use a tpl template to generate the markup including the normal Angular tags.  For example, here is the Drupal module code, javascript and template for a simple Angular app:

Drupal myapp.module code PHP // Implements hook_menu() function myapp_menu() { $items['myapp'] = array( 'page callback' => 'myapp_menu_callback', 'access callback' => TRUE, ); return $items; } // The menu callback to display the page function myapp_menu_callback() { drupal_add_js('https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js'); $base = drupal_get_path('module', 'myapp'); // data you want to pass to the app drupal_add_js(array( 'myapp' => array( 'title' => t('Hello World'), ), ), 'setting'); drupal_add_js($base . '/myapp.js'); drupal_add_css($base . '/myapp.css'); return theme('myapp', array()); } // Implements hook_theme(). function myapp_theme() { return array( 'myapp' => array( 'template' => 'myapp', ), ); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 // Implements hook_menu()functionmyapp_menu(){  $items['myapp']=array(    'page callback'=>'myapp_menu_callback',    'access callback'=>TRUE,  );  return$items;}   // The menu callback to display the pagefunctionmyapp_menu_callback(){  drupal_add_js('https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0/angular.min.js');     $base=drupal_get_path('module','myapp');     // data you want to pass to the app  drupal_add_js(array(    'myapp'=>array(      'title'=>t('Hello World'),    ),  ),'setting');     drupal_add_js($base.'/myapp.js');  drupal_add_css($base.'/myapp.css');     returntheme('myapp',array());}   // Implements hook_theme().functionmyapp_theme(){  returnarray(    'myapp'=>array(      'template'=>'myapp',    ),  );}
myapp.js script JavaScript (function ($) { var app = angular.module("myApp", []); app.controller("myAppController", function($scope) { $scope.settings = Drupal.settings.myapp; $scope.count = 1; $scope.updateCount = function(value) { $scope.count = $scope.count + value; } $scope.myClass = function() { return "myclass-" + $scope.count; } }) }(jQuery)); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (function($){  varapp=angular.module("myApp",[]);  app.controller("myAppController",function($scope){    $scope.settings=Drupal.settings.myapp;    $scope.count=1;       $scope.updateCount=function(value){      $scope.count=$scope.count+value;    }       $scope.myClass=function(){      return"myclass-"+$scope.count;    }  })}(jQuery));
myapp.tpl.php template

{{settings.title}}

Click to increment {{count}}

1 2 3 4 5 6 7 div class="myapp" ng-app="myApp" ng-controller="myAppController">  h3 ng-class="myClass()">{{settings.title}}/h3>  p>    a class="btn btn-default" ng-click="updateCount(1)">Click/a>      toincrement{{count}}  /p>/div>

Now, obviously we aren’t using the full Angular framework here.  We aren’t using any directives, nor are we really using Angular as a MVC framework.  But it gives you the idea of how easy it is to get started playing with basic Angular functionality.

Angular plus jQuery

Developing javascript applications in Angular requires a different mindset from normal Drupal and jQuery development.  In jQuery you are often manipulating the DOM directly, whereas Angular is a full framework that allows data to be bound and manipulated on page elements.  Trying to combine both is often a source of frustration unless you understand more about how Angular works behind the scenes. Specifically, Angular has it’s own execution loop causing a mix of Angular and jQuery code to not seem to execute in a straightforward order.  For example, in the above code, we set the class of the H3 based on the current “count” variable.  What if we modified the updateCount function to try and set a css property for this class:

JavaScript $scope.updateCount = function(value) { $scope.count = $scope.count + value; $('.' + $scope.myClass()).css('color', 'red'); } 1 2 3 4 $scope.updateCount=function(value){  $scope.count=$scope.count+value;  $('.'+$scope.myClass()).css('color','red');}

If you click the button you’ll notice that the css color does NOT change to red! The problem is that Angular is executing the query function call BEFORE it actually updates the page.  You need to delay the jQuery so it executes after the current Angular event loop is finished.  If you change the code to:

JavaScript $scope.updateCount = function(value) { $scope.count = $scope.count + value; setTimeout( function() { $('.' + $scope.myClass()).css('color', 'red'); }, 1); } 1 2 3 4 5 6 $scope.updateCount=function(value){  $scope.count=$scope.count+value;  setTimeout(function(){    $('.'+$scope.myClass()).css('color','red');  },1);}

then it will work.  The timeout value can be anything greater than zero.  It just needs to be something to take the jQuery execution outside the Angular loop. Now, that was a horrid example!  You would never actually manipulate the css and class properties like this in a real application.  But it was a simple way to demonstrate some of the possible pitfalls waiting to trap you when mixing jQuery with Angular.

Drupal Behaviors

When doing javascript the “Drupal way”, you typically create a behavior “attach” handler.  Drupal executes all of the behaviors when the page is updated, passing the context of what part of the page has changed.  For example, in an Ajax update, the DOM that was updated by Ajax is passed as the context to all attached behavior functions. Angular doesn’t know anything about these behaviors.  When Angular updates something on the page, the behaviors are never called.  If you need something updated from a Drupal behavior, you need to call Drupal.attachBehaviors() directly.

Angular with CTools modals

In the Open Atrium site map, we have buttons for adding a New Space or New Section.  These are links to the Open Atrium Wizard module which wraps the normal Drupal node/add form into a CTools modal popup and groups the fields into “steps” that can be shown within vertical tabs.  This is used to provide a simpler content creation wizard for new users who don’t need to see the full node/all form, and yet still allows all modules that hook into this form via form_alters to work as expected. The tricky part of this is that as you navigate through the sitemap, Angular is updating the URLs of these “New” links.  But CTools creates a Drupal Ajax object for each link with the “ctools-use-modal” class in it’s Drupal behavior javascript.  This causes the URL of the first link to be cached.  When Angular updates the page and changes the link URLs, this Ajax object cache is not updated. To solve this within the Open Atrium Sitemap app, an event is called when the page is updated, and we update the cached Ajax object directly via the Drupal.ajax array. This was a rather kludgy way to handle it.  Ultimately it would be better to create a true Angular “Directive” that encapsulates the CTools modal requirements in a way that is more reusable.

Summary

Angular can be a very useful framework for building highly interactive front-ends.  Using Drupal as the backend is relatively straight-forward.  Angular allowed us to create a very cool and intuitive interface for navigating and creating content quickly within Open Atrium far easier than it would have been in jQuery alone.  In fact, we began the interactive site map tool in jQuery and the code quickly became unmanageable.  Adding functionality such as drag/drop for rearranging your spaces and sections would have been a mess in jQuery.  In Angular it was very straight-forward. Once you understand how Angular works, you’ll be able to blend the best of Drupal + jQuery + Angular into very rich interfaces.  Programming in Angular is very different.  Learn more about Angular on the Phase2 blog!

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web