Pathfinder Blog
Topic Archive: Javascript Libraries

Blackbird takes the pain out of JavaScript logging

Blackbird screenshot

I'm excited to announce the arrival of Blackbird, an open-source JavaScript logging and profiling utility written by G. Scott Olson, a former colleague from my days at Orbitz Worldwide.

A previous iteration of Blackbird provided no-nonsense, cross-browser logging on a variety of projects within Orbitz. Since that iteration, known as jsLogger, Scott has re-written the code from the ground up; provided tons of useful new features, including custom namespacing and a spiffy new graphical interface; and released it under an MIT license.

Why, you might ask, in the age of Firebug, would anybody need a JavaScript logging utility? Simple:

  • Blackbird works in a wide variety of modern browsers. Write one style of log statement for every browser.
  • Blackbird can be deployed to production. By stubbing out its public API with empty functions, you can leave your log statements in production code. (I'm not endorsing this approach to code maintenance, just pointing out that Blackbird makes it easy.)
  • Blackbird does one thing and does it well. It's not a debugger, it's just a logger and profiler.
  • Blackbird doesn't interfere with Firebug's console.log utility, but it does improve on its interface. You can hide or show the Blackbird modal with a single, customizable keystroke. You can also choose from four levels of logging (debug, info, warning and error) and atomically toggle their visibility within the console.

The Blackbird project now lives at Google Code, where you can download it and learn about how to contribute.

My IBM developerWorks series, part 2: Tooltips, lightboxes and more jQuery goodness

IBM developerWorks recently published my second "Ajax overhaul" tutorial. This series teaches intermediate-level developers how to layer Ajax features atop old-fashioned CRUD applications. My colleague Dietrich Kappe calls this the "Christmas tree" approach to Ajax development, and it's a valid choice for many companies. It's not the sexiest take on Ajax, but it often provides a lot of value for relatively little cost.

"Ajax overhaul" tackles a fictional shopping site for its use cases. Each installment examines a particular deficiency in the existing application's user experience and improves it using Ajax and progressive enhancement. Because I focus on the interface layer, the code examples feature only client-side code. I've completed four installments so far, each one employing some combination of jQuery, custom JavaScript and custom CSS.

I previously posted about the first installment. Here's the direct link to the second:

Ajax overhaul IBM developerWorks

Recent Ajax Framework Releases/Developments

Some noteworthy Ajax Framework releases have come out in the last few weeks, along with some other news of interest:

  • Ext JS 2.1 and Ext GWT 1.0 Beta - Better performance, new Slider, StatusBar components. REST support (support for other HTTP methods beyond POST and GET). The Ext GWT 1.0 Beta consummates the love affair between GWT (Google Web Toolkit) and Ext that was started by gwt-ext and MyGWT, but provides the comfort of knowing that it is supported by the Ext JS folks. Note: Ext GWT is pure GWT, not an Ext JS wrapper.
  • Dojo 1.1 - First off, API compatibility between 1.0 and 1.1. Unified timing loop (ala Scriptaculous) for animation effects, with increased performance. Syntactic improvements to dojo.query. Unification of XHR functionality into dojo.xhr() function.
  • Backbase Enterprise Ajax 4.2 - Backbase has been in the commercial framework game longer than almost anyone. Among the new features: hierarchical data bindings and improved performance. If you've wanted data binding for tree widgets, have a look.
  • Google Search, Feed and Translation API - I opined a while back that Google discontinued their SOAP search API because they didn't want people reordering or otherwise manipulating their search results. Looking at the terms of use of the new REST service, you can see that this continues to be a concern:  You agree that you will not, and you will not permit your users or other third parties to: (a) modify or replace the text, images, or other content of the Google Search Results, including by (i) changing the order in which the Google Search Results appear...
  • Google App Engine - it only runs Python apps right now, and it's a preview release available to a select few, but you can already see that this is Google's challenge to Amazon's EC2 compute cloud. In at most a year, unless you are security sensitive -- health care, financial services -- or running on Windows, you won't be building and maintaining data centers. The capital requirements for launching sophisticated and scalable online services is about to change.
  • Echo3 (beta) - it's getting close. Superior performance to Echo2. Easier development of new components. Automatic serialization of objects between client and server. All HTML rendering now done on client. Overall the JavaScript client code is now of a design quality on par with the server code.

Lots of exciting developments for Ajax developers and Web 2.0 entrepreneurs. I, for one, can't wait to see how the Google App Engine compares to EC2 for deploying and scaling Facebook applications.

Technorati Tags: , , , , , , ,

jQuery fade-in spoiler revealer: The failsafe, progressively enhanced version

Io9_morning_spoilers
***Spoiler Alert***

Don't read too much further if you haven't seen "Citizen Kane" and want to be surprised when you do.

I'm a big SF nerd and lover of teen-angst dramas. (That's why "Buffy the Vampire Slayer" is the Best Show Ever.) I love spoilers, but only when I've asked for them. When watching the entire run of "Dawson's Creek" on DVD years after it aired (don't ask), I accidentally spoiled Matt Laffey, the buddy who'd turned me on to the series, on how the big romantic triangle ended up in the final episode. I had no clue he had been saving Season 5 for a rainy day.

Since then, I have endeavored to include a ***SPOILERS*** alert in the subject line of any surprise-detroying emails - and to make liberal use of the return key to make sure the contents of such messages are below the fold in email clients with preview panes. This has made me a much more responsible citizen of fandom - though it hasn't lessened my outrage when entertainment websites post spoilers in their headlines or intro paragraphs without warning me and my fellow geeks. (Sci fi blog io9, by the way, is pictured to illustrate the right way to do things.)

Hats off, then, to Chris Coyier of tutorial site CSS-Tricks for his recent post on using jQuery to create a Fade-in Spoiler Revealer for use on websites. My only reservation about Coyier's technique was its reliance on JavaScript, and only JavaScript, to hide spoiler-laden content. With RSS and mobile browsing on the rise, lots of people read content in user-agents without JavaScript support. Shouldn't we try to protect them, too? I commented to this effect on the original article, then realized that I should just write the code myself as proof of concept.

Continue reading »

Alternate approaches to IE6 and transparent PNGs

Who knew IE6 and transparent PNGs could inspire so much discussion?

When you're churning out tips and tricks on a regular basis, you quickly learn to state things like this:

"I had Problem X, and my solution was Y given constraints Z."

... instead of this:

"The only way to solve Problem X is Y."

Ajaxian_logoI was reminded of this lesson last week, after I posted a series of beginner-level tutorials about overcoming some of IE6's shortcomings with jQuery. My two-part piece on transparent PNG support got picked up by the kind folks at Ajaxian, and boy did the comments come. Between the two sites, we've heard from 20 people so far. A few were from the usual cranky haters. (Thanks to whoever felt the need to write, "Is this year 2001? IE6 png transparency? News on Ajaxian? Good heavens..." instead of just moving on to the next post.) But the rest provided lively debate and some valuable alternative approaches to the problem.

For review, here are the posts (with comments):

And here are some of the various approaches suggested:

Continue reading »

Pathfinder website relaunch: Simple tools for simple problems

Pathfinder20
At long last, Pathfinder's new website launched on Friday. To see the results of all my fiddling with Radiant CMS, point your browser at www.pathf.com.

There's actually no Ajax and very little DOM scripting involved in our first iteration. Just a little jQuery to progressively enhance some links. Our strategy in redeveloping the website quickly and iteratively was to use simple tools for simple problems. jQuery fits the bill perfectly. Eventually we hope to show off our Ajax chops on the Showcase section of the site. In the meantime, we got a new site up and running in two months with part-time commitments from a team of five - and that includes our client, the CEO. Result!

An in-depth look at our use of jQuery and why we dig it after the jump.

Continue reading »

More jQuery plug-ins: jCarousel and the dreaded window.alert()

In addition to the jQuery UI Tabs plug-in I mentioned in yesterday's post, I've been playing with jCarousel, a fairly mature component for building DHTML and Ajax slideshows out of images or arbitrary markup. Overall, I was impressed enough with jCarousel to build half of my latest piece for IBM developerWorks around it. But despite the component's many configuration options, I found that I had to do some hacking to bend it to my will. jCarousel can get a little confused when you load dynamic content, especially if that dynamic content includes blocks of markup rather than just individual image files. It miscalculates some widths in its behind-the-scenes DHTML trickery, causing images to load but never appear in the carousel. To get around this problem, I used the brute-force approach of applying a hard-coded width to my carousel:

//build the carousel
jQuery('#imageCarousel').jcarousel({
	itemLoadCallback: itemLoadCallback,
	scroll: 1
});

//now recalculate the width of the unordered list
//that makes up the carousel items
jQuery('ul.productImages').css("width","3012px");

Continue reading »

Eating crow on jQuery

When I was first getting acquainted with jQuery, I blogged about it quite a bit (here, here and here). There was a lot I liked, but I got easily frustrated. Having previous experience with Scriptaculous, I found the built-in effects features and even the Interface plugin a little under-featured. In particular, the lack of fine-grained queue control bothered me. I also struggled at first with jQuery's lack of built-in object and class factories. I engaged in back-and-forth with jQuery team members about my posts. Then I moved on to other projects.

Recently, however, I returned to jQuery for Ajax Overhaul, my upcoming series of tutorials for IBM developerWorks. Ajax Overhaul tackles how to improve the user experience of dusty pre-Ajax applications with unobtrusive JavaScript. My series shows how to install lightboxes, modal dialogues, form validation and other Ajax goodies with minimal changes to server-side infrastructure. It was therefore imperative that I use a toolkit engineered from the ground up for progressive enhancement. Naturally, I thought of jQuery.

In the process of writing the first couple of installments, I discovered the not-so-secret ingredient to jQuery's success: the plugin ecosystem. By officially sanctioning a broad range of plugins and providing hosting, support and PR for mature plugin libraries, the jQuery team has galvanized a really impressive developer community. So far in my writing for the IBM site, I've used Greybox, Thickbox, jTip and jQuery Forms - all really fantastic tools that empower even novice Ajax programmers to do really cool things with very little custom code and 100% adherence to progressive enhancement.

As long as plugin authors follow the rules, their libraries offer the same compatibility as the core jQuery distribution. (See this post, including comments, for details on how to write plugins that don't create namespace collisions.) My only gripe with the plugin landscape is that it's so scattered. Despite the availability of hosting on the jQuery servers, lots of plugin authors opt to host their own sites. There's no one-stop shop like the one for, say, Firefox.

I still haven't found a good plugin for managing classes, mix-ins and other inheritance schemes, but just by typing that I'm sure I'll get comments to the contrary. Regardless, it's easy to use jQuery alongside, say, MooTools.

As for my desire for fine-grained control of jQuery's effects queues, all it took was one enterprising plugin author. jQuery team member Rey Bango pinged me a few months ago about developments on this front, and now the code is live. I haven't yet worked with Luciano G. Panaro's Fx Queues plugin, but at first glance it looks like exactly what I need. As Really Simple History matures, I'm looking to get involved in other open-source projects. Contributing to the jQuery ecosystem is at the top of my list.

Technorati Tags

All guns blazing on Ext JS 2.0

The day has finally arrived: Ext JS 2.0 received its final public release today. Judging by the timeouts I've been getting all morning when trying to connect to the Ext site and blog, I'm not the only one who's been waiting anxiously. Originally tied to the YUI framework but now a standalone library, Ext provides the best example I've yet seen of a completely client-side framework for desktop-style RIAs.

As Ajax libraries multiply, they need to differentiate themselves to get my attention. Ext does just that, providing a top-to-tails solution for GUI components: tabs, trees, grids, you name it. Prototype, jQuery, Mootools and Dojo provide powerful general-purpose tools for Ajax development. Ext, on the other hand, uses a component model whose baseline configuration gets you 90% of the way there and whose extensibility gets you the rest of the way. It's a huge achievement that Ext does so while embracing Web standards and user experience design. You don't use Ext to sprinkle a little Ajax on your Web 1.0 application; you use it to build powerful, data-centric applications from the ground up without relying on GWT or .Net to write your JavaScript for you.

For the example application I'm building to show off Really Simple History, I'm constructing parallel versions with Prototype, jQuery and Ext. Frankly, it's the Ext version that I'm most excited to show off. I've been waiting patiently for a couple of months to find a hole in the schedule of busy Ext creator Jack Slocum. Now that Ext 2.0 is out there, I hope to publish an interview with him in this space soon.

With the Ext servers struggling to meet demand, you may have trouble trying to download the library, read the announcement or peruse the completely overhauled documentation. In the meantime, you can read this exhaustive Ajaxian post about all of the changes and new capabilities.

Technorati Tags

Really Simple History and the compulsion to tinker

I committed several weeks ago to roll Really Simple History 0.6 RC1 over into a final release on Dec. 1. That's still the plan, but I'm having trouble resisting the urge to tinker. I'm starting to see how a project like this could remain in indefinite limbo if I don't just draw a line in the sand and release a final "hard" version that people can rely on for production code. The current SVN trunk includes a number of backwards-compatible enhancements. I'm working hard to test them all as comprehensively as possible, but I'm going to avoid adding risk to 0.6. Instead, I'll save them for 0.7, which will come out in beta before Christmas.

The enhancements targeted for 0.7, some of them already complete, include the following:

  • Configurable location for the blank.html file: You'll be able to override the name and directory location of blank.html in case your server architecture demands that it be served from a different location.
  • Automatic handling of history points whose keys contain spaces or other special characters; encodeURIComponent is my friend.
  • Automatic title changes: You'll be able to pass a third argument to dhtmlHistory.add and update the document title with its value. You'll also be able to pass in a base document title through the options bundle so that each call to add just swaps out the subtitle.
  • One-line initialization: You'll be able to pass your listener into the dhtmlHistory.initialize without having to call addListener separately.
  • Private methods that are actually private: Methods that are currently marked "private" but are accessible to anyone will become inner functions with no public access.
  • Better global namespacing: I was hesitant to change the API for the 0.6 version, but I'd like to follow the YUI model and group all of RSH's objects into a single global variable. Instead of dhtmlHistory and historyStorage, we'll end up with RSH.history and RSH.storage.

The roadmap for 1.0 includes the following:

  • JSUnit and Selenium test suites.
  • Adapters for major frameworks using the MooTools model.
  • Customizable downloads.
  • Serious tutorials and documentation.

In the meantime, downloads for 0.6 RC1 have crept to just under 350 almost 700. Look for me to "flip the switch" on Saturday, promote 0.6 to the stable production version, and deprecate 0.4 once and for all.

Technorati Tags

DOMContentLoaded and the quick rise of de facto standards

There's never been a better time to be a JavaScript developer. JS hit the big time with the advent of Ajax, and overnight client-side programmers went from being the redheaded step-children of the web-development world to front-and-center participants in the RIA revolution. Even if you're working on a team of one, there's a thriving global community of JavaScript gurus constanly pushing the language forward, working out browser kinks, demonstrating how to import concepts from other languages and computing paradigms, and otherwise inspire you. It's a big change from 10 years ago, when you had Webmonkey and the David Flanagan book and a few cookbook-style compendiums of tips and tricks. This is seriously the golden age.

Working for the last couple of months on Really Simple History, though, I've become interested in the way specific techniques become the flavor of the month and quickly redefine how we conceptualize "good" JavaScript coding practices. Take, for instance, DOMContentLoaded. After the first version came onto the scene, within a matter of months we had many competing implementations of the basic concept that your scripts shouldn't have to wait for window.onload to fire before getting down to business. Now, DOMContentLoaded is the de facto start point for many, many JavaScript applications. Heck, it's even the basis of the entire jQuery event model. There's nothing wrong with that, but widespread use of Ajax toolkits can conceal the fact that DOMContentLoaded is just a collection of hacks. It works, as long as our toolkits keep iterating one step ahead of the browser vendors. But do we really need it?

At Orbitz Worldwide, my previous employer, we implemented a first-rate unobtrusive-JavaScript architecture. (See it in action at Ebookers UK.) Everything was progressively enhanced from dumb markup. That meant our application worked just about everywhere. But it also meant lots of DOM parsing and initialization that couldn't begin until window.onload. As our application grew, this caused enough of a UI flicker - form controls turning into widgets, links morphing into Ajax calls - that we needed to jump the gun on window.onload if we wanted a good experience for the majority of users ... the ones with modern, JS-capable browsers. DOMContentLoaded made sense, and architect Nik Krimm pounced on it.

But for a large subset of websites, there really isn't THAT much difference between window.onload and DOMContentLoaded. If you're merely adding an unobtrusive behavior layer to a traditional, content-driven website, chances are that good old window.onload will perform just fine. There's usually little reason NOT to use DOMContentLoaded. But unless you are developing a desktop-style webapp or implementing progressive enhancement on a foundational level, it's not really necessary. And in certain cases, it just flat-out won't work.

An enthusiastic beta-tester discovered such a case when trying to initialize RSH's dhtmlHistory object from DOMContentLoaded. It broke, and I immediately knew why: RSH relies on the ability of modern browsers to auto-save form data for the life of a session. RSH uses a hidden textarea to cache serialized Ajax application state and render the back button useful again. Internet Explorer doesn't repopulate the cached value of that textarea until an instant after window.onload. If you try to access that cache during DOMContentLoaded, it simply isn't there. This is true of both IE6 and IE7. Therefore, you need to wait for window.onload.

I'm not really sure there's any big conclusion to draw from this example. As I said, it's just interesting to me that in the space of a year or two, DOMContentLoaded has become a de facto standard. As we pile browser hacks on top of one another to push the web forward, sometimes they're going to conflict. Luckily, we can always peek past the curtains and figure out what's  going on behind the scenes.

Developer’s Notebook: Mastering (your fear of) regular expressions

Regular expressions are one of the most difficult programming concepts for novices and journeymen to wrap their heads around. Take a look at most any blog posting about RegExes and the comments will invariably be littered with words like "hate," "pain" and "AAAAARGH!"

Once you get comfortable with them, though, regular expressions become one of the most powerful tools in your programming arsenal. For Ajax/JavaScript developers, they can lend power and elegance to everything from form validators to keystroke interpreters to JSON, CSS and DOM parsers - in short, many of the thing you'll want to do on the client side of any powerful webapp. Take a look under the hood of any respected Ajax/DHTML library and you'll see RegEx literals being used liberally.

It's no secret that I'm big on programming books, especially O'Reilly ones, but I can think of few books that have been more useful than Jeffrey E. F. Friedl's "Mastering Regular Expressions". One difficult aspect of JavaScript regular expressions is their syntax, which is completely different from the better-known Perl variety. Friedl steps back from the implementation details of individual RegEx engines to explain the central concepts common to them all. After having this book for a year, I still refer to it so often that I can't say when I'll be ready to graduate to Tony Stubblebine's "Regular Expression Pocket Reference." In the meantime, when I'm away from my copy of the Friedl book, there are plenty of online resources to guide me.

Cheat sheets & quick references

These links don't offer really in-depth tutorials, but they do show you the JavaScript RegEx syntax at a glance.

Interactive terminals

Apparently, everybody and their mother decided to build a little DHTML/Ajax app to let you create regular expressions, run arbitrary text against them, and check out the results. This is a fantastic way to play with the technology and get more confident in your abilities. Here are 9 different implementations of the same basic idea. I haven't used all of them, so let me know in the comments which are most useful.

And for the over-achievers

For those of you so advanced in your RegEx powers that you've hit the limitations of the built-in JavaScript implementation, check out XRegExp, an open-source regular-expression library that supports named capture and other advanced features.

Technorati tags

JavaScript style and the art of anal retention

Years ago, in a former life, I was not just a journalist, but the worst kind of journalist: a copy-editor. I was paid to rewrite other people's copy, often while they were sitting next to me, so that it conformed to a set of stylistic conventions. Copy had to follow, in descending order of importance, the stylebook of whatever publication I actually worked for, the Associated Press stylebook, and standard grammatical rules.

The theory behind copy-editing is that convention is good even when it's arbitrary: "gray" rather than "grey" even though both spellings are listed in the dictionary. The more consistently a given publication handles specific ambiguous cases, the easier that publication is to read - or so the theory goes. ***

Software development, especially in a large, shared codebase, can work a lot like a newsroom: Project leads function like desk editors, technical leads like copy editors, code reviews like editing sessions, and releases like print runs. The big difference with code is that it will get rewritten repeatedly even after it's "gone to press."

When I sit down to extend other people's code, I find it difficult to dive in until I've cleaned things up a bit. I don't fix bugs. I simply reformat the code so it looks more like my own. With JavaScript, I move braces around, tweak the commenting style, rewrite certain if-then-else statements using the ternary operator, and so on. Ideally, none of this affects the functioning of the code. It simply makes the code easier for me to read, understand and extend. It puts it into my vernacular.

None of that would matter if I didn't have to check the edited code back in after I was done. Once somebody notices that you've altered non-functional attributes of their code, they can get a little peeved. Back in my Orbitz days, we tried to cut off any discussion about such issues by intoning, "Tabs vs. spaces! Let's move on!" Indention style is the most basic code-style decision, but it's practically a religious issue. There's no way of proving that your side is right. It's a matter of faith, so why waste time arguing? The same goes for most other arbitrary stylistic tics. IDEs, with their auto-format options, help ease this kind of conflict. But if you're an adherent of, say, Hungarian notation, you're still going to get caught up in some pretty endless debates.

When it comes to JavaScript, some stylistic tics aren't so arbitrary. Subtle changes in our coding style can improve not just readability, but also shelf life, cross-browser compatibility, and ease of minification. Several brand-name JavaScript authors and projects have weighed in with style guides. Some follow the AP stylebook model: seemingly arbitrary commandments. Douglas Crockford provides more context than most with his Strunk & White-inspired "Elements of JavaScript Style" (in two parts: 1 and 2). But the Mozilla and Dojo folks certainly have a lot of good advice.

Are all of you JavaScript authors aware of these resources? If so, which do you find most useful? Which are worth following slavishly? Which need to be ignored? Personally, I find all of these examples useful because they force me to think about my own coding conventions - many of which I picked up unconsciously - and decide which ones actually add value rather than simply manifesting my anal-retentiveness. Understanding these guidelines is like studying the source code of third-party libraries. It helps you see how other people are solving the same problems you're facing.

Here are the links:

*** Incidentally, that's why most newspaper journalism does so many
weird things consistently, such as putting all modifiers before all
helper verbs. The phrase "already had been working" may be harder to
read than "had already been working," but it's easier to codify in
absolutes. What was that about "the hobgoblin of little minds"?

YUI Bubbling Library: a seriously cool design pattern

Caridy Patino recently posted to the YUI Blog about his event-bubbling library, which uses the subscriber/publisher design pattern to abstract an entire webapp's event binding into its own unobtrusive behavioral layer. Instead of attaching events to individual DOM nodes using addListener, you intercept and process all events near the document root, then use CSS classes or other criteria to match up individual events with the correct handlers. In effect, you end up with a big, global switch statement for handling mouse clicks, mouseovers, keydowns and other events.

Patino, a respected contributor to the YUI mailing list, makes a strong case for the usefulness of his library on larger, more event-driven webapps. For one thing, it can reduce the overhead of handling IE memory leaks. For another, it can simplify the process of attaching custom JavaScript behaviors to dynamically loaded content. Best of all, it can be used to improve performance of really complex apps.

Of course, such an abstract approach isn't for every developer or every application. Patino is pretty frank about both the pros and cons of his approach for specific situations. Even if the actual technique isn't for you, though, the post is a fantastic primer on the intricacies of DOM events and the publisher/subscriber pattern.

Really Simple History: Onwards and upwards

I'm excited to announce that I've heard the call and volunteered to tackle maintenance and stewardship of Really Simple History, Brad Neuberg's intuitive, lightweight Ajax history library. Brad developed RSH a couple of years ago, drawing inspiration from the Dojo Toolkit folks to deliver a standalone library that provides back-button and bookmarking support for Ajax apps in IE6 and various Gecko-based browsers. Since, then, many additional Ajax frameworks have implemented back-button and bookmark support, some of them drawing on Brad's work.

Meanwhile, Brad's been too busy with other projects to upgrade RSH for a variety of new and existing browsers: IE7, Opera, Safari/Mac and Safari/Windows. I asked Brad to let me take care of his baby for several reasons. For one thing, I've been an enthusiastic user of the library. For another, I've been wanting to get involved on a more formal basis with open-source JavaScript projects. But most of all, I believe RSH remains a great tool for folks who want a solution to the Ajax history issue without the overhead of a larger Ajax framework.

I'm currently working with Brad to migrate RSH to Google Code, get acquainted with the bug base, and start tackling the thorny issues surrounding Ajax history support in the 2007 browser landscape. I look forward to shamelessly pilfering the many fine solutions uncovered by a large community of developers since Brad's initial work. (Brad was kind enough to point me to this blog post from Bertrand Le Roy, which lays out many of the aforementioned fine solutions and thorny issues.)

In the meantime, I'd love to hear from RSH users about their hopes for the future of the framework. Comments, please, or ping me directly at bdillard (at) pathf.com. Thanks!

About Pathfinder

  • We design and build extraordinary applications for companies looking to make the next great idea a reality.
  • learn more

Topics