Detecting advanced CSS features
I decided to do some fancy typography in my résumé, but I wanted to have a sane fallback for browsers that don't support the latest features. I have my name rotated 90 degrees using CSS3 transforms, but simply applying the transform: rotate(-90deg) CSS property isn't enough to get the effect I want. To make it look right I also need to measure the text (since its dimensions will change depending on the viewer's fonts) and absolutely position it based on that. Standard CSS fallback (where unrecognized rules are ignored) doesn't give a good result. At all.
After a bunch of fiddling and cross-browser testing I found that testing the existence (!==undefined) of properties on element.style worked well. So I put all of my rotation rules in a CSS class that's applied only if the style properties exist.
<style type="text/css">
h1.rotated {
-webkit-transform-origin: left bottom;
-moz-transform-origin: left bottom;
-o-transform-origin: left bottom;
transform-origin: left bottom;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
position: absolute;
left: 0px;
top: 0px;
margin-top: 16px;
}
</style>
<script type="text/javascript">
// rotate the title, if that's even possible
var h1 = document.getElementsByTagName('h1')[0];
var s = document.body.style;
// test for the presence of CSS3 transform properties
if (s.transform !== undefined ||
s.WebkitTransform !== undefined ||
s.MozTransform !== undefined ||
s.OTransform !== undefined) {
// move
h1.setAttribute('style',
'top:' + ( h1.offsetWidth - h1.offsetHeight) + 'px');
// and rotate
h1.setAttribute('class', 'rotated');
}
</script>
Of course, if I could just check for transform: support rather than -webkit-transform:, -moz-transform: and -o-transform: but we're not there yet. Anyway, based on browsershots.org it seems to do the right thing on every known browser. It shows rotated text in very recent Webkit and Gecko browsers, and in Opera nightlies, and unrotated, correctly text everywhere else.
Implementing Information Retrieval Systems
I believe that it's valuable to implement software components that you use from scratch merely to gain a better understanding of the systems you use. I've never implemented an operating system from scratch or designed and implemented a programming language like many of my friends but I'd really like to one day. Part of the problem is that looking at a large, complicated, system from the outside is intimidating.
A couple of years ago I spent many months working with CLucene, the C++ port of the Lucene IR (full text search). Both CLucene and Lucene are huge, complicated and often appear to be buggy. While learning about the Lucenes I did a bunch of reading about full text search theory. It never seemed that complicated, but all of the implementations I came across clearly were.
A basic full text search engine works like this:
- collect all of the words in the input documents
- simplify the words so that similar ones are the same, so I can search for "look" and get "looked", "looking" and "looker"
- organize the words in a special tree data structure called a trie that lets you look up the words in linear time, the leaves of the trie for each word indicate which documents it's present in
- to search for a word you walk the trie and get the list of documents
Yesterday I spent a couple of hours walking (Tel Aviv public transport was failing me) and I worked through most of how to implement this in my head. I also thought about how to implement phrase matching. I'm not really sure how phrase matching works in other systems (like I said, they're too complicated for me to understand easily) but I represent each instance of a word in a document and then link from one instance to the instance of the next word in the document. That way after walking down the trie to find the first word in the phrase I can walk along each word in the phrase, comparing it to the next word in the document.
Last night I implemented it in Python. I called it Tripe and it's less than 250 lines of Python. It doesn't have a stemmer worth talking about and it's pretty space inefficient but it seems to generally work. There's a library file called tripe.py and then some test utilities: tripe_add.py (add documents), tripe_search.py (search the index), tripe_dot.py (visualize the index with Graphviz). Take a look:
% echo "The cat sat on the mat." | ./tripe_add.py test.tripe 1 % ./tripe_dot.py test.tripe|dot -Tpng -o test1.png% echo "The quick brown fox..." | ./tripe_add.py test.tripe 2 % ./tripe_dot.py test.tripe|dot -Tpng -o test2.png
% echo "There is a light that never goes out." | ./tripe_add.py test.tripe 3 % ./tripe_dot.py test.tripe|dot -Tpng -o test3.png
% ./tripe_search.py test.tripe the matched in document 1 at 15 matched in document 1 at 0 matched in document 2 at 0 % ./tripe_search.py test.tripe cat matched in document 1 at 4
I first implemented this as a tree in memory, but I found that implementing persistent storage was really easy too. I've always been hesitant to implement structured binary file formats, and I know that efficient implementation can be really complicated, but a naive implementation like I did was relatively straight-forward.
So now what? There are already a bunch of full-text search engines out there, but they all seem relatively complicated. I wonder if it's worth tidying up what I have, extending it enough to be useful for site search applications and publishing it. In the meantime it's on github.
Flipy, a new Python library for Flickr
In the past day or so I've written a new Python library for Flickr. It came from some frustration using other Python libraries. They're all great, but none of them work quite how I want.
My goal was to have a library that feels like Python and the Flickr API at the same time. I think it's worked out pretty well so far. You can make calls using the standard Flickr API calls as documented on the Flickr site, but the response objects feel like normal Python objects. For example you can do something like this:
from flipy import Flipy flickr = Flipy(MY_API_KEY) me = flickr.people.findByUsername(username='ianloic') me_info = flickr.people.getInfo(user_id=me.nsid) print 'My name is %s. I have %s photos at %s.' % (me_info.realname, me_info.photos.count, me_info.photosurl)
I've put more details about the mapping in the README.
Beyond simple mapping of methods to responses I'm working on decorating certain important response objects such as users and photos with more object oriented methods. For example right now if you have a user object you can call user.photos() and get a iterator for of a user's photos. My code takes care of all of the paging behind the scenes.
Since I haven't implemented authentication or uploading yet so right now it's mostly useful for simple mashup-style applications, but I'll get uploads and authentication complete when I get back from Jordan next week.
Check out the code on github and let me know what you think.
Give me convenience
I used to track WordPress updates, plugin updates and my own manual site changes using git. I wrote a whole post about it. Either fortunately or unfortunately WordPress launched their update framework which is so much easier than what I was doing. I've given up on my old technique and moved to using WordPress' update mechanisms.
Of course what I really want is to be able to white a WordPress plugin that is notified any time an update occurs or a file update completes so that I can automatically track site changes in a SCM, but I don't think that's easy right now.
jQuery selector escaping
jQuery selectors are powerful and simple to use, until you have attribute
values (including ids and classes) that have funny characters. I wrote a plugin
adds a simple function $.escape that will escape any special characters.
For example if I have a string s that contains an id but I'm not sure that
all of the characters are safe I can use $('#'+$.escape(s)) to find the
element with that id. If I want to find all of the links to
http://ianloic.com/ I can do $('a[href='+$.escape('http://ianloic.com/')+']')
The first release is here. I don't know that there will be any more releases - this is a pretty damn simple plugin.
A brighter future for mobile applications?
Since the Chrome OS announcement the other day I've been thinking more about what a world with rich enough web APIs to support all general purpose applications might look like. I'm not sure that it'll happen, but it sounds like Google is putting their weight behind it and they've been successful in the past at moving our industry in new directions (remember the world before GMail and Google Maps?).
A richer set of standard web APIs might form the basis for a cross-manufacturer mobile platform. The Palm WebOS stack already kind of looks like Chrome OS (though with local HTML+JS apps rather than remote ones) and the original iPhone application model was exactly what Chrome OS proposes. The limitations that forced Apple to create a native developer platform are exactly the ones that Chrome OS plans to address.
Of course Google's own mobile platform is decidedly non-web and Apple's much larger volume of applications discourage it from supporting standard APIs. The handset manufacturers, OS developers and carriers are all making a ton of money selling applications in a model that's reminiscent of pre-web software models. The only real winners from a move to a web model for mobile applications would be the users.
Users’ names and usernames
A few years ago my friend Jack built a cute little application. It was a text message multiplexer. You could send it a text message and it would send that message to all of your friends. You signed up using your phone number and gave it your name. It was somewhere between addictive and annoying but completely social, since basically all of the users were our friends. We mostly used it as a free-form Dodgeball, to work out when friends were out at bars and inevitably they added the ability to send a message directly to a contact. There were no usernames so twttr would cleverly work out who you meant based on the first name you supplied and your contacts list. This never worked right, they added usernames and now I'm @ian. Unfortunately then the whole @reply thing happened and people do just use their friends first names. Look at how many people use @ian - most of them are not talking to me, but other Ians.
Facebook resisted giving people anything other than a free-form name and a numeric user ID for the longest time. They finally gave in and let people pick vanity URLs but still refuse to make that URL useful for anything but getting to your profile. When they added @ support they pop up UI to autocomplete friends names from your contacts list. It works really well, but it depends on a rich message composition UI, something that's not possible on the simple mobile devices that twitter was targeting.
I wonder how the next site will approach this.
Google Chrome OS
If I was building an OS today I'd be building what Google just announced.
Like most heavy technology users I've been moving heavily toward hosted web applications over the past few years. I don't use Evolution or mutt anymore, I use GMail. I don't organize my photos on my laptop and use my own hosted Gallery, I use Flickr. I've never been a big office application user, but when I'm forced to open a Powerpoint deck, edit an Excel file or print out a Word document, I do it using Google docs.
I've also spent the past four or five or so years working on blurring the line between what's on your desktop and what's online. At Flock I worked to synchronize your bookmarks to online services and between machines, to integrate personalized web search into your desktop workflow and to make publishing media from your devices as easy as publishing text from your keyboard. At Songbird we developed APIs to allow web apps to interact with your desktop media player and APIs to let your desktop media player access content from the web. At Rdio I worked on similar things, from a slightly different approach, I don't think I can talk about them yet.
I'm really excited that Google has the balls (and the skills) to go all out. To commit to offering enough APIs to web applications to allow them to provide the same functionality and user experience as desktop applications would. This isn't the first time that this has been attempted, but I think this time it just might work. Just a couple of years ago when the iPhone launched and Apple announced that the only way to write applications was to write web applications users and developers rebelled. The iPhone browser wasn't capable enough. Google have taken the right approach by committing to improving the web platform to support whatever APIs are needed before shipping the product.
I'll never be running Chrome OS. I rely on too many specialized applications, but I am looking forward to when Flickr can pull photos right off my camera and GMail's offline features are widely tested enough to actually work right. Much of the innovation in Chrome OS will benefit us all.
Not solving the wrong problem
I like a great deal of what Google does for the open web. They sponsor standards work, they are working on an open source browser, they are building documentation on the state of the web for web developers. It's all really great. Today they posted what they called A Proposal For Making AJAX Crawlable. It seems like a great idea. More and more of the web isn't reached by users clicking on a conventional <a href="http://... link but by executing JavaScript that dynamically loads content off of the server. It's somewhere between really hard and impossible for web crawlers to fully and correctly index sites that work that way without the sites' developers taking crawlers into account.
Google's proposal is to define a convention for URLs that contain state information in the anchor and to define a convention for retrieving the canonical, indexable contents of the an URL with such an anchor tag. First let me dismiss the suggestion that you make a headless browser available over HTTP to render your AJAX pages to HTML out of hand. If it's so easy for HtmlUnit to render your AJAX to HTML, surely Google can do it. And basically offering HtmlUnit as a web service on your server doesn't sound that secure or scalable to me.
The bigger question is that if your solution requires the server to be able to serve the correct HTML for any state, would you come up with the same solution as Google? There is a simple, straight-forward solution that works today and is used on sites all over the internet. If the content you serve includes the static, non AJAX URLs in anchor HREFs but uses JS click handlers to do AJAX loads then crawlers can scrape all of your pages, users of modern browsers get the full shiny experience and users on old mobile browsers that don't support JS get to work for free!
To do this you can either make your AJAX templates include onclick handlers or you can write a simple piece of JS to do the right thing when any link is clicked on. A contrived example using jQuery might look like:
$(function(event) {
$('body').click(function(event) {
var href = $(event.target).attr('href');
// don't try to AJAX absolute URLs
if (href.match('https?://')) return;
// don't let the normal browser navigation operate
event.preventDefault()
// based on event.target.href, decide what AJAX URL to load.
$('#ajaxframe').load('/load-fragment', {path: href});
// update the URL bar
document.location.hash=href;
});
});
This will intercept clicks on relative anchor tags and let your page JS do its AJAX magic. It doesn't require special conventions. If you build your site this way you'll probably find that the state that is in your URL fragments is a the relative URL for the page on your site. So http://www.example.com/random/page and http://www.example.com/#/random/page have the same meaning. That turns out to be a pretty good convention. After all, aren't our URLs supposed to refer to resources anyway?



