Fetch (a stand-alone CSS querying engine)

I've started work on a new side-project called Fetch. It's a stand-alone CSS querying engine, designed to be super lightweight and offer high performance across all modern browsers. It's still very much in development and the source is a bit sloppy, but it currently weighs in at only 2.5k compressed and gzipped and (in Firefox3) either outperforms or is on par with all other major Javascript libraries out there*. It's definitely not ready for production use, but when it is, it will become the new CSS querying engine for Gimme and will also be available as a stand-alone library, which will be extensible (so you can add your own rules) and which will also be guaranteed to play nicely with other libraries by not adding to the prototype of any native objects and only introducing one single global variable (fetch). For anyone interested, the current (i.e. sloppy) source is available on CodePlex: fetch.js. As I said, it's not ready for prime time and the code is sloppy (and probably buggy). Consider yourself warned. * Fetch was not compared with libraries that use XPath or querySelectorAll, because it uses neither, but rather is a pure Javascript implementation. Upon release, it will take advantage of querySelectorAll where appropriate, but will not use XPath.

Fresh Nuggets of Gimme Awesomeness

Some really cool stuff happening with Gimme lately, especially with regards to its animation capabilities.

I whipped together something that started off (in my head anyway) as a sort of copy of CoverFlow on Mac OS. However, before I was finished, it had morphed into a general purpose animation-along-an-arbitrary-path-type-routine.

This code isn't yet checked into the repository, but for anyone who is interested in taking a look, here are two different demo pages to see the "bleeding edge" of what is going on with Gimme:

There are a few things worth nothing. The first link is a really quick-n-dirty prototype, so there are bound to be bugs. The page takes a few seconds to load as it is pulling remote photos from my Spaces photo album. You'll have to wait until everything is loaded and in place to try out the demo. Just click on any image and you'll get the idea. You can also change the shape of the curve that the images follow by clicking on the "S Shape" button.

The second link is a general animation demo page. By providing a set of Bezier control points (comma separated coordinates / spaces separated points), you can instruct the script to plot any curve and then have the red block follow that curve. You can also chose the AccelerationLine you want to use with the animation (note that Linear is a bad name, and should really be None -- I will fix this one day).

You can even use the text area on the right side of the page to execute arbitrary custom code. I've provided a sample that animates the red block counter-clockwise around an ellipse 2 and 1/2 times.

As you can see, it doesn't take much code at all to achieve some fairly sophisticated animation with Gimme.

In a future blog entry, I hope to polish up this code and provide a deeper explanation of the samples (as well as get this code checked into the repository).

Comments welcome

IE quirk with onload event for <img> elements

I rediscovered something yesterday that I know I've come across in the past. Still, it was driving me crazy until I finally did a little web searching and found what I needed. It was one of those "oh right! I've encountered that before" moments.

I was writing a script that dealt with images and I needed to know when an image had finished loading. Simple enough. Using Gimme (or your favorite addEvent(..)) equalizer the code looks something like:

var myImage = document.createElement('img');
myImage.src = 'http://source.to.image/image.jpg';
g(myImage).addEvent('load', function(e) { alert('Image is done loading!'); });

It turns out that my load handler wasn't always firing in IE (in Firefox it worked consistently). The solution though is super simple.

You just need to make sure that you wire up the load handler before setting the image element's .src property. Why? Because if the image is being loaded from cache, IE (and Opera too) will load the image instantly. In fact, it will have finished loading the image before your load event handler is even wired up, which means, it won't fire!

Just change the code to something like this:

var myImage = document.createElement('img');
myImage.addEvent('load', function(e) { alert('Image is done loading!'); });
myImage.src = 'http://source.to.image/image.jpg';

And there you have it.

Nothing earth-shattering and probably common knowledge for a lot of scripters, but a little refresher never hurts.

setCapture with Gimme

In my last blog post I talked about how to re-route events in Javascript. It comes in handy from time to time, but some people prefer an alternate paradigm -- one that is probably more familiar to Win32 programmers.

It's called setCapture. It's proprietary, and it's native to only the IE browser (as far as I know).

What's .setCapture() good for?

Put simply, .setCapture() redirects all mouse events to a specified DOM element until a call to .releaseCapture() is made. Why would you want this? I can think of a couple scenarios.

  1. Drag n' Drop: you wire up mousedown to some element to start the drag operation, but wiring up mousemove and mouseup on that same element could be problematic -- the user may mouse too fast and your code might not be able to "keep up." Using .setCapture() on said element however, will redirect all mouse events to the appropriate element so that the mousemove and mouseup handlers are guaranteed to execute.
  2. Dropdown Menus: you want the next mousedown to dismiss a dropdown menu. A first crack at this might be to wire up mousedown to the <body> element, but if some other element on the page is listening for mousedown and is stopping event propagation, the mousedown may never make it to the <body> element -- .setCapture() will solve this problem.

These aren't the only applications of .setCapture() of course, but they're two of the most common and they're a good illustration of how .setCapture() can be useful.

Gimme's version of .setCapture(..)

Even though Mozilla/Firefox and other W3C browsers don't natively support .setCapture(), Gimme solves this problem by simulating the capability for browsers that don't natively support it. Using Gimme's version is a snap.

// just a shortcut for document.getElementById(..)
var dragElem = Gimme.id('dragElem');

// #dragElem will capture all mouse events until
// a call to Gimme.Events.releaseMouse() is made
Gimme.Events.captureMouse(dragElem);

A simple call to Gimme.Events.captureMouse(myElem) will do the trick (where myElem is the DOM element that should capture all mouse events).

This example, of course, doesn't accomplish anything very useful, but here's an example that's a bit more practical.

This snippet gives drag/drop capabilities to all elements marked with the class "draggable" (it looks long but that's just comments and a big font).

g('.draggable').iterate(makeDraggable);
function makeDraggable(idx)
{
   // variables to keep track of the drag element's start position
   // and the initial mousedown position
   var elemStartPos, mouseStartPos;

   // wire up mousedown to start the drag operation
   // and stop click events from propagating (just in case)
   this
   .addEvent('mousedown', mouseDown)
   .addEvent('click', stopEvent);

   function mouseDown(e)
   {
      // instruct all mouse events to be redirected to the current dragElem
      Gimme.Events.captureMouse(this);

      // prevent the mousedown event from propagating
      e.stopPropagation();

      // create a gimme object from the element being moused down on
      var gObj = g(this);

      // keep a ref to the dragElem's start position
      // and to the initial mousedown position
      elemStartPos = gObj.getPagePosition();
      mouseStartPos = Gimme.Screen.getMousePosition(e);

      // now, hook the mousemove and mouseup events
      gObj
      .addEvent('mousemove', mouseMove)
      .addEvent('mouseup', mouseUp);
   }

   function mouseMove(e)
   {
      // prevent the mousemove event from propagating
      e.stopPropagation();

      // get the current mouse position
      var mousePos = Gimme.Screen.getMousePosition(e);

      // position the dragElem (element's start position + mouse delta)
      g(this)
      .setStyle('top', elemStartPos.y + (mousePos.y - mouseStartPos.y) + 'px')
      .setStyle('left', elemStartPos.x + (mousePos.x - mouseStartPos.x) + 'px');
   }

   function mouseUp(e)
   {
      // instruct Gimme to stop capturing mouse events
      Gimme.Events.releaseMouse();

      // prevent the mouseup event from propagating
      e.stopPropagation();

      // remove the mousemove and mouseup handlers
      g(this)
      .removeEvent('mousemove', mouseMove)
      .removeEvent('mouseup', mouseUp);
   }

   function stopEvent(e)
   {
      e.stopPropagation();
      e.preventDefault();
   }
}

Before I get into an explanation of the code, the following note is significant:

The current version of Gimme (Belisar) doesn't offer Gimme.Events.captureMouse(..). This capability is new to Gimme (Caspian) which is currently in a pre-release state. You can access Caspian now by visiting the Source Code Section on the Gimme Codeplex site, or you can wait for the official release of Caspian, which should be sometime in late January.

Explanation

If you understand Gimme, the code here is really pretty straight-forward.

A mousedown event starts the drag operation by recording the mouse down position and the element's start position. Then, the mouseMove handler ensures that the element moves along with the mouse delta (delta from the mouse start position). Finally, the mouseUp handler ends the drag operation by removing event handlers that are no longer needed and releasing capture.

We prevent events from propagating (bubbling) in all cases, so that we don't get other elements interfering during a drag operation. And we use Gimme's Gimme.Events.captureMouse(..) capabilities to ensure the the mousemove and mouseup events are directed to the appropriate element. Otherwise we risk some other element "stealing" the mousemove or mouseup away from the intended element.

What does this refer to exactly?

Good question; it's the one thing in the Gimme drag/drop code that I can see tripping people up. The this keyword is somewhat polymorphic in Gimme, so what does it refer to exactly?

The answer is that is depends on context. Most of the time in Gimme, you'll be using this inside of an event handler function. In that case, this refers to the DOM element to which the event was wired up. For example:

g('#myElem').addEvent('click', function(e)
{
   // alerts "myElem"
   // since 'this' refers to #myElem in the current context
   alert(this.id);
});

In some cases though, you may want to use the Gimme .iterate(..) function. In that case, this refers to the current Gimme Object being iterated over, not a DOM element, but it's possible to combine both concepts. For instance:

g('#container > .funny').iterate(function(idx)
{
   // here, 'this' refers to the current Gimme Object,
   // which is why .addEvent(..) is okay here
   this.addEvent('click', function(e)
   {
      // here, 'this' refers to the DOM element receiving the click event,
      // so we can access native properties, like .parentNode
      this.parentNode.removeChild(this);
   });
});

In case you hadn't figured it out, the above snippet makes it so that all elements with the class "funny" that are direct children of #container will be removed from the DOM when clicked -- pretty trivial with Gimme.

But this is all secondary to the discussion at hand: capturing mouse events, which, thanks to Gimme, is also pretty trivial!

If you're new to Gimme, check out the Gimme Promo Page and the Gimme Codeplex Page.

You can also download a debug version of Caspian here if you just want to monkey around with it.

Note once again, that an official (and compressed) version of Caspian will be available sometime in late January.

Comments welcome.

Managing animations with Gimme (a fade demo)

Animation can be a tricky business in Javascript, particularly if the animation is invoked by some user action (such as moving the mouse over an element). Users cannot be trusted to behave in a calm, rational manner. For instance, instead of clicking the mouse button just once, they'll click it 25 times. Instead of pressing a key just once, they'll mash the key a dozen times in a row. Instead of smoothly mousing from one element to the next, they'll frantically move the mouse around as though it's being chased by a hungry cat.

If your animation is invoked by a user action like those described above, then there is good chance that the animation may come off looking sort of silly (at best) or even causing seizures (at worst). I've written animations before, that didn't account for unpredictable user behavior, and the result was a chaotic mess of flickering DOM elements. An animation to be sure, but not the one I was aiming for.

No, users should not be trusted, and we should never fall into the trap of letting ourselves believe they can. Of course, this makes our job as UI designers more difficult, but that's no reason to whimp out!

Gimme to the rescue

Gimme has an extension called Gimme Effects which, when combined with the Gimme Animation Module offers a sophisticated set of tools for creating and managing Javascript-based animations. Besides several, useful, stock effects, Gimme also offers developers the ability to create custom animations quickly and easily (but that's another blog entry).

Focus on the fade!

One of the stock effects available in Gimme Effects is the fade (in, out, or to/from arbitrary values). It's super easy to invoke a fade too. Take the following:

g('#nav > a').fadeOut('quickly');

This snippet quickly fades out all <a> elements that are direct children of the #nav element. How quick is 'quickly'? 750 milliseconds to be precise, but you're not limited. Pass in a keyword like 'quickly' or 'slowly', pass in a numeric value in milliseconds, or even pass in nothing at all (default is 'quickly'); Gimme will handle it all just fine.

Specifying more parameters

All animation effects in Gimme take at least 3 parameters (some take more). These 3 parameters, which are all always optional are:

  1. duration: positive integer value indicating the duration of the animation in milliseconds.
  2. guid: a string value that uniquely identifies this animation (you'll see later, why this can be useful). Note that this value can be null.
  3. callback: a function to be executed when the animation completes.

Suppose I wanted to alert a message that the fade operation was done. I could do the following:

g('#nav > a').fadeOut(2000, null, function() { alert('all done!'); });

There is a problem here though. Remember back at the beginning of this post, I said that users could not be trusted to behave in calm and predictable ways? Well, suppose the above code snippet is executed whenever the users clicks on a button that says "Fade the navigation." What happens if the user clicks on that button 15 times in a rows? The animation takes 2 full seconds to complete, so if the user invokes it 15 times, that's 15 different animation requests, all on the same DOM elements.

The result will be a night-club-strobe-light-flickering effect that might give your users a seizure (or a head-ache at the very least).

Unique Animations with Gimme

With Gimme, it's pretty easy to avoid this kind of flickering. You simply specify a GUID when invoking the animation, and this instructs Gimme to make sure that an animation with the specified GUID is not already running. If it is, the request to start the animation will be ignored until the animation is no longer running. Take the following example:


g('body').fadeOut(1000);
g('body').fadeOut(500);

The code above doesn't specify any GUID; the animations are not uniquely identified and therefore Gimme will happily attempt to execute both of them, even though they operate on the same element (the <body> element) and will cause flickering.

One solution, is to give both animations the same GUID, as in:


g('body').fadeOut(1000, 'FADE');
g('body').fadeOut(500, 'FADE');

When Gimme encounters the second .fadeOut(..) call, it will ignore it since an animation with the GUID "FADE" is already running. Once the animation finishes though, it will once again be eligible for invocation.

Of course, you wouldn't ever write code like what I've written above -- that was just to illustrate a point. But think again about the possibility of a user repeatedly clicking a button to invoke some animation, and you'll see how the GUID can help.

Stopping an animation

The fact that Gimme can ignore subsequent requests to start an animation that already started is nice, but it begs the question: "what if I don't want to ignore subsequent requests? What if I want subsequent requests to cause the currently running animation to abort in favor of the new request?"

Gimme can do this too; it's just not the default behavior, so it take a couple extra lines of code on your part. Nothing major though, and it's still super easy:

Gimme.Animation.end('GUID');

This instructs Gimme to abruptly stop the animation uniquely identified by the GUID, "GUID" (not a very good name for a GUID by the way), provided it exists and is running. Need to stop more than one animation at a time? No problem; just pass in a comma delimited list of GUIDS as in:

Gimme.Animation.end('FADEIN', 'FADEOUT');

Gimme will stop any animations that are uniquely identified by either "FADEIN" or "FADEOUT."

It's worth noting that all animations have a GUID, even if you don't specify one. For animations where you fail to specify a GUID, one is auto generated for you. It's a good idea to specify your own though, as this allows you to manually stop the animation at will.

.fadeTo(startOpacity, endOpacity, duration, guid, callback)

Besides .fadeIn(..) and .fadeOut(..) (which are somewhat restrictive since they're "all or nothing"), Gimme also offer .fadeTo(..). This function works like its in/out couterparts, but it allows you to specify the starting and ending opacity for the fade. As an added bonus, you can pass in null for either value. A null value for startOpacity indicates that you want the starting opacity to be the object's current opacity, while a null value for endOpacity means zero.

For example:

g('#btn1').fadeTo(null, .5, 'slowly', 'PARTIAL_FADE');

This code snippet will fade the element, #btn1 starting at its current opacity (which is most likely 1, but doesn't have to be) and decreasing to .5 (or 50%) opacity.

A working Gimme demo involving fade

To help tie everything I've been writing about together, I've created a snazzy little fade demo for all to enjoy. In it there is a #navigation <div> with a few <button> elements inside of it. Whenever the user mouses over a button, all other buttons should partially fade out. When the user mouses out of the #navigation <div> completely, all buttons should return to their full opacity.

With Gimme, this turned out to be pretty darn easy. It's only about 12 lines of code in all:

View the Gimme Fade Demo (source is visible on the page)

New to Gimme?

If you're new to Gimme, you should know that it's free and open source! Check out these links for more information:

Comment welcome.