Friday, December 23, 2011

Reusing HTML/CSS/JS across Web & Mobile

At this point, there's only one of me working on EatDifferent. That's awesome because it means I get to learn everything about what it means to make a user-facing website, but it also means that I need to be careful about where I spend my time, since it is such a precious resource.

When I started it, EatDifferent was a web-only service, but I soon realized that when it comes to tracking your daily habits, users really want to be able to track on the go, from their mobile device. I realized I needed a real strategy for how I could offer it as a website, mobile-optimized website, and as multi-platform mobile app while keeping them all up-to-date and still having time to iterate on the core functionality. So I decided to find a way to reuse as much of my code across those offerings as possible. Here's how I do it:

Basic setup


When a user logins in, I store their authentication in a session cookie. From then on, either my web app or mobile app can make XMLHttpRequests to fetch or save information for the user. In the mobile app, the calls are made over SSL (and in case you're wondering, the cross-domain restrictions aren't applied to files in Phonegap apps so that is not an issue).


The Flask microframework comes bundled with support for Jinja2 templates (like Django templates but better) so that's what I use for server-side templating in my web app. I mostly use the templates for includes/inheritance and not for variable rendering, as then I can keep my logic in JavaScript, making it easier to re-use that logic in the mobile app. For example, log.html extends from a base HTML and includes Jinja2 templates for the log sections.

I wanted to reuse much of the same HTML in my mobile app, so I use the Jinja2 template engine for it as well. My mobile app is actually one single HTML page, where each "page" is a DIV with a .mobile-page class, and many of the "pages" include Jinja2 templates (the same ones that are used by the web app). After I make changes to the base HTML or templates, I test them in a browser (using a Chrome extension to mimic a small screen), and then when I want to output them to a device, I render the templates using a script and copy them to the Android/iOS app folders.


I start off with Twitter Bootstrap for my CSS, because it makes for a slick but easy-to-customize foundation. I then use SASS for writing my own CSS rules, as I can write cleaner cross-browser CSS that way and can also do things like includes. I define my shared styles and variables in common.scss, and include that in web.scss (for the web app) and phonegap.scss (for the mobile app). I use CSS media queries in both common.scss and web.scss to define rules for smaller screens &emdash; some of those rules apply also to the mobile app, but some just to the web app.


As I mentioned in an earlier post, I recently refactored my JavaScript to make it easier to share logic across the web and mobile app. I now have a shared.js for shared functionality, a web.js for web-specific functionality, and phonegap.js for mobile-specific functionality (makes sense, doesn't it? :). Since I also use different JS libraries across the web and mobile app versions, I use different rules in my Makefile to generate the final compressed JavaScript for the web and mobile apps.


Here's a Venn diagram that summarizes what's different and what's shared:

And here are screenshots comparing the log on the web versus in the mobile app:

I suspect that my HTML/CSS/JS for the mobile app version will diverge more as I try to make the app conform more to the expectations of mobile users (and iPhone users in particular), but I still like the idea of reusing as much of my code as I can. The less time I spend writing redundant code, the more time I can spend adding features and improving the EatDifferent service for all my users.

Friday, December 9, 2011

Using 3-Legged OAuth APIs with Flask

The Withings body scale is a nifty device — after you connect it to your wireless network and create an account, it wirelessly transmits your measurements to its site. It also tries to estimate your body fat ratio, so that even when your weight stays the same, you can see if your body fat ratio is changing. In the last week, I had several EatDifferent users start using a Withings scale to monitor their weight while they improve their eating habits, so I wanted to let them connect their accounts to their Withings accounts.

Fortunately, Withings offers an API for accessing user measurements. Their API uses OAuth for authentication and JSON for the responses. Since users could enter measurements at any time and they don't want developers polling their API constantly, they do the smart thing and let developers add subscriptions for each user, so that a URL on your server is pinged whenever there are updates for a user. The API is well-designed and standards-compliant, so I was looking forward to integrating with it.

The tricky part of using any OAuth API is setting up the flow — your site has to generate a request token from the OAuth provider, redirect the user to the OAuth provider to grant access, and then once the provider redirects back, your site has to exchange the request token for an access token and save the credentials. Finally, with those credentials, you can actually start using the API. Since the OAuth flow involves redirects and saving session information, the implementation varies depending on the server-side framework you're using.

For EatDifferent, I'm using the Flask Python microframework on Google App Engine, so I started my integration by looking for Flask OAuth examples. I started with the Flask OAuth extension, which wraps on top of the oauth2 library and provides various decorators for retrieving the session tokens and handling the redirect. The extension worked, but since I wanted to customize it more, I decided to go straight to the source and just write URL handlers and a Withings client library based on that oauth2 library. You can check out that gist to see the Withings client code, and read on for a description of my URL handlers.

To start off the authentication process, I have this authorize_withings() URL handler that creates a WithingsClient with my key and secret (provided by Withings on registration), gets a request token, saves them in the session as a tuple, and redirects to the authorization URL.

def authorize_withings():
    withings_client = withings.WithingsClient(WITHINGS_KEY, WITHINGS_SECRET)
    callback_url    = (util.get_host() + url_for('handle_withings_authorization') 
    request_token   = withings_client.get_request_token(callback=callback_url)
    auth_url = withings_client.get_authorization_url(request_token) 
    return redirect(auth_url)

Withings should then redirect back to my handle_withings_authorization() URL handler that creates a WithingsClient (with the request token from the session), requests an access token, and saves the token as a property on the current User entity. By saving it in the datastore instead of session, I can access it at any time in the future too, not just for this session.

def handle_withings_authorization():
    request_token   = session[SESSION_WITHINGS_TOKEN]
    withings_client = withings.WithingsClient(
        consumer_key       = WITHINGS_KEY,
        consumer_secret    = WITHINGS_SECRET,
        oauth_token        = request_token[0],
        oauth_token_secret = request_token[1])
    access_token       = withings_client.get_access_token(
        oauth_verifier = request.args['oauth_verifier'])
    withings_auth   = {
        'oauth_token':        access_token['oauth_token'],
        'oauth_token_secret': access_token['oauth_token_secret']
    g.user.withings_auth   = util.serialize(withings_auth)
    g.user.withings_userid = access_token['userid']
    return redirect(url_for('device_settings'))

Whenever I want to use the Withings API for a user, I fetch their authentication information, create a WithingsClient, and fire off requests to the API. For example, I use this parse_withings() URL handler to respond to notifications from their API.

def parse_withings():
    user_id         = request.form.get('userid')
    user            = models.User.all().filter('withings_userid =', user_id).get()
    withings_auth   = util.deserialize(user.withings_auth)
    withings_client = withings.WithingsClient(
        consumer_key       = WITHINGS_KEY,
        consumer_secret    = WITHINGS_SECRET,
        oauth_token        = withings_auth['oauth_token'],
        oauth_token_secret = withings_auth['oauth_token_secret'],
        userid             = user.withings_userid)
    measurement_groups = withings_client.get_measurements(
    imports.import_withings(user, measurement_groups)
    return Response(status=200)

Now that I have the OAuth flow setup for Withings, I can easily support other APIs — whatever my users need most. ☺

Monday, December 5, 2011

Upgrading from jQuery Templates to jsRender

In making my recent port from jQuery to Zepto, I was the most worried about porting over from jQuery templates - I knew there were other templating engines out there (like mustache.js, which comes highly recommended) but I also know templating engines can be vastly different and I didn't want to spend a lot of time porting.

Fortunately, I found out that jQuery templates have actually been deprecated in favor of jsRender, a revision of the library that isn't dependent on jQuery. Perfect!

The templating language for jsRender is largely the same as jQuery templates, with a few purely aesthetic syntax changes and one logical difference — atleast from the perspective of the parts that I was using. The jsRender library is still in development, so the differences might change in the future, but in the meantime, I thought I'd write up a quick upgrade guide for anyone else porting over.

The operators are now prefixed with a "#". You can pretty much do a search and replace to port them over - {{if}} to {{#if}}, {{each}} to {{#each}}.

Previously, variables were outputted with ${var} - now, variables are outputted using the same double brackets as operators, like so: {{=var}} and {{}}. If you're using a server-side templating engine that also uses that bracket notation for templating, you will need to instruct the engine to ignore the jsRender templates. For Jinja2 templates, that means surrounding the script tags with {% raw %}{% endraw %}.

In addition, the syntax for specifying you don't want HTML escaping changed from {{html var}} to {{var!}} (notice the exclamation mark at the end). Finally, if you want to access the value of the currently referenced variable, the syntax changed from ${$value} to {{=$data}}.

To give you an idea of what an upgraded template would look like, here's some example data and templates:

var templateData = {
  label: 'Comments on post for Wednesday, Dec. 5th',
  comments: [
     type: 'freetext',
     creator: {fullName: 'Pamela Fox', profileUrl: ''},
     textHtml: 'Have you tried macademia nut oil instead? You can get it from <a href="">.'

Using jQuery templates:

var dom = $('#stream-comment-tmpl').tmpl(templateData);
<script id="stream-comment-tmpl" type="text/x-jquery-tmpl">
  {{each comments}}
  <div class="stream-comment">
    {{if type == 'highfive'}}
      <span class="icon-highfive"></span>
      High five from <a href="${creator.profileUrl}">${creator.fullName}</a>!
     <a href="${creator.profileUrl}">${creator.fullName}</a>:
     {{html textHtml}}

Using jsRender templates:

var template = $.template('stream-comment-tmpl', document.getElementById('stream-comment-tmpl').innerHTML);
var html = $.render(templateData, template);
<script id="stream-comment-tmpl" type="text/jsrender-tmpl">
  {{#each comments}}
  <div class="stream-comment">
    {{#if type == 'highfive'}}
      <span class="icon-highfive"></span>
      High five from <a href="{{=creator.profileUrl}}">{{=creator.fullName}}</a>!
     <a href="{{=creator.profileUrl}}">{{=creator.fullName}}"</a>:

In jQuery templates, you could pass null as a value in an array and use the if operator to test whether it was defined. In jsRender, the template will not be called at all for a null value. Instead, you need to define objects in the array, set an object property to null, and check to see if that object property is defined. The reasoning behind the change is discussed more in this issue.

To give you an idea of the change, here's a before and after - notice I had to change the data format itself.

Using jQuery templates:

var templateData = ['Walked to work', null, null, 'Biked to work'];
<script id="notes-mini-tmpl" type="text/x-jquery-tmpl">
  {{each dates}}
    {{if $value}}
     No notes for this date.

Using jsRender templates:

var templateData = [{notes: 'Walked to work.', {notes: null}, {notes: null}, {notes: 'Biked to work'}];
<script id="notes-mini-tmpl" type="text/jsrender-tmpl">
  {{#each dates}}
    {{#if notes}}
     No notes for this date.