DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone >

Derive.js 0.10 - A Prototype.js Extension For Object Derivation, Mixins, And Method Chaining

Snippets Manager user avatar by
Snippets Manager
·
Feb. 09, 07 · · Code Snippet
Like (0)
Save
Tweet
587 Views

Join the DZone community and get the full member experience.

Join For Free
http://revolutiononrails.blogspot.com/2007/02/derivejs-010-prototypejs-extension-for.html

Prototype.js is perfectly sufficient 99% of the time, so this is definitely overkill unless you want to build a big app in Javascript.

It has a strong Ruby 'feel' to it, but I'm definitely not trying to hide Javascript behind a Ruby to JS compiler or JS generator like RJS with this.

Scenerio: You've got a JS app with 20-30 Classes, each looking like:



var Foo = Class.create();
Object.extend(Foo.prototype, {
  initialize: {},
  foo: function(){
    // ...
  }
});
Object.extend(Foo, {
  classMethod: {}
});

var Bar = Class.create();
Object.extend(Foo.prototype, Bar.prototype);
Object.extend(Foo, Bar);
Object.extend(Foo.prototype, {
  initialize: {},
  foo: function(){
    // ...
  }
});
Object.extend(Foo, {
  fooClassmethod: function(){}
});

// ...


It gets a little verbose, not to mention that you've got to manually apply 'superclass' methods to objects to call 'super' variants, etc. There's also no support for mixing things in 'horizontally' like you can in Ruby (where the message path goes out to modules and then up the inheritance stack, zigzag like.) When writing GUIs, this mixin capability is really useful for stacking event listeners.

Example of usage, with derivation and mixin:


var SomeMixin = Mixin.create({
  one: function (teststr){
    console.debug('Entering (SomeMixin instance mthd one) with argument ' + teststr);
    console.debug('Calling super...');          
    this.sup('one', teststr);        
    console.debug('Exiting (SomeMixin instance mthd one)'); 
  },
  self: {
    included: function(mixin){
      console.debug("Something mixed-in SomeMixin")
    }
  }
})

var Foo = Class.derive(Object,{
  initialize: function(){},
  one: function(teststr){
    console.debug('Entering (Foo instance mthd one) with argument ' + teststr);
    console.debug('Exiting (Foo instance mthd one)'); 
  },
  self: {
    one: function(){
      console.debug('Entering (Foo.one) class method');
    },
    two: function(){
      console.debug('Entering (Foo.two) class method');
  	},
  	inherited: function(){
  	  console.debug("Someone inherited Foo!");
  	}
  }
});

Foo.include(SomeMixin);

var Bar = Class.derive(Foo,{
  initialize: function(teststr){},
  one: function(teststr){
    console.debug('Entering (Bar instance method one) with argument ' + teststr);     
    console.debug('Calling super...');
    this.sup('one', teststr);
    console.debug('Exiting with (Bar instance method one)');
  },
  self: {
    one: function(){
      console.debug('Entering (Bar.one) class method');
    }
  }
});

// Singleton is a Mixin defined for you in derive.js
Bar.extend(Singleton);

b = Bar.instance();
b.one('test');
Bar.one();
Bar.two();



Full source:


// derive.js: edward.frederick@revolution.com, 2/07
// By: Revolution Dev Team
// Questions/Resources: rails-trunk@revolution.com, revolutiononrails.blogspot.com, www.edwardfrederick.com

Object.chain = function(dest,source){			
  for (var property in source) {
	  var chainprop = '__' + property + '_chain';
		if (!property.match(/__.*_chain$/)){
			if (!dest[chainprop]){
				dest[chainprop] = new Array();
			}
			if (dest[property]){
				dest[chainprop].unshift(dest[property]);
			}
			if (source[chainprop]){
				dest[chainprop] = source[chainprop].concat(dest[chainprop]);
			}
			dest[property] = source[property];
		}
  }
  return dest;	
}

Object.extend(Class, {
  derive: function(superclass, body){
		var ctr = Class.create();		
		if (superclass){
			ctr.superclass = superclass;
			Object.chain(ctr.prototype,superclass.prototype);			
			Object.chain(ctr,superclass);
			if (superclass.inherited)
			  superclass.inherited(ctr);
		}		
		if (body){
			if (body.self)
				Object.chain(ctr,body.self);		
			body.self = undefined;
			Object.chain(ctr.prototype,body);
		}
		Object.extend(ctr,Class._deriveClassExtensions);
		Object.extend(ctr.prototype,Class._deriveInstanceExtensions);		
		return ctr;			
  },
	_deriveClassExtensions: {	
		include: function(mixin){
			if (!mixin._mixin)
				throw "Can only include a mixin!";				
			Object.chain(this.prototype,mixin.methods);
			if (mixin.self && mixin.self.included)
				mixin.self.included(this);
		},
		extend: function(mixin){
			if (!mixin._mixin)
				throw "Can only extend a mixin!";
			Object.chain(this,mixin.methods);
			if (mixin.self && mixin.self.extended)
				mixin.self.extended(this);
		}
	},
	_deriveInstanceExtensions: {
		sup: function(method){
			var currentLinkVar = '__' + method + '_current_chain_link';
			var chainVar = '__' + method + '_chain';

			if (!this[currentLinkVar])
				this[currentLinkVar] = 0;
			if (this[currentLinkVar] && this[currentLinkVar] >= this[chainVar].size())
				throw "NoPreviousMethod: " + method;	
	
			var mine = this[chainVar][this[currentLinkVar]];
			this[currentLinkVar]++;
			
			var shiftedArguments = new Array();
			for (var i = 1; i < arguments.length; i++)
				shiftedArguments.push(arguments[i]);
				
			var result = mine.apply(this,shiftedArguments);
			this[currentLinkVar] = undefined;
			return result;
		}
	}	
});

/* Can also mix things in horizontally, as the derive heirarchies are 
	 intended to be singly-rooted */
var Mixin = {
	create: function(object){
		var mixin = {};
		var methods = Object.extend({},object);
		Object.extend(mixin,{
			self: methods.self,
			_mixin: true,
			methods: methods
		});
		mixin.methods.self = undefined;
		Object.extend(mixin, Mixin._classMethods);
		return mixin;
	},
	_classMethods: {
		included: Prototype.emptyFunction,
		extended: Prototype.emptyFunction
	}
}


/* Singleton mixin provided as an example (albeit a useful one) */
var Singleton = Mixin.create({
	instance: function(){
		if (this._instance)
			return this._instance;
		else
			return this._instance = new this(arguments);
	},
	self: {
		included: function(klass){
			// nothing here, but could be
		},
		extended: function(klass){
			// nothing here, but could be
		}
	}
});
Method chaining Object (computer science)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • 6 Things Startups Can Do to Avoid Tech Debt
  • 10 Steps to Become an Outstanding Java Developer
  • Ultra-Fast Microservices: When Microstream Meets Wildfly
  • Composable Architecture

Comments

Partner Resources

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo