Ignored By Dinosaurs 🦕

I screwed this one up pretty bad when I first got started with Fastly. That have that link that says “VCL”, as if you should download that to get started. So I did. But that's the generated VCL, and all their special macros are already blown up, so you don't want to copy that one.

And the one on their website has HTML entities encoded, so I'm putting this one here in case I need it again later.

The original

sub vcl_recv {
#FASTLY recv

  if (req.request != "HEAD" && req.request != "GET" && req.request != "FASTLYPURGE") {
    return(pass);
  }

  return(lookup);
  }

sub vcl_fetch {
#FASTLY fetch

 if ((beresp.status == 500 || beresp.status == 503) && req.restarts < 1 && (req.request == "GET" || req.request == "HEAD")) {
 restart;
 }

 if(req.restarts > 0 ) {
 set beresp.http.Fastly-Restarts = req.restarts;
 }

 if (beresp.http.Set-Cookie) {
 set req.http.Fastly-Cachetype = "SETCOOKIE";
 return (pass);
 }

 if (beresp.http.Cache-Control ~ "private") {
 set req.http.Fastly-Cachetype = "PRIVATE";
 return (pass);
 }

 if (beresp.status == 500 || beresp.status == 503) {
 set req.http.Fastly-Cachetype = "ERROR";
 set beresp.ttl = 1s;
 set beresp.grace = 5s;
 return (deliver);
 }

 if (beresp.http.Expires || beresp.http.Surrogate-Control ~ "max-age" || beresp.http.Cache-Control ~"(s-maxage|max-age)") {
 # keep the ttl here
 } else {
 # apply the default ttl
 set beresp.ttl = 3600s;
 }

 return(deliver);
}

sub vcl_hit {
#FASTLY hit

 if (!obj.cacheable) {
 return(pass);
 }
 return(deliver);
}

sub vcl_miss {
#FASTLY miss
 return(fetch);
}

sub vcl_deliver {
#FASTLY deliver
 return(deliver);
}

sub vcl_error {
#FASTLY error
}

sub vcl_pass {
#FASTLY pass
}

#varnish

So tracking pixels. They sound awful. They sort of are, but we all use them. One just fired off on you a minute ago when you loaded this page. That's how Google Analytics works its magic. But how do they work? The GA tracking code is Javascript and doesn't say anything about an image pixel.

Step inside...


Dat JS

So that javascript does a few things, primarily it creates another javascript tag that pulls down the real “payload”, which is a hackerish term for “a bigger ball of code”. I haven't analyzed that code yet, but one of the things that it does is build a profile of your browser that you're on and the page that you're looking at. Once it does that it pings GA's tracking servers with that profile which counts as a “pageview”. That's the ga('send', 'pageview') bit. But how does that work?

A tracking pixel!

Placement in the DOM, you need not...

So a pretty interesting thing about tracking pixels, and anything in the browser really is that it doesn't actually need to be put on the page to exist in memory somewhere. In fact, even if that pixel is only 1x1 in size, it could bump something out of the way enough to trigger a repaint of the webpage, which might alert you to that pixel's existence, which is something that advertisers and their ilk stringently avoid.

So basically, that ga('send', 'pageview') ends up generating a request to a server somewhere. That request looks like this

https://www.google-analytics.com/collect?v=1&_v=j29&a=806595983&t=pageview&_s=1&dl=http%3A%2F%2Fwww.ignoredbydinosaurs.com%2F2014%2F09%2Fdeconstructing-the-google-analytics-tag&ul=en-us&de=UTF-8&dt=Deconstructing%20the%20Google%20Analytics%20tag%20%7C%20Ignored%20by%20Dinosaurs&sd=24-bit&sr=1440x900&vp=1334x479&je=1&fl=15.0%20r0&_u=MACAAAQAI~&jid=&cid=1626931523.1412365384&tid=UA-8646459-1&z=962163205

In the network tab of your devTools in your favorite browser you can break down all those query string params into something a little more interesting.

v:1
_v:j29
a:806595983
t:pageview
_s:1
dl:http://www.ignoredbydinosaurs.com/2014/09/deconstructing-the-google-analytics-tag
ul:en-us
de:UTF-8
dt:Deconstructing the Google Analytics tag | Ignored by Dinosaurs
sd:24-bit
sr:1440x900
vp:1334x479
je:1
fl:15.0 r0
_u:MACAAAQAI~
jid:
cid:1626931523.1412365384
tid:UA-8646459-1
z:962163205

Some of that stuff is understandable, some of it is not. But the point is that that request actually trigger a response of a 1x1 pixel.

# Response headers

access-control-allow-origin:\*
age:66666
alternate-protocol:443:quic,p=0.01
cache-control:private, no-cache, no-cache=Set-Cookie, proxy-revalidate
content-length:35
content-type:image/gif
date:Fri, 03 Oct 2014 18:23:52 GMT
expires:Mon, 07 Aug 1995 23:30:00 GMT
last-modified:Sun, 17 May 1998 03:00:00 GMT
pragma:no-cache
server:Golfe2
status:200 OK
version:HTTP/1.1
x-content-type-options:nosniff

If this were the first time I'd visited the internet, there would almost certainly be a set-cookie header in there as well, but since they set that cookie on me a LONG time ago, it doesn't get sent.

The kinda creepy thing is that since Google Analytics is on a large number of sites, and their origin servers are on the same domain (cookies), they can follow you around the internet from site to site to site in a way that nobody else can (save perhaps the other giant analytics providers, which probably have nowhere near the reach, unless you count Facebook).


Wow, cool. So what?

So that image pixel is not really the point. It gets returned in that response, but doesn't get put on the page. It plants a cookie on you, big deal.

But what happens at Google is that the request that was made in the first place gets logged. It gets broken down by its query string params, and that's how they build the tool. That's how you know what size browser people are on, what part of the world they're from, what they looked at, and what they clicked on (if you're tracking events).

The really interesting part to me, and the part I haven't figured out yet, is how they store ALL that data on the backend to build the reports out of. Think about it — they're basically logging every request made to every website that is running their code. That's a really big number, even for Google. And then they're able to pull your report suite out of all that data, and sort it out by whatever you wanna know. Seems pretty cool, and also well beyond the capability of normal relational DBs.

A post in the future, I imagine....

#javascript #analytics

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

On Tue, Sep 9, 2014, at 10:58 AM, San wrote:

Dear business owner of Ignoredbydinosaurs.com,

I would like to take a few minutes from your schedule and ask for your attention towards Internet marketing for Ignoredbydinosaurs.com.

As a business Owner you might be interested to gain profit by placing your website among top in search engines. Your website needs immediate improvement for some major issues with your website.

  • Low online presence for many competitive keyword phrases
  • Unorganized social media accounts
  • Not compatible with all mobile devices
  • Many bad back links to your website

Looking at the above issues and other additional improvements for your website, I would request you to give us a chance to fix those issues. Our team of Search Engine and Social Media experts are here to serve you with best inputs. If you are interested in learning more about current status of your website, we would be glad to share WEBSITE AUDIT REPORT of Ignoredbydinosaurs.com for FREE.

You will feel the difference once you get services from our company as we never let our clients expectations go down. Being at the top left of Google (#1- #3 organic positions) is the best thing you can do for your company's website traffic and online reputation. You will be happy to know that, my team is willing to guarantee you 1st page Google ranking for your targeted local keyword phrases.

If my proposal sound's interesting for your business goal, feel free to email us, or can provide me with your phone number and the best time to call you. I am also available for an online meeting to present you this website audit report.

Best Regards,

Jessica

Marketing Consultant

PS I: I am not spamming. I have studied your website and believe I can help with your business promotion. If you still want us to not contact you, you can ignore this email or ask to remove and I will not contact again.

PS II: I found your site using Google search and after having a look over your website I recommend you to implement future technologies such as HTML5 and Responsive Design to make your site more accessible in mobile phone, tablets, desktop etc.

Dear whatever your name is,

I promise I'm better at all aspects of what you're trying to sell me than anyone in your entire “company”.

Thank you, John Grubb

#life

If you're a web developer, I'm sure you've placed this snippet of code more into more than a few projects.

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1\*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', 'UA-XXXX-Y', 'auto');
ga('send', 'pageview');

Let's unpack it a little bit -

(function(i, s, o, g, r, a, m) {
 i['GoogleAnalyticsObject'] = r;
 i[r] = i[r] || function() {
 (i[r].q = i[r].q || []).push(arguments)
 }, i[r].l = 1 \* new Date();
 a = s.createElement(o),
 m = s.getElementsByTagName(o)[0];
 a.async = 1;
 a.src = g;
 m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

Now let's make those local variables a little more clear -

(function() {
	var a, m;
	window['GoogleAnalyticsObject'] = 'ga';
	window['ga'] = window['ga'] || function() {
	(window['ga'].q = window['ga'].q || []).push(arguments)
	}, window['ga'].l = 1 \* new Date();
	a = document.createElement('script'),
	m = document.getElementsByTagName('script')[0];
	a.async = 1;
	a.src = '//www.google-analytics.com/analytics.js';
	m.parentNode.insertBefore(a, m)
})()

So if you go to your javascript console and type “GoogleAnalyticsObject”, you'll get back the string “ga”. window.ga is a function, but since functions in javascript are also objects, it has a property called q, which is just an array. This is reminiscent of the old ga.js syntax which went something like this —

var _gaq = _gaq || [];
 _gaq.push(['_setAccount', 'UA-XXXX-Y']);
 _gaq.push(['_trackPageview']);

 (function() {
 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
 })();

_gaq is/was just a plain old Javascript array, which gives it the push() method for free. This new ga.q property serves the exact same purpose, an array to push things into and wait for something to come along later and pop them off. That something that comes along later is whatever is contained in that async script that this snippet also builds.

This is super clever because it doesn't have to wait for anything, it can go ahead and do all its business the instant the page loads and even if the main tracking script doesn't come down for some reason, nothing breaks.

Back to analytics.js...

Whatever you hand as arguments to ga() gets fed into ga.q right here —

window['ga'] = window['ga'] || function() {
	(window['ga'].q = window['ga'].q || []).push(arguments)
}

If you pop open the console on the front page of this blog, and type in ga.q, you'll get this —

> ga.q
[
Arguments[3]
0: "create"
1: "UA-8646459-1"
2: "ignoredbydinosaurs.com"
callee: function (){
length: 3
__proto__: Object
, 
Arguments[2]
0: "send"
1: "pageview"
callee: function (){
length: 2
__proto__: Object
]

Those are stashed in the queue because as soon as that first bit of code is parsed out, there are two quick calls to ga(), and that's exactly what they have as their arguments. It's so simple, it's almost stupid to explain, but the script is so heavily optimized it's not at all obvious on first glance what's going on here.

Moving on, there's another property of the ga function/object – ga.l. ga.l gets initialized to a javascript timestamp (in milliseconds). new Date() returns a javscript Date object, but multiplying it by the integer 1 casts it into a number, which automatically converts it into the number of milliseconds since the epoch. Another way of writing this would be +new Date() – another, albeit less clear way, of performing the same casting to a timestamp. ga.l's purpose is to provide a time for the initial “create” and “pageview” calls to ga().

Lastly, an asynchronous javascript tag is written to make the call to fetch the ga.js script from Google's servers so the real magic can start.

Another interesting bit is that the a and m parameters are not assigned anything at the IIFE call at the end. This leaves them as undefined in the script until they are assigned the script tags toward the end of this snippet. Another way of writing the exact same thing would be to only have (i,s,o,g,r) as parameters to the function, and then declaring var a, m; somewhere in this snippet. I'm not sure off hand if this is a memory or performance optimization or if it's just a handy way to save a couple bytes over the network, but someday I'll figure it out.


Thanks for sticking with me – this is one of the most common little snippets that I've probably placed in my web development career, and I'd never totally dug in to understand what exactly it does beyond writing an async script tag. The pattern of declaring a “plain old javascript array” and then pushing your “stuff” into it as a queue for working with later is an extremely common pattern in 3rd party javascript, since you want everything to be as performant as possible, and you want to make sure you don't break anything if for some reason the rest of your payload script doesn't actually load.

#javascript #analytics

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

For some reason I got it into my head the other day to tinker around with MongoDB and Drupal, masochism I guess. Anyway, I finally had the opportunity to start tinkering with it last night.

The set up was fairly easy – if you're on a Mac with Homebrew just hit up the josegonzalez builds of PHP5.5 and then brew install php55-mongo or something like that. Just brew search php55-mongo before you go blindly copying that command in.

Making the connection between Drupal and Mongo was also pretty straightforward, just follow the instructions on this post.

After that I was ready to migrate some content, so I did the old drush | grep mongo to see a list that looked like this —

All commands in mongodb: (mongodb)
  mongodb-cli (mdbc) Open a mongodb command-line interface using Drupal's credentials.
  mongodb-conf Print mongodb connection details using print_r().
  mongodb-connect A string for connecting to the mongodb.
  mongodb-query (mdbq) Execute a query against the site mongodb.
Other commands: (adaptivetheme,archive,browse,cache,coder_review,topic,features_plumber,apachesolr_site,redirect,image,libraries,make,mongodb_migrate,nodequeue_generate,print_pdf,queue,rules_scheduler,runserver,search,shellalias,sitealias,ssh,acquia_search,acquia_spi,strongarm,test,usage,uuid,variable,views_bulk_operations,views_data_export,watchdog,xmlsitemap)
  mongodb-migrate Migrates fields. Run mongodb-field-update first.
  mongodb-migrate-prep Prepare for migrate. Resets existing migration. No data is lost.

So, cool! Only problem is I repeatedly got this back —-

The drush command 'mongodb-migrate-prep' could not be found. Run 'drush cache-clear drush' to clear the commandfile cache if you have installed new extensions.

Over and over, clearing drush cache over and over, until I finally figured to look in the drush files that came with the Mongo module. The trick is that Chx actually meant to say that command is called drush mongodb-migrate-prepare instead of just “prep”.

It's not that reassuring about the experience that lay ahead of me that an error this simple and fixable is lying there unpatched, apparently after 2 years worth of development, since I first tried the green “official” release before going -dev on it. I suppose I'll be submitting a patch for that.

I have another blog post about the migration tribulations, but I eventually got through it and it's kinda cool. Instead of a giant throbbing schema full of field_data_this_and_that, all you have is —-

> show collections
cache
cache_bootstrap
fields_current.file
fields_current.node
fields_current.taxonomy_term
fields_current.user
fields_revision.node
migrate.file
migrate.node
migrate.taxonomy_term
migrate.user
queue.feeds_importer_expire
queue.feeds_push_unsubscribe
queue.feeds_source_clear
queue.feeds_source_import
queue.file_entity_type_determine
queue.print_mail_send
queue.views_bulk_operations
session
system.indexes

Each node document in that collection has every field that it needs to have, right on the node! Win! Now to figure out what to do with it!!

#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

Enlightenment is realizing that the straight job with the commute and the boss actually provides more of what you were seeking from the experience of being a professional grade musician than being in a professional grade band.

For the first time ever I'm part of a team with stated goals and long term plans laid out in service of attaining those goals. It's an amazing feeling that I wouldn't have had I not given the band thing the commitment that I did.

So thank you, Railroad Earth, for letting me have the experience that gives me the perspective that I have now.

And thank you, ABM teammates. Looking forward to it.

#life

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