A delicious way to work with prototypes in JavaScript.
Keylime is small library for working with prototype objects in JavaScript. It provides some syntax sugar for defining your prototype's properties & methods, and APIs for sharing behavior between your prototypes through mixins.
// Keylime lets you turn this:
function Jedi(options) {
this.name = options.name;
this.side = options.side || 'light';
this.powers = option.powers || ['push', 'jump'];
this.createdAt = options.createdAt || Date.now();
}
Jedi.prototype.meditate = function meditate() {
return this.name + ' is meditating...';
}
// ... into this:
var Jedi = keylime('Jedi')
.attr('name')
.attr('side', 'light')
.attr('powers', ['push', 'jump']);
.attr('createdAt', Date.now)
.method('meditate', function meditate() {
return this.name + ' is meditating...';
});
- Declarative, chainable syntax.
- Easily define attributes and methods.
- Create mixins to share behaviour between your objects.
- Use extensions to customize Keylime's syntax.
- Great browser support (IE8 and up).
- Leightweight (~9kb minified). Zero dependencies.
Using in the browser:
- Browser/AMD version (~9kb minified)
Using npm:
npm install --save keylime
Using bower:
bower install --save keylimejs
In the browser:
<script src="keylime.min.js"></script>
In Node:
var keylime = require('keylime');
Using AMD/Require:
require([keylime], function(keylime) {
// ...
});
The top-level Keylime function generates new constructor functions. These functions not only create new instances for you, but also have special functions that allow you to define how the function should behave.
var Jedi = keylime('Jedi');
All the following API examples use
Jedi
to refer to a keylime constructor.
Keylime allows you to define "attributes" that every instance of the constructor should have, with optional default values.
// no default value
Jedi.attr('name');
// default value
Jedi.attr('side', 'light');
You can use functions to calculate default values at time of creation:
Jedi.attr('createdAt', function() {
return Date.now();
});
// or for short:
Jedi.attr('createdAt', Date.now);
If you set an array or object as the default value, it will be copied to every instance, not shared:
Jedi.attr('powers', ['push', 'jump']);
var obi = new Jedi();
var mace = new Jedi();
obi.powers.push('mind trick'); //=> ['push', 'jump', 'mind trick']
mace.powers; //=> ['push', 'jump']
You can create instances of your objects using the either new
keyword, or you can call the
.create()
function.
new Jedi();
// or
Jedi.create();
Attributes can be passed in an object as the first parameter during creation. Properties that are not attributes will be filtered out during creation:
var yoda = Jedi({
name: 'yoda',
invalidAttr: 10
});
yoda.name; //=> 'yoda'
yoda.invalidAttr; //=> undefined
You can define methods on objects, and Keylime will put these methods on the prototype chain for
you. Inside the function body, this
refers to the instance, like usual.
Jedi.method('meditate', function meditate() {
return this.name + ' is meditating...';
});
var anakin = new Jedi({
name: 'anakin'
});
anakin.meditate(); //=> anakin is meditating...
Mixins allow you to abstract shared or complex logic into re-usable modules. Think of mixins similarly to middlewhere. A mixin is a function that receives the constructor it was included in, and allows you to modify the constructor.
For example, you could create a mixin that defines how HP is managed in characters on a game.
function hp(model, maxHealth, minHealth) {
model
.attr('health', maxHealth)
.method('receiveHeal', function receiveHeal(amount) {
var newHealth = this.health + amount;
this.health = (newHealth > maxHealth) ? maxHealth : newHealth;
return this.health;
})
.method('isAlive', function isAlive() {
return this.health <= minHealth;
})
.method('receiveDamage', function receiveDamage(amount) {
var newHealth = this.health - amount;
this.health = (newHealth < minHealth) ? minHealth : newHealth;
return this.health;
});
}
var Jedi = keylime('Jedi')
.include(hp, 0, 100)
.attr('name');
var yoda = new Jedi({
health: 50
});
yoda.health; //=> 50
yoda.receiveDamage(80); //=> 0
yoda.isAlive(); //=> false
Keylime does not provide any APIs for inheriting other constructors. Instead, Keylime encourages abstraction through the use of mixins.
For example, instead of creating a base Character
constructor that defines how healthpoints and
damage are managed, create a healthpoint
mixin, and include that in your constructors.
Mixins can be used to wrap other libraries, create database adapters, etc.
Keylime allows you to extend the syntax with your own functionality. A Keylime extension looks identical to a mixin.
Extensions are useful for wrapping other libraries, or providing new ways to describe constructor functionality. For example, here is an extension wrapping validate.js:
keylime.registerExtension('validate', function validate(model, constraints) {
model.constraints = constraints;
model
.method('validate', function validate() {
return validatejs(this, model.constraints);
})
.method('isValid', function isValid() {
return this.validate() === undefined;
});
});
var Jedi = keylime('Jedi')
.validate({
name: {
presence: true
}
})
.attr('name');
var yoda = new Jedi();
yoda.isValid(); //=> false
yoda.name = 'Yoda';
yoda.isValid(); //=> true
Keylime allows you to hook into object creating through handler functions, similary to handling events in jQuery.
Unlike event handlers however, these functions are not asyncrounous. Keylime will wait for all of your handlers to finish before returning the new instance. This allows you modify the instance before the its sent back to the caller.
Jedi.on('init', function(instance) {
// instance with attributes already set
});
Since Keylime waits for all handler functions to finish before returning new instances, it provides a special API you should use if you need to modify specific attributes.
This prevents you from having to loop through attributes in multiple handlers to update values.
Jedi.on('attr', 'id', function appendTime(idValue) {
var timestamp = new Date();
return idValue + '_' + timestamp.toISOString();
});
var yoda = new Jedi({
id: 'yoda'
});
yoda.id; //=> 'yoda_2015-01-28T21:46:15.574Z'
Logo by the awesome @ronniecjohnson.