Package Exports
- stampit
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (stampit) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Stampit
Create objects from reusable, composable behaviors.
Status
Consider this a developer preview. Stampit is in production use right now, but there may be bugs. Please add bug reports and contribute fixes if you are able.
Known issues
stampit.convertConstructor()is an experimental feature that doesn't work for constructors that require parameters. If you need to do something that isn't supported, consider usingstampit().enclose()to turn the constructor into a proper factory, instead.
Features
Create functions (called factories) which stamp out new objects. All of the new objects inherit all of the prescribed behavior.
Compose factories together to create new factories.
Inherit methods and default state.
Supports composable private state and privileged methods.
State is cloned for each instance, so it won't be accidentally shared.
For the curious - it's great for learning about prototypal OO. It mixes three major types of prototypes:
- differential inheritance, aka delegation (for methods),
- cloning, aka concatenation/exemplar prototypes (for state),
- functional / closure inheritance (for privacy / encapsulation)
What's the Point?
Prototypal OO is great, and JavaScript's capabilities give us some really powerful tools to explore it, but it could be easier to use.
Basic questions like "how do I inherit privileged methods and private data?" and "what are some good alternatives to inheritance hierarchies?" are stumpers for many JavaScript users.
Let's answer both of these questions at the same time. First, we'll use a closure to create data privacy:
var a = stampit().enclose(function () {
var a = 'a';
this.getA = function () {
return a;
};
});It uses function scope to encapsulate private data. Note that the getter must be defined inside the function in order to access the closure variables.
Let's see if that worked:
a(); // Object -- so far so good.
a().getA(); // "a"Yes. Got it. In both of these instances, we actually created a brand new object, and then immediately threw it away, because we didn't assign it to anything. Don't worry about that.
Here's another:
var b = stampit().enclose(function () {
var a = 'b';
this.getB = function () {
return a;
};
});Those a's are not a typo. The point is to demonstrate that a and b's private variables won't clash.
But here's the real treat:
var c = stampit.compose(a, b);
var foo = c(); // we won't throw this one away...
foo.getA(); // "a"
foo.getB(); // "b"WAT? Yeah. You just inherited privileged methods and private data from two sources at the same time.
But that's boring. Let's see what else is on tap:
// Some more privileged methods, with some private data.
// Use stampit.mixIn() to make this feel declarative:
var availability = stampit().enclose(function () {
var isOpen = false; // private
return stampit.mixIn(this, {
open: function open() {
isOpen = true;
return this;
},
close: function close() {
isOpen = false;
return this;
},
isOpen: function isOpenMethod() {
return isOpen;
}
});
});
// Here's a mixin with public methods, and some state:
var membership = stampit({
add: function (member) {
this.members[member.name] = member;
return this;
},
getMember: function (name) {
return this.members[name];
}
},
{
members: {}
});
// Let's set some defaults:
var defaults = stampit().state({
name: 'The Saloon',
specials: 'Whisky, Gin, Tequila'
});
// Classical inheritance has nothing on this. No parent/child coupling. No deep inheritance hierarchies.
// Just good, clean code reusability.
var bar = stampit.compose(defaults, availability, membership);
// Note that you can override state on instantiation:
var myBar = bar({name: 'Moe\'s'});
// Silly, but proves that everything is as it should be.
myBar.add({name: 'Homer' }).open().getMember('Homer');More chaining
You can chain .methods() ...
var obj = stampit().methods({
foo: function () {
return 'foo';
},
methodOverride: function () {
return false;
}
}).methods({
bar: function () {
return 'bar'
},
methodOverride: function () {
return true;
}
}).create();And .state() ...
var obj = stampit().state({
foo: {bar: 'bar'},
stateOverride: false
}).state({
bar: 'bar',
stateOverride: true
}).create();And .enclose() ...
var obj = stampit().enclose(function () {
var secret = 'foo';
this.getSecret = function () {
return secret;
};
}).enclose(function () {
this.a = true;
}).enclose({
bar: function bar() {
this.b = true;
}
}, {
baz: function baz() {
this.c = true;
}
}).create();
obj.getSecret && obj.a && obj.b && obj.c; // truePass multiple objects into .methods(), .state(), or .enclose()
Stampit mimics the behavior of _.extend(), $.extend() when you pass multiple objects into one of the prototype methods. In other words, it will copy all of the properties from those objects to the .methods, .state, or .enclose prototype for the factory. The properties from later arguments in the list will override the same named properties of previously passed in objects.
var obj = stampit().methods({
a: function () { return 'a'; }
}, {
b: function () { return 'b'; }
}).create();Or .state() ...
var obj = stampit().state({
a: 'a'
}, {
b: 'b'
}).create();Stampit API
Source: stampit.js
stampit()
Return a factory function that will produce new objects using the prototypes that are passed in or composed.
@param {Object} [methods]A map of method names and bodies for delegation.@param {Object} [state]A map of property names and values to clone for each new object.@param {Function} [enclose]A closure (function) used to create private data and privileged methods.@return {Function} stampA factory to produce objects using the given prototypes.@return {Function} stamp.createJust like calling the factory function.@return {Object} stamp.fixedAn object map containing the fixed prototypes.@return {Function} stamp.methodsAdd methods to the methods prototype. Chainable.@return {Function} stamp.stateAdd properties to the state prototype. Chainable.@return {Function} stamp.encloseAdd or replace the closure prototype. Chainable.
Extending the prototypes
stamp.methods()
Take n objects and add them to the methods prototype.
- @return {Object} stamp The factory in question (
this).
stamp.state()
Take n objects and add them to the state prototype.
- @return {Object} stamp The factory in question (
this).
stamp.enclose()
Take n functions, an array of functions, or n objects and add the functions to the enclose prototype.
- @return {Object} stamp The factory in question (
this).
Utility methods
stampit.compose()
Take two or more factories produced from stampit() and combine them to produce a new factory. Combining overrides properties with last-in priority.
@param {...Function} factoryA factory produced by stampit().@return {Function}A new stampit factory composed from arguments.
stampit.mixIn()
Take a destination object followed by one or more source objects, and copy the source object properties to the destination object, with last in priority overrides.
@param {Object} destinationAn object to copy properties to.@param {...Object} sourceAn object to copy properties from.@returns {Object}
stampit.extend()
Alias for mixIn.
stampit.convertConstructor() Warning: experimental
Take an old-fashioned JS constructor and return a stampit stamp that you can freely compose with other stamps.
- @param {Function} Constructor
- @return {Function} A composable stampit factory (aka stamp).
// The old constructor / class thing...
var Constructor = function Constructor() {
this.thing = 'initialized';
};
Constructor.prototype.foo = function foo() { return 'foo'; };
// The conversion
var oldskool = stampit.convertConstructor(Constructor);
// A new stamp to compose with...
var newskool = stampit().methods({
bar: function bar() { return 'bar'; }
// your methods here...
}).enclose(function () {
this.baz = 'baz';
});
// Now you can compose those old constructors just like you could
// with any other factory...
var myThing = stampit.compose(oldskool, newskool);
var t = myThing();
t.thing; // 'initialized',
t.foo(); // 'foo',
t.bar(); // 'bar'