Friday, April 23, 2010

Inheritance - Example Mixin Classes (augment function)

Using Mixin Classes

--------
Augment Function so you can follow

/* Augment function, improved. */

function augment(receivingClass, givingClass) {
 if(arguments[2]) { // Only give certain methods.
  for(var i = 2, len = arguments.length; i < len; i++) {
   receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
  }
 } else { // Give all methods.
  for(methodName in givingClass.prototype) {
   if(!receivingClass.prototype[methodName]) {
    receivingClass.prototype[methodName] = givingClass.prototype[methodName];
   }
  }
 }
}

You can now write augment(Author, Mixin, 'serialize'); to only augment Author with the single serialize method. More method names can be added if you want to augment with more than one method. Often it makes more sense to augment a class with a few methods than it does to make one class inherit from another. This is a lightweight way to prevent code duplication. Unfortunately, there aren’t many situations where it can be used. Only methods general enough to be used in very dissimilar classes make good candidates for sharing (if the classes aren’t that dissimilar, normal inheritance is often a better choice). …end of augment part…… ------- We will repeat the example one more time using mixin classes. We will create one mixin class with all of the methods we want to share. Then we will create a new class and use augment to share those methods:
/* Mixin class for the edit-in-place methods. */


var EditInPlaceMixin = function() {};  //empty Constructor

EditInPlaceMixin.prototype = {
 createElements: function(id) {
  this.containerElement = document.createElement('div');
  this.parentElement.appendChild(this.containerElement);
  
  this.staticElement = document.createElement('span');
  this.containerElement.appendChild(this.staticElement);
  this.staticElement.innerHTML = this.value;
  
  this.fieldElement = document.createElement('input');
  this.fieldElement.type = 'text';
  this.fieldElement.value = this.value;
  this.containerElement.appendChild(this.fieldElement);
  
  this.saveButton = document.createElement('input');
  this.saveButton.type = 'button';
  this.saveButton.value = 'Save';
  this.containerElement.appendChild(this.saveButton);
  
  this.cancelButton = document.createElement('input');
  this.cancelButton.type = 'button';
  this.cancelButton.value = 'Cancel';
  this.containerElement.appendChild(this.cancelButton);
  
  this.convertToText();
 },
 attachEvents: function() {
  var that = this;
  addEvent(this.staticElement, 'click', function() { that.convertToEditable(); });
  addEvent(this.saveButton, 'click', function() { that.save(); });
  addEvent(this.cancelButton, 'click', function() { that.cancel(); });
 },
 convertToEditable: function() {
  this.staticElement.style.display = 'none';
  this.fieldElement.style.display = 'inline';
  this.saveButton.style.display = 'inline';
  this.cancelButton.style.display = 'inline';
  
  this.setValue(this.value);
 },
 save: function() {
  this.value = this.getValue();
  var that = this;
  var callback = {
   success: function() { that.convertToText(); },
   failure: function() { alert('Error saving value.'); }
  };
  ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback);
 },
 cancel: function() {
  this.convertToText();
 },
 convertToText: function() {
  this.fieldElement.style.display = 'none';
  this.saveButton.style.display = 'none';
  this.cancelButton.style.display = 'none';
  this.staticElement.style.display = 'inline';
  this.setValue(this.value);
 },
 setValue: function(value) {
  this.fieldElement.value = value;
  this.staticElement.innerHTML = value;
 },
 getValue: function() {
  return this.fieldElement.value;
 }
};


The mixin class holds nothing but the methods. To create a functional class, make a constructor and then call augment:
/* EditInPlaceField class. */
function EditInPlaceField(id, parent, value) {
   this.id = id;
   this.value = value || 'default value';
   this.parentElement = parent;
   this.createElements(this.id);
   this.attachEvents();
};

augment(EditInPlaceField, EditInPlaceMixin);     //AUGMENT CALL

You can now instantiate the class in the exact same way as with classical inheritance. To create the class that uses a text area field, you will not subclass EditInPlaceField. Instead, simply create a new class (with a constructor) and augment it from the same mixin class. But before augmenting it, define a few methods. Since these are in place before augmenting it, they will not get overridden:
/* EditInPlaceArea class. */


function EditInPlaceArea(id, parent, value) {
 this.id = id;
 this.value = value || 'default value';
 this.parentElement = parent;
 this.createElements(this.id);
 this.attachEvents();
};

// Add certain methods so that augment won't include them.
EditInPlaceArea.prototype.createElements = function(id) {
 this.containerElement = document.createElement('div');
 this.parentElement.appendChild(this.containerElement);
 
 this.staticElement = document.createElement('p');
 this.containerElement.appendChild(this.staticElement);
 this.staticElement.innerHTML = this.value;
 
 this.fieldElement = document.createElement('textarea');
 this.fieldElement.value = this.value;
 this.containerElement.appendChild(this.fieldElement);
 
 this.saveButton = document.createElement('input');
 this.saveButton.type = 'button';
 this.saveButton.value = 'Save';
 this.containerElement.appendChild(this.saveButton);
 
 this.cancelButton = document.createElement('input');
 this.cancelButton.type = 'button';
 this.cancelButton.value = 'Cancel';
 this.containerElement.appendChild(this.cancelButton);
 
 this.convertToText();
};

EditInPlaceArea.prototype.convertToEditable = function() {
 this.staticElement.style.display = 'none';
 this.fieldElement.style.display = 'block';
 this.saveButton.style.display = 'inline';
 this.cancelButton.style.display = 'inline';
 
 this.setValue(this.value);
};

EditInPlaceArea.prototype.convertToText = function() {
 this.fieldElement.style.display = 'none';
 this.saveButton.style.display = 'none';
 this.cancelButton.style.display = 'none';
 this.staticElement.style.display = 'block';
 
 this.setValue(this.value);
};

augment(EditInPlaceArea, EditInPlaceMixin);   //AUGMENT CALL



The mixin technique works in this example, but not as well as the other two techniques.
In the end, the objects created by each of the techniques are almost identical, but from an
organizational standpoint, strict inheritance makes more sense than augmentation.

Mixin classes work well for methods that are shared between several disparate classes, but in this example, the mixin class is used to provide all of the methods, for two very similar classes.

Code maintenance would be easier with the first two examples because it is immediately obvious
where the methods came from and how the classes and objects were organized.

Sharing general-purpose methods that can act on all types of objects is a much better use
of mixin classes. Some examples of this are methods that serialize an object to a string representation, or output its state for debugging.

It is also possible to use mixin classes to emulate
enumerations or iterators, as found in some other object-oriented languages.

No comments:

Post a Comment