Using AngularJS, Tabletop and Google Sheets to extend signup pages and user tracking on NationBuilder

When we implemented our action program at Climate for Change, we needed to create a system to support it on our website that was relatively easy to administrate and grow, and that allowed us insight into user behaviour.

Essentially what was needed was a bunch of signup pages, one per action that the user could take, but they would need some custom details to support all the dynamic generation of content and tagging of users.

NationBuilder doesn’t support custom fields for pages, but Angular, Tabletop and Google Sheets allowed me to quickly roll my own.

The example below is so simple you could well question the effort involved to build it, but of course, you probably don’t want something that simple, and once the foundation is in place, it really pays dividends as you expanded functionality. Creating menu systems, seasonal actions, guides and reminders were all possible by adding new sheets and some simple functions to our factory to serve that up to the page.

Administratively, it’s really simple for anyone to make changes since they just edit the Google Sheet.

Full Source Code

Full code for this example is available at GitHub, or you can preview and step through a demo.

Before we go any further, a couple of things you should be aware of:

Note: If you’re planning to use this in anything that needs to take a large number of requests, you should read this post on why you shouldn’t rely on a Google Spreadsheet for production and follow one of the workarounds suggested in the comments.

Note: Your spreadsheet has to be published publicly for the app to use it, so everything in your spreadsheet is potentially available to anyone who wants to find it, so don’t go putting anything that should be private or secret in there.

Configure Tabletop

Tabletop provides a really simple, intuitive access to configuration stored in Google Sheets. Each sheet is an array of rows in the sheet. Each row is an object where the headings in the top row are used as the keys for each row object.

Angular Tabletop wraps that up in a neat angular factory that ensures you only load the spreadsheet once no matter how many times it’s referenced in your code.

Configuring them is a breeze. Download the source files for both, put them in your theme folder, and add them to the bottom of your layout.html

While you’re there, let’s add some other things we’ll need: load our application javascripts, and a file for configuration settings.

And we’ll also add the ng-app directive to the top of layout.html

Then configure the provider in your app.js

The URL for the spreadsheet is basically the URL you’d use to access it, plus /pubhtml Rather than hardcode the spreadsheet URL, I’ll put it into a global variable config which we’ll define in config.html.

Before we can do that though, we’ll need to setup the spreadsheet, so go ahead and setup your own spreadsheet. For this demo, I’m going to give each action a name, description, slug for it’s signup page, and tags for when the user starts and finishes.

Once you’ve created the spreadsheet, you’ll need to publish it (File menu -> publish) so that the Javascript will be able to access it for any visitor to your site.

Once you publish it, you can take the URL of your spreadsheet and put it in your config.html.

Why not create a config.js instead? By including it as an HTML file using Liquid include, you get access to all the variables that Liquid offers. For example, in our system, we get the list of users tags, and use that to highlight the actions that the user hasn’t already done.

So now, you’ve got Angular setup and a provider that knows where to find the spreadsheet. The provider creates a promise to load the spreadsheet, that we can use to run our code once the spreadsheet has been loaded.

Let’s create a quick controller to load the spreadsheet and dump some fields to the console to check that everything is working as expected.

Create a simple element somewhere on your page that loads the controller

If all is going well, open up a page, and open up the Javascript console and you should see something like this:

Creating an actions factory

Accessing tabletop directly in your controller is ok for simple access, but for anything complicated, you’re better off wrapping it in a factory with some helpers, it will make your code a lot more readable and quicker to build upon.

We’ll need to wrap it in our own promise that resolves once Tabletop has loaded the spreadsheet.

Here’s a simple factory that returns an object that exposes just one method: allActions

(If you’re wondering about the layout of the file, it’s based on the angular style guide)

You’ll notice I like to log the URL of the Google Sheet, it makes debugging a little easier for those that are fresh to the code and wondering where the data’s coming from.

Add the factory to your layout.html

Let’s change our controller to make use of the factory – we’ll need to update the controller definition to use our new factory instead of Tabletop, and instead of putting the actions to debug logging, let’s put them on the $scope where they can be more useful.

Now we can create a signup page that uses the actions. We’ll give it the slug actions, and override it’s theme (not it’s intro) to put the actions in a table.

The last column of the table we’ll create a new directive to create the code for the sign up button.

Let’s use the techniques from the previous posts on dynamic tags and destinations to tag the user and send them to the desired page.

So we’ll add a directive definition and controller for our button to make all of those things happen in actions_controller.js. The controller will handle updating the destination page_id, while the template will include a line for tagging the user.

Don’t forget to add jQuery to your layout, as the selector used is too complicated for Angular’s built in version of jQuery.

To map page ids to slugs, we’ll need to add some Liquid to config.html to create that map.

And lastly a template for the button is needed. We’ll embed that in actions.html as that’s the only place it’s needed.

Note the second hidden input, signup_email. Normally on a signup page you would ask for people’s details. If the user is logged in, then the button will work just fine without this, but if the user is not logged in, then the submit will fail. We can work around this by forcing the use of the sorta_logged_in email with a hidden input. For users that are totally logged out, we created a custom button that would popup a sign in form, instead of submitting the signup.

If all is working well, you should see a table with the name, description and signup button for your actions.

Inspect the source code to check that your signup button has the correct page_id assigned.

There you have it, dynamic signup actions based on a Google Spreadsheet.