ruk·si

JS Pattern
Composite

Updated at 2013-05-04 11:40

Composite pattern allows tree-like structure for JavaScript objects. This is much more useful pattern than inheritance.

/**
 * Gallery Composite
 * @param  {String} heading
 * @param  {String} id
 */
var GalleryComposite = function (heading, id) {
  this.children = [];
  this.$element = $('<div>').attr('id', id).addClass('composite-gallery');
  this.$element.append('<h2>' + heading + '</h2>');
}

GalleryComposite.prototype = {

  add: function( child ) {

    // Add child to the list we maintain.
    this.children.push( child );

    // Render the child.
    this.$element.append( child.getElement() );
  },

  remove: function( toRemove ) {

    // Search for the child to remove.
    for ( var child, i = 0; child = this.getChild(i); i++ ) {

      // If this contains the child that is to be removed.
      if ( child == toRemove ) {
        this.children.splice(i, 1);
        this.$element.detach( toRemove.getElement() );
        return true;
      }

      // If a child contains the child that is to be removed.
      if ( child.remove(toRemove) ) {
        return true;
      }

    }

    // Did not find the child.
    return false;

  },

  getChild: function(i) {
    return this.children[i];
  },

  hide: function() {

    // Hide all children.
    for (var child, i = 0; child = this.getChild(i); i++) {
      child.hide();
    }

    // Hide this element.
    this.$element.hide();

  },

  show: function() {

    // Show all children.
    for (var child, i = 0; child = this.getChild(i); i++) {
      child.show();
    }

    // Show this element.
    this.$element.show();

  },

  getElement: function() {
    return this.$element;
  }

}
/**
 * Gallery Image
 */
var GalleryImage = function (src, id) {
  this.children = [];
  this.$element = $('<img>').attr({
    'id': id,
    'src': src
  });
}

// This uses other style of defining a prototype.

// Due to this being a leaf, it doesn't use these methods,
// but must implement them to count as implementing the
// Composite interface.
GalleryImage.prototype.add = function() {};
GalleryImage.prototype.remove = function() {};
GalleryImage.prototype.getChild = function() {};
GalleryImage.prototype.hide = function() {
  this.$element.hide();
};
GalleryImage.prototype.show = function() {
  this.$element.show();
};
GalleryImage.prototype.getElement = function() {
  return this.$element;
};
/**
 * Following code generates:
 * <div id="allgalleries" class="composite-gallery">
 *     <h2></h2>
 *     <div id="gallery1" class="composite-gallery">
 *         <h2>Gallery 1</h2>
 *         <img id="img1" src="image1.jpg">
 *         <img id="img2" src="image2.jpg">
 *     </div>
 *     <div id="gallery2" class="composite-gallery">
 *         <h2>Gallery 2</h2>
 *         <img id="img3" src="image3.jpg">
 *         <img id="img4" src="image4.jpg">
 *     </div>
 * </div>
 */

var container = new GalleryComposite('', 'allgalleries');
var gallery1 = new GalleryComposite('Gallery 1', 'gallery1');
var gallery2 = new GalleryComposite('Gallery 2', 'gallery2');
var image1 = new GalleryImage('image1.jpg', 'img1');
var image2 = new GalleryImage('image2.jpg', 'img2');
var image3 = new GalleryImage('image3.jpg', 'img3');
var image4 = new GalleryImage('image4.jpg', 'img4');

gallery1.add(image1);
gallery1.add(image2);

gallery2.add(image3);
gallery2.add(image4);

container.add(gallery1);
container.add(gallery2);

// Make sure to add the top container to the body,
// otherwise it'll never show up.
container.getElement().appendTo('body');
container.show();