views:

40

answers:

2

Here's what I'm aiming to achieve:

HTML

<fieldset id="addmore">
   <p>blah</p>

   <a class="remove">remove me</a>
</fieldset>

<a class="add">add more fieldsets</a>

Javascript

var addmore = new AddMore($('fieldset'));
addmore.buildCache(/*this will pull the innerHTML of the fieldset*/);

// bind the buttons
addmore.bind('add', $('a.add'));
addmore.bind('remove', $('a.remove'));

I've found myself having a lot more 'addmore' stuff in my HTML lately so I've been trying to build a class that will do all the leg work for me that I can just reuse in all my projects. The above code will, hopefully, be all I have to add each time and then the rest is done for me.

I've been winging this thing so, off the top of my head, here's what the class has to do:

  • Apply the jQuery bindings to the supplied 'button' objects so we can add/remove fieldsets
  • When a new fieldset is added, we have to recall the bind function so the new fieldset's 'a.add' button will work (I've found jQuery's .live() function to be buggy, for whatever reason, and try to avoid it)
  • It will hopefully do this with no memory leaks :}

Javascript Class

/*
   Class to handle adding more data to the form array

   Initialise the class by passing in the elements you want to add more of
   Then bind 'add' and 'remove' buttons to the functions and the class will do the rest
*/

/*
   Pass the jQuery object you want to 'addmore' of
   Ex: var x = new AddMore($('fieldset.addmore'));
*/
function AddMore($element)
{
   if (!$element || typeof($element) != 'object')
      throw 'Constructor requires a jQuery object';

   this.element = $element; // this is a jQuery object
   this.cache   = null;
}

/*
   Supply clean HTML to this function and it will be cached
   since the cached data will be used when 'adding more', you'll want the inputs to be emptied,
   selects to have their first option selected and any other data removed you don't want readded to the page
*/
AddMore.prototype.buildCache = function(fieldset)
{
   if (!fieldset)
      throw 'No data supplied to cache';

   this.cache = fieldset;
}

/*
   use this to create the initial bindings rather than jQuery
   the reason? I find .live() to be buggy. it doesn't always work. this usually means having to use a standard .bind()
   and then re-bind when we add in the new set

   that's what this class helps with. when it adds in the new data, it rebinds for you. nice and easy.
*/
AddMore.prototype.bind = function(type, $button)
{
   if (!type || !$button && (type != 'add' && type != 'remove'))
      throw 'Invalid paramaters';

   // don't reapply the bindings to old elements...
   if ($button.hasClass('addmore-binded'))
      return;

   // jQuery overwrites 'this' within it's scope
   var _this = this;

   if (type == 'add')
   {
      $button.bind('click', function()
      {
         _this.element.after(_this.cache);
      });
   }
}

I was going to have the .bind() method (in my class) call itself upon adding the new fieldset to reapply the binding but lost confidence with efficiency (speed/memory).

How should I tackle this? Do you have any pointers? Can you recommend improvements?

Thanks for the help.

A: 

In the most simplest form, you can do something like this:

var html = '{put html to add each time here}';

$('.add').click(function() {
    $(html).insertAfter($('fieldset').last());
    return false;
});

$('.remove').live('click', function() {
    $(this).parent().remove();
    return false;
});

You may need to tweak it based on your exact needs, but this should accomplish what you described in your example.

Update: sorry, remove should use the live method.

Daniel
I know how to add/remove the elements. I'm having trouble coming up with a good way to reapply the click() binding after I've added in the new set
steve
The jQuery live() method should work. I haven't ran into problems using it before.
Daniel
An alternative to binding events every time is to move the `$('.remove').click()` into `$('.add').click()`. This would essentially bind the newly created remove link every time a new fieldset is added.
Daniel
A: 

For creation of the new DOM elements, allow the specification/parameters to be any of the following:

  1. simple HTML as a string (like the example above),
  2. a function returning either a DOM element or HTML text. You can skip bind() or live() issues by adding the onclick element when creating the HTML/element in the function. Although doing it in the AddMore() scope would be more tedious if it's not a DOM element that gets returned.
  3. inputs to a helper/factory method (maybe a template and name/value pairs) - postpone this unless you know enough patterns already.

Option #1 seems almost useless, but #3 might be hard unless you have extra time now.

Notes:

  • You might want to use $(theNewDomElement).insertBefore(_this.button); rather than _this.element.after(theNewDomElement); so that new items are append to the end of the list.
  • Actually, for insertBefore() you might just use this rather than _this.button since presumably the button (or anchor) is this, but then that limits the functionality - just make it some sort of DOM element (or a jQuery object that equates to one).
  • In most cases, I'd presume your DOM elements represent data that you'll want to save/send/transmit to your server, so provide a before-removal function, too. Even allow the function to skip removal -- like after a confirm().

Good luck.

Carter Galle