-
-
CSS Dev Conf
Last week I was at CSS Dev Conf in Hawaii - the first of an annual series of conferences. Not only was I there, I actually gave a talk!
I gave an overview of the tools and techniques available for automated CSS testing. I didn't mention it much here or on twitter because, well, I was kinda nervous and didn't want to jinx it. In my daily job, I organise Tech Talks and give pointers to people on how to make their presentations exciting and full of awsm but I tend to avoid actually standing up at the front of the room myself. Beyond introducing the speakers, that is. This is probably why I used to help organise gigs more than play them.
I won't repeat the content of the talk here. For that, you should start out by checking out the slides. The main point I finished on, however, is that the web dev community has fantastic tools for testing every aspect of web development except CSS and that it appears that every developer interested in the subject has started from scratch and built a tool that does what they need it to do. I'm hoping that I can turn http://csste.st/ into a community-driven site where people can share best-practices, techniques, tools and terminology around CSS testing. That way, we can create some sturdy processes around testing our CSS and not reinvent the wheel every few months.
The conference
Firstly, the trip to Hawaii: it was fantastic. I spent the weekend before the conference staying in a backpackers hostel on Waikiki beach. If you ever need somewhere to stay for a fraction of the price of a hotel on Waikiki, I totally recommend the 'Waikiki Backpackers Hostel'. Backpackers' hostels are the same everywhere - there are the people who hang around reception that may or may not work there, you can't quite tell; there's the Australian guy who comes down drunk for breakfast, smokes half-a-dozen cigarettes quickly then goes back to bed until the afternoon; there's the guy in his mid 70s who looks like a cross between everybody's favourite grandad and a stereotypical acid-casualty.
I swam with turtles in Hanauma Bay, climbed up Diamonhead Ridge, took a bus to the far side of downtown then spent 4 hours walking back via Iolani Palace. Like I say, it was a fantastic trip, even for someone who doesn't like the sun that much.
On the Tuesday, I moved into the Outrigger Reef on the Beach hotel for the conference. Ooh, posh. I could practically step out of my room into the elevator then step out of the elevator onto the beach. I then got to spend my remaining time in Hawaii hanging out with a huge bunch of cool, clever people (Rachel Nabors put together a Twitter list of the speakers), all of whom know an awful lot about web development.
With any multi-track conference there are always going to be a few sessions you have to miss but with CSS Dev Conf being four-track and having such a high level of quality, I kinda worry that I missed out on about three-quarters of what I wanted to see. Fortunately, everybody's been sharing their slides so I can at least get the gist of what they were talking about.
The journey
All in all, I'd totally recommend keeping an eye open for the announcement of next year's CSS Dev Conf. It might not be held in Honolulu again but, from my point of view, that's a good thing. It really is a fantastic place to spend time (seriously: turtles!) but getting there from Berlin is not the easiest thing. In fact, if you use the 'Earth Sandwich' lab on here.com, you can see how close Hawaii is to being the Exact Opposite Side of the World from Berlin. On the way out there, it didn't seem too bad as I left on Friday morning (Berlin time) and landed on Friday evening (Hawaii time) but on the way back, I left on Thursday and landed on Saturday. That was quite a journey.
-
jHERE playground
Every couple of months, we have a Research Week. It's kind of like the well-known Google 20% time but instead of doing it one day a week, we gather up our time and do a week of maps-based hacking. It's totally cross-discipline so we usually gather a couple of developers, a couple of UX and visual designers, a QA and build something cool. In past Research Weeks, I've built or helped build the Alien Easter Egg, Maps Labs and CoverMap.Me. I also built a maps-based JSBin fork called JotApp.
When my partner-in-crime Max started working on the latest version of his jQuery plugin (formerly called jOvi, now called jHERE), I wanted to build a new playground where people could play and build their own maps mashups and hacks really easily. My first thought was to rework the fork of JSBin again and maybe add in a slightly nicer default theme. I wanted users to be able to save to Gists so, seeing as there is already an SQLite and a MySQL adapter, I wrote a Gist adapter which appeared to the application as if it were a database but actually saved to anonymous Gists. The problem was that it was a bit too… heavy.
Don't get me wrong, JSBin is a fantastic project. It just does a lot more than I needed. I didn't need the MySQL adapter, the alternate themes, the localstorage or the user registration. Also, it's a bit too weighty for my phone. When someone tweets a link to a JSBin or a JSFiddle, I usually check it out on my phone and it's not the best experience. Seeing as HERE maps work on mobile, I wanted my playground to work, too. Rather than spend a couple of hours cutting out all the bits I didn't want from JSBin, I decided to spend a couple of hours building my own version from scratch. So, this past Sunday afternoon, that's exactly what I did:
It's written in NodeJS on top of express and works nicely on desktop, iPad and mobile.
The project is open-sourced on GitHub (naturally) and can be modified to be a general JS-playground for anything. If you fancy a simple, self-hosted JS hackspace, just change the default HTML, CSS and JS and it's ready to go.
-
Open Source Snacks
In my opinion, seed snacks are pretty much the perfect web dev snack: they're tasty, they're low-fat, they're vegan-friendly, they're gluten-free. I've always got a tub of some next to my monitor so, when I'm chewing over a tricky layout, I can grab a handful and chew them, too.
This repository collects some of my favourite seed recipes and I'm hoping that other web devs can clone the project, roast their own, suggest improvements and submit a pull request. If you have any other easy to make snacks (such as nut snack recipes), feel free to submit them, too.
Eating tips
From observation, seed eaters tend to fall into one of these categories:
Pour-and-snarf
Pour a few into your hand, tip them into your mouth in a oner. Good when you're in a hurry. Can lead to stray seeds falling into the keyboard.
Considerate Ant-eater
Pour a few into your hand, stick your tongue into the pile of seeds, retract.
Solo Ant-eater
Stick your tongue directly into the tub of seeds. Clean, efficient, not good for sharing.
Ice-cream scoop
Use a spoon. Good for sharing and minimises mess. Prevents multi-tasking. Feels kind of like using your mouse to go Edit > Copy when ctrl-C is right there.
Rousing call-to-arms
The stereotypical image of the geek - bottle of cola on one side, jumbo bag of chips on the other, little desire to do anything beyond the computer - has never really been true for the majority. We're all kinds of different people - mountain climbers, cyclists, needlepoint workers, knitters. The people that played on the football team and the ones who didn't get picked. We deserve more than just nachos.
Also nachos.
-
Some App.net recipes
This is a collection of code snippets for various common tasks you might need to accomplish with the App.net API. Most of these are focused on creating or reading geo-tagged posts. They require a developer account on app.net and at least one of an App ID, App Code, App Access Token or User Access Token. The calls here are implemented using jQuery but that's just to make it easier to copy-paste into the console to test them out (so long as you fill in the blanks).
An important thing to bear in mind is the possibility for confusion between a 'stream' and 'streams'. By default, a 'stream' is a discrete chunk of the 20 latest posts served at a number of endpoints. This is the open, public, global stream:
https://alpha-api.app.net/stream/0/posts/stream/global
On the other hand, 'streams' are long-poll connections that serve up any matching posts as soon as they are created. The connection stays open while there is something there to receive the response. Streams are available under:
https://alpha-api.app.net/stream/0/streams
Totally not confusing. Not at all.
Creating a user access token
Required for any user-specific data retrieval. The only tricky thing you'll need to think about here is the
scope
you require.scope=stream email write_post follow messages export
should cover most requirements.
Requires
client_id
Visit this URL:
https://alpha.app.net/oauth/authenticate ?client_id=[your client ID] &response_type=token &redirect_uri=http://localhost/ &scope=stream email write_post follow messages export
Using a user access token to create a post (with annotations)
Requires
User Access Token
- text to post
The text is essential if you don't mark a post as '
machine_only
'. The annotations here are optional. Annotations don't appear in the global stream unless the requesting client asks for them.$.ajax({ contentType: 'application/json', data: JSON.stringify({ "annotations": [{ "type": "net.app.core.geolocation", "value": { "latitude": 52.5, "longitude": 13.3, "altitude": 0, "horizontal_accuracy": 100, "vertical_accuracy": 100 } }], "text": "Don't mind me, just checking something out." }), dataType: 'json', success: function(data) { console.log("Text+annotation message posted"); }, error: function() { console.log("Text+annotation message failed"); }, processData: false, type: 'POST', url: 'https://alpha-api.app.net/stream/0/posts?access_token={USER_ACCESS_TOKEN}' });
Using a user access token to post a machine_only post (with annotations)
Requires
User Access Token
In this example, we're creating a post that won't show up in user's timelines and adding the 'well-known annotation' for geolocation.
$.ajax({ contentType: 'application/json', data: JSON.stringify({ "annotations": [{ "type": "net.app.core.geolocation", "value": { "latitude": 52.5, "longitude": 13.3, "altitude": 0, "horizontal_accuracy": 100, "vertical_accuracy": 100 } }], machine_only: true }), dataType: 'json', success: function(data) { console.log("Non-text message posted"); }, error: function() { console.log("Non-text message failed"); }, processData: false, type: 'POST', url: 'https://alpha-api.app.net/stream/0/posts?access_token={USER_ACCESS_TOKEN}' });
Retrieve the global stream, including geo-annotated posts if there are any
Requires
User Access Token
This is a very basic call to retrieve the global stream but it also instructs the endpoint to return us all annotations and include machine-only posts.
var data = { "include_machine": 1, "include_annotations": 1, "access_token": "{USER_ACCESS_TOKEN}" }; $.ajax({ contentType: 'application/json', dataType: 'json', success: function(data) { console.log(data); }, error: function(error, data) { console.log(error, data); }, type: 'GET', url: 'https://alpha-api.app.net/stream/0/posts/stream/global', data: data });
Creating an App Access Token
This is necessary for many of the
streams
operations. It is not used for individual user actions, only for application-wide actions.Requires
client_id
client_secret
client_credentials
is one of the four types ofgrant_type
specified in the OAuth 2.0 specification. I had difficulty getting this to work when using a data object:var data = { "client_id": "{CLIENT_ID}", "client_secret":"{CLIENT_SECRET}", "grant_type": "client_credentials" };
The
client_credentials
kept throwing an error. Instead, sending this as a string worked fine:$.ajax({ contentType: 'application/json', data: 'client_id={CLIENT_ID}&client_secret={CLIENT_SECRET}&grant_type=client_credentials', dataType: 'json', success: function(data) { console.log(data); }, error: function(error, data) { console.log(error, data); }, processData: false, type: 'POST', url: 'https://alpha.app.net/oauth/access_token' });
One other thing to note is that this bit should be done server-side. This will throw a bunch of "…not allowed by Access-Control-Allow-Origin…" errors if you do it via jQuery.
Returns
{ "access_token": "{APP_ACCESS_TOKEN}" }
Creating a
streams
formatNow you have your app access token, you can use it to tell the service what kind of data you want back. The streams offered in the API have two quite powerful aspects. Firstly, filters allow you to run many kinds of queries on the data before it is streamed to you so you don't need to recieve and process it all. Secondly, the decoupling of filters from streams means you can specify the data structure and requirements you want once then just access that custom endpoint to get the data you want back any time.
Requires
App access token
This first example just creates an unfiltered stream endpoint
$.ajax({ contentType: 'application/json', data: JSON.stringify({"object_types": ["post"], "type": "long_poll", "id": "1"}), dataType: 'json', success: function(data) { console.log(data); }, error: function(error, responseText, response) { console.log(error, responseText, response); }, processData: false, type: 'POST', url: 'https://alpha-api.app.net/stream/0/streams?access_token={APP_ACCESS_TOKEN}' });
Returns
{ "data": { "endpoint": "https://stream-channel.app.net/channel/1/{LONG_RANDOM_ENDPOINT_URL}", "id": "77", "object_types": [ "post" ], "type": "long_poll" }, "meta": { "code": 200 } }
Using Filters to create a stream of geotagged posts
We'll specify some requirements for our filter now so that it only returns back a subset of posts. The rules we're specfying here are:
At least one item in the "/data/annotations/*/type" field must "match" the value "net.app.core.geolocation"
Requires
User access token
The
field
is specified in 'JSON Pointer' format. Within the response, there is a 'data' object and a 'meta' object. The data contains an 'annotations' object which contains an array of annotations, each of which has a type. This is represented as/data/annotations/*/type
.$.ajax({ contentType: 'application/json', data: JSON.stringify({"match_policy": "include_any", "clauses": [{"object_type": "post", "operator": "matches", "value": "net.app.core.geolocation", "field": "/data/annotations/*/type"}], "name": "Geotagged posts"}), dataType: 'json', success: function(data) { console.log(data); }, error: function(error, responseText, response) { console.log(error, responseText, response); }, processData: false, type: 'POST', url: 'https://alpha-api.app.net/stream/0/filters?access_token={USER_ACCESS_TOKEN}' });
Returns
The filter rules you just specified, the
id
of the filter (remember that for later) and the details of the application used to make the request.{ "clauses": [ { "field": "/data/annotations/*/type", "object_type": "post", "operator": "matches", "value": "net.app.core.geolocation" } ], "id": "527", "match_policy": "include_any", "name": "Geotagged posts", "owner": { "avatar_image": { "height": 200, "url": "https://d2rfichhc2fb9n.cloudfront.net/image/4/Pr63PjEwJ1fr5Q4KeL3392BMgSnIAYlHxv8OkWwzx75V8quNfpaFp4VPpKnDRxdXtYYPtIutrDVdU9NbJn7hKApQL84T5sfB1D9bWTgtizMWInignv0WyPPfM2DpqSThQgvkB68vbPzjZ8VeKM02M2GySZ4", "width": 200 }, "canonical_url": "https://alpha.app.net/thingsinjars", "counts": { "followers": 30, "following": 65, "posts": 96, "stars": 0 }, "cover_image": { "height": 230, "url": "https://d2rfichhc2fb9n.cloudfront.net/image/4/UWZ6k9xD8_8LzEVUi_Uz6C-Vn-I8uPGEBtKb9jSVoFNijTwyEm1mJYpWq6JvnA6Jd4gzW76vFnbSWvM3jadhc1QxUl9qS4NTKiv3gJmr1zY_UpFWvX3qhOIyKrBPZckf2MrinqWay3H0h9rfqY0Gp9-liEg", "width": 960 }, "created_at": "2012-08-12T17:23:44Z", "description": { "entities": { "hashtags": [], "links": [], "mentions": [] }, "html": "<span itemscope="https://app.net/schemas/Post">Nokia Maps Technologies Evangelist; CreativeJS team member; the tech side of museum140; builder of The Elementals; misuser of semi-colons;rn</span>", "text": "Nokia Maps Technologies Evangelist; CreativeJS team member; the tech side of museum140; builder of The Elementals; misuser of semi-colons;rn" }, "id": "3191", "locale": "en_GB", "name": "Simon Madine", "timezone": "Europe/Berlin", "type": "human", "username": "thingsinjars" } }
Listening to the geotagged post stream
This wil return a link to a long-lasting connection to the app.net stream that will only return posts with the geolocation annotation.
Requires
filter_id
from the previous call
Note: the
filter_id
was returned asid
in the previous response.$.ajax({ contentType: 'application/json', data: JSON.stringify({"object_types": ["post"], "type": "long_poll", "filter_id": "527"}), dataType: 'json', success: function(data) { console.log(data); }, error: function(error, responseText, response) { console.log(error, responseText, response); }, processData: false, type: 'POST', url: 'https://alpha-api.app.net/stream/0/streams?access_token={APP_ACCESS_TOKEN}' });
Returns
The same kind of response as the 'Creating a
streams
format' example except the data coming down on the stream is filtered.https://stream-channel.app.net/channel/1/{LONG_RANDOM_ENDPOINT_URL}
Open that URL up in your browser (seeing as we're testing) and, in a different tab, create a geo-tagged machine-only post (see above). Your post will appear almost instantly after you've submitted it.