December 20, 2010

DIY with Perforce's JavaScript API, Pt. 2

Traceability
Flexible Workflows
Integration
What's New

Part Two: Nuts And Bolts

Here in the second part of DIY with P4JsApi, I’ll dig in and show you how I used P4JsApi, point out some helpful tools, and also point out some things that tripped me up.  First up: How do I debug this stuff?  Onward!

Debugging: WebKit’s Web Inspector Is Your Friend

Once I’d done a little coding using JavaScript, I realized that it’s something of a second-class citizen in the software development world.  As far as I know, there aren’t any IDEs or debuggers for it that are on par with, say, Visual Studio’s C# debugger.  Fear not though, you can set a break point and look at the values of your variables. Perforce’s JavaScript technology is enabled by an HTML rendering engine called WebKit, and WebKit has a tool called Web Inspector that allows you to do some real debugging.

To use the Web Inspector, you’ll first need to insert this snippet into a likely spot in your JavaScript (I put mine in an init() function):

P4JsApi.setWebKitDeveloperExtrasEnabled(true);

Once this is in place, the next time you fire up your applet you’ll be able to right-click or control-click and see the following context menu:

Accessing the debugger

Clicking on Inspect will launch the Web Inspector; take some time to poke around the toolbar to get a feel for what it can do.  To set breakpoints in your code, you’ll need to choose the file that you want to debug; click the Scripts tool button, then select your file from a list of files just beneath the button:

Now you’ll be able to set breakpoints, just like with a real programming tool.  You’ll also get to see the call stack and step through your code.  I found the tool to be a bit awkward, but hey, it’s better than relying on alert boxes for everything.  Once you’re done debugging (hah!), just comment out the call to setWebkitDevelopersExtrasEnabled.

Talking To The Server

Now that you’ve got some HTML and JavaScript running on one of the P4 clients, it’s incredibly easy to start pulling data out of the server.  For example, the code snippet below retrieves the email address for a specified user:

function SetEmailAddress(user){

P4JsApi.p4("user -o " + user, EmailCallback);

}

function EmailCallback(){

var emailAddress = arguments[0].data[0].Email;

//do something interesting with emailAddress here…

}

Very simple, and there are a couple of interesting bits that are worth talking about.  First, about that “EmailCallback”: this is telling P4JsApi where to send the data once it retrieves it.  This happens asynchronously, and it’s very handy if your doing some fancy drawing and you don’t want your drawing code to stop and wait for the server to respond.  Since even a tiny hesitation is noticeable when drawing, waiting for the data on a separate thread is a big win.

The second interesting bit is in the EmailCallback function itself.  See that business with the arguments[0].data[0].Email call?  Basically you just have to know that you get back an ‘arguments’ array, that you want the first entry in that array, and that that entry is an array called data.  I know all of this because I found it with the Web Inspector (and also because I had a couple of hallway conversations with engineers who worked on the JavaScript API project).

By the way, if you don’t need your function to be asynchronous, you can skip the arguments array and do something like this:

function SetEmailAddress(user){

var myUser = P4JsApi.p4("user -o " + user);

//do something interesting with myUser here…

}

I tend to use the asynchronous callback method wherever possible though; you just never know how long that round trip to the server is going to take, and you want to keep your applet responsive; using an asynchronous callback function will keep it from stumbling around like a zombie (and not one of those fast zombies from 28 Days Later either).

Dead ends, Pitfalls, and Opportunities

So a few things slowed me down during my inaugural JavaScript API for Visual Tools excursion.  Figuring exactly where and why the ship wrecked was an important part of my learning process, but I’ll try to save you a little pain by telling you where some of the rocks are.

When I first started this project, I knew I wanted to do my own drawing, but I figured I should at least try to use some canned charts.  So I dutifully checked out Google’s visualization API at http://code.google.com/apis/visualization/interactive_charts.html.  They make some beautiful stuff, but the charts have a couple issues that made them unsuitable for my purposes: first, they weren’t flexible enough; I couldn’t access the colors of individual bars in a bar chart, something that was essential to my design.  Second, some of the charts use Flash, which doesn’t work in the P4 clients.  I was actually happy when these shortcomings showed up, because it was all the excuse I needed to do my own drawing (and to explain to my boss why I was re-inventing the wheel).

Another blind alley was my initial approach to drawing the animated bars; I was getting a lot of flickering, so I decided to use blitting, a technique that game developers use that involves drawing your graphic elements offscreen, then swapping the offscreen elements with the onscreen elements.  In the end, this effort turned out to be unnecessary.  Remember above where I wrote about the roundtrip to the server slowing down your drawing?  As soon as I figured out the asynchronous callback approach, my flickering magically went away and I was able to go back to drawing straight to the onscreen canvas.  Canvas is pretty fast!

Speaking of animation trouble, I also ran into a maddening bug where my bars would just stop moving for no apparent reason.  If I waved my mouse over the applet however, they would magically start moving again.  After much hair pulling, I finally asked one of the JavaScript API engineers if he had any idea what was going on.  It turns out that it was the environment I was programming in; I’m on a Mac, but was using VMWare and programming in Windows.  Something about that combination causes the applet to quit painting when there is no mouse activity.  The (admittedly unsatisfactory) solution turned out to be switching my development environment to the Mac OS, where everything then worked fine.  Clearly there’s a bug there somewhere, probably with the VMWare implementation.  I’ve got an older version of VMWare, so maybe this is fixed by now.

Another bit of trouble I ran into was in trying to save state information.  I’ve got a Preferences dialog, and wanted to save any changes so that the user wouldn’t have to re-do them every time they restart the applet.  It turns out that the HTML5 spec has a JavaScript function called localStorage that promises to do just that, but I couldn’t get it working.  I finally broke down and read our API documentation again, and sure enough, localStorage is disabled.  This is not the end of the story though; in a future release localStorage will be turned on and I’ll be able to save client states to my heart’s content.

My last pitfall/warning is actually more of a potential for optimization.  After I finished the majority of the coding, I went back and read the JQuery docs again and found out about their dialogs (http://jqueryui.com/demos/dialog/).  They look cooler than the dialogs I hand-coded and I could have saved myself a bunch of work.  Check out the whole JQuery UI site for examples of good free widgets: http://jqueryui.com/.  Make sure you look in the upper right-hand corner on each widget’s page at the different examples—each widget has different flavors and some of them are very cool.

Finally…

At this point, I feel like I’ve acquired a good foundation for creating my own custom add-ons for P4 clients.  Hopefully this article will help you get there more quickly too.  I can imagine a P4 world populated by tons of user-made custom applets.  Maybe we could share?

If you like to take a look at my code, or even use the Process Monitor, you can find it at the Perforce Public Depot at:

public.perforce.com:1666

//guest/david_george/ProcessMonitor/