A library to help manage mixins and help abstract other JavaScript libraries
JavaScript
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
.gitattributes
.gitignore
README.md
interface.js
library.js
shim.js

README.md

The SK80 Mixin Micro-library

Contents

  1. Intro
  2. Setting up the library
  3. SK80.create
    1. Cloning an object
    2. Bolting on mixins
    3. Initialising a new object
    4. Bolting on mixins and initialising the object
  4. SK80.enhance
    1. Enhancing an object
    2. Finding the parent
  5. SK80.mixins
    1. How mixins look for SK80
    2. SK80.mixins.add
    3. SK80.mixins.exec
    4. SK80.mixins.list
  6. Change log

Intro

This micro-library is designed to help with object-oriented JavaScript and make it easier to work with mixins. Using this technique it's failry straight forward to abstract out another JavaScript library so it may be changed without affecting the application (blog post explaining how to do this is pending).

This micro-library adds 3 properties to an existing object:

  • SK80.create() - allows an object to be created from another, also enables the mixins to be added and can be used to trigger an init method.
  • SK80.enhance() - can add new properties to an existing object.
  • SK80.mixins - a object containing methods to manage the mixins.

The library also has another property, SK80.version, that is not added to the new object - this property identifies the current version of the library using the following notation:

majorVersion.minorVersion[.bugFix][a|b]

The bugFix extension is optional, as is the "a" or "b" at the end. A few examples are:

  • 0.4b (4th minor version of version 0, beta)
  • 1.0.18 (18th bug fix of version 1.0, public release)

Setting up the library

Out-of-the-box the library is bound to the SK80 namespace. It is possible to bind the library to an existing namespace by using the following code:

SK80.call(MYNAMESPACE);

It is also possible to bing the library to an entirely new variable:

var myObject = new SK80();

SK80.create(parentObject [,settings]) (returns Object)

The core method of the micro-library. It's responsible for creating new objects based on previous ones. It can also add mixins and automatically execute an init method.

Cloning an object

Creating a new object from another one is the main function of SK80.create(). Without a second argument, this methods works identically to Object.create():

var foo = {
    prop: 1,
    meth: function () {
        console.log(this.prop);
    }
};

var bar = SK80.create(foo);

bar.meth(); // logs 1
bar.prop = 'hi';
bar.meth(); // logs "hi"
foo.meth(); // logs 1

The link is live so any changes to the old object are reflected in the new one.

foo.newbie = 'new';
bar.newbie; // "new"

Bolting on mixins

To bolt one or more mixins onto the newly created object, pass in an object as the second argument of SK80.create() with a mixins property. The property should be an array of strings listing all the mixins to be added to the object. The mixins property should be an own property, i.e., it should not be a property that the object has inherited.

// Assume there is a "Classes" mixin that contains the property "addClass"

var foo = SK80.create({
    init: function (elem) {
        this.element = elem;
    }
}, {
    mixins: ['Classes']
});

foo.init(document.getElementById('myElementId'));
foo.addClass('SK80ified'); // Adds a class to the "myElementId" element.

Adding a mixin will overwrite any properties with the same name, no checking is currently done to prevent this. An error is thrown if the mixin is not found and be aware that mixins are case-sensitive.

Initialising the new object

It is often useful to have a constructor function for an object; this is particularly hand for creating arrays or strings unique to the object and not inherited. The SK80.create() method will treat an init method as the constructor if the second argument has an args property. The args property should be an array of the arguments to pass to the init method and, like mixins, should be an own property.

var foo = SK80.create({
    init: function (str, arr) {
        this.str = str;
        this.arr = arr;
    }
}, {
    args: ['foo', ['bar', 'baz']]
});

foo.str; // "foo"
foo.arr; // ["bar", "baz"]

It is probably obvious, but if the args property is an empty array, the init method will be executed without any arguments being passed in.

If the new object does not have an init method, it will not be initialised even if the args property exists. This does not throw any errors.

Bolting on mixins and initialising the object

The second argument of SK80.create() may have both the mixins and args properties, allowing the object to be initialised and have mixin bolted on. The order that the properties appear in the object is not important - mixins will always be bolted on before the object is initialised.

var foo = SK80.create({
    init: function (elem) {
        this.element = elem;
        this.addClass('SK80ified'); // This method will exist when the object is
                                    // initialised.
    }
}, {
    mixins: ['Classes'],
    args: [document.getElementById('myElementId')]
});

// The "myElementId" element will now have the "SK80ified" class added to it.

SK80.enhance(parentObject, enhancements [,settings]) (returns Object)

Enhancing an object allows new object to be created from existing ones without affecting that existing object. The new object will have all the properties of the old one as well as any new ones that were added at this stage. Any objects will be combined. A new $proto property is also added as a reference to the parent object.

Enhancing an object

To enhance an object, simply pass it to SK80.enhance() with some enhancements:

var foo = SK80.create({
    init: function (elem) {
        this.element = elem;
        this.draw();
    },
    draw: function () {
        this.addClass('SK80ified');
    }
}, {
    mixins: ['Classes']
});

var bar = SK80.enhance(foo, {

    // This new init method works in place of the parent's one.
    init: function (elem) {
        foo.init.call(this, elem);
        this.drawMore();
    },
    
    // No need to bold the mixins on again, they're inherited from the parent.
    drawMore: function () {
        this.addClass('GnarlySK80ified');
    }
});

console.log(typeof bar.draw); // "function" <- parent's properties are inherited.
console.log(typeof foo.drawMore); // "undefined" <- parent is unchanged.

Any plain objects that are found are combined rather than replaced. This allows things like class names to be created in the parent, augmented in the child and still accessible.

var foo = SK80.create({
    constants: {
        NAME:     'foo',
        CSS_SK80: 'SK80ified'
    },
    init: function (elem) {
        this.element = elem;
        this.draw();
    },
    draw: function () {
        this.addClass(this.constants.CSS_SK80);
    }
}, {
    mixins: ['Classes']
});

var bar = SK80.enhance(foo, {

    constants: {
        NAME:       'bar',
        CSS_GNARLY: 'GnarlySK80ified'
    },

    // This new init method works in place of the parent's one.
    init: function (elem) {
        foo.init.call(this, elem);
        this.drawMore();
    },
    
    // No need to bold the mixins on again, they're inherited from the parent.
    drawMore: function () {
        this.addClass(this.constants.CSS_GNARLY);
    }
});

foo.constants; // {NAME: 'foo', CSS_SK80: 'SK80ified'}
bar.constants; // {NAME: 'bar', CSS_SK80: 'SK80ified', CSS_GNARLY: 'GnarlySK80ified'}

If a third argument it passed to SK80.enhance(), it is treated the same as the settings argument of SK80.create().

var bar = SK80.enhance(foo, {
    init: function (elem) {
        foo.init.call(this, elem);
        this.drawMore();
    },
    drawMore: function () {
        this.addClass('GnarlySK80ified');
    }
}, {
    args: [document.getElementById('myElementId')]
});

// The "myElementId" element will now have the "SK80ified" and "GnarlySK80ified"
// classes added to it.

Finding the parent

As you may have noticed, the bar example was tightly coupled to foo. Often this is undesirable and a general link to the parent would be more useful. For these times, SK80.enhance() adds a static $proto property which is a link to the parent.

// A less tightly coupled version of the "bar" example.
var bar = SK80.enhance(foo, {

    // bar.$proto is a link to the parent.
    init: function (elem) {
        bar.$proto.init.call(this, elem);
        this.drawMore();
    },
    drawMore: function () {
        this.addClass('GnarlySK80ified');
    }
});

Try to avoid using this.$proto as the instance will be continuously updated and may not refer to the object you expect.

SK80.mixins

Mixins are objects whose properties can be added to another object. They allow a developer to create functionality once and have it added to any number of objects.

How mixins look for SK80

Whereas some libraries insist that mixins should be objects, SK80 insists that they should be functions that manipulate this. This is based on Angus Croll's fresh look at JavaScript mixins. Here is an example of the "Classes" mixin that was used in the previous examples:

var ClassesMixin = function () {
    this.addClass = function (elem, className) {
        if (className === undefined) {
            className = elem;
            elem = this.element;
        }
        elem.classList.add(className);
    };
};

The function will be called with the new object as the context, so adding properties to this will add them to the new object.

An advantage of this style is the ability to re-use the mixins as stand-alone functionality. As a result, I tend to name all my mixins with a leading capital, but that is not a requirement. The following piece of code will create an object that allows the developer to add a class to any element:

var classTweaker = new ClassesMixin();
classTweaker.addClass(document.getElementById('myElementId'), 'SK80ified');

If you prefer to use anonymous functions rather than variables like this example, mixins may be retrieves using SK80.mixins.get().

When a mixin is added to the object by SK80.create(), no arguments are passed to the function. This may be useful to identify whether the mixin has been added using SK80.create() or executed as above.

SK80.mixins.add(name, mixin)

This method allows mixins to be created. The mixins are stored privately but are accessible using SK80.mixins.get(). A lot of validation checks are done at this stage, including checking that the mixin will add properties to an object. Be aware that the only way to do that is to execute the mixin function.

Here is an example of adding the "Classes" mixin:

SK80.mixins.add('Classes', ClassesMixin);

// Or, if you prefer to use anonymous functions:
SK80.mixins.add('Classes', function () {
    this.addClass = function (elem, className) {
        if (className === undefined) {
            className = elem;
            elem = this.element;
        }
        elem.classList.add(className);
    };
});

The "Classes" mixin may now be bolted onto any object using SK80.create() or executed as described in the next section.

SK80.mixins.exec(name [, args]) (returns instance of name mixin)

The mixins are stored privately to protect the data. However, fans of anonymous functions may wish to use the re-use example higher up. The SK80.mixins.exec() method allow the mixin to be executed and arguments to be passed to it.

The following piece of code will execute the "Classes" mixin.

var classTweaker = SK80.mixins.exec('Classes');
classTweaker.addClass(document.getElementById('myElementId'), 'SK80ified');

To pass arguments to the mixin as it's executed, pass an Array to SK80.mixins.exec as the second argument.

// A mixin that traverses the DOM.
SK80.mixins.add('Tree', function (baseElement) {
    this.find = function (element, selector) {
        if (selector === undefined) {
            selector = element;
            element = baseElement || this.element;
        }
        return Array.prototype.slice.call(element.querySelectorAll(selector));
    }
});

var tree = SK80.mixins.exec('Tree', [document.body]);
tree.find('div'); // Finds all <div> elements on the page.

No arguments are passed to the mixin as it is bolted onto an object using SK80.create(), therefore the baseElement variable would always be undefined when bolted onto an object but have a value when executed as above.

SK80.mixins.list() (returns Array)

As the mixins are stores privately, it may be useful to know which mixins have been registered. SK80.mixins.list() will reveal all the registered mixins in the form of an array of strings containing all the names of the mixins.

Here is an example of the list, assuming that the "Classes" and "Tree" mixins have been added as above.

var list = SK80.mixins.list();
list; // ['Classes', 'Tree']

Manipulating this list has no effect on the mixins themselves and SK80.mixins.list() will always return a fresh list.

Change log

0.6.2b (30 November 2012)

  • Fixing a syntax error.
  • General script tidying up.

0.6.1b (25 November 2012)

  • Allowed SK80.mixins.add() to replace existing mixins.
  • Removed reserved word checking from SK80.mixins.add().
  • Improved SK80.enhance() so it will combine plain objects rather than replacing them.
  • Updated coding style to make it more maintainable and customisable, removed unnecessary checks and errors.
  • Updated comments to reflect changes forgotten in previous version.

0.6b (14th November 2012)

  • Added SK80.enhance()

0.5b (4th October 2012)

  • Added SK80.mixins.exec()
  • Removed SK80.mixins.get() - use SK80.mixins.exec() instead.