luc

0.3.1 • Public • Published

Luc

====

Build Status

Selenium Test Status

What is Luc?

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.

Node

npm install luc

Browser

Download the latest zip or check out the hosted build files luc, luc-es5-shim. Source maps come packaged with the non minified versions.

Supported Browsers

  • 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)

Examples/Code

For in depth examples and API documentation check out our docs.

Class System

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()
 
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: functiondestroy: functionname: "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]});
 

Array, Object, Function and other utility functions

Luc.is*

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

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.Array

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

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]

Luc.Function

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

Luc.compare

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

Luc.id

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"

Where does Luc sit now?

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.

The future

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.

Issues/Discussion

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.

FAQ

  • 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?
    • Feel free to run the tests in your favorite or least favorite browser. 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?

Package Sidebar

Install

npm i luc

Weekly Downloads

2

Version

0.3.1

License

MIT

Last publish

Collaborators

  • pllee