      1 var Proxy = require("node-proxy"), object = require("./object"), merge = object.merge, functions = require("./functions"), misc = require("./misc");
      2
      3 var comb = exports;
      4 var handlerMaker = function (obj) {
      5     return {
      6         getOwnPropertyDescriptor:function (name) {
      7             var desc = Object.getOwnPropertyDescriptor(obj, name);
      8             // a trapping proxy's properties must always be configurable
      9             if (desc !== undefined) {
     10                 desc.configurable = true;
     11             }
     12             return desc;
     13         },
     14         getPropertyDescriptor:function (name) {
     15             var desc = Object.getPropertyDescriptor(obj, name); // not in ES5
     16             // a trapping proxy's properties must always be configurable
     17             if (desc !== undefined) {
     18                 desc.configurable = true;
     19             }
     20             return desc;
     21         },
     22         getOwnPropertyNames:function () {
     23             return Object.getOwnPropertyNames(obj);
     24         },
     25         getPropertyNames:function () {
     26             return Object.getPropertyNames(obj);                // not in ES5
     27         },
     28         defineProperty:function (name, desc) {
     29             Object.defineProperty(obj, name, desc);
     30         },
     31         delete:function (name) {
     32             return delete obj[name];
     33         },
     34         fix:function () {
     35             if (Object.isFrozen(obj)) {
     36                 var result = {};
     37                 Object.getOwnPropertyNames(obj).forEach(function (name) {
     38                     result[name] = Object.getOwnPropertyDescriptor(obj, name);
     39                 });
     40                 return result;
     41             }
     42             // As long as obj is not frozen, the proxy won't allow itself to be fixed
     43             return undefined; // will cause a TypeError to be thrown
     44         },
     45
     46         has:function (name) {
     47             return name in obj;
     48         },
     49         hasOwn:function (name) {
     50             return ({}).hasOwnProperty.call(obj, name);
     51         },
     52         get:function (receiver, name) {
     53             return obj[name];
     54         },
     55         set:function (receiver, name, val) {
     56             obj[name] = val;
     57             return true;
     58         }, // bad behavior when set fails in non-strict mode
     59         enumerate:function () {
     60             var result = [];
     61             for (var name in obj) {
     62                 result.push(name);
     63             }
     64             return result;
     65         },
     66         keys:function () {
     67             return Object.keys(obj);
     68         }
     69
     70     };
     71 };
     72
     73 var noSuchMethodHandler = function (obj, handler) {
     74     return {
     75         get:function (receiver, name) {
     76             return obj[name] ? obj[name] : handler.call(obj, name);
     77         }
     78     }
     79 };
     80
     81 /**
     82  * Creates a proxy for an object.
     83  * @param obj object to proxy
     84  * @param {Object} opts object with methods to define on the handler.
     85  * @memberOf comb
     86  */
     87 comb.handlerProxy = function (obj, opts) {
     88     opts = opts || {};
     89     return  Proxy.create(merge(handlerMaker(obj), opts));
     90 };
     91
     92 /**
     93  * Creates a method missing proxy for an object.
     94  * <b>NOTE:</b> This method does not gurantee that the property will be used as a function call.
     95  *
     96  * @example
     97  *
     98  *  var x = {hello:function () {return "hello"}, world:"world"};
     99  *  var xHandler = comb.methodMissing(x, function (m) {
    100  *              //you can do more interesting stuff in here!
    101  *               return function () {
    102  *                   return [m].concat(comb.argsToArray(arguments));
    103  *               }
    104  *   });
    105  *  xHandler.hello(); //=> "hello"
    106  *  xHandler.world //=> "world"
    107  *  xHandler.someMethod("hello", "world"); //=> [ 'someMethod', 'hello', 'world' ]
    108  *
    109  * @param {Object} obj object to wrap with a method missing proxy
    110  * @param {Function} handler handle to call when a property is missing
    111  * @param {Object} opts prototype to assign to the proxy
    112  * @memberOf comb
    113  * @returns {Proxy} a proxy
    114  */
    115 comb.methodMissing = function (obj, handler, proto) {
    116     proto = proto || {};
    117     return  Proxy.create(merge(handlerMaker(obj), noSuchMethodHandler(obj, handler)), object.isHash(proto) ? proto : proto.prototype);
    118 };
    119
    120 /**
    121  *  Determines if the object is a proxy or not.
    122  *
    123  * @param {Anything} obj object to test
    124  * @memberOf comb
    125  * @returns {Boolean} true if it is a proxy false otherwise
    126  */
    127 comb.isProxy = function (obj) {
    128     var undef;
    129     return obj !== undef && obj !== null && Proxy.isProxy(obj);
    130 }
    131
    132 /**
    133  * Creates a function proxy for an object.
    134  *
    135  * @example
    136  *
    137  * //create an object that can use properties or as a function through the new operator
    138  * var MyObject = comb.define(null, {
    139  *     instance : {
    140  *         hello : "hello",
    141  *         constructor : function(){
    142  *             this.args = comb.argsToArray(arguments);
    143  *         }
    144  *     }
    145  * });
    146  *
    147  * //NOTE: this will not work properly for native objects like Date.
    148  * var createNewMyObject = function(){
    149  *    try {
    150  *      p = new MyObject();
    151  *     } catch (ignore) {
    152  *          //ignore the error because its probably from missing arguments
    153  *     }
    154  *     //Now lets take care of arguments supplied!!!
    155  *     return MyObject.apply(p, comb.argsToArray(arguments));
    156  * };
    157  *
    158  * //This example creates an object with a world property but its not a function!
    159  * var handle = comb.createFunctionWrapper({world : "world"}, createNewMyObject, createNewMyObject);
    160  *
    161  * handle.world //=> "world"
    162  * var a = handle(1);
    163  * a.hello;  //=>"hello"
    164  * a.args; //=> [1];
    165  * a = new handle(1,2);
    166  * a.hello; //=>"hello"
    167  * a.args; //=> [1,2];
    168  * @param obj the object to proxy
    169  * @param {Function} handler the handler to call when the object is used as a function
    170  * @param {Function} constructTrap the funciton to use when using new on the object
    171  * @param {Object} opts the prototype of the object.
    172  * @memberOf comb
    173  */
    174 comb.createFunctionWrapper = function (obj, handler, constructTrap, opts) {
    175     var args = misc.argsToArray(arguments), ret;
    176     if (args.length != 4) {
    177         opts = object.isHash(args[args.length - 1]) ? args.pop() : null;
    178         constructTrap = functions.isFunction(args[args.length - 1]) ? args.pop() : null;
    179         handler = functions.isFunction(args[args.length - 1]) ? args.pop() : null;
    180     }
    181     if (misc.isUndefined(obj)) throw new Error("obj required when using create function wrapper");
    182     if (functions.isFunction(constructTrap) && !functions.isFunction(handler)) {
    183         ret = Proxy.createFunction(handlerMaker(obj), constructTrap);
    184     } else {
    185         ret = Proxy.createFunction(handlerMaker(obj), handler, constructTrap);
    186     }
    187
    188
    189     if (opts) {
    190         Proxy.setPrototype(ret, object.isHash(opts) ? opts : opts.prototype);
    191     }
    192     return ret;
    193 };
    194
    195
    196 comb.Proxy = Proxy;