====
Luc is a lightweight JavaScript framework that is built from the ground up targeting all browsers and Node.js. Luc provides a class system that can add compositions, mixins and statics functionality to any class without messing up the inheritance chain. Luc comes with over 40 utility Array/Object/Function methods along with over 150 Array utility methods that follow the same API and grammar. It also comes with the ability to add EventEmiter and Plugin functionality to any pre existing class. Luc has zero dependencies and currently sits at less than 650 SLOC and it is less than 7.5Kb minified and gzipped.
npm install luc
Download the latest zip or check out the hosted build files luc, luc-es5-shim. Source maps come packaged with the non minified versions.
- IE8 - latest with tentative support for IE6/7 (Our tests currently pass in them now)
- FF3 - latest
- Chrome
- Opera
- Safari 5.1 - latest
- Tentative support for mobile (Our tests pass for the platforms that we are testing)
For in depth examples and API documentation check out our docs.
###Simple define Luc.define just takes the passed in config and puts the properties on the prototype and returns a Constructor.
var C = Luc.define({
a: 1,
doLog: true,
logA: function() {
if (this.doLog) {
console.log(this.a);
}
}
});
var c = new C();
c.logA();
>1
c.a = 45;
c.logA();
>45
c.doLog = false;
c.logA();
new C().logA()
>1
Simple super class
function Counter() {
this.count = 0;
};
Counter.prototype = {
getCount: function() {
return this.count;
},
increaseCount: function() {
this.count++;
}
}
var C = Luc.define({
$super:Counter
});
var c = new C()
c instanceof Counter
>true
c.increaseCount();
c.getCount();
>1
c.count
>1
Call a super's method:
var C = Luc.define({
$super:Counter,
increaseCount: function () {
this.count += 2;
this.callSuper();
}
});
It can also be done this way:
var C = Luc.define({
$super:Counter,
increaseCount: function () {
this.count += 2;
C.$superclass.increaseCount.call(this);
}
});
var c = new C();
c.increaseCount();
c.count
>3
###Compositions
Compositions allow the ability to add functionality to any class without changing or messing up the inheritance chain. They are more powerful than mixins because they don't have to worry about putting a state on classes using them or have to know that they may be a mixin or a standalone class.
var C = Luc.define({
$compositions: {
Constructor: Luc.EventEmitter,
name: 'emitter',
methods: 'allMethods'
}
});
var c = new C();
c.on('hey', function() {
console.log(arguments);
});
c.emit('hey', 1,2,3, 'a');
>[1, 2, 3, "a"]
c instanceof Luc.EventEmitter
>false
###Default Compositions Luc comes with two default composition objects.
##Luc.compositionEnums.EventEmitter
Luc.EventEmitter is preferred as a composition over a mixin because it adds a state "_events" to the this instance when on is called.
var C = Luc.define({
$compositions: Luc.compositionEnums.EventEmitter
});
var c = new C();
c.on('hey', function() {
console.log(arguments);
});
c.emit('hey', 1,2,3, 'a');
>[1, 2, 3, "a"]
c instanceof Luc.EventEmitter
>false
c._events
>undefined
##Luc.compositionEnums.PluginManager
The PluginManager adds a plugin functionality to any Class. Check out the methods that get added to the instance and more info in the docs
A plugin follows the following life-cycle:
plugin is added to the instance -> plugin is created -> plugin init is called with instance -> if needed destroy called by instance -> destroy called on plugin
Here is the most basic example using the default plugin.
var C = Luc.define({
$compositions: Luc.compositionEnums.PluginManager
});
var c = new C({
plugins: [{
init: function() {
console.log('im getting initted')
},
myCoolName: 'cool'
}
]
});
>im getting initted
c.getPlugin({myCoolName: 'coo'}) instanceof Luc.Plugin
> true
Plugins can be of any class and can be added with addPlugin
function MyPlugin(){}
var C = Luc.define({
$compositions: Luc.compositionEnums.PluginManager
});
var c = new C();
c.addPlugin({Constructor: MyPlugin});
//getPlugin takes a Constructor or match object
c.getPlugin(MyPlugin) instanceof MyPlugin
>true
c.getPlugin(Luc.Plugin)
>false
Plugins can also be destroyed individually or all of them at once.
var C = Luc.define({
$compositions: Luc.compositionEnums.PluginManager
});
var c = new C({
plugins: [{
init: function() {
console.log('im getting initted ' + this.name)
},
destroy: function() {
console.log('destroyed : ' + this.name)
},
name: '1'
},{
init: function() {
console.log('im getting initted ' + this.name)
},
destroy: function() {
console.log('destroyed : ' + this.name)
},
name: '2'
}]
});
>im getting initted 1
>im getting initted 2
c.destroyPlugin({name: '1'});
>destroyed : 1
>Plugin {init: function, destroy: function, name: "1", owner: Object, init: function…}
c.destroyPlugin({name: '1'});
>false
c.destroyAllPlugins();
>destroyed : 2
###Mixins
Mixins are a way to add functionality to a class that should not add state to the instance unknowingly. Mixins can be either objects or Constructors.
function Logger() {}
Logger.prototype.log = function() {
console.log(arguments)
}
var C = Luc.define({
$mixins: [Logger, {
warn: function() {
console.warn(arguments)
}
}]
});
var c = new C();
c.log(1,2)
>[1,2]
c.warn(3,4)
>[3,4]
###Statics Statics are good for defining default configs.
var C = Luc.define({
$statics: {
number: 1
}
});
var c = new C();
c.number
>undefined
C.number
>1
Using statics prevent subclasses and instances from unknowingly modifying all instances.
var C = Luc.define({
cfg: {
a: 1
}
});
var c = new C();
c.cfg.a
>1
c.cfg.a = 5
new C().cfg.a
>5
###$class Every class defined with Luc.define will get a reference to the instance's own constructor.
var C = Luc.define()
var c = new C()
c.$class === C
>true
There are some really good use cases to have a reference to it's
own constructor.
Add functionality to an instance in a simple
and generic way:
var C = Luc.define({
add: function(a,b) {
return a + b;
}
});
//Luc.Base applies first
//arg to the instance
var c = new C({
add: function(a,b,c) {
return this.$class.prototype.add.call(this, a,b) + c;
}
});
c.add(1,2,3)
>6
new C().add(1,2,3)
>3
Or have a simple generic clone method :
var C = Luc.define({
clone: function() {
var myOwnProps = {};
Luc.Object.each(this, function(key, value) {
myOwnProps[key] = value;
});
return new this.$class(myOwnProps);
}
});
var c = new C({a:1,b:2,c:3});
c.d = 4;
var clone = c.clone();
clone === c
>false
clone.a
>1
clone.b
>2
clone.c
>3
clone.d
>4
###Luc.Base Luc.Base is the default class of for define. It is a simple class that by default applies the first argument to the instance and then calls Luc.Base.init, init is just an emptyFn which is meant to be overwritten by the defining class.
var b = new Luc.Base({
a: 1,
init: function() {
console.log('hey')
}
})
b.a
>hey
>1
We found that most of our classes do this so we made it the default. Having a config object as the first and only param keeps a clean api as well.
var C = Luc.define({
init: function() {
Luc.Array.each(this.items, this.logItems)
},
logItems: function(item) {
console.log(item);
}
});
var c = new C({items: [1,2,3]});
>1
>2
>3
var d = new C({items: 'A'});
>'A'
var e = new C();
If you don't like the applying of the config to the instance it can always be "disabled"
var NoApply = Luc.define({
beforeInit: function() {
},
init: function() {
Luc.Array.each(this.items, this.logItems)
},
logItems: function(item) {
console.log(item);
}
});
var c = new NoApply({items: [1,2,3]});
- Luc.isArguments
- Luc.isArray
- Luc.isDate
- Luc.isEmpty
- Luc.isFalsy
- Luc.isNumber
- Luc.isObject
- Luc.isRegExp
- Luc.isString
- Luc.isFunction
###Luc.isFalsy Return true if the object is falsy but not zero.
Luc.isFalsy(false)
>true
Luc.isFalsy(0)
>false
###Luc.isEmpty Return true if the object is empty. {}, [], '',false, null, undefined, NaN are all treated as empty.
Luc.isEmpty(true)
>false
Luc.isEmpty([])
>true
The native js type methods work as you think they should.
Luc.isObject([])
>false
Luc.isArray([])
>true
You can see that we don't have an isNull isUndefined or isNaN. We prefer to use:
obj === null
obj === undefined
isNaN(obj)
###Luc.Object.apply / Luc.apply Apply the properties from fromObject to the toObject. fromObject will overwrite any shared keys. It can also be used as a simple shallow clone.
var to = {a:1, c:1}, from = {a:2, b:2}
Luc.Object.apply(to, from)
>Object {a: 2, c: 1, b: 2}
to === to
>true
var clone = Luc.Object.apply({}, from)
>undefined
clone
>Object {a: 2, b: 2}
clone === from
>false
No null checks are needed.
Luc.apply(undefined, {a:1})
>{a:1}
Luc.apply({a: 1})
>{a:1}
###Luc.Object.mix / Luc.mix Similar to Luc.Object.apply except that the fromObject will NOT overwrite the keys of the toObject if they are defined.
Luc.mix({a:1,b:2}, {a:3,b:4,c:5})
>{a: 1, b: 2, c: 5}
No null checks are needed.
Luc.mix(undefined, {a:1})
>{a:1}
Luc.mix({a: 1})
>{a:1}
###Luc.Object.each Iterate over an objects properties as key value "pairs" with the passed in function.
var thisArg = {val:'c'};
Luc.Object.each({
u: 'L'
}, function(key, value) {
console.log(value + key + this.val)
}, thisArg)
>"Luc"
###Luc.Object.filter Return key value pairs from the object if the filterFn returns a truthy value.
Luc.Object.filter({
a: false,
b: true,
c: false
}, function(key, value) {
return key === 'a' || value
})
>[{key: 'a', value: false}, {key: 'b', value: true}]
Luc.Object.filter({
a: false,
b: true,
c: false
}, function(key, value) {
return key === 'a' || value
}, undefined, {
keys: true
})
>['a', 'b']
Luc is optionally packaged with es5 shim so you can write es5 code in non es5 browsers. It comes with your favorite Array methods such as Array.forEach, Array.filter, Array.some, Array.every Array.reduceRight ..
###Luc.Array.toArray Take an object and turn it into an array if it isn't one.
Luc.Array.toArray()
>[]
Luc.Array.toArray(null)
>[]
Luc.Array.toArray(1)
>[1]
Luc.Array.toArray([1,2])
>[1, 2]
###Luc.Array.forEach Runs an Array.forEach after calling Luc.Array.toArray on the item. It is very useful for setting up flexible API's that can handle none one or many.
Luc.Array.each(this.items, function(item) {
this._addItem(item);
});
vs.
if(Array.isArray(this.items)){
this.items.forEach(function(item) {
this._addItem(item);
})
}
else if(this.items !== undefined) {
this._addItem(this.items);
}
###Luc.Array.pluck Flatten out an array of objects based of their value for the passed in key. This also takes account for null/undefined values.
Luc.Array.pluck([undefined, {a:'1', b:2}, {b:3}, {b:4}], 'b')
>[undefined, 2, 3, 4]
###Luc.Array.insert Insert or append the second array/arguments into the first array/arguments. This method does not alter the passed in array/arguments.
Luc.Array.insert([0,4], [1,2,3], 1);
>[0, 1, 2, 3, 4]
Luc.Array.insert([0,4], [1,2,3], true);
>[0, 4, 1, 2, 3]
Luc.Array.insert([0,4], [1,2,3], 0);
>[1, 2, 3, 0, 4]
###Luc.Array.last Return the last item of the array
var myLongArrayNameForThingsThatIWantToKeepTrackOf = [1,2,3]
Luc.Array.last(myLongArrayNameForThingsThatIWantToKeepTrackOf);
vs.
myLongArrayNameForThingsThatIWantToKeepTrackOf[myLongArrayNameForThingsThatIWantToKeepTrackOf.length -1]
###Luc.Array.removeAtIndex Remove an item from the passed in arr from the index.
var arr = [1,2,3];
Luc.Array.removeAtIndex(arr, 1);
>2
arr;
>[1,3]
###Luc.Array.fromIndex Return the items in between the passed in index and the end of the array.
Luc.Array.fromIndex([1,2,3,4,5], 1)
>[2, 3, 4, 5]
###Iterator and Matching functions
- Luc.Array.findAll
- Luc.Array.findAllNot
- Luc.Array.findFirst
- Luc.Array.findFirstNot
- Luc.Array.findLast
- Luc.Array.findLastNot
- Luc.Array.removeAll
- Luc.Array.removeAllNot
- Luc.Array.removeFirst
- Luc.Array.removeFirstNot
- Luc.Array.removeLast
- Luc.Array.removeLastNot
All remove* / find* methods follow the same api. *All functions will return an array of removed or found items. The items will be added to the array in the order they are found. *First functions will return the first item and stop iterating after that, if none is found false is returned. remove* functions will directly change the passed in array. *Not functions only do the following actions if the comparison is not true. All remove* / find* take the following api: array, objectToCompareOrIterator, compareConfigOrThisArg
for example:
//most common use case
Luc.Array.findFirst([1,2,3, {}], {});
>Object {}
//pass in optional config for a strict === comparison
Luc.Array.findFirst([1,2,3,{}], {}, {type: 'strict'});
>false
//pass in an iterator and thisArg
Luc.Array.findFirst([1,2,3,{}], function(val, index, array){
return val === 3 || this.num === val;
}, {num: 1});
>1
//you can see remove modifies the passed in array.
var arr = [1,2,{a:1},1, {a:1}];
Luc.Array.removeFirst(arr, {a:1})
>{a:1}
arr;
>[1, 2, 1, {a:1}]
Luc.Array.removeLast(arr, 1)
>1
arr;
>[1,2, {a:1}]
Luc.Array.findAll([1,2,3, {a:1,b:2}], function() {return true;})
> [1,2,3, {a:1,b:2}]
//show how not works with an iterator
Luc.Array.findAllNot([1,2,3, {a:1,b:2}], function() {return true;})
>[]
The Array functions are also combined with the Luc.is* functions. There are over 150 matching functions. Almost every public method of Luc.is* is available it uses the following grammar Luc.Array["methodName""isMethodName"] These functions have a consistent api that should make sense what they do from the function name. Docs
//compact like function
Luc.Array.findAllNotFalsy([false, true, null, undefined, 0, '', [], [1]])
> [true, 0, [], [1]]
//Or remove all empty items
var arr = ['', 0 , [], {a:1}, true, {}, [1]]
Luc.Array.removeAllEmpty(arr)
>['', [], {}]
arr
>[0, {a:1}, true, [1]]
Luc.Array.findFirstNotString([1,2,3,'5'])
>1
var arr = [1,2,3,'5'];
Luc.Array.removeAllNotString(arr);
>[1,2,3]
arr
>["5"]
As of right now there are two function sets which differ from the is api.
InstanceOf
Luc.Array.findAllInstanceOf([1,2, new Date(), {}, []], Object)
>[date, {}, []]
>Luc.Array.findAllNotInstanceOf([1,2, new Date(), {}, []], Object)
[1, 2]
In
Luc.Array.findAllIn([1,2,3], [1,2])
>[1, 2]
Luc.Array.findFirstIn([1,2,3], [1,2])
>1
//defaults to loose comparison
Luc.Array.findAllIn([1,2,3, {a:1, b:2}], [1,{a:1}])
> [1, {a:1,b:2}]
Luc.Array.findAllIn([1,2,3, {a:1, b:2}], [1,{a:1}], {type: 'deep'})
>[1]
Most of these functions follow the same api: function or function[], relevant args ... with an optional config to Luc.Function.createAutmenter as the last argument.
###Luc.Function.createAugmenter
Augment the passed in function's thisArg and or arguments object based on the passed in config. Read the docs to understand all of the config options.
function fn() {
console.log(this)
console.log(arguments)
}
//Luc.Array.insert([4], [1,2,3], 0)
Luc.Function.createAugmenter(fn, {
thisArg: {configedThisArg: true},
args: [1,2,3],
index:0
})(4)
>Object {configedThisArg: true}
>[1, 2, 3, 4]
//Luc.Array.insert([1,2,3], [4], 0)
Luc.Function.createAugmenter(fn, {
thisArg: {configedThisArg: true},
args: [1,2,3],
index:0,
argumentsFirst:false
})(4)
>Object {configedThisArg: true}
>[4, 1, 2, 3]
Luc.Array.insert([4], [1,2,3], true)
var f = Luc.Function.createAugmenter(fn, {
args: [1,2,3],
index: true
});
f.apply({config: false}, [4])
>Object {config: false}
>[4, 1, 2, 3]
###Luc.Function.createThrottled Create a throttled function from the passed in function that will only get called once the number of milliseconds have been exceeded. Read the docs to understand all of the config options.
var logArgs = function() {
console.log(arguments)
};
var a = Luc.Function.createThrottled(logArgs, 1);
for(var i = 0; i < 100; ++i) {
a(1,2,3);
}
setTimeout(function() {
a(1)
}, 100)
setTimeout(function() {
a(2)
}, 400)
>[1, 2, 3]
>[1]
>[2]
###Luc.Function.createRelayer Return a functions that runs the passed in functions the result of each function will be the the call args for the next function. The value of the last function return will be returned. Read the docs to understand all of the config options.
Luc.Function.createRelayer([
function(str) {
return str + 'b'
},
function(str) {
return str + 'c'
},
function(str) {
return str + 'd'
}
])('a')
>"abcd"
###Luc.Function.createSequence Return a function that runs the passed in functions and returns the result of the last function called. Read the docs to understand all of the config options.
Luc.Function.createSequence([
function() {
console.log(1)
},
function() {
console.log(2)
},
function() {
console.log(3)
console.log('finished logging')
return 4;
}
])()
>1
>2
>3
>finished logging
>4
###Luc.Function.createSequenceIf Return a function that runs the passed in functions if one of the functions returns false the rest of the functions won't run and false will be returned. Read the docs to understand all of the config options.
Luc.Function.createSequenceIf([
function() {
console.log(1)
},
function() {
console.log(2)
},
function() {
console.log(3)
console.log('finished logging')
return 4;
}, function() {
return false;
}, function() {
console.log('i cant log')
}
])()
>1
>2
>3
>finished logging
>false
###Luc.Function.createDeferred Defer a function's execution for the passed in milliseconds. Read the docs to understand all of the config options.
###Luc.Function.emptyFn / Luc.emptyFn A reusable empty function
###Luc.Function.abstractFn / Luc.abstractFn A function that throws an error when called. Useful when defining abstract like classes
Return true if the values are equal to each other. By default a deep comparison is done on arrays, dates and objects and a strict comparison is done on other types. Pass in 'shallow' for a shallow comparison, 'deep' (default) for a deep comparison 'strict' for a strict === comparison for all objects or 'loose' for a loose comparison on objects. A loose comparison will compare the keys and values of val1 to val2 and does not check if keys from val2 are equal to the keys in val1.
Luc.compare('1', 1)
>false
Luc.compare({a: 1}, {a: 1})
>true
Luc.compare({a: 1, b: {}}, {a: 1, b: {} }, {type:'shallow'})
>false
Luc.compare({a: 1, b: {}}, {a: 1, b: {} }, {type: 'deep'})
>true
Luc.compare({a: 1, b: {}}, {a: 1, b: {} }, {type: 'strict'})
>false
Luc.compare({a: 1}, {a:1,b:1})
>false
Luc.compare({a: 1}, {a:1,b:1}, {type: 'loose'})
>true
Luc.compare({a: 1}, {a:1,b:1}, {type: 'loose'})
>true
Luc.compare([{a: 1}], [{a:1,b:1}], {type: 'loose'})
>true
Luc.compare([{a: 1}, {}], [{a:1,b:1}], {type: 'loose'})
>false
Luc.compare([{a: 1}, {}], [{a:1,b:1}, {}], {type: 'loose'})
>true
Luc.compare([{a:1,b:1}], [{a: 1}], {type: 'loose'})
>false
Return a unique id. A prefix is an optional argument
Luc.id()
>"luc-0"
Luc.id()
>"luc-1"
Luc.id('my-prefix')
>"my-prefix0"
Luc.id('')
>"0"
Luc is now in its first official release. It still sits at version 0.* and follows the http://semver.org/ versioning spec. We want to get input on what the community thinks about the API and functionality Luc provides. Luc will officially release an unchanging API after taking account for everyone's input. Once input has been gathered a 1.* version will be released.
Right now Luc provides the the building blocks for small and large scale applications. It does not have any dom or html building functionality. We want to add this functionality as a submodule down the road. For the most part Luc's core API should be pretty set now. Any functionality that we want to add down the road will be done through submodules.
Log issues with the appropriate tag. For discussions about the API or documentation use the discussion tag. Please read the known caveats of es5-shim.
- How does Luc support Node and IE6?
- Luc uses es5-shim and browserify to write pure node code with all of its es5 goodness and it just works on all browsers.
- Can I run the tests and see code coverage?
- Why do some tests show failing in testling
- Not sure we are talking to them for support. There are currently only two failure but they are timing out or all the tests pass but they show as failed.
- How do you pronounce Luc?
- It is pronounced like the name Luke
- What is Luc named after?
- Everyone's favorite former Milwaukee Bucks Forward and Cameroonian Prince Luc Richard Mbah a Moute