Lukasvan3L

A pragmatic programmer

0 notes &

Open Data Anthology

I was at the The Hague Open Data Meetup this evening. It was held at the Dutch National Library (KB), and I recognized a scenario they’d encountered during their first Hackathon: the developers didn’t know what to do with the data the KB offered, and nothing came out of it.

The problem is that the data of the KB wasn’t appealing to the attendees of the hackathon. The data was OCR-scanned 18th century books. Who attends hackathons? Developers. What do they know of 18th century literature? Nothing.

To get meaningful information out of the dataset, you need other people than just developers. Researchers and people that have insight into the content itself, they are the people that come up with interesting concepts, so you have to get them aboard! 

Let’s start with giving the event a different name. “Hackathon” is a really nerdie term, and going to scare away others. 

How about “Jam”, “Anthology”, “Assembly”, “Poem night”…

0 notes &

Single Page Interface using Backbone

Here’s a little snippet that contains the bare basics of a single page interface built in backbone.

var AppRouter = this.Backbone.Router.extend({
    routes: {
        “*actions”: “defaultRoute”
    }
});

var app_router = new AppRouter();

This creates a Backbone Router. When this router receives a “navigate” command, it in turn triggers the “route:defaultRoute” event.

app_router.on(‘route:defaultRoute’, function (actions) {
    var contentcontainer = $(‘#body .main-content’);
    contentcontainer.html(‘Loading…’);
    $.get(‘/’ + actions).then(function (data) {
        contentcontainer.html(‘<div>ajax loaded:</div>’ + data);
    }, function (data) {
        contentcontainer.html(‘An error occurred: ’ + data);
    });
});

Now, when the defaultRoute is triggered, the url it is navigating towards is retrieved via ajax and then inserted in the contentcontainer.

this.Backbone.history.start({
    pushState: true,
    root: “/”,
    silent: true
});

To keep the history and url’s working, we initiate the Backbone history, with pushState.

$(function () {
    $(‘body’).on(‘click’, ‘a’, function (evt) {
        evt.preventDefault();
        app_router.navigate($(this).attr(‘href’), {
            trigger: true
        });
    });
});

And then to tie things together: on each link in the page, prevent the default action and trigger the routing.

On the server you can see if the request is AJAX or not. If it is, don’t return the header and footer, just the contents of the main-content element. If it’s not and AJAX call, then serve the entire page. This way the entire site works like a single page interface, but when you reload the page it’ll work too.

This is especially neat because search engines often don’t use javascript the same way your typical user does. Using the above approach makes the website navigable without javascript.

Best of all, it’s extensible! Want a specific pagetransition to animate? No problem, create a route for it and handle that specific transition.

Enjoy!

1 note &

OkCupid put a smile on my face

A colleague encouraged me to try out OkCupid. For educational purposes of course: they had a very addictive way of getting users to answer a bunch of questions about themselves. He had himself answered hundreds (!!) of questions.

But what I liked most about my 5 minutes on OkCupid was the feedback on the registration flow. Check out the city box, when I fill in that I’m from Leiden:

image

I really liked the feedback and it’s tone of voice. But it got me wondering what it would do with more difficult input. So I typed “haag”:

image

Awesome, so it had a good location search. Now let’s just pick one and move on:

image

Whow! Didn’t see that coming :)
The password feedback also put a smile on my face:

image

After this experience I was looking forward to the questions that I was actually here for. But I didn’t even get beyond the first question: “What are you looking for? A: Sex or B: True love”. How am I going to explain either answer to my wife?

0 notes &

It’s w00tcamp time!!! And I’m creating awesome stuff with the Philips Hue lamps! George from Philips brought about 50 lamps this way and we’re lighting up the entire office :)

2 notes &

Social responsibility

Today I held a presentation at Q42, about Q42. The listeners were four entrepreneurs from developing countries (kosovo, pastina) who are in the process of building or running a technical enterprise. The BiD Network helps these people by getting them in contact with companies that can aid them in their development process.

When they asked me, I couldn’t say no to an oppertunity to tell people how awesome Q42 is. And if I could help these entrepreneurs by doing so, all the more reason.

So I told them about our developer-centric working environment, with project- and company management as facilitators to the all-mighty developers. About our 48 employees of which 45 are developers. Each one motivated by the cool projects they do, the cutting-edge technology they’re using, weekly passion-time, yearly w00tcamp, company outings, and so on.

The entrepreneurs were quite amazed when they learned that our founder Kars at some point hired a CEO so he could spend more time developing again. The people I told the story to were at this point founders of small companies, but they hadn’t foreseen this possible future for themselves yet ;-)

For me personally it was a good exercise in explaining outsiders what Q42 is and what it stands for. The devil is in the details, and it’s quite a challenge to put your finger on the sweet spot. And of course, a little CSR (that’s what google translate makes of it…) never hurts!

0 notes &

marketing.css

Small tip: don’t name your static files like “marketing.css”, because some ad blockers might think it’s untrustworthy and block the download of it :-$

Just found out the hard way…

0 notes &

Class not enhanced correctly - Siena

When calling my EnhancedModel.findById(Object) function, I get this error:

UnsupportedOperationException occured : Class not enhanced correctly

Turns out this is because inside the EnhancedModel I created an overload of the findById function with two parameters: findById(String, String).

This is also the case with getByKey(Object) function; if you create an overload with other parameters; class not enhanced correctly.

0 notes &

PlayFramework & GAE localization editor

Play Framework includes the messages-file way of handling the translation of values. Unfortunately, this won’t do for the site I’m creating: it has 7 different languages and editors need to be able to translate on-the-fly.

So I thought I’d share the solution that’s now implemented: translations via the BigTable datastore.

Datastore
First of all, there’s a model:

@Entity
public class Translation extends EnhancedModel {

  @Id(Generator.AUTO_INCREMENT)
  public Long id;

  public String locale;
  public String name;
  public String translation;

  @Index(“qidx”)
  public List<String> q;

  @PreSave
  @PreInsert
  @PreUpdate
  public void updateSearchFields()
  {
    if (this.translation != null && this.translation.isEmpty())
      this.translation = null;
    this.q = Utils.splitSearchWords(String.format(“%s %s”, name, translation));
  }
}

I’m using Siena 2.0.7, that’s where the annotations are coming from. The “q” field gets indexed and the editors can search on that field when they’re looking for a translation. OnSave of a record this field also gets updated.

PlayPlugin
Next up is the code that actually translates the &{‘translatable message’} that the views are asking for. This is going to be a PlayPlugin overriding the getMessage function:

public class MessagesDbPlugin extends PlayPlugin {
  @Override
  public String getMessage(String locale, Object key, Object… args) {   
    return String.format(getTranslation(locale, key.toString()), args);
  }

Now we’re set up to get the actual translations from the database. Of course we need to also cache them using the gae memcached client, as it’s much faster then the actual datastore.

private static String getTranslation(String locale, String key)
{
  if (Utils.isNullOrEmpty(key))
    return “”;

  // look up in cache
  String value = Cache.get(Translation.generateCacheKey(locale, key), String.class);
  if (!Utils.isNullOrEmpty(value))
    return value;

  // look up in database
  Translation t = Translation.all().filter(“locale”, locale).filter(“name”, key).get();
  if (t != null)
  {
    cache(t);
    return Utils.isNullOrEmpty(t.translation) ? t.name : t.translation;
  }

  // validate the locale: is it possible?
  if (!Play.langs.contains(locale))
  {
    Logger.error(“Invalid language: %s”, locale);
    return “”;
  }

  // not found: insert into the database and the cache for each language
  for (String lang : Play.langs)
  {
    Translation newT = new Translation();
    newT.locale = lang;
    newT.name = key;
    newT.translation = null;
    newT.save();
    cache(newT);
  }

  // now return the key, as the value is empty

  return key;
}

That’s it, you’re set! Each time a translation is requested that doesn’t yet exist in the database, it gets inserted. So if you accidentally skip one and don’t enter it in the database, is pops up in the cms anyway.

CMS
I’m not going to display all the javascript, html and css involved in making the translations editable, but it’s fairly easy. Getting the translations you searched for is plain and simple:
Translation.all().filter(“locale”, locale).filter(“q”, q.toLowerCase().trim()).fetch();
And don’t forget to invalidate the cache entry after you saved a new translation!
That’s it! I might make a Play Module out of it some day, if enough people ask me to :)

0 notes &

Set play framework id for gae:deploy

We’re working on setting up our playframework / google appengine project with a full DTAP roadmap. The way we did that at Q42 is first to integrate our project in Teamcity; teamcity now even does automatic deploys to google appengine!

One problem I encountered was that I wanted to set a specific Framework ID when executing the gae:deploy command. Turns out, you can just append the id just as you would on a run command. So:

play gae:deploy - -%gae-dev

or

play gae:deploy - -%gae-prod

Now you can have different values in your application.conf for different appengine environments, for instance

%gae-dev.application.baseUrl=http://project-dev.appspot.com/

or

%gae-prod.application.baseUrl=http://project.appspot.com/