dans.blog


The miscellaneous ramblings and thoughts of Dan G. Switzer, II

jNotify jQuery Plug-in v1.2.00

The jNotify plugin was just updated, with a new "click to dismiss" feature, which lets a user click on a notification to dismiss the notification!


Overriding jQuery Mobile's default behavior when loading a page has generated a 500 status code

I'm working on a jQuery Mobile project at the moment and I was not happy with the way that jQM was handling errors from the server. Our application's error handling code sets the HTTP's status code to 500. We do this, because it makes it much easier to track problematic requests. The issue we were running into was that jQM wants to just display a "Error Loading Page" dialog whenever it detects a 500 status code.

In order case, our error pages are formatted for use with jQM, so we really wanted to display the error, just as if the page had successfully loaded. We could have returned a status code of 200, but we wanted to respond with the correct status code.

To work around this issue, jQM has a "pagecontainerloadfailed" event which it fires whenever a page fails to load. Unfortunately, it's not very clear on how to use this event to handle the request as a successful page load. You can use the event.preventDefault() to cancel the default behavior, but how do you handle it as a successful page load?

Well that took some digging, but I finally found a solution that appears to work really well:

// if an error occurs loading the page, the server will make sure we have a properly formatted document
$(document).on("pagecontainerloadfailed", function(event, data) {
  // if the error response is formatted for jQM, it'll have a custom response header that flags to use the standard page handler
  if( data.xhr.getResponseHeader("X-jQueryMobile-HasLayout") === "true" ){
    // let the framework know we're going to handle things.
    event.preventDefault();

    // load the results as if they had succeeded
    $.mobile.pageContainer.data("mobilePagecontainer")._loadSuccess(data.absUrl, event, data.options, data.deferred)(data.xhr.responseText, "success", data.xhr);
  }
});

What this code does is look for a custom response header of "X-jQueryMobile-HasLayout" and if it sees this header is true, will then stop the default behavior and load the page as if it detecting a 200 status code.

I decided to use a custom header to determine if I should run the code or not, because then any unexpected error that occurred outside our framework would still behave the way jQM acts by default. However, if our application error handler runs, we set the header to "true". This tells jQM that the resulting HTML should be in a format that jQM needs to render the page.

I ended up posting this basic solution in GitHub 6866 - Add documentation for presenting server generated error pages and Alexander Schmitz, a jQuery Foundation member, confirmed this is the basic approach they plan on implementing in the future.


MouseIntent jQuery Plug-in

One common pattern I've seen in usability, is that users don't always have great control over their mouse. It's easy to accidentally overshoot a target area with you mouse, which is really frustrating with things like nested dropdown menus, where accidentally mousing away from a target area may end up collapsing/closing the open menu.

Giva's recently released the MouseIntent jQuery Plug-in which aims to give developers a way to control this behavior. It works by monitoring an invisible border around the element to see what the user appears to be doing. If a user quickly moves back into the original element, then then no mouseaway event is ever fired. The plugin has a number of settings that allow you to control the behavior—such as monitoring whether or not the user is still moving the mouse in the invisible border area.

I've used the plugin in one of our dropdown menus that has nested menus and it's really helped to improve our user's experience.


Maskerade Date Mask Input Plugin

Giva has released released a new plugin (Maskerade jQuery Plug-in) which can convert a normal text field into a power date mask input field. The plugin supports a large array of date masks (even quarters) and even supports copy/paste. Here's a list of some of it's key features:

  • Keypress validation (ie. you don't need to submit the form for the mask to be applied)
  • Full keyboard support, including number to text-date interpretation (eg. typing 6 for a month will show June) and number-entry interpretation (eg. typing 02 in a yyyy date field will be interpreted as 2002)
  • Full mouse support
  • Masks can be defined as attributes of the input field; individual jQuery mask calls are not needed
  • Includes time-mask capability, with a date or alone
  • Default values and masks set as placeholders in the input field
  • Ability to set min and max dates allowed on a field
  • Allows for enforcing relational validation (ie. date1 must be before date2)
  • Automatic adjusting for invalid dates (eg. Feb 29, 2001 is adjusted to Feb 28, 2001)
  • Each date/time part fully highlighted on focus
  • Automatic tabbing to next date/time part once interpreted (eg. typing 2 in a "mm" date part will automatically tab you to the next date part), which allows quick keyboard entry
  • Allows for dask masks by quarters (eg. Q1, Q2, etc.)
  • Ability to support multiple languages
  • Custom event handlers; for example, a single keystroke can be defined to change the date to the current date
  • Detach/attach Maskerade behavior from the element

We have actually been using this plugin in production for a long time with great success.


Update to the Linkselect jQuery Plugin (v1.5.11)

I just pushed out an update to the Linkselect jQuery Plugin.

Revisions

v1.5.11 (2013-07-09)

  • Linkselect plugin now annouces an "update" event whenever the value changes--which allows you to set up listeners for when the value has changed.


Update to the mcDropdown jQuery Plugin (v1.3.3)

I just pushed out an update to the mcDropdown jQuery Plugin.

Revisions

v1.3.3 (2013-07-09)

  • Fixed issue where menu option underneath a selected sub-menu option would sometimes cause the option to disappear from the menu
  • Fixed issue where sub-menus would sometimes still be open after re-opening a menu


Adding custom callbacks to existing JavaScript functions

This morning I was reading Adding your own callbacks to existing JavaScript functions by Dave Ward—which covers how to overwrite an existing function so you can add some additional functionality (in this case, adding callbacks.) While the article is informative, a couple of improvements can dramatically improve his suggestion.

If you don't want to take the time to read Dave's article, in a nutshell he describes how we can overwrite a JavaScript function by storing a reference to the original function in a variable. So, we can take the following function:

function sayHello(name){
  alert("Hello, " + name + "!");
}

And we can now overwrite that function by storing a reference to the original function in a variable:

var sayHelloOld = sayHello;

function sayHello(){
  var name = prompt("Enter your name");
  sayHelloOld.apply(this, [name]);
}

Now there's a couple of problems with the above code.

  1. As written, this would only work if the original sayHello() function was defined in another <script> tag because of function hoisting.
  2. We're polluting the global name space.

We can solve both those problems by using a closure around our code:

// define a closure and pass in a reference to the global window object
(function (w){
  var sayHelloOld = w.sayHello;

  w.sayHello = function (){
    var name = prompt("Enter your name");
    sayHelloOld.apply(this, [name]);
  }
})(window || {});

(NOTE: You can see a working copy on JSFiddle.)

The other topic Dave discusses is how to add callback hooks to run before and after a the original function code runs. His suggestion is built around using the global name space to declare some function names. Since the example is based around jQuery, I'd suggest a much better method would be to add in custom events to your function. This gives you a way to bind callbacks to run, but your neither cluttering the global namespace nor running into issues if the callbacks aren't needed.

Dave's Original Solution

Here's what Dave's original solution looks like:

var oldTmpl = jQuery.fn.tmpl;

// Note: the parameters don't need to be named the same as in the
//  original. This could just as well be function(a, b, c).
jQuery.fn.tmpl = function() {
  if (typeof onBeforeTmpl === 'function')
    onBeforeTmpl.apply(this, arguments);

  // Make a call to the old tmpl() function, maintaining the value 
  //  of "this" and its expected function arguments.
  var tmplResult = oldTmpl.apply(this, arguments);

  if (typeof onAfterTmpl === 'function')
    onAfterTmpl.apply(this, arguments);

  // Returning the result of tmpl() back so that it's actually 
  //  useful, but also to preserve jQuery's chaining.
  return tmplResult;
};

Improved Solution

Using the two previously mentioned techniques combined, here's how I'd change that code:

(function ($){
  var oldTmpl = $.fn.tmpl;

  // Note: the parameters don't need to be named the same as in the
  //  original. This could just as well be function(a, b, c).
  $.fn.tmpl = function(){
    // trigger the before callback
    // to attach a callback, we just bind() this custom event to our jQuery object
    this.trigger("onBeforeTmpl", arguments);

    // Make a call to the old tmpl() function, maintaining the value 
    //  of "this" and its expected function arguments.
    var tmplResult = oldTmpl.apply(this, arguments);

    // trigger the after callback
    // to attach a callback, we just bind() this custom event to our jQuery object
    this.trigger("onAfterTmpl", arguments);

    // Returning the result of tmpl() back so that it's actually 
    //  useful, but also to preserve jQuery's chaining.
    return tmplResult;
  };
})(jQuery || {});

This gives use a few benefits over Dave's original solution:

  1. We're not polluting the global namespace
  2. We can now attach custom callbacks to each jQuery object separately

To use our code, we can do:

$("#id")
  .bind("onBeforeTmpl", function (){
    alert("before!");
  })
  .bind("onAfterTmpl", function (){
    alert("after!");
  })
  .tmpl(data, options, parentItem);
NOTE:
If your prefer to run the same callbacks for all $.tmpl() calls, you could attach the custom events globally.

Any comments on how to make this solution even better?


jQuery Field, mcDropdown and iButton Plugins Updated!

I'm working on migrating our application to use the latest version of jQuery. In the process of migrating, I've been working on fixing compatibility issues with several of the plug-ins I've worked on to make sure they work with the latest version of jQuery. So, here's a list of updated plug-ins and their release notes:

Field Plug-in v0.9.4

  • Fixed issues with jQuery v1.6.x
  • Fixed file fields not currently supported by getValue or getType
  • Fixed formHash breaks on fields with apostrophes
  • Fixed moveNext() did not work, it have invisible parent

mcDropdown jQuery Plug-in v1.3.1

  • Fixed support for jQuery v1.6.2

iButton jQuery Plug-in v1.0.03

  • Fixed compatibility issues with jQuery v1.6.2
  • Added className option for adding an additional class name to the main container—which is useful for adding alternative styles to the button
  • Revised image sprite to be better organized (handles are now grouped together)
  • Updated CSS to reflect changes to image sprite
  • Fixed bug where iButton behavior could be attached multiple times if code attempts to re-initialize on the same element


Easy AJAX using ColdFusion, jQuery and CFCs

A recent post on CF-Talk asked whether ColdFusion could use AJAX to do a database lookup. This is actually extremely easy to do in ColdFusion 8+, because it natively supports returning data in the JSON format.

To show how easy this is to do, I decided to throw together a little demo. This took me about 10 minutes to write—most of which was writing the markup. However, the bulk of the work is going to be handled automatically by ColdFusion, which will handle converting your data to JSON and by the jQuery Field Plug-in (which I wrote) which will handle populating your form from the data it receives from ColdFusion.

To show just how easy this all is, here's the jQuery code required to make an AJAX call to a CFC:

$.ajax({
  // the location of the CFC to run
    url: "example.cfc"
  // send a GET HTTP operation
  , type: "get"
  // tell jQuery we're getting JSON back
  , dataType: "json"
  // send the data to the CFC
  , data: {
    // the method in the CFC to run
      method: "getUserById"
    /*
      send other arguments to CFC
    */
    // send the ID entered by the user
    , userId: $("#userId").val()
  }
  // this gets the data returned on success
  , success: function (data){
    // this uses the "jquery.field.min.js" library to easily populate your form with the data from the server
    $("#frmMain").formHash(data);
  }
  // this runs if an error
  , error: function (xhr, textStatus, errorThrown){
    // show error
    alert(errorThrown);
  }
});

Our CFC looks like this:

<cfcomponent output="false">
  <cffunction name="getUserById" access="remote" returnType="struct" returnFormat="json" output="false">
    <cfargument name="userId" type="numeric" required="false" />

    <cfset var user = structNew() />
    <!---//
      The only tricky part here is to use the bracketed notation
      to match the case in your HTML, JS is case sensentive.
      If you use the dot notation (user.name) then the keys will
      be returned in uppercase.
    //--->
    <cfset user["name"] = "User " & arguments.userId />
    <cfset user["email"] = "user_" & arguments.userId & "@example.com" />
    <cfif (arguments.userId mod 2) eq 0>
      <cfset user["gender"] = "f" />
    <cfelse>
      <cfset user["gender"] = "m" />
    </cfif>

    <cfreturn user />
  </cffunction>
</cfcomponent>

I've posted a working example so that you can see how this code looks. For the "User ID" just enter any number. If an error occurs, the error callback will handle displaying the error to the screen. I've also posted the source code as a zip file you can download.

NOTE:
If you're still on ColdFusion MX & 7, there's a little more work because non-string data automatically gets converted to WDDX. While there are JS libraries for converting WDDX to native JS objects, they can be hard to find now that the OpenWDDX site has been shut down. You can find tools at RIAForge though that can convert your data into a JS string--which jQuery will automatically evaluate when it's received.
UPDATE:
I've added some sample code to the download to show how you can use a proxy template in you're using ColdFusion 7 to get the same results. Just look at the example_cf7.cfm. The only difference is we call the example_cf7_proxy.cfm template instead of calling the example.cfc directly. The proxy template uses CFJSON to convert the results to JSON.

[UPDATED: Friday, March 04, 2011 at 9:59:11 AM]


jNotify – A Light-weight Notification System for jQuery

One thing that's essential for any web application to be successful, is to implement a method for providing users with feedback based on their input. People want feedback based on their actions in real time. Users want to know that their actions had the intended consequence and if not, why it failed.

In the golden age of web applications, most applications solved this problem by returning a completely new page to the user any time they took an action on your website. If a user filled out a form on your website, the form's data would be sent to the server and the server would then generate a completely new page that either told the user that their action was successful or would show them the errors that occurred.

As web applications began to advance, more applications began to rely on JavaScript to provide more instantaneous feedback to users. If a user would leave a required field empty, developers would use a JavaScript alert() functions to notify users that they left the field incomplete. Over time, more and more applications have come to rely on JavaScript to provide users with a better user experience. The problem is that while the browsers have made many great strides over the years, we're still left with these very rudimentary UI controls for providing native feedback with users.

The alert(), prompt() and confirm() functions basically haven't changed at all since they were introduced. They offer essentially no control over the presentation and they're completely obtrusive to the user's experience. They all halt execution and force user action before any more processing occurs. While there are certain times this behavior is desired, there are many times when it's less than ideal.

For example, let's say you have you have a page that allows users to update bulk records in real time. On each row there is an "Update" button that allows a user to save updates back to the server. You obviously want to provide users with feedback after each update operation has completed. A quick solution is to use the alert() function when the update is successful.

The problems with this method are:

  • It's obtrusive—every time I display the "success" message to the user, I'm required to interact w/the alert box. This can really slow down the user's workflow.
  • No design control—I have no control over how the alert() dialog looks to the user. There's no difference between a successful message and an error message.
  • Only one message at at a time—I can never display multiple alerts to the user at the same time.
  • The alert() halts all activity—including any background tasks.

What we need is a better notification system that's just as easy to use, but allows us greater control over how the messages are display. This is where the jNotify Plug-in comes in. At just about 3KB, it's a very light-weight solution that gives us lots of flexibility over how we display notifications to the user. It allows us:

  • To display multiple messages to the user at one time
  • Use an HTML in the notification
  • Specify whether a notification is "sticky" or not (sticky messages require the user's interaction to close them)
  • Displays notifications in an unobtrusive manor—messages appear on the screen for short time, then fade away
  • Complete control over the positioning and appearance of the notification

I put together a short little video that illustrates some of the typical UI problems that the use of the alert() function introduces and show how jNotify can be used to solve those problems.

The jNotify Plug-in for jQuery

Implementing jNotify on your site is extremely easy. Just include the jquery.jnotify.js and jquery.notify.css files on your page, then substitute your use of alert() with $.jnotify().

So, instead of:
alert("An e-mail notification has been sent!");

You'd use:
$.jnotify("An e-mail notification has been sent!");

We've been using this plug-in in our own applications for over a year and we've had great success with it—especially when used with conjunction of providing feedback to users when AJAX operations complete.

You can see a live example on the jNotify jQuery Plug-in page.


Attaching mouse events to a disabled input element

I was doing some UI work this morning and ran into a bit of a problem. We have some HTML input elements that need to be disabled so the user can not interact with them. The problem is, we wanted to display a message to the user if they tried to interact with the element. While some browsers respond to a mousedown event, Firefox and Opera ignore all mouse-related events on a disabled form field. Since they seem to completely ignore mouse interaction, that means not even event delegation works.

After thinking about various ways to work around the behavior, I thought the most elegant solution might be to add an overlay over the disabled element which I'd use as the hotspot for mouse interaction. The overlay lays on top of the element and intercepts all mouse interaction.

Let's look at some sample HTML:

<label for="normal">
  <input type="checkbox" disabled="disabled" />
  Disabled Option
</label>

With a little jQuery magic, we can find the disabled element, get the parent <label /> element and then place a <div /> element over where the label element resides. The code looks like this:

// on DOM ready
$(document).ready(function (){
  // attach a click behavior to all checkboxes
  $(":checkbox").click(function (){
    alert("Clicked!");
  });

  // find the disabled elements
  var $disabled = $("#overlay-example input:disabled");

  // loop through each of the disable elements and create an overlay
  $disabled.each(function (){
    // get the disabled element
    var $self = $(this)
      // get it's parent label element
      , $parent = $self.closest("label")
      // create an overlay
      , $overlay = $("<div />");

    // style the overlay
    $overlay.css({
      // position the overlay in the same real estate as the original parent element 
        position: "absolute"
      , top: $parent.position().top
      , left: $parent.position().left
      , width: $parent.outerWidth()
      , height: $parent.outerHeight()
      , zIndex: 10000
      // IE needs a color in order for the layer to respond to mouse events
      , backgroundColor: "#fff"
      // set the opacity to 0, so the element is transparent
      , opacity: 0
    })
    // attach the click behavior
    .click(function (){
      // trigger the original event handler
      return $self.trigger("click");
    });

    // add the overlay to the page  
    $parent.append($overlay);
  });
});

You can see a live example here.


Custom jQuery selector for finding usable elements

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

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!

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)

[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).)