Code Monkey home page Code Monkey logo

stampit's Introduction

stampit

Stampit npm Gitter Twitter Follow UNPKG

Create objects from reusable, composable behaviors

Stampit is a 1.4KB gzipped (or 3K minified) JavaScript module which supports three different kinds of prototypal inheritance (delegation, concatenation, and functional) to let you inherit behavior in a way that is much more powerful and flexible than any other Object Oriented Programming model.

Stamps are standardised composable factory functions. Stampit is a handy implementation of the specification featuring friendly API.

Find many more examples in this series of mini blog posts or on the official website.

Example

import stampit from 'stampit'

const Character = stampit({
  props: {
    name: null,
    health: 100
  },
  init({ name = this.name }) {
    this.name = name
  }
})

const Fighter = Character.compose({ // inheriting
  props: {
    stamina: 100
  },
  init({ stamina = this.stamina }) {
    this.stamina = stamina;    
  },
  methods: {
    fight() {
      console.log(`${this.name} takes a mighty swing!`)
      this.stamina--
    }
  }
})

const Mage = Character.compose({ // inheriting
  props: {
    mana: 100
  },
  init({ mana = this.mana }) {
    this.mana = mana;    
  },
  methods: {
    cast() {
      console.log(`${this.name} casts a fireball!`)
      this.mana--
    }
  }
})

const Paladin = stampit(Mage, Fighter) // as simple as that!

const fighter = Fighter({ name: 'Thumper' })
fighter.fight()
const mage = Mage({ name: 'Zapper' })
mage.cast()
const paladin = Paladin({ name: 'Roland', stamina: 50, mana: 50 })
paladin.fight()
paladin.cast()

console.log(Paladin.compose.properties) // { name: null, health: 100, stamina: 100, mana: 100 }
console.log(Paladin.compose.methods) // { fight: [Function: fight], cast: [Function: cast] }

Status

Install

NPM

Compatibility

Stampit should run fine in any ES5 browser or any node.js.

API

See https://stampit.js.org

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.

// Some privileged methods with some private data.
const Availability = stampit({
  init() {
    let isOpen = false; // private

    this.open = function open() {
      isOpen = true;
      return this;
    };
    this.close = function close() {
      isOpen = false;
      return this;
    };
    this.isOpen = function isOpenMethod() {
      return isOpen;
    }
  }
});

// Here's a stamp with public methods, and some state:
const Membership = stampit({
  props: {
    members: {}
  },
  methods: {
    add(member) {
      this.members[member.name] = member;
      return this;
    },
    getMember(name) {
      return this.members[name];
    }
  }
});

// Let's set some defaults:
const Defaults = stampit({
  props: {
    name: "The Saloon",
    specials: "Whisky, Gin, Tequila"
  },
  init({ name, specials }) {
    this.name = name || this.name;
    this.specials = specials || this.specials;
  }
});

// Classical inheritance has nothing on this.
// No parent/child coupling. No deep inheritance hierarchies.
// Just good, clean code reusability.
const Bar = stampit(Defaults, Availability, Membership);

// Create an object instance
const myBar = Bar({ name: "Moe's" });

// Silly, but proves that everything is as it should be.
myBar.add({ name: "Homer" }).open().getMember("Homer");

For more examples see the API or the Fun With Stamps mini-blog series.

Development

Unit tests

npm t

Unit and benchmark tests

env CI=1 npm t

Unit tests in a browser

To run unit tests in a default browser:

npm run browsertest

To run tests in a different browser:

  • Open the ./test/index.html in your browser, and
  • open developer's console. The logs should indicate success.

Publishing to NPM registry

npx cut-release

It will run the cut-release utility which would ask you if you're publishing patch, minor, or major version. Then it will execute npm version, git push and npm publish with proper arguments for you.

stampit's People

Contributors

angryobject avatar arikon avatar boneskull avatar danielkcz avatar delapouite avatar dwiyatci avatar eladchen avatar ericelliott avatar fpoumian avatar fredyc avatar gfot avatar gitter-badger avatar greenkeeper[bot] avatar greenkeeperio-bot avatar javascript-journal avatar johnthad avatar jonboylailam avatar joseherminiocollas avatar josephclay avatar koresar avatar kurtpeters avatar nkbt avatar paulfalgout avatar popgoesthewza avatar rudolf avatar ruffle1986 avatar sethlivingston avatar tcrosen avatar troutowicz avatar unstoppablecarl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

stampit's Issues

Can stampit generate the sourcecode you need to create objects?

One of the cool things about Stampit is that it generates factory functions that not only produce object instances, but also tell you everything you need to know in order to build objects that conform to the stamp. That knowledge about how to build the objects is shared publicly so that the stamps can be easily composed together to create new stamps.

Each stamp has a property called fixed, where it stores the prototypes so that it can be easily composed with other stamps.

Let's take a look at what this means a little more in-depth:

var stampit = require('stampit');

var myCoolStamp = stampit().methods({
    someMethod: function () {
      return 'this.foo';
    }
  }).state({
    foo: 'Some state'
  }).enclose(function () {
    var isAwesome = true;

    this.isAwesome = function() {
      return isAwesome;
    };
  });

// Methods get stored on the object's delegate prototype.
myCoolStamp.fixed.methods.someMethod.toString();
// => 'function () {\nreturn \'this.foo\';\n}'

// State will be copied over to object properties.
JSON.stringify(myCoolStamp.fixed.state);
// => '{"foo":"Some state"}'

// Closures get against your object after it's instantiated, using .apply()
myCoolStamp.fixed.enclose[0].toString();
/*
 => 'function () {\nvar isAwesome = true;\n\nthis.isAwesome = function() {\nreturn isAwesome;\n};\n}'
 */

There you go... a look under the hood of a stamp.

So, can stampit generate the sourcecode you need to create objects? Not really, but if you really want to, you could use this information to write a utility function that could do it.

Make it easier to customize instance initialization

If I see that correctly than properties passed in to the factory will be mixIn as instance properties. https://github.com/stampit-org/stampit/blob/master/stampit.js#L77
So {a: 'a', b: 'b'} become this.a = 'a' and this.b = 'b'. So far so good, but there's no way any longer to access them as object.

factory = function factory(properties) {
    var state = merge({}, fixed.state),
    instance = mixIn(create(fixed.methods || {}),
    state, properties),
    closures = fixed.enclose;

Is there a way to preserve them somehow, so that they can be accessed within closures for some kind of housekeeping/processing?

grouping stampit arguments list

Add an option to create stamps in the following way:

var stamp = stampit({
  refs: {},
  methods: {},
  init: function () {}, // or an array of functions
  props: {}
});

Standard way of identifying object instance origin

Given an object instance, it'd be useful to know if it has certain behaviors:

var getADrink = function( cocktail ){
  if ( cocktail.has( 'drink', 'gin' ) ){
    /* Do something */ 
  }
};

This would require some explicit meta data about the stamp, but none-the-less, is useful.

I see JS's non-linear inheritance as being pretty similar to the Components pattern in OO. The problem is that there's nothing to say what a thing is with any guarantee because types suck in JS.

In C#/Unity, if you wanted to see if an object had certain behaviors, you would do something like this:

Gin ginComponent = cocktail.GetComponent<Gin>();

if ( ginComponent != null ){
  /* Cocktail has the gin component */
}

Having used stampit for a little while, here's what I'd like to see:

  1. An easy/standard way to identify what components an object has
  2. Access to the factory(s) that instantiated the object
    • Both access to the composed factory and the individual components
    • Something akin to instance.prototype.constructor, but better

Proposal: isFactory/isStamp static function

Hello Eric,

In our development we sometimes need to understand if it's a stamp/factory or anything else. Ducktyping, you know.

I know how to check if an object is a stamp, but a better readable syntax provided by the library itself would be beneficial for all of us devs.

What do you think of:

var stampit = require('stampit');
stampit.isStamp(obj); // returns true/false.
// or
stampit.isFactory(obj);
// or both.

// kind of an implementation
function isStamp(obj) {
    return
        obj.fixed !== undefined && 
        obj.methods !== undefined && 
        obj.state !== undefined && 
        obj.enclose !== undefined;
}

Arguments in stamp.create

I read in in docs:

stamp.create([arg1] [,arg2] [,arg3...])
Just like calling stamp(), stamp.create() invokes the object creation factory and returns a new instance. Arguments are passed to all the functions passed into .enclose().

But first argument arg1 pass to base Object.create() as prototype. It not passed to all functions passed into .enclose(). Arguments passed only from arg2, ..

Should methods really be called prototype?

The naming of fixed aside, fixed.methods acts as the de facto prototype of objects created by the factory:

var stamp = stampit();
var instance = stamp();
console.log(Object.getPrototypeOf(instance) === stamp.fixed.methods); // true
console.log(stamp.fixed.methods.isPrototypeOf(instance)); // true

So shouldn't methods be called prototype or proto or something to that effect? After all you can pass in other things than methods (e.g. getters, setters and values) and they will behave just like they would on a prototype.

I guess a counter-argument is that stampit doesn't really have inheritance (types are instead composed) and prototypes don't seem to provide a sane way to support multiple inheritance.

stampObject.compose() handy method(s)

Hello @ericelliott.

I constantly find myself typing the following.

var stamp = stampit(x, y, z);
var resultingStamp = stamp.compose(anotherStamp);

But then I realize I should have used a bit longer syntax

var stamp = stampit(x, y, z);
var resultingStamp = stampit.compose(stamp, anotherStamp);

What do you think of few additional handy methods to stamp objects?

The one which creates new stamp based on current stamp (please ignore these function names, I couldn't come up with a better name than 'append'/'prepend'):
append: function append(stamp){
  var stamps = arguments.length > 1 ? [].slice.call(arguments) : [stamp];
  stamps.unshift(this); // <-- this line brings the main logic of this function
  return compose(stamps);
},
The one which creates new stamp based of argument stamp(s)
prepend: function prepend(stamp){
  var stamps = arguments.length > 1 ? [].slice.call(arguments) : [stamp];
  stamps.push(this); // <-- this line is different
  return compose(stamps);
},

I hope you got the idea.

This will allow us to create stamps from stamps without require-ing the stampit module.

Example:

File baseStamp.js

var stampit = require('stampit');
module.exports = stampit({
  foo: function () {}
}, {
  property: 'default value'
},
function () {
  this.foo(); // <-- will be overriden
});

File concreteImplementation.js

module.exports = require('stampit')({
  foo: function () { /* OVERRIDE */ }
});
  1. File creatingObjects.js using stampit v1.0.0
var stampit = require('stampit');
var BaseStamp = require('./baseStamp');
var ConcreteImplementation = require('./concreteImplementation');
var resultingObject = stampit.compose(BaseStamp, ConcreteImplementation).create();
  1. File creatingObjects.js using the proposed syntax
var BaseStamp = require('./baseStamp');
var ConcreteImplementation = require('./concreteImplementation');
var resultingObject = BaseStamp.append(ConcreteImplementation).create();

Initializing private state the correct way

Hey,
Assuming I have a very simple enclosed factory for producing new models where each model has a name:

f = stampit().enclose(function() {
  var name;

  return s.mixIn(this, {
    getName: function() {
      return name;
    }
  });
});

What would be the appropriate approach to initialize the name with some initial data?
I'd like external users to simply pass the name as : f.create("SomeName")

I guess I can simply create an 'init' method on the enclosed object which instantiate itself but I was wondering if there's any better approach to do that with stampit, I assume state() is public data and cannot be used for enclose.

Thanks

tape-run with pantomjs does not show unhadled exceptions

  1. Make a typo in a test (like enclos instead of enclose).
  2. Run npm test.
    Expected: an error text saying something like Uncaught TypeError: stampit(...).enclos is not a function
    Actual: the process freezes forever.

Interestingly, but if I use chrome (tape-run -b chrome) everything works as expected.

@JosephClay any ideas? Can you test it, and maybe even fix?

Broken in IE8!

IE8 is in decline, but there are still a lot of people using it. We should support it.

Suggestion Feature. 'compose' method with deep extend.

Can we use deep recursive copy / extend on 'compose' method?
Right now it doesn't. Can't we do this?

var Validator = require('../libs/Validator');
// Validator has state.rules.flight = { ori: 'required|size:3', dst: 'required|size:3' }
// I want to create another factory with 
//  rules.flight = { ori: 'size:3', dst: 'required|size:3', rute: 'required' }; 
//  with 'ori' now is not required and 'rute' is required
describe('#compose with other states', function() {
        var _states = {
            rules: {
                flight: {
                    ori: 'size:3',
                    rute: 'required',
                }
            }
        };
        var states = stampit()
            .state(_states);
        var Child = stampit.compose(Validator, states);
        it('should fails rules', function(done) {
            var query2 = {
                action: 'flight'
            };
            var child2 = Child({
                query: query2
            });
            var _query2 = child2.run();
            console.log(Validator.fixed.state.rules.flight); // { ori: 'required|size:3', dst: 'required|size:3' }
            console.log(Child.fixed.state.rules.flight); // { ori: 'size:3', rute: 'required' }
            // if we use deep recursive extend, rules.flight should be { ori: 'size:3', dst: 'required|size:3', rute: 'required' }
            expect(_query2.dst)
                .to.exist; // expecting query.dst to exist because it filled with error message that dst is still required
            done();
        });

on console:

1) Base validator #compose with other states should fails rules:
     AssertionError: expected undefined to exist

Factory state not safe [Yes it is, as your example demonstrates. Closing. - EE]

The following example shows the problem:

var stampit = require("./stampit.js");

// Make factory with some default state

var my_regular = { tap: "beer", bottle: "water" };
var my_saloon = { name: "The Saloon", specials: "Whisky, Gin, Tequila", regular: my_regular };
var SaloonDefaults = stampit().state(my_saloon);

// Create a new saloon
var new_saloon_1 = SaloonDefaults();

// Modify the local "my_regular" state
my_regular.tap = "limonade";

// Create another saloon
var new_saloon_2 = SaloonDefaults();

// Both saloons should be the same, but are different!
console.log(new_saloon_1);
console.log(new_saloon_2);

When I create the factory "SaloonDefaults", I would assume that all created objects will be the same. But this example shows otherwise.

The problem is that the "my_saloon" state is not deep copied into the factory.

Deep cloning state for v2.0

  1. New .refs() method for stamps which should shallow copy references.
  2. state() should be deprecated (in docs, not in code yet) in favour of new refs(). state() should be v1.x compatible. state() will essentially be an alias to refs().
  3. New props() methods should deep clone the given object(s).

Convenience methods stampit.state(), stampit.methods(), stampit.enclose()

Normally you have to type stampit().state() etc.. to start a new stamp. Sometimes it's easy to forget that you have to invoke stampit as a function before you have access to those methods.

It would be convenient if there were convenience methods on stampit that would create a new stamp automatically.

using stampit with jsdoc

This is an important issue, but I am new to all this -- please tell me if not the place to write this: I cannot find a recommended way to use jsdoc to document the creation of a stampit factory in addition to the pieces that go into it, i.e., method, state, and enclose pieces. I have had some success treating the factory as mixing in the three pieces like this:
/**

  • Use stampit to compose methods, state, and private data to create a factory object.
  • @mixes platonicMethods
  • @mixes platonicState
  • @mixes platonicPrivate
  • @return {Object} platonicFactory()
    */
    platonicFactoryCreator = function ( ) { ....}

/**

  • Methods going into the factory.
  • @mixin
    /
    platonicMethods = {
    /
    *
    • Generated function used to test masking of the interface function of the same name.
    • @LenD platonicMethods
    • @return {String} "platonic1() WIP"
      */
      platonic1 : function ( ) {...} , BUT THIS SHOWS UP AS A STATIC FUNCTION in Jsdoc output.

Is there a better or accepted way?

Use lodash instead of mout

Since lodash v3.0 you can require('lodash/object/merge') exactly the same way as we do with mout.

Lodash is a bit faster, a bit smaller, uses a bit less memory.

Additionally, lodash has _.matches function which could be handy implementing #50.

Also lodash is under active development, have better online docs and JSDoc.

Useless mixInChain() code

@ericelliott The mixInChain function looks, feels and behaves exactly as mixIn function. Sometimes we are using mixInChain to merge methods, but sometimes - mixIn.
Why?
I assume the mixInChain is redundant. Is it okay if I remove it?

react components with stamps

Just opening this for discussion. I am currently porting my app from ES6 classes to stamps, and was wondering if anyone else has thought of doing the same.

So far, I have ran into a couple issues, related to childContextTypes and component testing.

The new `init()` to substitute `enclose()` with different args in v2.0

enclose() function name confuses people. Make the new init() function an alias to enclose() (or vice versa). It should receive an object with options.

  • instance
  • stamp
  • args

Thanks to ES6 destructuring, using them is almost as convenient as using formal params:

stamp.init(({ instance, stamp, args }) => {
  console.log(instance, stamp, args);
});

This way we leave the created hook open for extension like .enclose() should have been.

This will be useful in many scenarios.

clone() method for object instances
var cloneable = stampit.init(function (options) {
  var stamp = options.stamp;

  // add .clone() method to prototype (which is 'fixed.methods')
  stamp.fixed.methods.clone = function () { return stamp(this); };

  // or add .clone() method to each object istance
  options.instance.clone = function () { return stamp(this); }
  // same as above
  this.clone = function () { return stamp(this); }
});

var myStamp = stampit(null, { data: 42 }).compose(cloneable);
var instance = myStamp.create().clone().clone().clone().clone().clone();
assert(instance.data === 42);
getStamp() method for object instances
var stampProvidable = stampit.init(function (options) {
  var stamp = options.stamp;

  // add .getStamp() method to prototype
  stamp.fixed.methods.getStamp = function () { return stamp; };

  // or add .getStamp() method to each object istance
  options.instance.getStamp = function () { return stamp; }
  // same as above
  this.getStamp = function () { return stamp; }
});

var myStamp = stampit().compose(stampProvidable);
var stampFromInstance = myStamp.create().getStamp();
assert(myStamp === stampFromInstance);

Support for fabricating callable objects (functions)?

Im trying to make composable callable objects with stampit. Since i do not have access to the object creation in the factory. The factory can only fabricate non callable objects (non functions).

model = stampt().enclose( function () {

   defined = this.models;

   return function (options) {
       defined.push(options.resource);
   }

}).state({

   models: []

}).methods({

    remove: function (name) {
       // removeByName would remove an array item
       this.models.removeByName(name); 
    }

});

// Just some usage examples for better understanding
person = model({resource: 'people'});
arthur = person({name: 'Arthur'})

// Here is the main problem
// the next line will throw undefined method remove for model
model.remove('person') 

Basically if the object returned by the enclosed function is not this or some this extended it does not receive the state or the methods declared on the moment of the factory definition.

I know this is probably intended by design, since on the factory source code it works just like that

โ‹ฎ
factory = function factory(properties, enclose) {
      var instance = mixIn(create(fixed.methods || {}),
        fixed.state, properties),
        alt;

      if (typeof fixed.enclose === 'function') {
        alt = fixed.enclose.call(instance);
      }
      โ‹ฎ
      return alt || instance;
    };

โ‹ฎ

So i propose some questions:

Is the creation of objects from reusable, composable behaviors, intended to be added on function objects as well? Or maybe i'm looking things the wrong way? Or even yet, i'm designing the domain in some weird wrong way?

Thanks for the awesome library!

Update README to reflect changes in v2.0

A list of things to mention:

  • New aliases (refs, init).
  • New props method and fourth argument to stampit(). The way it works.
  • Immutable stamps (chaining always creates new stamp).
  • The way (init (aka enclose) works - {instance, stamp, args}.
  • Short list of breaking changes...
  • Two examples (clone and getStamp) from #62 as code samples.
  • New core stampit API (stampit({methods, refs, init, props }).
  • New static feature.
  • ...

IDE context help for stampit

Hi @ericelliott

You probably heard of DefinitelyTyped project. I have just implemented stampit integration. https://github.com/koresar/DefinitelyTyped

As you might noticed my English sucks from time to time. So, in order to distribute proper, clean, and non-confusing contextual help in various IDE's I need few minutes of yours. Could you please open this commit and add proper text to the places you think should be changes. Otherwise, I would submit existing code as a PR to the DT project in few days.

With hope to see context help in my IDE.

Regards,
Vasyl

add stampit.getStampOf method

It might be useful to add a method for getting the stamp of an instance (e.g. stampit.getStampOf)

Consider this use case: I want to add a Cloner mixin, which just adds a clone method to a stamp. I would like to write something like:

var Cloner = stampit().methods({
  clone: function() {
    var stamp = stampit.getStampOf(this)
    return stamp(this)
  }
})

Right now, I have to do a mixin factory:

var ClonerFactory = function(stamp) {
  return stampit().methods({
    clone: function() {
      return stamp(this)
    }
  })
}

But adding it as a mixin to another stamp is not straightforward:

var SomeStamp = stampit()
  .state({ ... })
  .methods({ ... })

SomeStamp = SomeStamp.compose(ClonerFactory(SomeStamp))

instead of:

var SomeStamp = stampit()
  .state({ ... })
  .methods({ ... })
  .compose(Cloner)

stampit instance passed in as state to another stampit loses its stampiness

I've got a live demo here: http://codepen.io/anon/pen/qEgjyo (open the console to see the result).

The code to reproduce is at the link above, but I'll repeat it here:

var a = stampit().state({
  n: 1
}).methods({
  num:function () { return this.n; }
})();

console.log(a); // b {n: 1, num: function}

var b = stampit().state({a: a})();

console.log(b.a); // Object {n: 1}

If I pass a stampit instance in as state to a stampit factory, the factory un-stampifies the instance into a plain Object. (Do forgive me if my terms are inaccurate.) As a consequence, the 'num' method is missing from b's reference to a.

Is this intended behaviour? If not, please consider this a bug. If this is intended behaviour, what is the reason for it, and/or is there a different pattern I should be aware of?

JSDoc annotation of stamps

Hello (few) people,

There is a huge downside of using stampit - no context help while typing methods of an object.

var stamp = stampit({
  method: function () {}
});
var obj = stamp.create();
obj... // nothing here.

Whereas when you are using classic prototypes my IDE picks the context easily.

function ctor() {}
ctor.prototype.method = function method() {}
var obj = new ctor();
obj... // the 'method' pops up in the list automagically.

Can someone provide an example on how to properly annotate a stamp so that IDE (WebStorm in my case) would bring a context help while typing?

If Bower consumption is intended, please tag the current build. (0.6.1)

I just spent the better part of the day tracking down a bug in the deep copy of state, once I identified it I found it had previously been fixed. I am managing client side deps using bower, and was pulling "latest" from the stampit package. As there is no tag for 0.6.1 there is no way to get bower to pull in the current build as a dependency.

Thanks.

Cloning state containing objects does not work as expected

The following example shows my point:

var stampit = require("./stampit.js");

// Make factory with some default state

var my_regular = { tap: "beer", bottle: "water" };
var my_saloon = { name: "The Saloon", specials: "Whisky, Gin, Tequila", regular: my_regular };
var SaloonDefaults = stampit().state(my_saloon);

// Create two new saloons
var new_saloon_1 = SaloonDefaults();
var new_saloon_2 = SaloonDefaults();

console.log(new_saloon_1);

// Change the regular.tap field of the first saloon
new_saloon_1.regular.tap = "rude beer";

// At this point the contents of "new_saloon_2" has changed also!
console.log(new_saloon_2);

// Not exactly: the regular field is not a "full" clone, it's just a reference to
// the "my_regular" var.

console.log(my_regular);

The "my_saloon" contains a reference to "my_regular" and is not "deep copied", when creating both the factory and the instances.

The problem is that the state is "copied" using the mixIn function and this does not do a deep copy at the following lines:

https://github.com/stampit-org/stampit/blob/master/stampit.js#L80
https://github.com/stampit-org/stampit/blob/master/stampit.js#L113
https://github.com/stampit-org/stampit/blob/master/stampit.js#L149

The state needs to be deep copied, otherwise is will be shared between the factory and all the instances created from this factory.

Set default of enclosed variable with passed in defaults.

What is the best way to default an enclosed var to a passed in value? Currently setting it and deleting it looks kind of janky. Just curious if anyone thought of a better approach.

I am also wondering about how to run some JS on instantiation, I'm currently putting an enclose at the end of my declaration to act as an initialize function. Was wondering if I could put it at the top with a prettier syntax like .init(function() {})

Currently I have something that works like this:
http://jsbin.com/josof/2/edit?html,js,console

var label = stampit()
.enclose(function () {
  var label = this.label;
  delete this.label;
  this.getLabel = function () {
    return label;
  }
})
.methods({
  sayLabel: function () {
    console.log(this.getLabel());
  }
})
.enclose(function () {
  this.sayLabel();
})

label({label: 'hello'}); // --> "hello"

Add travis-ci

We should have Travis-ci badge and GitHub connection. Don't worry about Sauce Labs for this. Just have it run tests on Travis machines.

Ping me after the travis.yml file is ready and I'll make sure the GitHub integration works.

why is `.enclose()` named that way?

just curious. I understand that a method I pass to .enclose(method) has a comparable role to a constructor when using the new keyword with a function, right?

Why did you call it enclose? The rest of the API makes sense to me, just can't wrap my head around this one. I wonder if there could be a more descriptive name for it?

Interop with Existing Constructors

Cool library. I like the concepts.

I was wondering what the prescribed approach is for working with existing Class/Constructors such as Node's EventEmitter.

I first thought to try this:

var Collector = stampit();
Collector.methods(EventEmitter.prototype);
Collector.enclose(EventEmitter);
Collector.enclose(function() {
  // perform concrete implementation
});

But the second 'enclose' overrides the first. Is that intended behavior? It seems like it wouldn't be hard to generalize the code used in compose:

https://github.com/dilvie/stampit/blob/master/stampit.js#L117...L119

I found that this approach works as intended:

var Collector = stampit().enclose(function() {
  // perform concrete implementation
});

var EE = stampit(EventEmitter.prototype, null, EventEmitter);
Collector = stampit.compose(EE, Collector);

The 'inheritence' portion of this seems a little verbose though. Maybe a helper method would clarify things?

EE = stampit.wrap(EventEmitter);

// or

Collector = stampit.compose(stampit.wrap(EventEmitter), Collector);

I think documenting a preferred backwards-compatibility solution would be a good idea.

Lastly, I would have submitted a pull request with some code, but alas, I can't get the tests running. I don't have a Sauce Labs account. I'm trying to use qunit from the command line... Suggestions?

obj instanceof stamp

My colleagues complain that they cannot ducktype objects. One of them really insisted on the syntax:

var Stamp = stampit(bla, bla, bla);

// Somewhere deep inside the app
obj = Stamp(ble, ble, ble);
if (obj instanceof Stamp) {
 // true
}

I implemented it. This would works too.

obj = new Stamp(ble, ble, ble);
if (obj instanceof Stamp) {
 // true
}

This is easily achievable without breaking the existing API and without performance penalties. I actually already implemented this on my machine.

Sounds like good feature?

LICENSE?

you say it's MIT but you need to include the actual license

Add new static() method to stamps

Depends on #73.

The following should add properties to stamps (not objects).

var stamp = stampit({ static: { myProp: "prop value" } });
assert(stamp.myProp === "prop value");

var anObject = {};
var stamp2 = stamp.static({ secondProp: anObject });
assert(stamp2.secondProp === anObject);

Warn on stamp propTypes conflicts

For 2.x, it would be great to have a static propTypes property that does structural type checking compatible with React's propTypes.

If composed stamps contain conflicting property names, Stampit could optionally warn in a special debug build, or continue without warning in production.

Warnings would not be the only benefit of a propTypes static. It could also be used for static analysis to provide type hints for stamps, or even automatically test for stamp compatibility in graphical dataflow tools like NoFlo.

See also: #64 (comment)

Where to best put "abstract" method and possibilities for "protected" members feature

Hi @ericelliott,

first off, please let me express my gratitude for your brilliant work on this Stampit library and your enlightenment on JS in general โ€” it has changed the way I code JavaScript, for good. ๐Ÿ˜บ I've been starting using Stampit in my project for a couple of weeks now, and I'm feeling so cool about it.

That said, there are two things that I'm still not sure of:

1. Should I put "abstract" method in the delegate prototype (methods) or in the closure (enclose) of the "abstract" stamp?

/*
 * Abstract .foo() method in the delegate prototype.
 */
var abstractStamp1 = stampit()
    .methods({
        foo: function () {
            throw new Error('.foo() not implemented.');
        }
    });

/*
 * Abstract .foo() method in the closure.
 */
var abstractStamp2 = stampit()
    .enclose(function () {
        stampit.mixIn(this, {
            foo: function () {
                throw new Error('.foo() not implemented.');
            }
        })
    });

/*
 * Concrete .foo() method in the privileged area,
 * with the need of accessing private `bar` property.
 */
var concreteStamp = stampit()
    .enclose(function () {
        var bar = 'bar';

        stampit.mixIn(this, {
            foo: function () {
                bar = bar + 'foo';
                return bar;
            }
        })
    });

/*
 * Which one is better? 
 */
var objStamp1 = abstractStamp1.compose(concreteStamp);
var objStamp2 = abstractStamp2.compose(concreteStamp);

2. Are there possibilities to express "protected" members in JS by using Stampit or is it also considered as an anti-pattern? Any suggestions for alternative?

var abstractStamp = stampit()
    .enclose(function () {
        var foo = 'foo';

        stampit.mixIn(this, {
            getFoo: function () {
                return foo;
            }
        })
    });


var concreteStamp = stampit()
    .enclose(function () {
        var bar = 'bar';

        stampit.mixIn(this, {
            getFooBar: function () {
                // Use getter method to access `foo` property of the `abstractStamp`.
                return this.getFoo() + bar;
            }
        })
    });

var obj = abstractStamp.compose(concreteStamp).create();

/*
 * Supposed-to-be-protected `foo` property,
 * yet it is now exposed to the world.
 * And I'm not quite satisfied about it :/
 */
obj.getFoo();  // "foo"

Prost! ๐Ÿป

Local tests

There are a couple options:

  1. Port the tests to tape
  2. Keep the QUnit suite and use phantomjs to get the test results.

Option 1 is probably best, but we'll need to bundle the test scripts for browsers so we can still run them on Sauce Labs, too.

Check out tape-run

Passing arguments to a composed factory and how to break up a classical inheritance architecture

Hey Eric,

I was thinking about how you would recommend passing arguments to composed factories and came up with the following approach:

var stampit = require('stampit');

var defaults = stampit().state({
    name: 'Max',
    link: '/',
    obj: {mine: 'mine'},
    node: {myNode: 'myNode'}
});

var a = stampit().enclose(function () {
    var a = 'a';

    this.getA = function () {
        return a;
    };
});

var b = stampit().enclose(function () {
    var b = 'b';

    this.getB = function () {
        return b;
    };
});

var factory = stampit().compose(a, b, defaults);
var c       = factory();

var link = '/beer';
var obj  = {other: 'other'};
var node = {otherNode: 'otherNode'};

// change default state by creating instance an overwriting defaults
var d = factory({
    link: link,
    obj: obj,
    node: node
});

With this approach, I set all properties at the beginning and compose it into my new factory. When I create a new instance (d) I just overwrite the defaults.

Is this the way to do something like this? Any recommendations?

Would be very nice to have a simple example in the docs to see how to break up a classical inheritance/constructor pattern with your approach. At the moment I`m trying to figure out how to break up a CoffeeScript/Bacon.js architecture with classical inheritance and use object composition (stampit) instead.

How would you implement something like this in plain JavaScript + stampit?

class A
    constructor: (a, b, c) ->
        console.log(a)

    funcA: () ->
        return 'funcA'

class B extends A
    constructor: (a, b, c) ->
        super
        console.log(b)

    funcB: () ->
        return 'funcB'

class C extends A
    constructor: (a, b, c) ->
        super
        console.log(c)
        console.log(@funcA())
        console.log(@funcC())

    funcC: () ->
        return 'funcC'

new C('a', 'b', 'c')

I like the flexibility which object-composition (stampit) gives me though ... ;-)

Thanks, Max

v2.0.0 breaking changes

I have just created the branch v2_0. It contains fixes for #40 which break compatibility with stampit 1.x.
But I am not creating a PR yet because I believe there are few more breaking changes necessary.

Immutable stamps

Let's make all chain methods to return new stamp. Let's make stamps immutable after it's created.

A long story.

When I first started using stampit I thought that stamp.state, stamp.methods, and stamp.enclose create new stamps from existing. I was wrong eventually.
Today @raabbajam showed me his code snippets. He assumed the same!

Why it is good.

  1. This is what people expect by default.
  2. This will make all chaining methods consistent (with stamp.compose).
  3. In some cases the code would be much shorter: bigDog = dog.state({ size: 'big' }) instead of bigDog = dog.compose(stampit(null, { size: 'big' })).
  4. (IMO) Changing existing stamp is useless feature. Nobody need changes to the this of his stamps.
  5. And a mini bonus. All stamps are currently referencing entire stampit closure (methods like extractFunction, slice, forEach, forOwn. 10 in total). We could avoid that!

combine instead of compose CANCELLED

I browsed few English dictionaries. I believe the better wording for the compose function would be combine.

@ericelliott I would really love to hear your opinion on these two.

Provide options for satisfying stampit's dependencies

Give stampit users different options to satisfy its dependency on lodash/mout/underscore functions (the _ functions). My initial thought is to create separate builds:

  1. stampit with the _ functions bundled - no dependencies listed in package.json.
  2. stampit with a dependency on lodash.
  3. stampit with dependencies on the individual lodash function modules.
  4. stampit with a dependency on underscore.

Options 2-4 shouldn't have those functions/libraries bundled--just listed as dependencies.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.