Ignored By Dinosaurs 🦕

angular

So it took me days of trying to piece together the correct config options to make my local protractor test suite run in a bunch of different setups on Sauce Labs.

// An example configuration file.
exports.config = {

	sauceUser: 'jgrubb',
	sauceKey: 'fill-in-the-blank',
	sauceSeleniumAddress: 'localhost:4445/wd/hub',
	
	// Capabilities to be passed to the webdriver instance.
	// This option is called "capabilities" in the protractor docs
	// but whatever. this also works.
	multiCapabilities: [{
	// by default, these first two browsers will come up in 
	// Linux if you don't specify an OS
	'name': 'Chrome',
	'browserName': 'chrome'
	}, {
	'name': 'Firefox',
	'browserName': 'firefox'
	}, {
	'name': 'Win XP/IE8',
	'os': 'Windows XP',
	'browserName': 'internet explorer',
	'version': '8.0'
	}, {
	'name': 'Win7/IE8',
	'os': 'Windows 7',
	'browserName': 'internet explorer',
	'version': '8.0'
	}, {
	'name': 'Win7/IE9',
	'os': 'Windows 7',
	'browserName': 'internet explorer',
	'version': '9.0'
	}, {
	'name': 'Win8/IE10',
	'os': 'Windows 8',
	'browserName': 'internet explorer',
	'version': '10.0'
	}, {
	'name': 'Win8.1/IE11',
	'os': 'Windows 8.1',
	'browserName': 'internet explorer',
	'version': '11.0'
	}],
	
	// Spec patterns are relative to the current working directly when
	// protractor is called.
	specs: ['e2e/**/\*_spec.js'],
	
	// Options to be passed to Jasmine-node.
	jasmineNodeOpts: {
	showColors: true,
	defaultTimeoutInterval: 30000
	},

};

I burned tons of time just trying to figure out the correct names for the options, can't find where those are documented. This page – https://docs.saucelabs.com/reference/platforms-configurator/#/ turned out to be hugely helpful.

you probably figured this out already, but the Sauce Connect thing is awesome, and really easy to set up – https://docs.saucelabs.com/reference/sauce-connect/

#testing #angular

Hello there! There has been a lot of discussion in the Drupalsphere lately about a concept that has been coined “Headless Drupal”, and rightly so. It's basically theming, but throwing out the theme layer. The theme layer in Drupal is very powerful, but has always felt severely over-engineered to me, especially in contrast to pretty much any MVC framework I've played with. With those, you define view-layer variables in a controller class, and you write HTML with a little bit of code to print them out into HTML. It's more work up front, since you have to write code, but it's vastly easier once you get over that hump.

The company I work for has been doing exactly this with AngularJS since early this year, and I've yet to see a concise post about how to get started with it in the context of Drupal. I rarely write “how-to” posts, but I figured it'd be a good way to inaugurate the ABM tech blog.

Our use case (feel free to skip)

Early this calendar year, my boss' boss came to us with a business request — build us a framework on which we can make our up-until-lately desktop-only websites a little more mobile friendly. We were using a rather ungainly combination of Panels and Adaptivetheme, and though those should have given us a good base on which to build, we had managed to mess it up.

Our original themer was a designer who learned CSS on the job, and our stylesheets were an enormous mess. Absolute positioning, widths specified in pixels, subthemes that had huge amounts of repetitive CSS rules that could've been rolled up into the parent theme. Rewriting these sheets would've been prohibitively expensive, and wouldn't have gained us anything in the eyes of the business.

To add to that, the aforementioned boss' boss was really keen on what we in the biz call “HTML5 mobile apps” that felt more like a native app rather than just a website that is readable on the phone. UI patterns would include swiping to navigate between stories, storing articles to read later, offline support, etc. Basically, not things you can do in any Drupal theme that I know of.

I spent a few days in R&D mode trying to figure out how to fake these things with pre-fetching pages so they'd be rendered already when the user swiped, but it was a mess.

I knew in the back of my head that I was doing it the wrong way, but it took some prototyping to convince myself that what we indeed needed to do was to throw out the book on Drupal theming and do this another way.

I love writing Javascript, and I'd finally found a use case for which one of these JS MV* frameworks might actually fit the bill.

The Drupal setup

So, assuming you have a clean Drupal install spun up already (if you don't, may I suggest drush core-quick-drupal?), you'll want to download a couple of modules to get going.

  • Views (obviously! don't forget the ctools dependency!)
  • Views Datasource (this lets you spit out views as JSON).
  • Devel (for generating some content if you don't already have some)
  • CORS (just trust me, I'll get to this one later)

Or you can just do a drush dl views ctools views_datasource cors devel and be done with it.

Enable all these modules – drush en views views_ui devel devel_generate cors views_json -y.

Generate some content – drush generate-content 50 0 --types=article

Ok, you're ready to hop into the UI!


Pretty much all the action at this point is going to happen in Views, so navigate to admin/structure/views, and “Add new view”.

  1. Name it whatever you want, may I suggest “json articles”?
  2. You want to show “Content” of type “Articles” sorted by “Newest first”.
  3. Create a page, yes. Display format will be “JSON Data Document”. Continue and edit. Screenshot of these settings.
  4. Just add a few fields, since you only have the title so far. Add the Body, the nid, and the date in whatever format you please.
  5. You do want to create a label, but you'll be better off customizing it to be all lowercase and to have no spaces. ie. body, post_date, etc. What you should see at this point

In the preview, you should see a pretty-printed json list of your 10 most recent articles, as generated by Devel.


Congrats! The Drupal part is done! We'll be visiting the more interesting part in the next post.

#drupal #angular #javascript

Exposition

One of these days I'll get around to writing the post that's been in my head for 6 months now about how to get up and running with Angular in a Drupal setting. Today is not that day, however.

What I'd like to talk about today is a little hack that I came up with to allow me/us to maintain a single codebase to serve several different mobile apps.

Multisite Drupal

You're likely already familiar with this concept, and it has its proponents and its detractors. The second (anti) blog post has some good points — it is a “hack” and you can paint yourself into a corner if your feature set starts to diverge between your “sites”. Depending upon your business case however, it can be extremely useful.

We use this at my day job to generally good effect. Someday there should be a blog post about the pitfalls of Features and why its siren song leads so many Drupal developers to crash their ships upon the rocky shores of circular dependencies and conflicted configurations, but this is not that post either.

Multisite Angular (to the point)

So how do you do this with Angular? In an Angular setup, you probably don't have any “moving pieces”, ie – this thing is just HTML, JS, and CSS, talking to a server endpoint somewhere. That's cool! But it also means you don't really have the luxury of inspecting requests and setting environment variables on the server (like Drupal does it) to serve multisite. It has to happen in the browser. So what can you look at in the browser to set configuration for your multisite setup?

The URL, of course!

Angular “config” service

I'll just drop the code.

'use strict';

angular.module('mobileApp')
 .factory('Config', ['$window', function($window) {
 // Set up whatever variables you need here
 var test = {
 endpoint: 'http://testing-endpoint.com',
 randomVar: 'foo'
 };

 var prod = {
 endpoint: 'http://production-endpoint.com',
 randomVar: 'bar',
 otherVar: 'baz'
 };

 // A pointer object, basically. Keeps things
 // a little more organized
 var configs = {
 // production config
 'production-mobileapp.com': prod,
 // test config
 'testing-mobileapp.com': test,
 // dev config
 'localhost': test
 }

 return {
 fetch: function() {
 // 'configs' object returns whatever it's holding in the 
 // property with the key of 'window.location.hostname', 
 // which in this case is our config for this "site"
 var config = configs[$window.location.hostname];
 
 // Maybe you have some special snowflakes still
 // This can help you keep the divergence in check
 config.otherVar = config.otherVar || config.randomVar;
 

 return config;
 }
 }

 }]);

In the module that needs to know this stuff, you just pass in Config as a dependency and call fetch() on it.

var siteVars = Config.fetch();

We use this setup to specify, for instance, the path to site specific CSS, or site specific DFP ad tag configuration.

// In mainController.js, or wherever it makes sense

var stylePath = "/sites/" + siteVars.randomVar + "/styles.css"

And then that gets referenced in the head of the doc


A hack? Yes. I've built two fairly large apps with this approach and have yet to paint myself into a corner though, so it's a fairly useful and rather robust hack, IMHO.

#angular #drupal

Nginx configs

So I recently had a couple of seemingly disparate tasks come across my desk. We recently launched a HMTL mobile app, an Angular front end to our Drupal sites. We decided to completely decouple the mobile app from the Drupal codebase after a fairly long exploratory period trying out different approaches.

When launch day finally came, we set up the mobile app at app.$$BRAND.com, with our main sites at www.$$BRAND.com. Acquia has this cool feature in their Varnish layer that will filter user-agent strings for ones that match a set of “mobile” user agents that they have defined in a central file. So flipping folks on iPhones over to the mobile site was a piece of cake. What I forgot was that the same logic wouldn't be triggered for the reverse — flipping desktop users to the desktop version of the site from app.$BRAND.com. (Our mobile app is hosted on our own, not with Acquia).

I already had a big list of regexes to test mobile user agent strings with (thanks Acquia!), so the trick was to recreate that in Nginx, the webserver for our mobile app.

Not wanting to do a bunch of evil if {} statements in the Nginx configs, I cast about for a more elegant solution, eventually stumbling upon map.

The Nginx map module

http://nginx.org/en/docs/http/ngxhttpmap_module.html.

So basically what this does is to test any arbitrary Nginx variable for some condition, and spit out a custom variable that you can user in your config. An example —-

map $http_user_agent $device_redirect {
  default "desktop";
  ~(?i)ip(hone|od) "mobile";
  ~(?i)android.\*(mobile|mini) "mobile";
  ~Mobile.+Firefox "mobile";
  ~^HTC "mobile";
  ~Fennec "mobile";
  ~IEMobile "mobile";
  ~BB10 "mobile";
  ~SymbianOS.\*AppleWebKit "mobile";
  ~Opera\sMobi "mobile";
}

This takes a look at the incoming user agent string (fun fact — grab any request header with $http_NAME_OF_HEADER) and compares it against a set of regexes. If one of them is a match, then the $device_redirect variable gets set to “mobile”, otherwise, it's set to the default of “desktop”. This gets used later in the config —

if ($device_redirect = "desktop") {
  return 301 $scheme://$desktop_host$request_uri;
}

In other words, if the user agent doesn't match one of those regexes, redirect the user to the desktop site. Pretty neat!

As a side note, does anyone else think it's weird that the comparison syntax in that if statement only uses one '='? But yeah, that's the right way.

Later that day, on a different Angular app

So that mobile app and this one that I'm about to talk about kinda conform to the Drupal concept of “multisite”. That is, a single codebase that serves a variety of different sites. I figured out a pretty simple hack for this one that maybe I'll share in another blogpost if I get around to it, but basically it involves setting a siteVar in the bootstrapping phase of the Angular app based off of the window.location.hostname. I have a Config Angular service that stores the mapping of hostname to siteVar. It's easy and it works.

The way we serve different stylesheets to different brands is by setting up a sites/$BRAND/ folder that houses site specific styles, etc. When Angular bootstraps, it uses the siteVar variable to fill in $BRAND, and the site specific stuff is loaded. It's easy and it works. Except in Firefox.

Firefox doesn't like this setup, and particularly on the favicon, it proved to be a real PITA.

The default Yeoman favicon would always show up in Firefox, nothing else, since we had that favicon path set dynamically by Angular after the app loaded. it just wasn't responding to any of the usual hacks, and it turns out FF has a long and storied history with the favicon.

Having just found the perfect hammer for the previous problem, I thought I'd see if it could solve this one.

Map all the things...

So for this one, I have an Nginx map companion to the one that I have in Angular.

map $http_host $sitevar {
  default "";
  ~rdmag "rd";
  ~alnmag "aln";
  ~bioscience "bt";
  ~cedmagazine "ced";
  # etc...
}

This maps the incoming host variable on the request to a $sitevar variable, used like this...

location /favicon.ico {
  try_files /sites/$sitevar/favicon.ico $uri;
}

So the browsers that respect the dynamic favicon path continue to work, and if FF never cooperates, Nginx catches that request and fixes the problem before anybody knows...

#devops #generaldevelopment #angular

I spent about an hour pulling my hair over this one. I'm deploying an Angular to dev for the first time, it works fine locally, but everything is busted when I grunt build and push it up to a server. I'm using ngMin and using the supposedly safe syntax for defining all my dependencies, but unfortunately any Google search that includes “grunt build” and/or “minify angular” only turns up answer that pertain to that fairly well know problem.

So, I commented out Uglify in the build process and am still getting the error, only it's a lot easier to track down now, because my JS isn't minified. It blows up on the first one of my controllers that I wrote in Coffeescript, and is wrapped by Coffee's default function wrapper.

If this sounds like you, go to your Gruntfile and add an option to the coffeescript config —-

  coffee: {
    options: {
      sourceMap: true,
      sourceRoot: ''
    },
  }

becomes this —-

  coffee: {
    options: {
      sourceMap: true,
      sourceRoot: '',
      bare: true
    },
  }

Just make sure you're defining your scripts with (one of) the approved syntax(s) for keeping stuff out of the global scope -

angular.module('fooApp').controller('fooController', function(){
 // stuff here
});

#javascript #angular

The beginning

I'll make the “what is Angular” part as brief as possible.

Angular is a giant JavaScript framework from our friends at Google. It fits into a similar camp with Backbone, a framework that Drupalists will likely have heard of since it's included in D8 core. Angular aims to make it as easy as possible to build a single page CRUD app, and in my limited experience it succeeds.

I've never built anything with Backbone, but have the Peepcode series on it, and have been working heavily with Rails for a good while now. I'll avert a gush on Rails for the time being, but let's just say I really love the way that Rails doesn't really write any markup for you. It's much simpler to find your way through the request/response path, and in general I find developing with Rails to be a vastly more pleasant experience than developing with Drupal.

Alas, I've been a professional Drupal dev for about 4 years now.

The use case

I work at a publishing company. We publish about 26 different pubs, many of them still in print. Within the last year we finished a migration of all of our websites from various proprietary CMSs into a Drupal 7 multisite installation. The sites support desktop only at this point as we are a small company and resources are definitely constrained. (This also has it's upsides which we'll get to).

Last fall we rebuilt the company website from a static HTML site into Drupal 7. Since this was not a part of the multisite install, we were allowed to architect the site from scratch with my boss doing all the site building and myself doing all the theming. Mobile was a big priority this time, so I spent a good chunk of the development cycle implementing mobile-friendly behavior and presentation and generally getting a good feel for how to really do a responsive site. As an aside, for mobile/responsive site building and crafting responsive stylesheets, less is definitely more.

The end of this winter has brought a time table for offering a more accommodating mobile experience on our “brand sites”.

The dream

So my boss and his boss want a mobile site like “The financial Times has”. If you have an iOS device, go to app.ft.com, and if you're a Drupal developer, try and get your head around how you'd pull that off, but try and forget that this is a post/series about Angular first. Pretend that you were going to try and pull that off in a more or less responsive fashion.

I spent a couple of days surveying the landscape for JS libraries that help out with touch behavior, and trying to figure out how to prefetch content so that when the user swipes from page to page or section to section, there wouldn't be a giant delay while the network does its thing transferring 1,000 lbs. of panels-driven markup. This was Monday and Tuesday of last week.

A pause for reflection

My enthusiasm for the project already waning, I sat back and though about how we ought to be doing this thing. What they want is a mobile app, not a responsive website.

The way that you implement a mobile app is not by loading a page of HTML markup with every user action, it's by loading the app once and then communicating with the server via JSON (or XML or whatever if you wanna get pedantic). This kind of thing is supremely easy to do with Rails mainly due to Rails's deep embrace of Rest 6 years ago totally getting ahead of, perhaps even enabling, this whole javascript app revolution in which we find ourselves. Outputting a particular resource as JSON is as simple a line of extra code to allow the controller to respond to a request with a different format.

Step 1, Services

I'd never played with Services, so I didn't know how easy it was to output a node as JSON. On Wednesday of last week, some time after lunch, I decided to find out. Turned out we already had Services installed for another use case that we just recently implemented (offloading our Feeds module aggregation to another open source project called Mule, but that's a whole other series of posts), so all I had to do was bumble through a tutorial on how to set up a Node endpoint.

In less that 5 minutes I had exactly what I needed set up in terms of dumping a node to JSON. I've been reading Lullabot's recent posts about their use of Angular, so the rest of this series will follow along as I learn how to use this giant beast of a JS framework to build the mobile app my boss' boss wants without a minimum of Drupal hackery.

The next post will pick up Thursday morning (as in, 6 days ago) where I first downloaded Angular into the project.

#angular #javascript #drupal