wrapper.js 4.59 KB
Newer Older
Ketan's avatar
Ketan committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

/**
 * Utility methods used to wrap and extend functions.
 *
 * @example Usage of a 'wrap' method with arguments delegation.
 *      var multiply = function (a, b) {
 *          return a * b;
 *      };
 *
 *      multiply = module.wrap(multiply, function (orig) {
 *          return 'Result is: ' + orig();
 *      });
 *
 *      multiply(2, 2);
 *      => 'Result is: 4'
 *
 * @example Usage of 'wrapSuper' method.
 *      var multiply = function (a, b) {
 *         return a * b;
 *      };
 *
 *      var obj = {
 *          multiply: module.wrapSuper(multiply, function () {
 *              return 'Result is: ' + this._super();
 *          });
 *      };
 *
 *      obj.multiply(2, 2);
 *      => 'Result is: 4'
 */
define([
    'underscore'
], function (_) {
    'use strict';

    /**
     * Checks if string has a '_super' substring.
     */
    var superReg = /\b_super\b/;

    return {

        /**
         * Wraps target function with a specified wrapper, which will receive
         * reference to the original function as a first argument.
         *
         * @param {Function} target - Function to be wrapped.
         * @param {Function} wrapper - Wrapper function.
         * @returns {Function} Wrapper function.
         */
        wrap: function (target, wrapper) {
            if (!_.isFunction(target) || !_.isFunction(wrapper)) {
                return wrapper;
            }

            return function () {
                var args    = _.toArray(arguments),
                    ctx     = this,
                    _super;

                /**
                 * Function that will be passed to the wrapper.
                 * If no arguments will be passed to it, then the original
                 * function will be called with an arguments of a wrapper function.
                 */
                _super = function () {
                    var superArgs = arguments.length ? arguments : args.slice(1);

                    return target.apply(ctx, superArgs);
                };

                args.unshift(_super);

                return wrapper.apply(ctx, args);
            };
        },

        /**
         * Wraps the incoming function to implement support of the '_super' method.
         *
         * @param {Function} target - Function to be wrapped.
         * @param {Function} wrapper - Wrapper function.
         * @returns {Function} Wrapped function.
         */
        wrapSuper: function (target, wrapper) {
            if (!this.hasSuper(wrapper) || !_.isFunction(target)) {
                return wrapper;
            }

            return function () {
                var _super  = this._super,
                    args    = arguments,
                    result;

                /**
                 * Temporary define '_super' method which
                 * contains call to the original function.
                 */
                this._super = function () {
                    var superArgs = arguments.length ? arguments : args;

                    return target.apply(this, superArgs);
                };

                result = wrapper.apply(this, args);

                this._super = _super;

                return result;
            };
        },

        /**
         * Checks wether the incoming method contains calls of the '_super' method.
         *
         * @param {Function} fn - Function to be checked.
         * @returns {Boolean}
         */
        hasSuper: function (fn) {
            return _.isFunction(fn) && superReg.test(fn);
        },

        /**
         * Extends target object with provided extenders.
         * If property in target and extender objects is a function,
         * then it will be wrapped using 'wrap' method.
         *
         * @param {Object} target - Object to be extended.
         * @param {...Object} extenders - Multiple extenders objects.
         * @returns {Object} Modified target object.
         */
        extend: function (target) {
            var extenders = _.toArray(arguments).slice(1),
                iterator = this._extend.bind(this, target);

            extenders.forEach(iterator);

            return target;
        },

        /**
         * Same as the 'extend' method, but operates only on one extender object.
         *
         * @private
         * @param {Object} target
         * @param {Object} extender
         */
        _extend: function (target, extender) {
            _.each(extender, function (value, key) {
                target[key] = this.wrap(target[key], extender[key]);
            }, this);
        }
    };
});