Custom jQuery selector for finding usable elements

Categories: jQuery

While working on some code today, I faced a problem I've run into a few times in the past where I needed to find the first usable input element within a specific context. What I mean by "usable" is an element which is visible to the user and not disabled.

Finding the first usable input element can be handy when you're using tabbed form, as when the user changes tabs, you can place the focus in the first visible input element. In my use case, I was implement a "reset" on a form where the fields being displayed could vary. I wanted to automatically place the focus in the first visible field after the user triggered a "reset" of the form.

If you're using jQuery v1.3 (or higher) this custom selector is very simple, just add the following to your page:

// finds elements that are usable (i.e. visible on the screen and are not disabled)
$.expr[":"].usable = function (node, index, prop, nodes){
  var $n = $(node);
  // return if the element is viewable or not            
  return ($n.attr("disabled") !== true) && $n.is(":visible");
};

If you're using jQuery v1.2 (or lower) the code is slightly more complex because the ":visible" selector doesn't check with the parent elements to make sure they are all visible. So, in order to make our code work with jQuery v1.2 we'll need to first create a custom selector for which will find elements that are truly visible:

// finds elements that are viewable
$.expr[":"].viewable = function (node, index, prop, nodes){
  var r = false;

  function viewable(n){
    var $n = $(n);
    return (($n.css("display") !== "none") && ($n.css("visibility") !== "hidden"));
  }
  
  // if not usable, stop processing
  if( !viewable(node) ) return false;

  $(node).parents().each(function (){
    r = viewable(this);
    // if not viewable, stop processing chain
    return r;
  });

  // return if the element is usable or not            
  return r;
};

The new ":viewable" selector should pretty much behave the same way that the ":visible" selector does in jQuery v1.3.

The only difference in this version of the ":usable" selector is that we're going to use our custom ":viewable" selector instead of the ":visible" selector:

// finds elements that are usable (i.e. visible on the screen and are not disabled)
$.expr[":"].usable = function (node, index, prop, nodes){
  var $n = $(node);
  // return if the element is viewable or not            
  return ($n.attr("disabled") !== true) && $n.is(":viewable");
};

The ":usable" selector can be used in several ways:

// find all usable form elements
$(":input:usable")

// find the first usable form element
$(":input:usable:first")

While the most common usage of the selector will probably be :input:usable:first, I can see where occasionally you might want to get all the input elements visible to the user.

Hope this helps someone!

Create iPhone-style buttons with the iButton jQuery Plug-in

Categories: jQuery

At work we just released another jQuery plug-in called the iButton jQuery Plug-in (which brings the total of open source jQuery plug-ins we've released to four.) This plug-in allows you to generate iPhone-style buttons from checkbox and radio elements. While there are several libraries out there that generated iPhone-style buttons, we couldn't find one that did everything we needed, so I wrote one!

The users of our application are very keyboard centric, so it was very important that we supported keyboard entry. Keyboard support often seems to be overlooked in most UI plug-ins—developers get so focused on the mouse interaction, they forget completely about keyboard entry. So we always make keyboard support a key feature in the jQuery plug-ins we write.

Anyway, here's a list of the key features:

  • Works with checkboxes or radio elements
  • Full keyboard support — use the [TAB] key to move from field to field and use the spacebar to toggle the status of the iButton (or use the arrow keys for radio buttons)
  • Custom event handlers
  • Detach iButton behavior from the element
  • Metadata support — when used with the jQuery Metadata Plug-in, you can define the properties for your button completely within the class attribute of your input elements
  • Enable/disable drag support — while the dragging behavior is intuitive on touch-based devices, it's not always be the best or expected UI behavior and may cause some mouse users problems (NOTE: In order to help differentiate between an intended mouse click or an actual drag event, we're developed the clickOffset option. If the time (in milliseconds) is under this value (120ms by default) it's assumed the user was attempting to click the button and not drag the handle.)
  • Enable/disable animation
  • Single sprite image — easily change the look of your button by just replacing the image sprite
  • Customizable labels — use any labels you want for your buttons
  • Support for disabled buttons
  • Easing support for animations
  • iPhone support

You can see a demo of the plug-in on the Giva Labs - iButton Example Page.

We're pretty happy with the end result and are planning on using it in a few locations in our application. If you like plug-in, don't forget to Digg it!

Marquee jQuery Plug-in Released!

Categories: jQuery

My current employer (Giva, Inc) has released another jQuery plug-in today called the Marquee jQuery Plug-in. The jQuery Marquee plug-in converts a list element (<ul /> or <ol />) into an ESPN-style scrolling marquee. Messages are scrolled in from top or bottom (based on the yScroll option) and longer messages will then ticker to the left in order to show the full message.

The Marquee jQuery Plug-in has an example you can look at or you can see several different marquees in different configurations on the Giva Labs - Marquee Example Page.

Fixing jQuery's slideDown() effect (i.e. Jumpy Animation)

Categories: jQuery

[UPDATED: Wednesday, April 22, 2009 at 8:43:54 AM]

I was working on some code today and was using the jQuery slideDown() and slideUp() effects and was running into an issue if the height of my box wasn't greater than certain height. As the box would slideDown, I'd see this jump in the animation as the height originally grew too large, so when the animation finished and it would go to the original height, I'd see this "jumping" effect.

Remembering that I read a recent post by Remy Sharp on the subject, a quick Google search brought up Remy's SlideDown Animation Jump Revisited post.

This turned out to be the same issue I was experiencing. Since I needed a little more generic version of Remy's code, I modified his original source to come up with this workaround:

// this is a fix for the jQuery slide effects
function slideToggle(el, bShow){
  var $el = $(el), height = $el.data("originalHeight"), visible = $el.is(":visible");
  
  // if the bShow isn't present, get the current visibility and reverse it
  if( arguments.length == 1 ) bShow = !visible;
  
  // if the current visiblilty is the same as the requested state, cancel
  if( bShow == visible ) return false;
  
  // get the original height
  if( !height ){
    // get original height
    height = $el.show().height();
    // update the height
    $el.data("originalHeight", height);
    // if the element was hidden, hide it again
    if( !visible ) $el.hide().css({height: 0});
  }

  // expand the knowledge (instead of slideDown/Up, use custom animation which applies fix)
  if( bShow ){
    $el.show().animate({height: height}, {duration: 250});
  } else {
    $el.animate({height: 0}, {duration: 250, complete:function (){
        $el.hide();
      }
    });
  }
}
UPDATE 2009-04-22:
  • Added check so that if requested toggle state is the same as current state, no animation occurs

While I could have written this as a plug-in, I kept it as a function—mainly because this issue should be fixed in the jQuery core soon (if it's not fixed already in the SVN repository.)

To use the solution, just pass in a selector to a unique element (such as "#element-id") as the first argument. The second argument is optional and can be used to force the direction. Use true to force it to slideDown() or false to force it to slideUp(). If the argument is left off, it'll toggle the state of the element.

Anyway, thanks to Remy for figuring out this issue. I'm sure I would have spent way more time tracking down the glitch if it weren't for the fact that I remember reading this when he posted the solution.

NOTE:
If you're element changes dynamically changes height, this solution won't work for you—because it stores the original height and always uses that. You could workaround that by using the data() method to kill the stored height (i.e. $("#your-element").data("originalHeight", null).)

ColdFusion UDF for detecting jQuery AJAX operations…

Categories: HTML/ColdFusion, jQuery

Raymond Camden justed posted an article on detecting jQuery AJAX operation using ColdFusion. I honor of that post, I thought I'd share the UDF I've been using in my application for quite some time:

<cffunction name="isAjaxRequest" output="false" returntype="boolean" access="public">
    <cfset var headers = getHttpRequestData().headers />
    <cfreturn structKeyExists(headers, "X-Requested-With") and (headers["X-Requested-With"] eq "XMLHttpRequest") />
</cffunction>

The code uses the exact same logic the technique in Raymond's post, but having the code in a function makes your detection code a lot easier to read.

Now when you want to detect if a request came from an AJAX operation, you can just do:

<cfif isAjaxRequest()>
    Came from jQuery! :)
<cfelse>
    Didn't come from jQuery. :(
</cfif>

Doesn't get any easier than that!

Using jQuery to fix CFCHART's tooltip behavior

Categories: HTML/ColdFusion, jQuery

If you've ever tried using the "mouseover" tipStyle in <cfchart /> with an image, you may have noticed there are several issues with the JavaScript that the WebCharts3D engine generates:

  1. The position of the tooltips isn't always accurate--if you're got parent elements that are absolutely position from a relative element, the position is drastically mis-calculated
  2. If your chart happens to be on the right edge of the screen, the tooltips will push off to the right of the screen (both clipping content and creating horizontal scrollbars.)

Here we can see an example of the tooltip running off the screen:

CFCHART Tooltips run off screen

You can see the scrollbar and how the tip goes off the edge of the screen. Obviously, this isn't very usable. To fix the problem, we're going to have to override the native mouse handler functions. Unfortunately, this isn't particularly easy to fix because when the <cfchart /> is executed, it embeds an external call to a JavaScript file which looks something like:

<script language="javascript" src="/CFIDE/GraphData.cfm?graphCache=wc50&graphID=script.js"></script>

This makes overriding the functions a bit tricky. You can override the functions during the window.onload event, but what happens is you're loading your chart via AJAX? Plus, why load JS you have no intention on using?

The solution is to strip out the <script /> call. You can do this by wrapping your <cfchart /> call inside the <cfsavecontent /> tag. This will give you a string containing the HTML you'll need to embed in the page. You can then use a regular expression to strip the <script /> from the output before writing it to the output stream. What you end up with is some source code that looks like:

<cfsavecontent variable="sChartOutput">
     <!---// put your cfchart code here //--->
     <cfchart width="1" height="1" />
</cfsavecontent>
<!---// we need to output the chart, but remove the call for the external JS library //--->
<cfoutput>#reReplace(trim(sChartOutput), "<script[^>]+>
</script>", "", "all")#</cfoutput>

By using a regular expression we're able to completely remove the reference to external script that contains the mouse handlers. Now we've got to replace the functionality with code that actually works.

The WebCharts3D script file contains a number of functions, but there are only two functions that are actually used by the HTML generated: xx_set_visible() and xx_move_tag().

The xx_set_visible() function handles hiding/showing the tooltip and the xx_move_tag() function handles positioning the tooltip on the screen.

Since my site is already using jQuery, I'm going to leverage jQuery to handle the positioning of the tooltip. In the new code I'm going to:

  • Move the tooltip element (a <table /> tag) as a direct descendant to the <body /> tag. I do this to simplify positioning of the element—since we don't have to worry about positioning of any of the parent elements.
  • Set the tooltip element's visibility to "visible"—since it's hidden by default (once again we only need to do this the first time we view a tooltip.)
  • Calculate the edges of the screen (with some padding) to make sure my tooltip always stays in the viewport. We don't want our tooltip to ever be cropped or overlap off the screen. If the tooltip would run off the bottom of the page, we'll put the tooltip at the top of the page. If the tooltip would run off the right edge, we'll make sure it can't move any further than the very right edge of the screen (with some padding.)

So what does our code look like? Here it is:

<!---// we need to replace the WebCharts3D JS scripts with ones that actually work //--->
<script type="text/javascript">
// on first show, we need to move to the body
var __xx_set_visible = {};
function xx_set_visible(imgId, tipId, e, show){
    // get the table we're going to show
    var $tip = $("#" + tipId);
    if( !__xx_set_visible[tipId] ){
        // move to the body and make visible
        $tip.appendTo("body").css("visibility", "visible");
        __xx_set_visible[tipId] = true;
    }
    $tip[show ? "show" : "hide"]();
    // make sure we place the tip in the correct location
    xx_move_tag(imgId, tipId, e);
}
function xx_move_tag(imgId, tipId, e){
    // get the table we're going to show
    var $tip = $("#" + tipId);
    // get the scroll offsets
    var scroll = {top: $(window).scrollTop(), left: $(window).scrollLeft()};
    // if we're IE we need to create the e.pageX/pageY events    
    if( !e.pageY ){
        e.pageY = e.clientY + scroll.top;
        e.pageX = e.clientX + scroll.left;
    }
    var pos = {top: e.pageY + 20, left: e.pageX + 10}; // add padding for cursor
    var tip = {width: $tip.outerWidth() + 10, height: $tip.outerHeight() + 10}; // add padding for edge
    var screen = {right: scroll.left + $("body").width(), bottom: scroll.top + $(window).height()};
    // if we're going to be off the screen, adjust the position
    if( pos.left + tip.width >
screen.right ){
        // don't move past most right of screen
        pos.left = screen.right - tip.width; // pos.left - tip.width || screen.right - tip.width - 10;
    }
    if( pos.top + tip.height > screen.bottom ){
        // don't move past most right of screen
        pos.top = pos.top - tip.height - 15; // since we're moving tip above we need adjust for the original padding we add
    }
    // position the
    $tip.css(pos);
}
</script>

That's all there is to it! Now our tooltips will never run off the edge of the page! Here's the result of our new custom mouse handlers:

CHCART with fixed tooltip!

Fading a 24-bit transparent PNG in IE7

Categories: HTML/ColdFusion, jQuery

Internet Explorer 7 has some issues with fading transparent PNGs. If you've gotten to the this page because you're seeing a black border where the transparent edges in your PNG are, then here are some tips for fixing the problem:

  1. Do not fade the element directly, but fade a parent container holding the PNG. This may mean you need to add a wrapper element to your code.
  2. Give the parent element a background color.
  3. Lastly, if you're still having problems, try give your parent element the old "zoom: 1" trick. Give the parent element a style declaration of "zoom: 1" (either via CSS or an inline style.) This will force IE to give the element hasLayout—which tends to fix all sorts of weird display issues in IE7.

The above tips will usually sort out any fading issues I'm having in IE7.

Linkselect jQuery Plug-in Released!

Categories: JavaScript, jQuery

My current employer (Giva, Inc) has released another jQuery plug-in today called the Linkselect jQuery Plug-in. This plug-in converts a normal <select /> element into a component that can be highly stylized via CSS. While there are a number of similar plug-ins already, there are a several of key differences which we think make this unique:

  • Drop down menus are intelligently positioned to stay in the viewport
  • Specifically designed to work in a limited amount of real estate
  • Specifically designed to work well with elements aligned on the right edge of the viewport
  • Full keyboard support (emulates IE6's <select /> element)
  • Feature rich API (for updating value, replacing options, disabling elements, etc)
  • Many callback features to control behavior (on change, on init, on format, etc.)
  • Supports tabindex

We've put together an example page that demos many of the features and how to use the plug-in.

Using jQuery to determine if an element is a child of another element

Categories: jQuery

I seem to be writing a lot of code as of late that needs to check if a certain element is a child of another element. This is extremely useful in drag and drop operations (for determine where an element is being dropped) or if you want to make sure that a global event was trigger on a specific set of elements (I use this to check if a document.click occurred on a specific container.)

While jQuery makes this easy enough to do, I don't find the code very readable or reusable so I started using the following snippet:

jQuery.fn.isChildOf = function(b){
    return (this.parents(b).length >
0);
};

What this does is checks to see if the current element is a child of the specified selector. For example:

$("#list > li > a").isChildOf("#list"); // return true
$("#list > li > a").isChildOf("#list > li"); // return true
$("#list > li").isChildOf("#list > li > a"); // return false

This function definitely comes in handy when doing any sort of event delegation and I hope it eventually makes its way into the jQuery core in some form or another.

A quick and dirty swap() method for jQuery

Categories: jQuery

A few weeks ago I needed a jQuery swap() function for some drag and drop code I was writing. While it would have been easy enough to whip up a quick little function, I found this little snippet courtesy of Brandon  Aaron's blog:

jQuery.fn.swap = function(b){
    b = jQuery(b)[0];
    var a = this[0];
    var t = a.parentNode.insertBefore(document.createTextNode(''), a);
    b.parentNode.insertBefore(a, b);
    t.parentNode.insertBefore(b, t);
    t.parentNode.removeChild(t);
    return this;
};

This particular method only works with the first DOM element in each of the jQuery objects. Using the code is easy enough, the following example would swap the first and last nodes in an unordered list who's id is "list".

$('ul#list > li:first').swap('ul#list > li:last');

Sorting DOM elements using jQuery

Categories: JavaScript, jQuery

I'm working on an interface that uses drag-n-drop to arrange how some fields appear on the page. The page basically has two containers—a canvas and a toolbar. You drag fields from the toolbar to the canvas, and then you can position the fields in the canvas to order them anyway you want. To remove a field, just drag it from the canvas back to the toolbar.

One of the things I wanted to implement was to always make sure the fields in the toolbar stayed in alphabetically ordered. While I knew it would be easy enough to whip up a sorting function, I decided to first search the jQuery Plug-ins to see if I could find something that was already written. That's when I found the TinySort jQuery Plug-in.

This plug-in allows you to sort an number of sibling DOM elements and you can sort by either it's text, an attribute or even a child element. Here's some examples (taken from the Sjeiti website:)

// sort by the element's text
$("ul#people>
li").tsort();

// sort by a child's text
$("ul#people>li").tsort("span.surname");

// sort by the img element, order descending using the img's "alt" attribute
$("ul#people>li").tsort("img",{order:"desc",attr:"alt"});

// sort's element, but puts the sorted items at the end of the parent element
$("ul#people>li").tsort({place:"end"});

The "place" option (as seen in the last example) is interesting because it allows you to control how the matching siblings are ordered in context to their non-matching siblings. In most cases you're probably sorting all of the children items of an element, but there may be times when you're ignoring certain elements (like disabled items.)

There are lots of working examples on the authors home page. If you need a way to quickly sort some elements on the page, I definitely recommend checking this plug-in out. It seems to have every option one would need to implement some basic sorting to some generic elements on a page.

Firefox 3.1's TraceMonkey, color me impressed...

Categories: JavaScript, jQuery

I just installed the lastest nightly build of Firefox v3.1a2pre, because I was very curious to see how an application I've been working on (which is very JavaScript intensive) would work with Mozilla's new TraceMonkey (JavaScript JIT) engine.

I'm extremely impressed by the performance of the JIT. The application I'm working has a lot of dependencies on JS behaviors that are initialized on page load. I've spent a lot of time to minimize the the impact of this code on page load, but there can be a good 250-1250ms delay (depending on PC hardware, the configuration of the page, etc.) before every element on the page is completely usable. So while there is a delay, I've designed things so that it should be pretty transparent to the user because they see the page immediately and by the time they'd actually go to do anything on the page, everything should be initialized.

While just testing the page under the latest nightly Firefox 3.1 build, this page is blazingly fast. It's so fast, I generally can't even see the initialization occur. I'm very impressed and I think things will only get better.

The addition of JavaScript JIT compilers is just a natural progression and I think it'll be the way we see all browsers head. The dependence on JavaScript in web design is greater than ever and with good reason—it allows us to build better web-based applications. However, with the greater dependence on the usage of JavaScript, it can be a really battle at times to tweak performance out of an application. It looks like TraceMonkey is making great strides in handling this problem natively in the browser.

I definitely recommend reading John Resig's blog post on TraceMonkey. It contains a lot of technical detail as well as a brief overview on how it all works.

IE7 not firing onmouseover event properly on element with padding

Categories: HTML/ColdFusion, jQuery

I ran into a really weird bug this morning. I was having an issue with a jQuery plug-in I wrote, where for some reason IE7 was not triggering the onmouseover event properly. After spending a bunch of time trying to track down the problem, I finally realized that it wasn't triggering the event until it got inside the padding of the element—which is the wrong behavior.

I whipped up a quick test case of a <div> with onmouseevent and some padding, but that worked as expected (with the event firing as soon as it reached the padding of the element.) As I started to debug the problem, I added a background color to the root element in order to see if I could tell when the even actually fired. However, as soon as I added the background-color, the event started firing correctly.

I'm not exactly sure what combination of HTML/CSS is causing the problem. I've been trying to to put together a straightforward example that illustrates the problem, but I've yet to be able to recreate without really complex code.

I believe the problem is related to having an absolutely positioned parent element with relatively positioned children and then moving the parent item's position in the DOM. Even stranger was that if I hide the entire content and would re-show it, everything would work properly.

It sounds like it's a pretty obscure buried bug, but if you're ever having problems getting an event to fire properly in IE7, try defining a background-color for the element to see if that fixes the problem.

mcDropdown v1.2 released...

Categories: jQuery

Addressing some more behavior issues being brought up (and adding a few new features,) Giva just released a new version of the mcDropdown plug-in:

  • Added focus() method
  • Fixed autocomplete list from showing dropdown when go back levels in FF3
  • Fixed autocomplete list corruption in when go back levels after using mouse
  • Added tabindex="-1" to the dropdown arrow (so it shouldn't recieve focus on tabbing)
  • Fixed tabbing behavior so hitting [TAB] should go to the next element in the tabindex
  • Autocomplete no longer shows (by default) if the input is empty and recieves focus (use the setting.showACOnEmptyFocus to control this behavior)
  • Added setting.showACOnEmptyFocus (used for controlling whether the autocomplete list shows on focus if list is empty; default = false)
  • Fixed noConflict() bug (where $ wasn't being properly scoped)

jQuery mcDropdown Plug-in updated to v1.1a

Categories: jQuery

Just a quick note that over the weekend I updated the code to the jQuery mcDropdown Plug-in to v1.1a. The update contains:

  • Fixed Safari keyboard support
  • Added mouse support for keyboard autocomplete box
  • Menu mouseout behavior should be functioning better