Manage your data with the jQuery Collections plugin





Download the script

It’s a classic problem: you have a small set of data records that you want your users to interact with at the same time, usually with a need for ordering of the records. I’ve seen this exact problem pop up in just about every project that I’ve every worked on. The most obvious solution to the problem is an inline-editable datagrid-style widget. Often this provides a “good enough” solution, and many UI designers convince their users to integrate this type of widget, as it is relatively easy to do.
This sometimes leads to dis-joined user experiences, inconsistent look & feel, depending on what solution is employed and how much time you spend on integrating it.

First, here are some examples of data grids:

Datagrid from extjs.com

Datagrid based on mootools

Datagrid from paxjs.com

These widgets have the following in common:

  • Data is displayed in a single row by default
  • When editing a row, the data from other rows is not always accessible
  • The look and feel is “like a spreadsheet”, and usually doesn’t offer too much in the way of customisation.

This is of course not too bad, as long as it is consistent with the rest of your application, and performs the necessary
tasks for your user. Some datagrids allow you to customise colours and fonts, but that is usually about as far as you can take
it without dwelving deeply into customisation and spending lots of time setting up work arounds for how the
grid is laid out and interacts with your data.

So how do you create a good solution, which easily fits in with your design, and back-end api without spending a
bunch of time customising the datagrid script, and laboriously hacking at the CSS?

The collection manager

The premise is quite simple, it works a little like a datagrid in that there is a concept of data-rows, but
it doesn’t matter what HTML you use for each row; this gives us flexibility to customise exactly how the layout
looks, and at the same time has enough smarts to help you manage the collection of items very easily.

To create a collection, you simply setup a template, a target element, a add button and initialise
the collection using the collections manager script.

Let us start with the most simple of examples.

Example: Hello world

In this example, you can add names – enter a few, and click the “Submit form” button to see how the values map to back-end objects.



The html
<div id=‘collectionTarget0′ class=‘clearfix’></div>
<div class=‘clear’></div>

<button type=‘button’ id=‘addButton0′>Add item</button>

We have a div as the target to render the collection manager into, plus an “add” button. The clearing div is optional,
depending on your layout requirements. This HTML is inside a standard form tag that submits to collections.php, which simply
shows you the contents of the submitted form.

The template
<div id=‘collectionTemplate0′ style=‘display: none’>
    Name: <input name=‘name’ type=‘text’>

</div>

The template is where things get interesting; it is plain html, BUT, (and it’s a big BUT), there are no element IDs.
This is because the collections manager script takes the template, and adds IDs to the form elements as needed; it uses
the collection manager’s namePattern to name the field and give it a unique ID. This is a requirement, as we can
have multiple data rows.

The initialisation script
jQuery( document ).ready( function() {

    jQuery(‘#collectionTarget0′).collections( {
        template: jQuery(‘#collectionTemplate0′),
        addButton: jQuery(‘#addButton0′)

    } );
} );

The initialisation script simply brings everything together, it uses the template as the query target, specifies the
template element, and then the add button.

Let us look at a slightly more useful example, a customer profile – lets assume we have a data structure that maps to this JSON:

var customerProfile = {
    name: ,

    comments: ,
    customerType: [‘n’, ‘r’, ‘v’],
    newsletter: true

};

The customerType maps to one of the following values: n = new, r = repeat, v = vip; news letter is true or false.

Example 1: Collection manager for customer profiles

In this example, we start with two rows of example data, and use the default settings for everything else. We also include a
delete button, so that you can remove collection items. Change some of the values, or perhaps add a new row, then click the
“Submit form” button to see how the back-end sees your values.


The html
<div id=‘collectionTarget1′ class=‘clearfix’></div>

<div class=‘clear’></div>
<button type=‘button’ id=‘addButton’>Add</button>

Again, we have a div and an “add” button. Also, the HTML is inside a standard form tag that submits to collections.php.

The template
<div id=‘collectionTemplate1′ class=‘clearfix cp_collection’ style=‘display: none’>

    <input name=‘id’ type=‘hidden’>
    <div class="span-3">

        <div class="span-3">
            <label class=‘above’ for=‘name’>Name</label>

            <input class=‘coll medium’ name=‘name’ type=‘text’>

        </div>
        <div class="span-3">
            <label class=‘above’ for=‘customerType’>Customer Type</label>

            <select class=‘coll’ name=‘customerType’>
                <option value=‘n’>New</option>

                <option value=‘r’>Repeat</option>
                <option value=‘v’>VIP</option>

            </select>
        </div>
    </div>

    <div class="span-9">
        <label class=‘above’ for=‘comments’>Comments</label>

        <textarea class=‘mediumText coll’ name=‘comments’></textarea>
    </div>

    <div class="span-3">
        <label class=‘above’>Send newsletter</label>

        <div>
            <input class=‘coll’ name=‘spam’ type=‘radio’ value=‘true’>Yes

            <input class=‘coll’ name=‘spam’ type=‘radio’ value=‘false’>No

        </div>
    </div>
    <div class="span-2 last">

        <label class=‘above’>Action</label><br/>
        <button type=‘button’ name=‘deleteButton’>Delete</button><br/>

    </div>
</div>

The template is more complex than the hello world example, but it is just plain HTML with no IDs, just names for the form
fields. You will note that there is also a “Delete” button in this template, which allows us to remove items.

The initialisation script
jQuery(document).ready(function(){

    jQuery(‘#collectionTarget1′).collections( {
        template: jQuery(‘#collectionTemplate1′),
        data: [

            { id: 1, name: ‘David Lann’, comments: ‘David gets a discount at your discression’, customerType: ‘v’ },

            { id: 2, name: ‘Jill Jones’, comments: ‘call on 0295551231, or 0405551599′, spam: ‘true’ }

        ],
        addButton: jQuery(‘#addButton’),
        deleteButtonName: ‘deleteButton’

    } );
} );

The initialisation script now contains some initial data and a reference to the “Delete” button. The use of the
deleteButtonName parameter specifes that the script will attach a delete function to that button for each
record.

Example 2: collection manager with order

Here we expand on the previous example by adding ordering and move up / down buttons; this is a unique feature that the collection
manager has, compared to standard data grids: a concept of order.
As you move a field up or down, we persist the value of the order fields, so assuming the fields were in the correct order to start
with, we should have the correct order once we submit the form. Note that usually the order field would be hidden, but we’re showing
it here to make it easier to see what is happening.
Change some of the values, and move the fields around, then click the “Submit form” button to see the values, taking
note of the order field values.


The html
<div id=‘collectionTarget2′ class=‘clearfix’></div>

<div class=‘clear’></div>
<button type=‘button’ id=‘addButton2′>Add</button>

Again, we have a div and an “add” button. Also, the HTML is inside a standard form tag that submits to collections.php.

The template
<div id=‘collectionTemplate2′ class=‘clearfix cp_collection’ style=‘display: none’>

    <input name=‘id’ type=‘hidden’>
    <div class="span-3">

        <div class="span-3">
            <label class=‘above’ for=‘name’>Name</label>

            <input class=‘coll medium’ name=‘name’ type=‘text’>

        </div>
        <div class="span-3">
            <label class=‘above’ for=‘customerType’>Customer Type</label>

            <select class=‘coll’ name=‘customerType’>
                <option value=‘n’>New</option>

                <option value=‘r’>Repeat</option>
                <option value=‘v’>VIP</option>

            </select>
        </div>
    </div>

    <div class="span-9">
        <label class=‘above’ for=‘comments’>Comments</label>

        <textarea class=‘mediumText coll’ name=‘comments’></textarea>
    </div>

    <div class="span-3">
        <label class=‘above’>Send newsletter</label>

        <div>
            <input class=‘coll’ name=‘spam’ type=‘radio’ value=‘true’>Yes

            <input class=‘coll’ name=‘spam’ type=‘radio’ value=‘false’>No

        </div>
        <div class="span-3">
            <label class=‘above’ for=‘order’>Order</label>

            <input class=‘coll short orderField’ name=‘order’ type=‘text’>

        </div>
    </div>
    <div class="span-2 last">

        <label class=‘above’>Action</label><br/>
        <button type=‘button’ name=‘deleteButton’>Delete</button><br/>

        <button type=‘button’ name=‘moveUpButton’>Up</button><br/>

        <button type=‘button’ name=‘moveDownButton’>Down</button>
    </div>

</div>

The template is almost the same as the previous example, except that we now have an order field.

The initialisation script
jQuery( document ).ready( function() {

    jQuery(‘#collectionTarget2′).collections( {
        template: jQuery(‘#collectionTemplate2′),
        //  Initial data

        data: [
            { id: 1, name: ‘David Lann’, customerType: ‘v’, order: ’1′ },

            { id: 2, name: ‘Jill Jones’, customerType: ‘n’, spam: ‘true’, order: ’2′ }

        ],
        addButton: jQuery(‘#addButton2′),
        deleteButtonName: ‘deleteButton’,

        beforeAdd: function( data, args ) {
            //  Set the order, using the collection count

            var collectionCount = parseInt( jQuery( args.addButton ).attr( ‘collectionCount’ ), 10 );

            data[‘order’] = data[‘order’] || collectionCount;
            return data;

        },
        bindings: {
            //  Move up collection, freezing the order field values; you could

            //  optionally freeze other fields here
            ‘moveUpButton’: {

                event: ‘click’,
                func: function( e, collection ) {

                    jQuery.fn.moveUpCollection( collection, [‘order’] );

                }
            },

            //  Move down collection, freezing the order field values
            ‘moveDownButton’: {

                event: ‘click’,
                func: function( e, collection ) {

                    jQuery.fn.moveDownCollection( collection, [‘order’] );

                }
            }

        }
    } );
} );

The initialisation script now contains some initial data and a reference to the “Delete” button. The use of the
deleteButtonName parameter specifes that the script will attach a delete function to that button for each
record.

Example 3: collection manager for customer profiles with ajax delete

Yet again we expand on the previous example, this time we add ajax delete…
Change some of the values, and move the fields around, then click the “Submit form” button to see the values, taking
note of the order field values. We have also included the “namespace” parameter, which affects how the data is presented
to the back-end, but more about this later.


The html
<div id=‘collectionTarget3′ class=‘clearfix’></div>

<div class=‘clear’></div>
<button type=‘button’ id=‘addButton3′>Add</button>

Again, we have a div and an “add” button. Also, the HTML is inside a standard form tag that submits to collections.php.

The template
<div id=‘collectionTemplate3′ class=‘clearfix cp_collection’ style=‘display: none’>

    <input name=‘id’ type=‘hidden’>
    <div class="span-3">

        <div class="span-3">
            <label class=‘above’ for=‘name’>Name</label>

            <input class=‘coll medium’ name=‘name’ type=‘text’>

        </div>
        <div class="span-3">
            <label class=‘above’ for=‘customerType’>Customer Type</label>

            <select class=‘coll’ name=‘customerType’>
                <option value=‘n’>New</option>

                <option value=‘r’>Repeat</option>
                <option value=‘v’>VIP</option>

            </select>
        </div>
    </div>

    <div class="span-9">
        <label class=‘above’ for=‘comments’>Comments</label>

        <textarea class=‘mediumText coll’ name=‘comments’></textarea>
    </div>

    <div class="span-3">
        <label class=‘above’>Send newsletter</label>

        <div>
            <input class=‘coll’ name=‘spam’ type=‘radio’ value=‘y’>Yes

            <input class=‘coll’ name=‘spam’ type=‘radio’ value=‘n’>No

        </div>
        <div class="span-3">
            <label class=‘above’ for=‘order’>Order</label>

            <input class=‘coll short orderField’ name=‘order’ type=‘text’>

        </div>
    </div>
    <div class="span-2 last">

        <label class=‘above’>Action</label><br/>
        <button type=‘button’ name=‘deleteButton’>Delete</button><br/>

        <button type=‘button’ name=‘moveUpButton’>Up</button><br/>

        <button type=‘button’ name=‘moveDownButton’>Down</button>
    </div>

</div>

The template is structurally the same as the previous example; of course the names of the fields are different.

The initialisation script
jQuery( document ).ready( function() {

    var config = {
        template: jQuery(‘#collectionTemplate3′),
        //  Initial data

        data: [
            { id: 1, name: ‘David Lann’, customerType: ‘v’, order: ’1′ },

            { id: 2, name: ‘Jill Jones’, customerType: ‘n’, spam: ‘true’, order: ’2′ }

        ],
        addButton: jQuery(‘#addButton3′),
        deleteButtonName: ‘deleteButton’,

        beforeAdd: function( data, args ) {
            //  Set the order, using the collection count

            var collectionCount = parseInt( jQuery( args.addButton ).attr( ‘collectionCount’ ), 10 );

            data[‘order’] = data[‘order’] || collectionCount;
            return data;

        },
        bindings: {
            //  Move up collection, freezing the order field values

            ‘moveUpButton’: {
                event: ‘click’,

                func: function( e, collection ) {
                    jQuery.fn.moveUpCollection( collection, [‘order’] );

                }
            },

            //  Move down collection, freezing the order field values
            ‘moveDownButton’: {

                event: ‘click’,
                func: function( e, collection ) {

                    jQuery.fn.moveDownCollection( collection, [‘order’] );

                }
            }

        },
        //      Override the delete function, to send ajax request if necessary
        deleteFunction: function( template, args, data, deleteButton ) {

            jQuery( deleteButton ).text( ‘Deleting…’ ).attr( ‘disabled’, ‘disabled’ );

            
            //      If we have data, send it to the server to delete, otherwise simply remove

            var dataLength = 0;
            for( var x in data )dataLength++;

            
            if( dataLength > 0 ) {

                //      Add delete parameter to the data
                data[‘action’] = ‘delete’;

               
                var request = jQuery.ajax( {

                    type: ‘POST’,
                    data: data,

                    url: ‘collections.php’,
                    dataType: "text",

                    error: function() {
                        alert( "Ajax error occured" );

                    },
                    success: function( html ) { //      Remove collection

                        jQuery.fn.removeCollection(template, args, data);

                    }
                } );

            } else {
                jQuery.fn.removeCollection( template, args, data );

            }
        },
        namePattern: "contact{number}[{name}]"

    };
   
    jQuery(‘#collectionTarget3′).collections(config);
} );

The initialisation script now also has a deleteFunction parameter, with a function that ensures that we only
access the back-end if we need to; this allows the back-end to only

Back end integration

Of course the collections manager is useless without data from the back-end; it is very
easy to integrate the collection manager, thanks laregly to the namePattern parameter, which allows tight
control of how the fields are named; out of the box, you can quickly integrate with just about any php, python, java
(struts, springBeans), .NET, etc…

The namePattern parameter allows you to specify where the record number and name appears; in the last example,
we use a namePattern like so:

namePattern: "contact{number}[{name}]"

This creates individual objects for each data row, rather than the default of one array of data rows. This can be particularly
convenient if you have a set number of a type of object, for example if you have exactly four contact objects named “contact1″,
…, “contact4″, this will allow you to easliy map directly to those objects.
The default namePattern is:

namePattern: "collection[{number}][{name}]"

Which will map to one array named “collection” of data rows.

API

Here is a list of options, and what you can do with the collections manager

  • target – The target of the collection manager (defaults to jQuery result)
  • template – Element to use for cloning. Note: do NOT give any items IDs, they will be removed
  • data – Optionally include some data, default is 1 record
  • startCount – What number to start the count from.
  • minItems – Minimum number of items to show at any time (default 1)
  • maxItems – Maximum number of items to show (default 10)
  • namePattern – Pattern for naming the form fields (for mapping to back-end objects)
  • deleteButtonName – Name for delete button to use in the template
  • deleteFunction – Delete function override, params: ( template, args, data, deleteButton )
  • addButton – Button to assign the add function to.
  • bindings – name: {event:function}. WIP: we may be able to get rid of the specific addButton and DeleteButton events here.
  • beforeAdd – Function that runs before an item is added, params: ( data, args ), if we return anything, it will be used as data
  • afterAdd – Function that runs after an item is added, params: ( data, args )
  • beforeRemove – Function that runs before an item is removed – return false to cancel removal of item. Params: ( data, args )
  • afterRemove – Function that runs after an item is removed, params: ( data, args, wasRemoved )

9 Responses to “Manage your data with the jQuery Collections plugin”

  1. Stephane  on November 15th, 2010

    Is it me or it is impossible to add an item ? I cannot get the demo to work. When clicking on the Add button the page is instantly submitted.

  2. admin  on November 15th, 2010

    Hmm, you are right. I recently upgraded word press, I think that may have broken it :(

  3. admin  on November 16th, 2010

    Ok, I have restored a previous version :)

  4. Stefan  on February 28th, 2011

    This code in TableGear1.6.1-jQuery.js breaks the use of the jquery autocomplete plugin:

    // Fix for jQuery 1.4.2 strangely not firing browser native focus event.
    jQuery.fn.focus = function(){
    this.each(function(){
    if(this.focus) this.focus();
    });
    };

  5. Stefan  on March 1st, 2011

    Sorry I posted that report to the wrong author! :(

  6. admin  on July 14th, 2011

    No problem :)

  7. Top 10 jQuery Database Plugins | jQuery4u  on October 2nd, 2011

    [...] 1. jQuery Collections [...]

  8. Mike  on January 13th, 2014

    First example works, nothing else does.

  9. admin  on January 13th, 2014

    Works for me – IE6 and above – what browser / environment are you running in?


Leave a Reply