Thursday, August 23, 2012

Using the Maps API Autocomplete for User Profile Location

As a frontend engineer at Coursera, one of my current projects is to create shared profiles for our users. When users first sign up, all we ask them for is their name, and so that's all we can show to other users as well — like when they see each other's posts in the forums. We want to make it so that users can click on a classmate's name and find out more about them, like where they're from and what other classes they're taking, because that should make a classroom of thousands of strangers feel more intimate. Eventually, we also want to make it easy for users to search their classmates to find people to form in-person, location-specific study groups or language-specific discussion forums, because that will give users a mini community inside the class.

By the time users sign up for Coursera, they've already signed up for many other sites and likely filled out a profile information many times, and I wanted to make their Coursera profile editing experience as quick and easy as possible. Easier said than done! It's amazing how many debates and decisions can go into designing a single form.


Location, Location

For example: location. We want to let users share where they are—not their exact address, that'd be creepy—just their city and country. In my first draft of profiles, I started off with a single line text input for location, with a placeholder for "City, State, Country". But, I wanted to be able to query our database and easily find out which users are from what country or state, so I needed a good way of turning user entered locations into structured data.

Halfway down the path of implementing it myself, I discovered that the Google Maps API now offers an Autocomplete API and Autocomplete widget, and that it was exactly what I needed. When you attach the widget to a text input, it listens to a user typing and pops up suggestions (like Google instant search), lets them pick a suggestion, and it can call back your code with structured information about the picked address.

The Code

To use it for our profile editor form, I first created HTML for the single-line text input and a set of hidden inputs for each piece of structured location data that I cared about. We actually write our HTML in bracket-less Jade templates, but here's what it comes out as:

<input name="location" type="text" placeholder="City, State, Country">
<input name="location_city" type="hidden">
<input name="location_state" type="hidden">
<input name="location_country" type="hidden">
<input name="location_lat" type="hidden">
<input name="location_lng" type="hidden">
<input name="location_id" type="hidden">

Then in the JavaScript, I attach the Autocomplete widget to the location input and add an event listener for every time the user selects a new result. In the callback, I populate the hidden inputs with the relevant structured data. The results format for the Maps API is a bit complex, because they designed it to work for any address anywhere in the world (and not every address is divided into the same city/state divisions as US addresses), so you end up having to search through all the "address components" to find the component that most matches what you're interested in. To simplify that search, I wrote a wrapper function using Underscore's find method. Here's what that looks like:

var autocomplete = new google.maps.places.Autocomplete($('#input[name="location"')[0], {types: ['geocode']});
  
google.maps.event.addListener(autocomplete, 'place_changed', function() {
    var place = autocomplete.getPlace();
    $('input[name="location_id"]').val(place.id);
    $('input[name="location_lat"]').val(place.geometry.location.lat());
    $('input[name="location_lng"]').val(place.geometry.location.lng());
    $('input[name="location_country"]').val(findComponent(place, 'country'));
    $('input[name="location_state"]').val(findComponent(place, 'administrative_area_level_1'));
    $('input[name="location_city"]').val(findComponent(place, 'administrative_area_level_3') || findComponent(place, 'locality'));
});

function findComponent(result, type) {
  var component = _.find(result.address_components, function(component) {
    return _.include(component.types, type);
  });
  return component && component.short_name;
}

All the code is in this gist.


The Result

We launched the first iteration of profiles today, so you can try it our yourself, in your settings. Or you can check out this screenshot of what it looks like to a user when they're editing their profile.

When a classmate is viewing their profile (like you can view mine here, they'll see their location plus a little flag (courtesy of this free flag icons database), and that flag is based on the hidden country field.


Caveats

There are a few things to keep in mind with this approach:

  • If the user types their location but does not select it from the autocomplete, then we will not store the structured data. I could add in a pre-submit reverse geocode step in case that happens, but I'll wait until I see what users do.
  • If the user types a location that the Google Maps API simply doesn't know, then we won't have any structured data about it. That's okay- we'll still display what they typed on their profile, we just won't be able to do any clever location-based recommendations for them.
  • The Autocomplete widget includes the Google logo, and per the terms, that cannot be removed. I am okay with admitting that it is courtesy of Google, though. I think it improves the user experience enough that it's okay to share branding with them.
  • If the Maps API ever shuts down the Autocomplete widget (Google is on a bit of an API-killing roll these days), we will have to re-write the functionality ourselves. That's okay, we'll still have the data from before. There's no harm in saving time now while we can.
  • Per the Maps API terms of use, you shouldn't be storing the Places data forever - so you should store the place ID and refresh your cache every so often. Or at least, that's how I interpret the terms. Do your own interpreting, or hire a lawyer. :-)

All in all, it's a nice touch in our profile experience that should hopefully make it faster and more fun. Thoughts?.

Tuesday, August 21, 2012

Geo-Targeted House Ads

At Coursera — like many companies in the valley these days — we're desperate to hire more people, particularly for our engineering department. But hiring is hard, and the reasons can be boiled down to these: 1) there aren't enough qualified candidates in the world, 2) the qualified candidates that do exist don't know that we exist, and 3) the qualified candidates don't want to work for us. Problem #1 isn't something we can quickly solve — though hopefully we are indirectly helping to solve it by offering so many CS courses for free. Problem #3 is also not easily solved — candidates may not like our technology stack, our location, or our culture — all of which are hard to change. So, in the short-term, we're focusing on Problem #2, making sure that candidates know we exist.

Well, I realized that we might actually have potential candidates amongst our Coursera users, and I wondered if there'd be an easy way to let them know that we're hiring. I didn't want to message all 1 million+ of our users, since it wouldn't be relevant to many of them - like those in countries with visas we don't sponsor, or those studying courses outside of CS. So, I decided to display a "We're hiring!" house ad based on the combination of geolocation and course enrollment information. It's my ghetto version of a geo-targeted house ad, and I thought I'd share the idea and code here.

Here's what it looks like — quite basic:

Here's the HTML for the jobs ad, which uses the Twitter Bootstrap alert classes:

<div id="coursera-promo-cs-job" class="alert alert-info coursera-promo">
  <button data-dismiss="alert" class="close">x</button>
  Want to help us build the best online education platform?   <a href="/about/jobs">Join our team!</a>
</div>

And here's the JavaScript. I use the jQuery cookie plugin to check if the user already dismissed the promo, then use our internal methods to check if the user is enrolled in CS classes, and then finally, if I've made it that far, I load the Google AJAX APIs and use the google.loader.ClientLocation object to figure out if the user is in a target country. If the user meets all the conditions, then I show the ad and add a click listener to the close button. I also use our internal analytics to track when users are shown the ad and when they click or dismiss it.

var promoId = 'coursera-promo-cs-job';
if (!cookie.get(promoId)) {
  if (Coursera.user.isInCSClass()) {
    $.getJSON('https://www.google.com/jsapi?callback=?', function () { 
      if (google.loader.ClientLocation) {
        var countryCode = google.loader.ClientLocation.address.country_code;
        var state = google.loader.ClientLocation.address.region;
        if (countryCode == 'US' || countryCode == 'AU') {

          var $promo = self.$('#' + promoId);
          $promo.show();
          Coursera.tracker.push({key: 'user.promo.show.' + promoId});

          $promo.find('.close').on('click', function() {
            $promo.hide();
            cookie.set(promoId, 'closed');
            Coursera.tracker.push({key: 'user.promo.close.' + promoId});
          });

          $promo.find('a').on('click', function() {
            Coursera.tracker.push({key: 'user.promo.click.' + promoId});
          });
          
        }
      }
    });
  }
}

When we first deployed this, I set the targeting quite narrow, restricting it to only users in 2 CS courses and from California. Since then, we've broadened the targeting to all of US and 5 more countries.

As we find the need for more "house ads", like promoting new features that may be more relevant to some users than others, I imagine I will turn this bit of code into a more general purpose library, and maybe we'd move the logic into a database or deploy script, to make it easier to configure ads without changing code. Do you do anything similar on your own sites? Let me know in the comments.

Monday, June 18, 2012

Porting to the Paid Google Translate API v2

Last year, one of the little apps that I put together was Translation Telephone, which lets you enter a phrase and translates it across 20 languages until finally translating back to the original language. It's interesting to see how some phrases transform - and sometimes the results sound almost like the output of an intelligent being. For example, "Capitalism: God's way of determining who is smart and who is poor." transformed into "Capitalism: God's way of determining who is smart and who is poor."

Unfortunately, not long after I made the app, Google announced it was shutting down the Google Translate API. There was a pretty big outcry after this announcement, and Google eventually decided to replace the free API with a paid translate API. Well, it's taken a while, but I've finally ported my app over to the paid translate API. I thought about replacing it with a non-Google free translate API, but I didn't find many good options, and the Google Translate API pricing is transaction-based and inexpensive: $20 per million characters of text.

To port over to the paid Google translate API, I did a few things:

You can see all the code changes in this commit. Once I made the v1-compatible wrapper friendly, I didn't have to change much of my original code. And maybe it will help other folks to port more easily.

Lesson learned: I've been putting off this port for half a year now, and it only actually took 20 minutes. I should have just spent an afternoon trying to port earlier instead of telling myself it would take a long time. One never knows with ports!

My Next Step: Coursera and The World of Online Education

Over a year ago, I left Google to go out in the world and try and figure out what I wanted to do with my life. I spent much of the last year "hermit hacking", mostly on EatDifferent, and as I explained more in my recent Ignite talk, I realized something very important during that time: as much as I enjoy working on projects that I love and believe in, I can't do it alone. I need to be around other people working on the same project, who believe in the same mission, with energy and passion that I can feed off of. When it is just me on a project, I get lonely and lose confidence in what I'm doing. So I decided that it was time to join a team. But who and what?

I've always been plagued by the fact that I'm interested in too many things - language, travel, science, evolution, play, food - because my multitude of interests makes it hard for me to pin down what I want to focus the majority of my time on. But I think now I've figured out the common thread amongst everything I like working on: education. I love learning and teaching, across every topic and in every form. The more I think about it, the more I realize how much of my projects have actually been about education. In college, I spent my extracurricular time organizing workshops. At Google, I used my role in developer relations to figure out how best to teach APIs to developers (and have them teach eachother). In my spare time, I've taught at computer camps, created curriculum, tutored students, and made apps that enable learning like WordHunter and QuizCards. Even my mild obsession with nutrition for the last year has revolved around learning and teaching.

So, I figured out the "what": education. But who? There are a lot of interesting things happening in the education space right now — there are people putting together intensive academies to teach programming, there are efforts like Khan Academy to improve K-12 education, there are startups like Codecademy to teach programming to the masses, and of course, there's developer relations across API and web focused companies like Adobe.

I was in the midst of checking out all those possibilities when I attended FooCamp last weekend, an unconference bringing together people across many fields. I naturally found myself attending many of the education-focused sessions and ended up chatting with Andrew Ng, the co-founder of Coursera.

Coursera is the startup behind the online university level education (first from Stanford, but now from a few other top-notch universities), and their mission is to make quality education available for free to everyone. They've had a great start, with now 1 million sign-ups for their classes, but that's the thing: they're really just getting started. There's a lot of experimentation to be done to figure out how to best deliver online education: how to get students to stick with it, how to get a community growing around it, how to do peer grading, how to make their credentials matter.

After my chat with Andrew, I realized that there might be ways that I can contribute my experience, skills and ideas to their mission, and well, to make a short story shorter, I signed the job offer a few days later. On July 2nd, I'll be starting as a Frontend Engineer at Coursera, and I'm super stoked. An awesome team and an awesome mission, what more can a girl want?

Oh, and hey, now I get to put in an obligatory plug for my new company: Coursera has tons of courses and several job openings. Join us! ☺

Friday, June 15, 2012

A Year of Hermit Hacking

A bit over a year ago, I announced that I was leaving Google and going out in the world to see what it held for me. I spent the time since then as a "hermit hacker", working on my own projects from my home, cafes, co-working spaces, and really anywhere I could get an outlet. I learnt alot about myself and what I need in life to be happy, both physically and mentally.

I decided to share my story in an Ignite talk at Foo Camp last weekend. You can check out the slides below (or on SlideShare) or you can watch the video on YouTube.

...so that was my personal experience with hermit hacking. What's yours?

Tuesday, June 12, 2012

Alternatives for After-Parties at Conferences

This post is based on a discussion and brainstorming from FooCamp 2012.

I attend a lot of conferences, either as a speaker or attendee, and back when I was at Google, I even organized a few conferences. I love conferences because they give me a chance to learn a huge number of new topics at once and to meet developers like me - but I don't love everything about them. I'm increasingly disappointed with the decision by most conferences to host their after-parties at bars, and for the only activity to be drinking and networking. Before I go into why, first let's answer this question...

Why are most after-parties at bars?

Conference organizers want their attendees to meet eachother, and an open space like a bar provides the potential for every one to meet eachother. Most attendees are strangers, however, so they add in the alcohol to loosen people up so they have the balls to approach eachother - aka, "social lubrication."

Well, what's wrong with that?

Well, first of all, there's an over-reliance on alcohol to provide the social lubrication. There are many people that don't drink - pregnant women, recovering alcoholics, or just people like me that don't think it's the healthiest of activities. So, for those people that don't drink, they're left with no means of social lubrication but they're still in the awfully intimidating situation of being in a sea of strangers. When I enter a bar and don't immediately recognize anyone, I hide in the bathroom and come out every so often to see if I suddenly see someone I know... or eventually give up and leave. Yes, I'm probably more shy than others, but I'm sure that others feel some degree of intimidation in these situations.

Secondly, there's a danger on relying on alcohol as a social lubricant, because as I think many of us know from college parties, alcohol doesn't just make it easier to talk to eachother - it also makes it easier for us to do things that we shouldn't, and to cross physical boundaries that we often don't want crossed, particularly at a conference. When I'm a conference after-party and there's an open bar, I make doubly sure that I stay sober and aware, so that I can try and figure out if any of the attendees have shot past the limit of knowing the boundaries.

Alcohol besides, there are also logistical problems with bars: there's often loud music that makes it hard to hear eachother, the space is usually structured in a way that makes it hard to move freely (so you can get stuck in one conversation forever).

So, what are the alternatives?

Well, we should address a few needs in suggestions alternatives to the bar: 1) we want a way for people to get to know eachother, and 2) we want to provide a space for people who don't feel comfortable around alcohol or people drinking.


The Un-Bar

One basic alternative to the bar is the "un-bar" - an alcohol-free bar that would serve tea, juices, and the like, and would be an option for folks who don't want to be in the same space as drinking. However, the "un-bar" doesn't provide a solution to the social lubrication question (besides giving people the obvious conversation starter of "so, why are you in this bar?"), so there's more work to do.


Conversation Starters

Most people want to meet new people, but don't know the best way to start a conversation with a particular person. Sure, you can go up to someone and say "so, how do you like the conference?" but it always gives me more confidence if I have something more personal to say them, some reason why I'm talking to them and not someone else. Here are a few ideas:

  • People Bingo: Create bingo-like cards where every square is an attribute of a person like "Lived in Europe" or "Has 3 kids" or "Loves Python" (the attributes can be tailored to the conference or crowd). Then, give every attendee a different card, and tell them that they'll get some piece of inexpensive schwag at the end of the night if they get bingo. Attendees then can go up to eachother and say "Sooo...ever lived in Europe?" and that can launch them instantly into conversation. We did this once at a networking event in Australia, and it was a lot of fun. We also did something similar to people bingo at Webstock a few years ago, led by reality games expert Jane McGonigal. It was a people-oriented scavenger hunt where she gave us a mission ("find someone with a purple accessory!") and we had to find that person, bring them back, and have them introduce themselves. Lots of fun.
  • Conversation Cards: A less game-y version of People Bingo would be to simply provide "conversation cards" for attendees to pick up at the door. To avoid them coming across as super-lame, the questions could be fun what-if style questions - or stick with lame and watch attendees bond over the lame-ness of it.
  • Badged Name Badges: Most conference attendees wear name badges, so it's a great place to stick conversation starters. Conferences can encourage attendees to write tags on their badges for their main interests or give them a variety of topical stickers to decorate their badges. The NotConf in Texas let us pick from stickers like Android/iOS/JS/Python/beer/wine/etc, giving us some basic prompts (ie. "You like Android?? Psh!"). If you do this, try to make the badges and decorations as visible as possible, so that people can approach with topics from afar.
  • Tribe Tables: This is more for conference mealtimes than for after-parties, but you can do it wherever you have people sitting around tables. Figure out topics that interest your conference attendees (like based on the tracks or talks) and then put a big sign at each table declaring its topic. People will sit at the table who are interested in what's on the sign, and they'll have a more specific thing to talk about with eachother. The tricky part of this is getting the distribution of topics right so that most people end up at tables matching their interests, but in case it fails, they can always talk about how they're *not* into that. Many conferences already do this, like OSCON and Google I/O.
  • Ice Breakers: When there is a small enough number of people, you can actually kickstart an event with a round-the-room ice breaker. At FooCamp, we managed to do an ice breaker with 300 people, and we each said 3 things we're interested in (no verbs, just nouns), and then our name. That way, you could keep your ears perked for topics you were interested in, and remember the name of whoever said it. You can also do the kind of ice breakers we did as kids, like saying your name and a fruit that starts with the first letter. Once again, attendees could bond over the lame-ness. :)

Bonding Activities

Sometimes after a long day at a conference, I'm tired of conversing but I still want to spend time with the other attendees. That's why I love when conferences provide actual activities in addition to a networking space - like games, crafts, or recreation. Here are some ideas, but you can really just think of any activities you loved doing in recess, summer camp, afterschool, etc. You know, when you were a kid and couldn't resort to alcohol for fun.

Fun & Games

jenga
  • Trivia Night: This takes a bit of effort, but can be well worth it. Create trivia questions that are topical to the conference, organize people into random teams, and have them compete to win some pile of schwag. To make it so more teams can win stuff, offer bonus questions throughout the night.
  • Poker/Casino Games: These can be fun, as long as you don't spend the whole night losing (like I often do...) Don't involve real money, though - just start everyone off with a certain amount and make it tradeable for schwag at the end.
  • Dominos/Jenga: At JSConf, they setup a game of Jenga inside the crowded bar. Not only did you have to be careful when extracting pieces, you had to worry about the random people walking by that had no idea there was Jenga behind them. :)
  • Board Games: You can do strategic games like Settlers of Cattan, but they can take quite a while. I prefer more of the "party games" like Pictionary, Charades, and Balderdash.
  • Parlour Games: My favorite of these is WereWolf/Mafia. I've spent many nights of my life playing WereWolf until 3am at conferences. You've never bonded with someone until you've looked them in the eye and accused them of being a blood-thirsty werewolf. Another fun one we played at FooCamp was Psychiatrist. This game only really works if you find a person that's never played it before, but if you do find that, it can be pretty hilarious.
  • The Sentence Game: A cross between pictionary and telephone, and only requires pencil, paper, and imagination.

Arts & Crafts

  • Drawing: Instead of tablecloths on your tables, do what many restaurants do and cover them in butcher paper with crayons nearby. People can doodle as they chat, and chat about their doodles. You can refresh the butcher paper throughout the night when it gets covered, and stick the creations on the walls for passerbys to chat about.
  • Crafts: At the Japanese-themed Heroku Waza conference, they had tables in the open area where you could do origami or linoleum cutting along with pros. It was a fun way to spend 30 minutes and bond over what we were making.. it was actually so fun that I didn't attend many talks. So, don't offer it at the same time as talks if you want to maximize audience size. Other fun crafts: boondoggle, friendship bracelets, sand art, finger painting, etc.
  • Maker bots: At FooCamp, there was an area with maker bots where you could actually get your face scanned in 3d and wait for a print-out of the face. People love seeing the technology and talking about how it works - the only problem is that it can't handle a lot of people at once. Definitely a fun side booth for a technical conference, though.

Getting Physical

After a day of sitting in rooms, sometimes I'm itching to move around. Just keep in mind the risk inherent in each activity and your liability if someone gets hurt, and for activities where people's bodies are mingling, remember that not everyone will be comfortable with that and it is more likely for boundaries to be crossed.. those activities may not be appropriate for all conference crowds. Also, sadly, not all conference venues have the space necessary to do physical activities.

  • Twister: At a retreat I ran in college, we used to set up giant games of Twister using paper plates colored with markers, so quite a few people could play at once. Ridiculous but fun.
  • Adult Hippity-Hop: Bring back the simple childhood joy of hopping around on a giant ball.
  • Floaty Ball: At FooCamp, there was this huge inflated ball-thing with LEDs that we played keep-away with in the lawn. It was stupidly simple fun and quite beautiful against the night sky.
  • Frisbee: Somehow, throwing a disc around is pretty amusing, and it's an easy game for people to jump in on.
  • Mechanical Bull Riding: One of the JSConf after-parties in Arizona was at a bar with a bull, and everyone had a go at trying to stay on. It was quite entertaining, but it did get a bit awkward at times seeing people's bodies flail wildly (and the bull DJs at the bar kept jiggling the girls at opportune times... would have been nice if they refrained from that.)

What else?

This is not a comprehensive list, but it should hopefully get you thinking about alternative ways for encouraging social interaction at conference events. It's not that I expect every conference to remove drink-ups from their schedules, but I do hope that organizers spend a bit of time thinking whether they can mix it up a bit. And, hey, doesn't a lot of this sound like a lot of fun?

Let me know in the comments about your experiences with conference after-parties, and if there's more I should add to the list.

Tuesday, June 5, 2012

Client-Side Storage Article Series

After giving a talk a few months ago on Client-side Storage, I was asked my DrDobbs.com to write it up in article form. Well, thankfully, I'd actually written most of the slides up already in my speaker notes, so it wasn't too much work to article-ize them. We split the article into two parts: 1) Understanding Client-side Storage in Web Apps, an overview of all the options like cookies, file system, and indexed DB and then 2) the localStorage API, a deep dive into localStorage and ways to use it. Give them a read - they might give you an idea for ways to use client-side storage that you hadn't thought of before.

Now, if only I had time to turn all my talks into articles...!