jQuery validation: Indicate that at least one element in a group is required

Posted

I had a need today to indicate that at least one of a set of input fields was required. I was hoping there was a direct way to do this in the jQuery validation plugin; while the method isn't quite as straightforward as I was wishing for, it's still fairly simple. To start with, I put class="required_group" on each of the elements in the group. Then, I added a custom validation method:

jQuery.validator.addMethod('required_group', function(val, el) {
        var $module = $(el).parents('div.panel');
        return $module.find('.required_group:filled').length;
});
... a custom class rule to take advantage of the new method:
jQuery.validator.addClassRules('required_group', {
        'required_group' : true
});
... and finally a custom message for the new method:
jQuery.validator.messages.required_group = 'Please fill out at least one of these fields.';
What I'd love to see is a way to specify a dependent group without using a custom class rule, but I'm not sure what this would look like, as all validation rules are either keyed off an element's class or the presence of the element's name in the rules object. Thoughts? I'm open to the possibility that there's a far better way to solve this --

Filed under  //  form validation   jquery   plugins  
Comments (21)
Posted

Track user clicks on certain links

Posted

We recently added a new feature to dailystrength.org, and there was much debate about whether users would use it as we intended. To find out, I wrote a clicktrack ditty that sends some data to the server when a link is clicked. It was somewhat inspired by a post I wrote a while ago about tracking outbound links with Google Analytics and jQuery, but is actually even simpler. I've retooled my clicktrack script to work as a generic, 2.5k plugin, which you can download here.

Usage

To use this plugin, add a distinct classname to all links you would like to track. For example:
this is a link
You can also add additional classes to a link with a distinct prefix — the default prefix is "ct_". These classes will be passed to the server along with other clicktrack data:
this is a link
Then, include the script and a line of code to initialize it in the foot of your page. Note that you must at least pass a URL for the remote (server-side) script that will handle the clicktrack data ("foo.php" in the example below):
type="text/javascript">

$(document).ready(function() { $("a.clicktrack").clicktrack("foo.php"); });
This will pass the following data to a script called "foo.php" on your server:
  • source (string) the URL of the current page
  • target (string) the HREF of the clicked link
  • classes (array) classes on the link that matched the prefix (default prefix is "ct_")

Optional parameters

The only required parameter is the URL of the remote script. However, you can pass a more elaborate configuration object. The options and their defaults are as follows:
var defaults = {
                remote_script: null,
                prefix: 'ct_',
                extraData: null,
                callback: function(){},
                dataType: null,
                sendOnce: true,
                preventDefault: false
        };
  • remote_script the remote script that will handle the clicktrack data. this can be passed as a single, string argument to clicktrack(), or it can be passed as part of the configuration object, but it must be specified
  • prefix the prefix for classnames that should be passed as part of the clicktrack data. defaults to "ct_".
  • extraData additional data that should be passed as part of the clicktrack data.
  • callback function to be executed upon successful execution of the remote script. you'll need to set preventDefault to true for this callback to get executed, and then redirect the user using window.location within the callback as required.
  • dataType type of data you expect to be returned by the remote script. see the jQuery documentation on $.post() for details.
  • sendOnce whether clicktrack data for a given link should be sent just once. defaults to true.
  • preventDefault whether clicks on clicktrack links should be prevented from taking the user to the link destination. defaults to false (links will work as expected by default).

Filed under  //  analytics   howto   jquery   plugins  
Comments (3)
Posted

Update page using JSON data

Posted

updateWithJSON is a jQuery plugin that updates elements on your page based on key/value pairs in a JSON object. Usage:

$.updateWithJSON(jsonData)
Here's a demo, and here's how it works:
  • iterate over each property/value combination in the JSON object
  • look for an element in the DOM that matches the property name
    • first, look for an element with a matching id attribute
    • if no element with a matching ID is found, look for input, select or textarea elements with a matching name attribute
  • update the value, contents or selection of the matched element(s) based on the value in the JSON object
NOTE: If you have multiple checkboxes with the same name attribute, or a select that allows multiple values, you'll need to pass the values of the selected items in an array:
{ 
  text_input: 'value1',
  checkbox_group: ['value1','value2','value3'],  
  multi_select: ['value4','value5','value6']
}
If you use a name attribute like "input[]" on a set of related checkboxes, remember that you'll need to quote it in the JSON object:
{
  'input1[]': ['value1','value2','value3']
}

The back story

For a recent project, I needed to update elements on a page based on some server-side calculations. JSON is a handy way to transport data from the server back to the browser, and jQuery's getJSON() method makes it painless. So, for example, I was getting back
{ foo: 'bar' }
with which I needed to update an element corresponding to "foo" and give it the value "bar". Simple enough; on the page I was dealing with, it was easy to put a unique ID on every element that would need to be updated, and I matched that ID with the property name in the JSON object. It was (almost) as simple as this:
$.each(data,function(name,value) {
  $('#'+name).val(value);
}
It's not always the case, though, that you can easily put a unique ID on each element that will need updating; for example, PHP rewards you if you put the same name attribute on related checkboxes. Having checkboxes with the same name but different IDs can quickly get confusing when you're dealing with data on the client side and on the server side. The solution above worked in my particular case, but I wanted something that would work more broadly -- say, on pages that had related checkboxes -- and that's how I came up with the plugin.

Filed under  //  howto   jquery   plugins  
Comment (1)
Posted

Determine the order of two DOM elements

Posted

Inspired by this from PHP, I wanted a utility function to determine whether a given element came before or after another element in the DOM.

(function($){
  $.order = function($a,$b) {

    $a = $a.eq(0);
    $b = $b.eq(0);
    var c = 'order-test';

    $a.addClass(c);

    if ($b.hasClass(c)) {
        // elements are the same
        return 0;
    }

    $b.addClass(c);

    var $elements = $('.'+c);
    $elements.eq(0).addClass(c+'-first');

    if ($a.hasClass(c+'-first')) {
        // $a is first
        return -1;
    } else if ($b.hasClass(c+'-first')) {
        // $b is first
        return 1;
    }

};

})(jQuery)

Filed under  //  javascript   jquery   plugins  
Comments (0)
Posted

Flexible plugin for nested table of contents

Posted

My partner saw the search results page for this blog and, always on the lookout for a way to improve things, suggested that it would benefit from a table of contents at the top. I could do this with Wordpress, of course, but it sounded like a good idea for a jQuery plugin, too. A little googling showed that there were a couple of existing solutions, but there were two things I wanted that they didn't seem to offer:

  • the ability to select via selectors exactly which elements would be included (i.e., #content h2 instead of just h2)
  • the ability to nest the lists of sections to reflect the hierarchy of the page
After puzzling a bit over how to get a list of selected elements in the order they appear in the document (see note below), I wrote my own plugin. Usage:
var $toc = $.toc('#content h1, h2, h3.foo');
So you can do:
$.toc('h1,h2,h3').prependTo('body').attr('id','toc');

Note

$('h1,h2') will return a jQuery object containing all the h1's in a document, followed by all the h2's in a document: h1 h1 h1 h2 h2 h2. I needed all the h1's and h2's in the document in the order they appeared; so, perhaps, h1 h1 h2 h2 h1 h2 h2. The obvious-in-hindsight answer:
$('h1,h2').addClass('selectedElements');
var selectedElements = $('.selectedElements');
Of course, the bad thing about this is that, on pages with lots of elements, it's going to be slow. Any better suggestions?

Filed under  //  jquery   plugins   table of contents   toc  
Comments (8)
Posted

Anchor-based URL navigation, Take 2

Posted

Update: I encourage everyone who arrives at this post to check out Ben Alman's jQuery BBQ plugin -- it provides a ton of functionality above and beyond what this plugin offers, is much more robust, and is built to take advantage of the latest version of jQuery. While you're welcome to use the plugin on this page, it is no longer supported. I noticed I was getting a lot of visits via Google for my post on anchor-based URL navigation with jQuery, so I decided to write a plugin that would accomplish the same thing.

Call this function on an element that contains "panels" which, in turn, contain anchors. The container element should contain ONLY related panels, as this plugin will show only one first-child element of the container at once. This plugin will look at the current URL and see whether it contains an anchor. For example:
http://www.mysite.com/index.html#panel1
If it finds an anchor, it will look for the panel that contains the associated anchor tag; it will show this panel using the function defined by the showFn option, and it will hide the panel's siblings using the function defined by the hideFn option. If the nav option is set, it will use the nav option setting as a selector to locate the page's navigation. It will look for a link with an href matching the anchor; if it finds a matching link, it will run the function defined by the currentNavFn option on the link element (useful for setting the current nav item). If the nav option is set, it will also set up onclick functions on each of the links in the nav that refer to anchors on the page; the onclick function will use the show and hide options to show and hide the associated panels.

Options

  • showFn function to show the current panel
  • hideFn function to hide the current panel's siblings
  • nav selector for the page's navigation section
  • currentNavFn function to run on the link in the nav that is associated with the anchor
  • anchorClass class assigned to anchor tags; setting this will improve the speed on pages with lots of links
  • noAnchorFn function to run if the URL does not contain an anchor
Default option values:
var options = {
  showFn: function() { $(this).show(); },
  hideFn: function() { $(this).hide(); },
  nav: null,
  currentNavFn:
    function() {
      // this will add a class to the parent of the
      // link that matches the currently selected anchor
      $(this).parent().siblings().removeClass('anchor-nav-current');
      $(this).parent().addClass('anchor-nav-current');
    },
  anchorClass: null,
  noAnchorFn:
    function() {
      // this will show the first panel
      // if the URL doesn't contain an anchor
      $container.children().hide().eq(0).show();
    }
};

Filed under  //  anchor   jquery   navigation   plugins   progressive enhancement  
Comments (6)
Posted

Graph data from an HTML table using jQuery and flot

Posted

NOTE: A couple of commenters have pointed out that this plugin has issues in IE. I haven't had time to look into it, so I'm not sure whether they are related to my plugin or to flot. Make sure you read flot's readme about getting flot working in IE, I think it will address most IE issues. NOTE 2: I've posted a minor update to graphTable to remove an errant console.log call. NOTE 3: I've changed the license on the plugin; it is now dual-licensed MIT and GPL, like jQuery itself. I just got done with a first draft of the graphTable plugin, which lets you take a simple HTML table and turn the data in it into a graph using jQuery and flot. Here's a demo, and here's the jQuery plugin page where I'll continue development if there's any interest. The most basic usage is simple:

$('#table1').graphTable({series: 'columns'});
graphTable() takes up to two objects as arguments: the first is an object with the arguments for graphTable; the second is an object with arguments to be handed off to flot (read more about flot arguments). The graphTable arguments let you:
  • choose which parts of the table will be used for the graph data
  • choose where the graph will go in relation to the table and how big it will be
  • run a function on cell contents (to remove dollar signs, for example) before passing them to flot
Each of the following options can be set as part of an object passed to graphTable as the first argument. For example:
$('#table1').graphTable({
  series: 'columns',
  dataTransform: function(s) { return(s.replace('$','')); }
});

options for reading the table

defaults will work in most cases except you'll want to override the default args.series if your series are in columns
  • series 'rows', 'columns'
  • labels integer index of the cell in the series row/column that contains the label for the series; default is 0
  • xaxis integer index of the row/column (whatever args.series is) that contains the x values; default is 0
  • firstSeries index of the row/column containing the first series; default is 1
  • lastSeries index of the row/column containing the last series; will use the last cell in the row/col if not set; default is null
  • dataStart index of the first cell in the series containing data; default is 1
  • dataEnd index of the last cell in the series containing data; will use the last cell in the row/col if not set; default is null

graph size and position

  • position 'before', 'after', 'replace'; indicate whether the graph should go before the table, go after the table, or replace the table
  • width leave as null to use the width of the table for the width of the graph; otherwise, set to a width in pixels
  • height leave as null to use the height of the table for the height of the graph; otherwise, set to a height in pixels

data transformation before plotting

  • dataTransform function to run on cell contents before passing to flot; string -> string
  • labelTransform function to run on cell contents before passing to flot; string -> string
  • xaxisTransform function to run on cell contents before passing to flot; string -> string

Filed under  //  flot   front-end development   graph   javascript   jquery   plugins   table   tabular data  
Comments (57)
Posted

jQuery: Randomly reorder children elements of selected elements

Posted

I'm working on a new template for a client, and they asked me to display some list elements in a random order on each page load. I did my Google due diligence, but didn't come up with anything that wasn't slideshow-like -- what I wanted was just something that would show all of the children elements of an element, but in a random order. (In this particular case, the children elements were case case studies.) I was already using jQuery on the page, so I wrote a plugin: jquery.reorder.js (1k) Here's the code:

$.fn.reorder = function() {

  // random array sort from
  // http://javascript.about.com/library/blsort2.htm
  function randOrd() { return(Math.round(Math.random())-0.5); }

  return($(this).each(function() {
    var $this = $(this);
    var $children = $this.children();
    var childCount = $children.length;

    if (childCount > 1) {
      $children.remove();

      var indices = new Array();
      for (i=0;i

Thoughts? Suggestions? Let me know.

Filed under  //  front-end development   javascript   jquery   plugins  
Comments (10)
Posted