anywhichway/js-generics

View on GitHub
index.js

Summary

Maintainability
B
5 hrs
Test Coverage
//     js-generics
//
//     Copyright (c) 2015 Simon Y. Blackwell, AnyWhichWay
//     MIT License - http://opensource.org/licenses/mit-license.php
(function() {
    "use strict";
    var _global = this;
    /*
     * Returns a function that is built to choose and dispatch the best matching handler based on arguments.
     * Argument selectors can be primitive type names or functions that return a true for the argument passed
     * at runtime.
     * 
     * generic()
     *     .method("number",function(value) { console.log(value, " is a number.");})
     *     .method(function(o) { return o instanceof Object; },function(value) { console.log(value," is an Object"); });
     * 
     */
    function generic(defaultFunction) {
        // define the dispatch function
        var method = function() {
            var me = this, possible = [];
            var args = Array.prototype.slice.call(arguments);
            method.handlers.forEach(function(handler) {
                // handler matches if all its arg types or type checkers match args passed in and arg count is correct
                var f = handler[handler.length-1], count;
                if(typeof(f)==="number") {
                    count = f;
                    f = handler[handler.length-2];
                } else {
                    count = f.length;
                }
                if((count===generic.VARGS || args.length===count) && args.every(function(arg,i) {
                    return arg===undefined || i>count || (i>0 && count===generic.VARGS) || (handler[i] instanceof Function ? (count===generic.VARGS ? handler[i].call(me,args) : handler[i].call(me,arg)) : handler[i]===generic.VTYPE || handler[i]===typeof(arg));
                })) {
                    possible.push(f);
                }
            });
            var thehandler = (possible[possible.length-1] ? possible[possible.length-1] : defaultFunction);
            if(typeof(thehandler)==="function") { // won't be a function if no default was provided and no handlers matched
                //var args = [thehandler].concat(arguments);
                //return Function.apply(args);
                return thehandler.apply(me,arguments);
            }
            // no matching handler, no return value
        }
        // create storage for handlers
        method.handlers = [];
        // add handlers
        method.method = function() {
            var args = Array.prototype.slice.call(arguments);
            // don't add handlers twice
            if(!method.handlers.some(function(handler,i) {
                if(args.length===handler.length) {
                    if(args.every(function(arg,j) {
                        return handler[j]===arg;
                    })) {
                        handlers[i] = arguments;
                        return handlers[i];
                    }
                }
            })) {
                var count, f = args[args.length-1];
                if(typeof(f)==="number") {
                    count = f;
                    f = args[args.length-2];
                } else {
                    count = f.length;
                }
                if(count===generic.VARGS || f.length===count) { // make sure the number of type checks is the same as the number of arguments expected by function
                    method.handlers.push(args);
                }
            }
            return method;
        }
        return method;
    }
    generic.VARGS = Infinity;
    generic.VTYPE = undefined;
    
    if (typeof(module) !== 'undefined' && module.exports) {
        module.exports  = generic;
    } else if (typeof define === 'function' && define.amd) {
        // Publish as AMD module
        define(function() {return generic;});
    } else {
        // Publish as global (in browsers)
        var _previousRoot = _global.generic;
        // **`noConflict()` - (browser only) to reset global 'generic' var**
        generic.noConflict = function() {
            _global.generic = _previousRoot;
            return generic;
        };
        _global.generic = generic;
    }
}).call(this)