» tagged pages
» logout
Typo
Return to Typo

Encytemedia

(or Cancel)

(Editing anonymously: to be credited for your changes, login or register a new account)

other page actions:

Tags Applied to this Topic

No one has tagged this page.

Typo Wiki Pages

Team Typo’s designer.

Tuesday, June 17, 2008

MacRuby: The Path Forward

MacRuby is quietly making progress. Destined to replace RubyCocoa as the de facto standard for writing Cocoa applications in Ruby; it’s shaping up to become more than just a fun experiment, it’ll soon be a viable choice.

Burn Baby Burn

Unlike RubyCocoa, MacRuby is based on Ruby 1.9, powered by the YARV bytecode interpreter, resulting in significantly faster execution times. From the “Why MacRuby” page:

In MacRuby, all Ruby classes and objects are actually Objective-C classes and objects. There is no need to create costly proxies, convert objects and cache instances. A Ruby object can be toll-free casted at the C level as an Objective-C object, and the Ruby VM can also handle incoming Objective-C objects without conversion.

Also, the primitive Ruby classes, such as String, Array and Hash in MacRuby have been re-implemented on top of their Cocoa equivalents, respectively NSString, NSArray and NSDictionary. As an example, all strings in MacRuby are Cocoa strings, and can be passed to underlying C or Objective-C APIs that expect Cocoa strings without requiring a conversion. And it is possible to call any method of the String interface on any Cocoa string too.

That’s exciting news. MacRuby applications significantly outperform RubyCocoa applications and aim to enable the creation of full-fledged Mac OS X applications which do not sacrifice performance in order to enjoy the benefits of using Ruby.

Syntax

Considering String, Array, and Hash/Dictionary require no conversion, we get both the Ruby methods and the Objective-C methods without needing to cast back and forth.


#MacRuby
components = "foo/bar/baz".pathComponents
# RubyCocoa components = "foo/bar/baz".to_ns.pathComponents # or... components = OSX::NSString.stringWithString("foo/bar/baz").pathComponents
#Objective-C NSArray *components = [@"foo/bar/baz" pathComponents]; # or... NSArray *components = [[NSString stringWithString:@"foo/bar/baz"] pathComponents];

And the biggest change is probably keyed arguments. I’ve grown to like keyed arguments in Objective-C because of their descriptive nature. Normally you can look at a method definition and determine what’s what.


def fetch_url(url, timeout)
  ...
end

However, unless you look up the method definition in the source or documentation, you don’t fully understand what the second argument encompasses during invocation.

obj.fetch_url("http://foo.com", 60)

This is where keyed arguments come into play. Take a look at how this method would be composed in Objective-C.


- (void)fetchURL:(NSURL *)url timeout:(float)theTimeout {
  ...
}
[obj fetchURL:[NSURL URLWithString:@"http://foo.com"] timeout:60.0];

And finally, we could write this method in MacRuby with the new keyed arguments syntax.


def fetch_url(url, timeout:timeout)
...
end
obj.fetch_url("http://foo.com", timeout:60.0)

We get the added benefit of clarity at the cost of verbosity. And finally, there is an additional nuance: CamelCase or under_score. I think I’ve settled with camelCase for now, because it’s not possible (practical ATM) to use underscored Objective-C methods, however, I’ve been told it’s being considered.

The Big Macs: macruby, macrake, macirb, macgem

Once you’ve set yourself up with MacRuby, you should know your programs are given a prefix of mac unless you specify a different prefix when compiling by hand. This means we’ll need to use macruby to invoke our Ruby scripts written for MacRuby.

In a completely convoluted example, lets grab the name of every application running on our machine; print it’s application name, and replace “Applications” in it’s path with “FOO”.


framework 'AppKit'
NSWorkspace.sharedWorkspace.launchedApplications.each do |app| appname = app["NSApplicationName"] fullpath = app["NSApplicationPath"] puts appname puts fullpath.stringByReplacingOccurrencesOfString("Applications", withString:"FOO") end

Save this file and run with macruby thisfile.rb. You should see a list of applications running and their paths, with FOO replacing Applications. Got it? Schweet!

framework is a new method of Kernel that allows you to import any framework, including your custom frameworks. In this example, we’re including the AppKit framework and iterating over the launched applications.

Gems: A ruby in the rough

At the time of writing this article, it’s not possible to use rubygems or macruby gems in the traditional require and run method. However, you can vendor your gems and include them directly which should work perfectly fine. This is a bug that will be addressed very soon.

Get Involved

MacRuby is an official effort by Apple to make Ruby a first class citizen in Cocoa. It’s still very early in development and you’re definitely encouraged to start playing with it, writing about it, and getting involved in the project. And one final note, you can join the very helpful #ruby-osx channel on freenode.net if you’re looking for assistance.

Tuesday, May 06, 2008

Browser-based Developer Tools Come Full Circle

With the release of Opera’s DragonFly, IE 8’s Developer tools, Safari’s/Webkit’s Web Inspector and Drosera; and the Grand Daddy of them all, Firebug – we’ve now come full circle.

Thursday, May 01, 2008

Automating Rick Rolls with launchd

Lets face it: Your coworkers can be a real pain in the ass sometimes. What better way to remind them of this than automated Rick rolls? They’re likely to never know what hit them.

What is launchd?

The launchd daemon takes over many tasks from cron, xinetd, mach_init, and init, which are UNIX programs that traditionally have handled system initialization, called systems scripts, run startup items, and generally prepared the system for the user

launchd stores tasks in plist format. You can find your agents in ~/Library/LaunchAgents. Here is a typical launcd file that will open iTunes every 60 seconds:


  <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0">
  <dict>
    <key>Label</key>
    <string>com.alternateidea.article.test</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/open</string>
        <string>-b</string>
    <string>com.apple.iTunes</string>
    </array>
    <key>RunAtLoad</key>
    <false/>
    <key>StartInterval</key>
    <integer>60</integer>
  </dict>
  </plist>

Obviously this would be seriously annoying, but for the purpose of this demonstration, save this file as “com.alternateidea.article.test” in your ~/Library/LaunchAgents folder.

This script won’t run automatically, so we need to load it up first using the launchctl program:

launchctl load ~/Library/LaunchAgents/com.alternateidea.article.test.plist

Our agent is loaded up now. After 60 seconds, you should see iTunes.app open. Pretty nice, eh?

Ok, so we don’t want to keep running this agent. Lets unload it:

launchctl unload ~/Library/LaunchAgents/com.alternateidea.article.test.plist

Never gonna give you up. Never gonna automate your Mac.

So, here’s the trick: Wait until your coworker goes to lunch or you know they’ll be out of the office for 10 minutes or so. Then, let the mischief begin.

The first thing we need to do is create our agent:


  <?xml version="1.0" encoding="UTF-8"?>
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  <plist version="1.0">
  <dict>
    <key>Label</key>
    <string>com.youtube.rickroll</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/open</string>
        <string>-b</string>
            <string>com.apple.Safari</string>
            <string>http://www.youtube.com/watch?v=eBGIQ7ZuuiU</string>
    </array>
    <key>RunAtLoad</key>
    <false/>
    <key>StartInterval</key>
    <integer>1800</integer>
  </dict>
  </plist>

Everything should be pretty obvious in this plist. The StartInterval is in seconds. I want to be an asshole every 30 minutes (1800) seconds. If you want to be a bigger asshole (or Son of a Bitch) you could set this to run in shorter intervals.

Now, we need to save this and load it up on your coworkers computer. Save this to ~/Library/USERNAME/LaunchAgents/com.youtube.rickroll. And load it up:


  launchctl load ~/Library/LaunchAgents/com.youtube.rickroll.plist

If you want to test and make sure everything is in working order, you can run:

launchctl start com.youtube.rickroll

Question: Did you just Rick Roll yourself?

Now, if your test ran successfully slip back to your desk and wait in anticipation for that awesome intro.

Disclaimer: If you destroy your coworkers computer or end up scrapping in the street, I’m not responsible.

Friday, February 08, 2008

A strftime for Prototype


Object.extend(Date.prototype, {
  strftime: function(format) {
    var day = this.getUTCDay(), month = this.getUTCMonth();
    var hours = this.getUTCHours(), minutes = this.getUTCMinutes();
    function pad(num) { return num.toPaddedString(2); };
return format.gsub(/\%([aAbBcdDHiImMpSwyY])/, function(part) { switch(part[1]) { case 'a': return $w("Sun Mon Tue Wed Thu Fri Sat")[day]; break; case 'A': return $w("Sunday Monday Tuesday Wednesday Thursday Friday Saturday")[day]; break; case 'b': return $w("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec")[month]; break; case 'B': return $w("January February March April May June July August September October November December")[month]; break; case 'c': return this.toString(); break; case 'd': return this.getUTCDate(); break; case 'D': return pad(this.getUTCDate()); break; case 'H': return pad(hours); break; case 'i': return (hours === 12 || hours === 0) ? 12 : (hours + 12) % 12; break; case 'I': return pad((hours === 12 || hours === 0) ? 12 : (hours + 12) % 12); break; case 'm': return pad(month + 1); break; case 'M': return pad(minutes); break; case 'p': return hours > 11 ? 'PM' : 'AM'; break; case 'S': return pad(this.getUTCSeconds()); break; case 'w': return day; break; case 'y': return pad(this.getUTCFullYear() % 100); break; case 'Y': return this.getUTCFullYear().toString(); break; } }.bind(this)); } });

UPDATE: Bugs fixed. Thanks Andrew and Stephen

Also, some of my old and new code has been posted on GitHub. You might find something useful.

Friday, February 08, 2008

DoubleClick and Define using Apple Dictionary


var selection;
if(window.getSelection)
  selection = window.getSelection();
else if(document.selection)
  selection = document.selection.createRange();
document.observe("dblclick", function() { if(navigator.userAgent.include("Macintosh")) { location.href = "dict://" + selection; } });

A quick (and probably dirty) Prototype-based hack allowing Mac users to get the definition of any word by double clicking it.

Monday, February 04, 2008

Processing in Action

<object height="225" width="400"> <param /> <param /> <param /> <param /></object>

A beautiful, organic animation by Flight404 created using Processing.

Tuesday, January 08, 2008

Calling Ruby from JavaScript

Aaron Patterson shows us how to define ruby methods which can be called from JavaScript using RKelly.

Tuesday, January 08, 2008

Pimp My JavaScript Skillz

Dustin might not surprise you at your house with a book in hand, but he’ll damn sure send you his innermost fu in the form of a shiny yellow and black book. It’s an excellent read if you’re looking to put more funk in your functions and class in your classes. Check out “Pro JavaScript Design Patterns” by Dustin and Ross Harmes.

—I’m Justin Palmer and I approve this message.

Thursday, December 27, 2007

Widgets the YUI Way

Why are there so many lightbox implementations? Why are there numerous “versions” of widgets? Do you know which fork is the latest and greatest? Probably not, and for good reason. The current system sucks.

Over the past few days I’ve been studying the YUI library and it’s accompanying ecosystem. It’s truly a well oiled machine. But besides being developed by a web giant, what makes it tick? There are numerous things I’ve come to conclude, and it shines a light on why widget efforts by Prototype developers et al have largely failed.

The public face

  • Documentation: This is the most obvious one, but the one we get wrong 90% of the time. If you’ve written a widget, you have intimate knowledge of it’s inner workings and how to use it, knowledge your audience doesn’t have. We make the assumption that consumers will understand our products on the same level we do. This isn’t the case. Even YUI beta code has full documentation and a host of examples.

  • Centralization: If you want a YUI calendar control, where is the first place you look? On the YUI site. If you search Google for YUI, you get the official YUI calendar and also a collection of blog post showing some cool tricks and general usage of the official control. If you want to learn how to use the YUI Calendar control, where is the first place you look? The YUI site. It’s all organized and centralized.

  • Standardization: Everything is standardized. Everything from how articles are written and structured to the release cycles and code conventions. Take a look at the Drag and Drop landing page and then the Dom Collection landing page. Notice the clear and concise explanation of what the component is. Also note the bullet points for quickly navigating a page, the “Getting Started” headers, the cheat sheets and the examples—It’s extremely helpful. Don’t forget about YUI Theatre either.

The code

  • Modularity: The code is extremely well structured. It’s not all or nothing. I’ve not been a fan of deep namespacing (e.g. YAHOO.widget.Menu), but in this context I think it’s important. It not only has utility, it creates a visual hierarchy with only small overhead. I definitely prefer common utilities such as $('foo) and baked in enumerations (augmenting native prototypes), but I’m fine with doing var menu = new YAHOO.widget.Menu("mymenu");.

  • Event Driven: This one was enlightening for me. Most of YUI’s widgets rely heavily on custom events to pass messages internally and externally. Notice how all of the “interesting moments”, both private and public are handled through events setup in a Menu’s initialization routine. None of them are directly invoked by another method.

  • Inline documentation: I debated whether this was important enough to warrant a bullet point (it was encompassed with the public api docs), but as I continued to study the code the inline API comments were indispensable. Whenever I wondered what an argument meant, I could see it documented a few lines above where I was scanning the code. Check out this snippet from YUI’s CustomEvent object.


    /**
     * Notifies the subscribers.  The callback functions will be executed
     * from the scope specified when the event was created, and with the 
     * following parameters:
     *   <ul>
     *   <li>The type of event</li>
     *   <li>All of the arguments fire() was executed with as an array</li>
     *   <li>The custom object (if any) that was passed into the subscribe() 
     *       method</li>
     *   </ul>
     * @method fire 
     * @param {Object*} arguments an arbitrary set of parameters to pass to 
     *                            the handler.
     * @return {boolean} false if one of the subscribers returned false, 
     *                   true otherwise
     */
    fire: function() {
     //...
   }
   //...

Notice the method signature for fire. At first glance it looks like it doesn’t take any arguments, but take a look at the signature in the documentation. Pretty nifty trick. Notice this line in the comments: @param {Object*} arguments. The * tells JsDoc Toolkit to treat the following word as an argument for the method. JsDoc Toolkit can do some really cool stuff and has made vast improvements since it’s inception as JSDoc. It also now speaks Prototype.

Summing up

We can learn a lot from YUI, it has some of the smartest minds in the industry working on it. I encourage you to have a look yourself, study it, and take note of their practices and apply them to your own ventures in the world of widgets.

Sunday, December 23, 2007

IE 8 Passes Acid 2

IE 8 PAsses the Acid 2 Test for those not keeping tabs on things.

Monday, December 03, 2007

I'm Officially a Portland Local

2,300+ miles, 3 1/2 days and 10 states later, we’ve completed our move from Memphis, TN to Portland, OR.

Wow, what a drive! The Oregon countryside is breathtaking. Wyoming and Utah were also beautiful, but the drive alongside the Columbia River in Oregon made the trip that much more enjoyable.

My wife and I, along with a long-time friend of ours, took one of the most amazing cross-country drives you could take. We’ve been planning the move to Portland for some time, and now we’re officially here.

One of the primary reasons for moving to Portland was to consolidate Active Reload into one city. We’ll be sharing an office with the talented guys over at Kongregate and couple people from Caboo.se. I expect some exciting things to happen as a result of this.

In addition to the Active Reload news, Portland is just a great city full of talented people, not to mention the excellent food and topnotch beer. If you’re in Portland, maybe we’ll get together and experience some of that great food and beer.

Friday, November 09, 2007

Your Momma's So Fat...Prototype vs. JQuery Edition

The time has come once again to clear the air of fallacious statements by the myriad of people comparing Prototype to JQuery. I’m all for comparison; I believe it’s healthy to have choices in life. The problem usually isn’t Prototype or JQuery, it’s the article comparing them.

Fair and balanced

If you’ve spent your nights watching Fox News, you probably have an ill-advised definition of “Fair and Balanced”. Hell, we can’t have a decent discussion about Prototype, JQuery, Mootools, etc, without the zombie loyalist coming along and flooding the comments with “JQuery is Better”, “You should use Prototype”, etc. Stop it! Please, for the love of the web, stop doing this! It’s reminds me of the Ron Paul minions during a political discussion on the web. How are you forwarding the cause or providing anything relevant to the discussion by spouting off one-liners? We all have the same goals in mind. We all want to improve the web for developers using a language we love.

How not to write a comparison

I stumbled upon this post today. This is not fair, nor balanced. It’s a one sided argument that makes very little or no effort to define why. It basically shows how you can perform some tasks in JQuery and implies that it’s not possible or harder to do in Prototype. If you know better, they both perform fairly well doing the tasks shown in the article. Lets examine the article’s claims.

Behavior Driven Development

This has absolutely nothing to do with Behavior Driven Development. Behavior Driven Development is a form of testing. Please see Dave Astels paper titled “A New Look at Test-Driven Development”(PDF) if you want an overview of BDD.

If this isn’t BDD, then what is it? What I believe the author intended to say was that you can have a “Behavior Layer”, which is something completely different. A Behavior layer is basically a way to define behaviors separate of your content, structure and style. But the article states that as a reason for swapping, passively implying that this isn’t possible with Prototype. It is, here’s how:


$(element).observe('click', function() { });
// Or custom events
$(element).observe('article:updated', function() { });

I could go on to write a JQuery example of custom events, but I don’t know enough JQuery to make a comparison. If you provide one in the comments, I’ll gladly post it.

A Complex Example


//JQuery
$("div.speciallinks").attr("href","javascript:void(0)")
    .click(function() {console.log("div.speciallinks clicked");})
    .hover(function(){$(this).addClass("hovered");}, 
             function(){$(this).removeClass("hovered");});

Duct Tape Man I can’t even begin to describe how bad this is. You should never write code like this. There is no beauty in this example, and at best it’s barley legible. This is what I like to call a “Duct Tape” example. Duct Tape is great, but you wouldn’t use it to fix a leaky roof.

While at RubyConf, Stuart Holloway talked about code that ”just doesn’t look right”. If you balled this up into a function, I believe he would have called this a ”poorly hung method”. There is no beauty in this example. I’d love to go on to explain what code beauty means, but Marcel Molina has already done this much better than a common man like myself can. I highly encourage you to check out Marcel’s talk “What Makes Code Beautiful?”.

The firs thing I can’t understand is if div.speciallinks is an element, or a collection of elements because JQuery uses $ for both and from my understanding can either return an collection of elements or a single element (Array or JQuery object). I’ll write this in Prototype, assuming div.speciallinks is a collection. Also, javascript:void(0) is just bad practice. I could write this a few other ways, but to keep consistent with the example above, here is a condensed version.


//Prototype
$$('div.speciallinks').each(function(link) {
  link.observe('click', function(event) { event.stop(); console.log('div.speciallinks clicked'); }
  link.observe('mouseover', function() { this.addClassName('hovered'); }
  link.observe('mouseout', function() { this.removeClassName('hovered'); }
});

Chaining everything together is a nice trick, works well up until a certain point–the point where no one can comprehend what you’re doing. Choosing LOC over legibility is a sure fire way to end up with a child only a mother could love.

MVC+J

Really? Come on, this is fairly convoluted. Prototype has little to do with Rails other than the fact it’s included by default, and you can use the Ruby helpers. For those of you who want to use JQuery with Rails and want Ruby helpers yourself, there is jq4r. You don’t have to use the helpers—I don’t. I prefer to write my JavaScript using JavaScript. I also keep my JavaScript where it belongs. Hell, if you still want to use the Rails helpers unobtrusively, use UJS. JavaScript isn’t a component of MVC—it’s a language. And once again, the explanation has nothing to do with BDD.

Chaining of Actions

Haven’t we been over this before? This is possible with Prototype, but I use it conservatively. I personally find it hard to read and hard to debug. A few chains, great, but once you get beyond that it’s cumbersome.


$(element).show().insert("<p>Action has been executed successfully"</p>").addClassName('flash');

CSS Selectors

We have great support for CSS Selectors. The author states: ”Prototype does supports CSS Selectors via $$ function, but it doesn’t fully leverages the power of CSS Selectors. “ What the hell does that mean? Please explain fully leverage.

No more checks for presence of an element

Again, this has a bad smell to me. If statements aren’t considered harmful. It’s part of that clarity thing. So, I check for the presence of an element once, and run the code within the conditional. This leads me to believe there is a check every time you attempt to access an object in JQuery, and unless you chain methods, this check will be run with every method you attempt to call on the object. Feel free to speak up if I’m wrong.

Aids development process

Great! Thats the purpose of any library, those of us in Prototype land share the same luxury.

Which Library Should You Use?

Whatever makes you happy. It’s that simple.

UPDATE: Case in point

Wednesday, October 24, 2007

Upgrading Radiant to Prototype 1.6

Mislav has written up an excellent overview on upgrading Radiant (which used 1.5) to Prototype 1.6. The article does a great job of showing how 1.6 is superior to 1.5.

Friday, October 19, 2007

Daily Show Archives Online

According to the New York Times, the Daily Show Archives, with over 7,000 videos are online—free and searchable.

Thursday, October 11, 2007

Telemarketer gets owned

<object height="353" width="425"><param></param><param></param><embed src="http://www.youtube.com/v/un_PjRXV5l8&amp;rel=1" height="353" width="425"></embed></object> </o>

Wednesday, October 10, 2007

Making an iPhone app in about a minute

<object height="301" width="480"> <param /> <param /> <param /> <param /></object>

Erik Kastner shows us how it’s done.

Tuesday, October 09, 2007

Prototype Inheritance Updates

Defining classes and inheritance in Prototype–Incase you’ve missed it, Prototype 1.6 has a new inheritance model.

Tuesday, August 21, 2007

This Hurricane Rocks!

Hurricane Dean’s projected path. The most awesomest hurricane ever.

Tuesday, August 07, 2007

Dealing with Asynchronous Queries in Adobe AIR

Nearly all the methods of Adobe’s SQLConnection class are asynchronous. While this is nice when your running expensive operations (your interface won’t hang up), it can be quiet tough to deal with considering we come from a land where database operations have always been synchronous.

Before we begin, you might want to download this SQLite database .

A short example

If you’ve been using ActiveRecord like me, you’ve probably forgotten how to write SQL. Well, dust off your SQL skills because you’ll need to know how to write queries if you plan on using Adobe AIR’s SQLite support.


var connection = new air.SQLConnection();
connection.open(air.File.applicationResourceDirectory.resolve('development.sqlite'));
var statement = new air.SQLStatement(); statement.sqlConnection = connection; statement.text = "SELECT * FROM contacts"; statement.execute();

The code above, albeit a little long winded, is the bare necessities for executing a query. While it looks fairly straight forward on the surface, we’ve already run into our first problem. If the execute operation is asynchronous, how do we know when we can begin to retrieve and manipulate the data returned? We can do it one of two ways, register an event listener or pass a Responder object to the execute method.

Using a Responder to handle results

The first option we have is passing a Responder instance to execute. A Responder is an object that encapsulates two functions, one for success that passes an SQLResult object, the other for a failure that passes an SQLError object. Lets modify our execute method:


statement.execute(-1, new air.Responder(
 function(result) {
   result.data.forEach(function(row) {
     air.trace(row.name);
   })
 },
function(error) { air.trace(error.message); }));

If you execute this, you should get the names of our contacts printed to the terminal. Awesome! Now, change the query to add a table name that doesn’t exist, you should be the error message printed to the terminal.

The benefit of using a Responder object is there is no need to register and remove event listeners. In addition to that, there is no need to call the getResults method (see below) on the SQLStatement instance.

Registering Event Listeners

The second option we have is to register event listeners. Starting back at our original code, we can modify it to reflect the changes below.


//Directly below statement.sqlConnection = connection
statement.addEventListener(air.SQLEvent.RESULT, function callback(event) { var contacts = statement.getResult().data; contacts.forEach(function(contact) { air.trace(contact.name); }); statement.removeEventListener(air.SQLEvent.RESULT, callback); });
statement.addEventListener(air.SQLErrorEvent.ERROR, function error(event) { air.trace(event.error.message); statement.removeEventListener(air.SQLErrorEvent.ERROR, error); });
statement.text = "SELECT * FROM contacts"; statement.execute();

This is a lot more code and it looks fairly nasty in comparison to good ol’ synchronous connections. Unfortunately, this is the world we live in with AIR. The shit hasn’t really hit the fan yet, let’s see what happens when we try to run a new query directly after our first one.


...
     statement.text = "SELECT * FROM contacts";
     statement.execute();
     statement.text = "SELECT * FROM cases";
     statement.execute();

We get an error: ”Error: Text property cannot be changed while executing.” Oops! It seems we can’t execute two queries simultaneously. We have to wait for one query to finish before the other one begins1.

Dealing with multiple dependent queries

You can run single queries all day, preferably using a Responder object, but what happens when you need to run multiple queries and the later query is dependent on the results of the first one? It’s not pretty, but the cleanest way I’ve found to do it is to nest callbacks. I’m going to dump a load of code on you, and you can take a moment to soak it in.


 var SQLiteConnector = {
   connect: function() {
     return (function() {
       var connection = new air.SQLConnection();
       connection.open(air.File.applicationResourceDirectory.resolve('development.sqlite'));
       return new SQLiteAdapter(connection);
     })();
   }
 }
var SQLiteAdapter = Class.create(); SQLiteAdapter.prototype = { initialize: function(connection) { this.connection = connection; this.statement = new air.SQLStatement(); this.statement.sqlConnection = this.connection; },
execute: function(sql, params, callback) { this.statement.text = sql; this.statement.clearParameters(); for(param in params) { this.statement.parameters[':' + param] = params[param]; }
this.statement.execute(-1, new air.Responder(function(results) { if(typeof callback == 'function') callback(results.data); }.bind(this), this.onFail.bind(this))); },
onFail: function(error) { air.trace(error.message); } }

The above code is written using Prototype, but it’s fairly simple to see how to adapt this to PAJ. With that disclosure, lets see what’s going on here.

The first thing I’ve created is a singleton object that connects to the database and returns an instance of our ‘SQLiteAdapter’. The SQLiteAdapter just makes our life easier by wrapping some of the lower level database operations and also deals with setting up our callbacks so we don’t have to. Lets put it to use.


 var connection = SQLiteConnector.connect();
 connection.execute('SELECT * FROM contacts WHERE name = :name', {name: 'Bob'}, 
 function(contacts) {
   air.trace(contacts[0].name)
 });

So, I’m passing 3 arguments to execute, the sql, parameters and callback. The sql is an unprepared string (notice ’:name’). The parameters are what we use to replace the placeholders in our sql text. See here for more info on parameters. The final argument is our callback and it also accepts an argument of it’s own, our data returned by the query.

If you look in the execute method, you’ll see that we’re passing our own function to a Responder object, and within that function, we invoke our callback, passing it the results of the query via results.data. The bind isn’t necessarily needed, but if we wanted to invoke methods of our class, we use it to control the execution scope. If you run this you should see ”Bob” printed to the terminal. But now that we have a contact object, how can we use that in another query? Nested callbacks is the most efficient way I’ve found so far.

Why nested callbacks? Why not a separate function somewhere else in our file? The answer for me is simple; I want to group related code, I want to read my code as if it was being executed from top to bottom, I don’t want to have to fumble through potentially thousands of lines of code to find the callback that responds to a certain query. This is personal preference, if you want to use named functions or the like, go ahead. But, on with the show, what does this look like?


 var connection =  SQLiteConnector.connect();
 connection.execute('SELECT * FROM contacts WHERE name = :name', {name: 'Bob'}, 
 function(contacts) {
   connection.execute('SELECT * FROM cases WHERE contact_id = :id', {id: contacts[0].id},
    function(kase) {
      air.trace(contacts[0].name, kase[0].title);
    });
 });

Scary stuff, I know, but lets look at what’s going on here. The first thing we do is fetch a contact record from the database, and in the callback for this query, we execute another query that gets a case record for that contact, again passing in a callback so we can finally manipulate the data. In a traditional synchronous operation, this might look like the code below.


var bob = connection.execute("SELECT * FROM contacts WHERE name = :name", {name: 'Bob'})[0];
var kase = connection.execute("SELECT * FROM cases WHERE contact_id = :id", {id: bob.id})[0];
air.trace(kase.name);

So, it all boils down to either a) nested anonymous functions or b) named functions spread throughout your code.

AIR: The Good, Bad, and Ugly

The Bad

  • No command line support, won’t be in 1.0 either.
  • Can’t open native applications from within an AIR app.
  • Psychedelic 70’s Desktop. Just look at the samples, not one with a native look. It’s really easy to slap something together, but it’s also possible to create native looking applications (Minus the unified toolbar on OS X).
  • No direct Ruby support. You can use a local proxy server, but that server can’t be started from the application, so it has to be hand cranked if you want to use Ruby to write AIR applications.
  • No XPath support (document.evaluate). Webkit has this, not sure why AIR doesn’t considering it uses what I assumed to be a fairly recent Webkit.
  • Incomplete documentation (Note the Responder object says nothing about how we used it in this article, only refers to NetConnection.
  • All the developers seem to be on the bus. I’ve yet to get a response on a forum post or email.
  • Few details on when the next beta will be released or exactly what features/bugs will be addressed.

The Good

  • Cross platform
  • One CSS implementation, One JavaScript implementation. Sweet harmony.
  • HTML and JavaScript or Flex and Flash
  • Extensions to JavaScript, File System Access, SQLite support
  • Free Book and Videos
  • You can use Prototype, JQuery, Mootools, Dojo, YUI, it doesn’t matter.

The Ugly

  • Asynchronous Database operations
  • Did I mention the non native look?

Wrapping up

Despite my criticisms of AIR, I’m really really excited about it. It has real potential and I think we’ll see a majority of the issues outlined here addressed in future releases. I encourage you to download the host of free applications and play around with it.

Page 1 | Next >>
Username:
Password:
(or Cancel)