Monday, June 4, 2012

What I learned about the canvas element

So, I am apparently a sucker for challenges. A couple of weeks ago, my friends at the Des Moines Web Geeks issued the challenge to re-build in Javascript a simple dungeon game, which one of the organizers had built with his son in Python. I was unable to be at the coding dojo itself, but I joined in via Google Hangouts. And for some reason, I was hooked on the challenge.

But it was more than just the notion of rebuilding this simple game. The idea behind the whole thing was to teach someone how to code, a prospect which I have long thought to be a distinct challenge for our times. And we were supposed to use OOP JS as much as possible, something which I really enjoy. And then there was the fact that, beyond the basic parameters of the game, the concept was left wide open, which immediately got my imagination going.

So I decided from the very start that this little game, for me, would have two primary objectives:

  • I would learn something
  • Someone else would be able to learn something

For the thing that I would learn, I decided I would explore canvas to build a dashboard of sorts to display at a glance the status of the current player, the monster in the room, and a map of the world. Namely, since I had never before utilized the HTML5 canvas element in any project, I wanted to learn some of the basics and then explore just what the thing could do. I was incredibly pleased with what I came up with, and I wanted to share just a couple of quick lessons that I learned in the process.

The first lesson has to do with the canvas element itself. When I first started using canvas, I was simply putting the basic element into my HTML and then setting width and height with CSS. At first, things looked great. But as soon as I started drawing in the canvas, I realized that something was messed up. The images were distorted somehow. For example, a 5px vertical stroke was thicker than a 5px horizontal stroke. And then I noticed that filled rectangles that should have filled the entire canvas were all messed up. And I was compelled to realize that, somehow, there was a discrepancy between the internal dimensions of the canvas (i.e., how many pixels it thought it had) and the external dimensions of the canvas (i.e., how many pixels the page thought it had).

Sadly, it took me quite some time to find a tutorial or other documentation which acknowledged what I thought must have been an issue. But it wasn't an issue. Rather, it was a design feature of the canvas element.

<sarcasm>Imagine my surprise.</sarcasm>

So here's the deal. The canvas element's height and width attributes (i.e., the height="xxx" width="yyy" bits you put in the HTML) set the dimensions of the drawing space within the canvas element. While the CSS height and width rules set the dimensions of the element in relation to the rest of the page. The thing is, those two things don't necessarily correspond.

In fact, the canvas element, regardless of its size in the CSS, will have a drawing space of 300 pixels wide and 150 pixels in height. Unless, that is, you specify otherwise. The question is, how do you do that? Well, there are two options.

The first option is to specify it in the markup. Namely, by providing the height and width attributes in the opening tag, like this:

<canvas id="myCanvas" width="1000" height="1000" />

That's not so difficult. But what if you want the canvas drawing space to be exactly the same as the canvas element's actual dimensions on the page? And what if the actual dimensions are given in a percentage or may change? Try something like this in your JS onload callback (or onresize, etc.):

var el = document.getElementById('myCanvas');
el.setAttribute('width', el.offsetWidth);
el.setAttribute('height', el.offsetHeight);

And there you have it. One of the things I learned about canvas elements while playing with this simple little game.

i love my wife

Tuesday, November 15, 2011

Adventures in browser detection

So, today, I came across an interesting issue. I am working on a total overhaul of my personal CMS, and part of that includes a custom WYSIWYG editor. The editing happens inside an iframe element, but in order to make it a seamless experience, I want the iframe to dynamically resize itself according to whatever the contents need for dimensions. Makes sense, right? Well, then I come across this little beauty:

In order for it to work right, I have to use the dimensions of the iframe's body element in Chrome. But I have to use the dimensions of the iframe's html element in Firefox. Talk about irritating. So now I have to distinguish between these two great browsers. I think to myself, "Oh, well, that shouldn't be too difficult." And I tried using navigator.product. No joy.

Turns out, Chrome sets navigator.product to Gecko, just like Firefox. Even though Chrome is NOT Gecko.

So I thought maybe appName. Chrome sets its navigator.appName to... "Netscape".

appCodeName? Chrome sets it to - you guessed it - "Mozilla".

So I was compelled to check the userAgent string. But because Chrome and many other browsers report their UA strings as "Mozilla/5.0..." I knew Mozilla was out. I thought about looking for Firefox, but there are plenty of other Gecko-based browsers. So I thought, maybe "Gecko."

But, oh, wait. Chrome reports, among other things, that it is "KHTML, like Gecko."

So I ended up having to check for "Gecko" and then make sure that it wasn't "like Gecko."

If you're interested, here's the code I used...

if(navigator.userAgent.match(/Gecko/i) && !navigator.userAgent.match(/like Gecko/i)){ // Must be dealing with Firefox, etc.

Just because life in webdev land can't be easy.

Saturday, September 18, 2010

jOverviewMapControlV3

In my last post, I discussed a recent project in which I was porting a project which relied on Google Maps API v2 to Google Maps API v3 and the problems I ran into trying to utilize the LocalSearch component of Google's AJAX Search API with the new version of the Maps API. In this post, I want to talk about the second major pitfall I ran into while working on this project: several key features of v2 have not (yet) been added to v3.

Now, in most cases, this is inconsequential, to say the least. The Maps API dev team established performance as a higher priority than making sure the API included three different styles of kitchen sinks, so they eliminated some of the not-as-utilized features of the API, and v3 screams compared to the rusty groan of v2's bloated code as a result! (Read that, v3 is AWESOME and WELL WORTH the jump!)

But I do miss at least one of the features that they omitted this time around: the GOverviewMapControl. In fact, for the project that I was working on, the client specifically requested the overview map that appears in the lower-right corner on http://maps.google.com and gives you at least some semblance of where you are in the world when using the larger map. So this wasn't just a trivial thing for me. I needed an overview map control.

So I went searching, and I found an issue on the GMaps issues list where people requested this exact feature. As of this writing, 83 people had starred the issue, meaning that they wanted it. And in fact, it was actually "acknowledged" by the dev team. Unfortunately, though, the issue was created July 2009, acknowledged a day later, and now, over a year later, there is still no overview map in GMaps v3.

Considering that the project was not at the top of my priority list, I didn't worry about it too much. I simply starred the issue and decided to work around it as best I could, hoping that, by the time I was ready to finish the app, the control would be included in the v3 code. Alas, however, I reached a point this week where I was wrapping things up and still needed the control.

So I wrote one.

For anyone still looking for an equivalent to the GOverviewMapControl in Google Maps API v3, I've created a new Google Code project for my solution, which you can check out at http://code.google.com/p/gmaps-api-v3-overviewmapcontrol.

As with the last post (check it out here), though, I will take a couple of seconds to make some notes.

First, I did take a couple of "liberties" to make my version a bit more fun. Namely, I animated the opening and closing, and I made it automatic. This is the default behavior. In the next couple of days, I think I will make the automatic opening and closing optional. Maybe I'll make the animation optional, too. I just haven't decided on that part yet.

Second, you may notice that, when you drag the polygon to the edge of the overview map, the overview map does not pan automatically. This is something that I have thus far been unable to do smoothly. If you have any thoughts on how I could accomplish this, I would love to hear them.

Third, I am aware of an issue where releasing the polygon after dragging it around does not trigger the event to move the map. I'm working on it. If you have any ideas on fixing it, let me know.

Fourth, there is no fourth note. I just inserted this paragraph for kicks. It kind of breaks up the list of notes and gives me a chance to use the word "sixth" in a blog post.

Fifth, because I have no special permission from the GMaps team, I have made no effort to eliminate the Google logo and terms notices on the overview map. These do eat up screen real estate, but they also make sure that the Google Legal (Beta) team doesn't come after me.

And sixth, you should be aware that I am using a real hack to put the control on the map. Namely, this control is not added to the map by pushing it into one of the 8 custom control trays. Rather, it is simply appended to the map parent div. So you have complete control over where it is placed, etc., in the CSS. In theory, you could put it front and center on your map, or you could use it to cover one of the essential components of the map (e.g., the Google branding and/or licensing notices). I assume no responsibility for any consequences you may incur by changing the positioning rules specified in the default.css file.

So there you have it. An overview map control for GMaps API v3. Now go and map something!

jGoogleBarV3

For some time, I've been working to port a large project that I did utilizing the Google Maps API v2 to the new version 3. This work has been slowed by learning curve, scheduling, and the fact that I decided from the start that, rather than simply port the existing code I would re-engineer the application from the ground up in an effort to improve its performance and learn some new tricks (e.g., GMaps v3, Closure Compiler).

As I worked, though, I soon realized that I would eventually run into a couple of roadblocks to completing my project. The first of these was that the application incorporated the Google AJAX Search API to allow users to search the map for cities, neighborhoods, etc.

Now, to be clear here, the Search API will work with GMaps v3. The Maps dev team has cooked up a few examples to demonstrate this. But it will not integrate as tightly with v3 as it did with GMaps v2. Namely, you cannot hand a v3 map as an argument to a LocalSearch object without causing some interesting fireworks in the error console.

This is an unfortunate thing because the LocalSearch object (in my opinion) did a better job of finding relevant results when paired with a GMap than in any other mode (e.g., using a "near..." or GLatLng center). And I absolutely wanted that capacity in my application.

So I ran into problem 1: I needed a LocalSearch object which would integrate with my GMap v3 application.

But once I stumbled across that realization, I also knew something else. One of my favorite little toys from the AJAX Search API dev team is called the Local Search Control (LSC). GMaps aficionados will know it by its alias, the GoogleBar. In my humble opinion, a map that you can search for cities, businesses, etc., may be the most obvious and universally useful application of the GMaps APIs there is. But without a solution to problem 1, there could be no LSC.

Well, I guess you could make one if you really wanted. But it just wouldn't be the same. So I ran into problem 2: No Local Search Control (aka GoogleBar) in GMaps v3.

So, since (a) I needed a solution to problem 1 for my project at hand, (b) a solution for problem 2 wouldn't be all that much more work, and (c) I have a hard time resisting Ben Lisbakken when he's batting his eyes at me (okay, not that hard a time), I thought I would sit down and come up with something that would benefit the whole GMaps ecosystem.

The result is an open source project on Google Code, which you can check out at http://code.google.com/p/gmaps-api-v3-googlebar.

Since this is my soapbox (aka blog) and I can say anything I want here, I will point out three things about this project.

First, it was not my intention to duplicate every aspect of the LSC. The original LSC was a brilliant piece of coding which included multitudes of options, etc. It was my intention merely to duplicate its essential functionality and what I thought were likely its most-used options.

Second, I wanted to make it more consistent with the GMaps v3 syntax model, which is (I believe) cleaner and more efficient than v2 ever was. Therefore, there is no need for 53 lines of code (okay, that's an exaggeration) to initialize and set up the options for this control. You can initialize it and specify all the options you'll ever need in one line of code.

And third, I am not an ads fan. Furthermore, since I'm not on Ben Lisbakken's list of Facebook friends, I'm not allowed access to the API that Google used to implement AdSense support in the original LSC. In other words, my GoogleBar has no AdSense. Before you stone me or something equally extreme, please know that I am not particularly opposed to implementing this support in the future. It's just that I built this thing to allow webdevs like me to build searchable maps. Not revenue streams. So it won't be all that high on my priority list to work this out.

So, my version of the LSC has been tested successfully in current versions of FF, Chrome, Safari, Opera, and (much to my own chagrin) MSIE 7 and 8. My own aversion to MSIE means that it has had the least testing of all platforms. And I have no intention of testing it in MSIE 6. So if you fire that dinosaur up, you're on your own.

As always, thoughts, comments, issues, suggestions, and contributions are more than welcome. Otherwise, until next time!

Wednesday, May 5, 2010

cringer 2

So, it's been several months since I posted, so I thought it would be good to jot down a few notes about cringer. Especially since a few significant developments have occurred since the last time I posted.

In case you don't know what cringer is, it's an IRC bot designed to utilize Google's AJAX APIs (well, the RESTful side of them, anyway) and various other interfaces to provide a valuable resource for the #googleajaxapis channel on Freenode. In addition, the code can be easily adapted to provide similar resources for other channels. In fact, in recent weeks, it's been deployed in the #lilypond channel (also on Freenode) with some interesting tweaks under the name fringer.

So here we go with a few tidbits. First, after a botched upgrade to Snow Leopard, I ended up with an opportunity to rewrite cringer from scratch. I say opportunity because, even before the upgrade, I had been thinking about a number of significant improvements which would streamline the code and dramatically increase functionality, flexibility, and stability.

So second, cringer2 is now online and running. cringer2 is a complete rewrite and introduces a modular design. To add functionality, you have only to write a simple Perl module, drop it into the proper folder, and restart cringer. I have contemplated dynamic loading of modules, but I am leery of the security risk that this could entail (i.e., strange people loading arbitrary modules and causing trouble, etc.). Even with this limitation, adding functionality is dramatically simplified.

Third, among the modules for cringer2, I have included a number of functionality improvements. Thus far, the modules I have built and deployed are search (for use with the Google Search API), fetchfeed (for use with the Google Feeds API), smartalleck (for making obnoxious comments), translate (for use with the Google Language API), weather (utilizes the Yahoo! Weather RSS feeds to provide current conditions and forecast details for a given locale), and twittersearch (for use with the Twitter Search API). In addition to these, I intend to develop and deploy a yspell module (for use with the Yahoo! Spelling API), and I will contemplate a buzz module which will utilize the Buzz API to search Google Buzz posts.

Fourth, it is still my intention to develop and deploy a module which will eval simple Javascript code and return the results. However, I continue to be plagued by the security ramifications of such functionality. In addition, I am having a difficult time deciding which JS engine I want to use. OSX includes an interface to Safari's engine which is really very good, but I can't guarantee that the code will be cross-platform that way. Alternatively, I could install TraceMonkey or something like that, but that has proved to be something of a hassle.

And finally, I have posted both cringer1 and cringer2 code to the Google Code project. You can check it out at http://code.google.com/p/gajaxapis-irc-bot! Feel free to download the code and tinker with it, as long as you don't forget to share your improvements with everyone else!

Over the next several weeks, I intend to get the additional modules mentioned above up and running, but I also intend to allocate a box which will be dedicated to cringer and a couple other projects in the hopes of improving performance, up-time (as opposed to running on my laptop which is off at nights), and security for my personal system.

So that's all for now.

Sunday, August 23, 2009

The progress of cringer

So, in my last post, I talked about cringer, the gajaxapis-irc-bot that I'm working on. In the month+ since I posted that, cringer has gone from a concept to a functional, if still experimental, bot which utilizes the Google AJAX Search and Language APIs to provide useful functionality in the #googleajaxapis channel on irc.freenode.net . It has also been infused with the ability to make snide remarks about things like MSIE and Flash/Flex. And it has a help facility which allows users to utilize its different resources (and find interesting tidbits about several regulars to the channel).

To be certain, cringer has developed quite nicely, thanks in large part to the ideas shared by friends and colleagues in the channel. But one thing has eluded me thus far. I have found myself unable to implement the ability to evaluate Javascript in the channel via either SpiderMonkey or (as one of the Googlers in the channel suggested) V8. Now, this is undoubtedly due in large part to the fact that I've never attempted to compile one of these JS engines independently before. So this week, as I have opportunity, installing at least one of them - probably SpiderMonkey - on my Mac will consume my idle time. If anyone has any insights, please don't hesitate to send them!

Wednesday, July 15, 2009

Building a better bot

So, it's no secret that I spend an unhealthy amount of time hanging around the Google AJAX APIs. Between the Google Group and IRC channel, I certainly find enough to do to keep myself out of trouble (or get myself in trouble, if you ask my wife). For the last several months, people in the channel have kicked around the idea of building an IRC bot to help out in the channel and have a little fun. Well, this past weekend, I sat down and started tinkering.

The result: http://code.google.com/p/cringer

Now, I know there's no code there yet, but it's coming. cringer is a Perl-written IRC bot that will eventually run Javascript, search the web, listen to your troubles, and even make snide remarks about MSIE and Flash.

So far, I've learned that getting Perl to talk to IRC is extraordinarily simple with the right modules, giving it a very rudimentary level of artificial intelligence is a snap, and getting it to run with DBD::mysql means making sure you have the right version of MySQL installed on your computer. What I'm needing to learn now is how to make it interact with TraceMonkey, Mozilla's brand-spanking new JS engine. So it's back to work for me!