Saturday, July 27, 2013

Avoiding Code in KnockoutJS Data Bindings

I was reviewing some KnockoutJS code that created a dialog box. It had this line for generating the buttons:



Granted, the Knockout documentation recommends using the anonymous function in the click handler, however, once I start seeing Javascript appearing in the template, I get concerned. Back in the day, we'd stuff our HTML full of PHP tags to dynamically render a page. At the time, we didn't really know better (or have another option). Over time, we've evolved to separating the presentation from the logic that binds data and attaches behavior to that content. Today, there's a plethora of templating languages each with their own vernacular and, generally, many programming like qualities. So, while the capability exists to still write code inline in the template, its definitely not considered a best-practice. Given that, my question becomes: How much code is too much?

Tuesday, July 23, 2013

Detecting and Binding to Click Events Outside an Element

Binding to events that occur on a target element is one thing. Binding to the same event when it occurs anywhere but a target element is another. There's plenty of reasons to need to know when, say, a click happens somewhere other than a target element. A concrete example is a drop-down menu. You generally want to hide it if the user clicks somewhere other than the menu. Typically, you might do something like this:


   $('#menu').on('click', function( e ) {
      // process selection
   });
   
   $(document).on('click', function( e ) {
      // hide the menu
   ));


A click in the menu will result in changing the selection and then bubble up to the document to hide the menu. If the click happens outside the menu, only the document click handler executes. Now, let's say the menu has sub menus that only open when you click on its parent. You don't want to hide the menu at that point. In this case, you might just cancel bubbling to prevent the menu from closing:


   $('#menu').on('click', function( e ) {
      // process selection

      // don't bubble to document
      // it should not close yet.
      return false;
   });
   
   $(document).on('click', function( e ) {
      // hide the menu
   ));


This simple example quickly breaks down if you have multiple things that depend on click detection outside of the element.

Saturday, July 20, 2013

Finding a jQuery Tooltip Plugin that Meets Your Needs

The humble little Tooltip has evolved from simply showing a bit of text over the page using the title or alt attribute of a link or image element to something closer to an Infobox or Popover displaying rich, dynamic content enabling user interaction. There are so many libraries that provide tooltip features from the simple hover over text to these interactive information boxes that finding the one that meets your needs while staying small and maintaining consistency with your site can become a daunting task.

I set out to review a few of the more popular libraries out there to see what features they provided and how well they could adapt to the different uses I had in mind on my site. I limited my comparison to jQuery plugins that were either well followed on GitHub or part of another library. In the end, I settled on the following four evaluating them based on these criteria:

  qTip2 jQuery UI PowerTip Bootstrap
Content ***** *** *** ***
Styling ***** *** ** **
Positioning **** ***** ** *
Triggering ***** * **** **
Animation ***** ***** * *
Overall ***** **** *** **


The ratings simply indicate the depth of options available in each category. This means if you just need a basic tooltip, you can stick with PowerTip or Twitter Bootstrap. If you need more functionality and control, you may want to consider qTip or jQuery UI. I spent some time trying to use the various features available in each category and share those results below. I also copied all the demos to my sandbox for easier review and access to the source code.

Friday, July 12, 2013

Detect When a DOM Element is Scrolled into View using jQuery

The browser window can only show so much content before the user has to scroll. Knowing if or when a certain element has scrolled in or out of the visible area on the screen may be useful in certain situations. Not intending to reinvent the wheel or anything, I did some research to see what other libraries existed related to scrolling in the browser:



I'm sure others exist but I thought it was an interesting problem to explore so I set out to build a basic solution that would have the following functionality:

  • Add elements of interest to some global list with configuration options
  • Listen to the window scroll event and check each element to see if its in the viewport
  • When it first enters/leaves trigger an event/callback to allow further processing


Register Elements

First, we need to be able to register the elements we care about so their scroll position can be monitored. We'll use this global list in the scroll event handler later:



This just creates a tracking object which contains state, options, and the target element.

Monitor Scrolling

The next step is to listen for scroll events on the window. We can't listen to the individual items since they won't generate scroll events - the container that scrolls will emit these which means we need to listen to that container element and then check all the registered items to determine if they scrolled in or out of the visible part of the page. However, scrolling can generate a lot of events and probably will cause a performance hit if we are constantly trying to test for the position of all the registered elements. What we need is a way to only process one event per some interval of time. A possible solution is the Underscore throttle function which is a nice way to manage events that may rapidly fire to help boost performance in a situation where it might degrade if the underlying logic ran that frequently. Now, I didn't want a dependency on Underscore in this case so I just implemented a simple version specific to my needs:



Another benefit of this buffer is if the user scrolls quickly past an item, it won't be detected as being in the view.

Check and Fire Events

The checkInView() function referenced above iterates over all the registered elements we're interested in and checks the boundaries of the element against the visible part of the page. When the element was registered, an object was created with a invp key to track the state of the element. False means its not in the visible part of the screen and true means it is. This allows us to track when it changes and to fire the appropriate event:



Create a Plugin

At this point, we have the three parts required to enable the detection and trigger an event. Now, we just need to package it up so its easier to use with jQuery. For testing, I made a quick plugin:



This plugin is lacking the necessary cleanup handling and some other housekeeping but will work well enough for now. Next, we can create some markup:



And use the plugin to monitor the DIVs as they are scrolled:



I dropped this code into jsFiddle as a demo. You'll need to have your console open to see the output.

Next Steps

At this point, this is only a proof-of-concept to hash out the required functionality for a more robust version. Its missing some critical elements:
  • Only detects scrolling up and down not left and right
  • Only can detect elements scrolling in the window, not elements contained inside another element that scrolls
  • The jQuery plugin is lacking teardown logic required to stop monitoring an element and free up memory
In order to get there, some major refactoring is necessary to fill the gaps in functionality. As part of that reimplementation, I'd probably switch to using the jQuery UI widget factory which neatly encapsulates the details of initializing/destroying a jQuery plugin. Additionally, this plugin only detects the positioning and raises an event. It seems that including the ability to animate the scroll position instead of using another library would make sense as well.

I've built a more robust version of this plugin using the jQuery UI widget library as a basis. This enhanced version enables monitoring, querying, or changing the scroll position of an element relative to any scrolling container:

Friday, July 5, 2013

I'm Busy: Enhancing the jQueryUI Button with a Waiting Spinner

The Dojo Toolkit has a convenient BusyButton widget. This seemed like a nice feature to have available in my jQuery UI widget library. The motivation for having the button is two-fold:


  1. Prevent the user from clicking multiple times on a button that performs an action you only want to happen once
  2. Provide visual feedback to the user so they know they did click the button and something is actually happening
The circumstances for using a button like this makes the most sense on AJAX based action like form submissions or searching. The jQuery button fortunately provides a really good base to add this functionality. I wrote a quick prototype to see if my idea would work:

function toggleButton ( $el ) {

   if ( $el.button( 'option', 'disabled' ) ) {
      $el.button( 'enable' );
      $el.button( 'option', 'label', 'Save' );
      $el.button( 'option', 'icons', { primary: null } );
   } else {
      $el.button( 'disable' );
      $el.button( 'option', 'label', 'Saving ...' );
      $el.button( 'option', 'icons', { primary: 'ui-icon-waiting' } );
   }
}

function clickButton () {
   var $el = $(this);

   toggleButton( $el );
   
   /* This probably will be an AJAX-based call  */
   setTimeout(function () {
      toggleButton( $el );
      $el.one( 'click', clickButton );
   }, 2000);
}

$(function () {

   $('.ui-wait-button')
      .button({ disabled: false })
      .one( 'click', clickButton );

});
This code will result in the following button:



The code leverages the existing features of the button widget which allows setting an icon and controlling whether the button appears and acts disabled. I wanted to see how easy it would be to swap my own animated GIF into the spot where an icon would be displayed. Additionally, to ensure only one click is handled, I bound the click event using $.one() instead of $.on(). From there, its just a matter of adding the functionality to perform the action we're going to wait to complete and then reset the state back to the initial state so we can click the button again. In this experiment, I just used setTimeout() to emulate an action that may take a few seconds.

There is a little CSS needed to show the spinner as the icon. By default, the button widget is expecting a class that will position the palette of icons attached by the ui-icon class. Our class needs to replace this image with the animated GIF that represents our spinner:
.ui-wait-button .ui-icon-waiting {
   background-image: url("loading.gif");
   background-position: 0 center;
}

Its important to ensure the background of the spinner is transparent and it will fit in the expected 16px square defined by the theme. My spinner is 16X11 pixels so I had to adjust the positioning a bit to align it properly. You can use a site like ajaxload.info to select from a list of existing spinners and generate one with a transparent background.

Finally, we just need some simple HTML that will represent our button:
  

This approach might be enough for a one-time use scenario, but its not very DRY if you need to have several buttons that will provide a waiting state. Plus, there are some other features that might be better suited if it were built as a jQuery UI widget. By encapsulating it in a widget, I can use an event to signal the waiting state from the click and provide a callback function that the event handler can use to signal the waiting is complete. That function can take several arguments to indicate what to do next. In some situations, it might be desirable to change the label to something other than the original starting label and/or leave the button in the disabled state.

I used the above concept and built an extension to the button widget called WaitButton. I have the code available on GitHub (JS|CSS|GIF) along with some examples. I added two options to the button, waitLabel and waitIcon, which do basically what the name implies. Neither are actually required as the widget will use the default ui-icon-waiting class as the primary icon in the button and the existing label if a waiting label option is not specified. You can define your own class and set waitIcon, override the existing ui-icon-waiting class, or replace the animated GIF (the class loads waitbutton-loading.gif from the images folder). In addition to those options, I added a waiting event that should be used to handle the click from the user and call the done() callback function to signal the processing is complete. Without any arguments, the button will return to the starting state. However, several variations are available:

Default completion - return to the start state
   $('#save')
      .waitbutton({ waitLabel: 'Saving ...' })
      .on( 'buttonwaiting', function ( e, ui ) {
            
            setTimeout(function () { ui.done(); }, 2000);
         });

Return to original label, but leave disabled
   $('#save')
      .waitbutton({ waitLabel: 'Saving ...' })
      .on( 'buttonwaiting', function ( e, ui ) {
            
            setTimeout(function () { ui.done(false); }, 2000);
         });

Change to a different label, but enable the button
   $('#save')
      .waitbutton({ waitLabel: 'Saving ...' })
      .on( 'buttonwaiting', function ( e, ui ) {
            
            setTimeout(function () { ui.done( 'Saved' ); }, 2000);
         });

Change to a different label and leave disabled
   $('#save')
      .waitbutton({ waitLabel: 'Saving ...' })
      .on( 'buttonwaiting', function ( e, ui ) {
            
            setTimeout(function () { ui.done( 'Saved', false ); }, 2000);
         });

The last three require calling waitbutton() on the element to force it back to the initial state. In my demos, I used a reset button to enable that step.

Just a few simple tweaks to the base UI framework and we have a handy button that can provide visual feedback about state and prevent extra clicks from messing up our processing.