Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript Sandbox Pattern example implementation

On Page 101 of Stoyan Stefanov's great book "JavaScript Patterns" he explains the sandbox pattern. I liked his book much but I really missed some real life examples here and then to better understand what he talks about. Like the sandbox pattern!

I'm looking for a real life working implementation, like a copy&paste starting point, just a simple example that will work to fully understand it.

Is there any?

like image 860
steros Avatar asked Jun 25 '12 10:06

steros


3 Answers

I've simplified Stoyan's example in an attempt to make it easier to understand what's going on. I've also commented it more thoroughly.

/*First define the modules of the sandbox.  These will be defined 
as properties on the constructor function because this is a 
convenient place to keep them.*/

Sandbox.modules = {};

Sandbox.modules.returnNumbers = function(MYAPP) {
    MYAPP.return100 = function() {return 100;};
};

Sandbox.modules.returnLetters = function(MYAPP) {
    MYAPP.returnABC = function() {return "ABC";};
};


function Sandbox() {

    /* Because Sandbox is a constructor, an new object is automatically 
    created.  Because we're in the constructor, we refer to this new object 
    as 'this'. 

    A constructor would typically be used as part of an assignment, e.g. 
    myObject = new Sandbox().  

    However, it's also legitimate javascript to use a constructor without 
    the assignment by just writing new Sandbox() with no assignment.  The 
    constructor does return an object, it's just that it doesn't get 
    assigned to anything so  is discarded.

    We're going to add functionality (methods) to the 'this' object, but 
    rather than returning it, we will pass it to the callback function, so 
    the methods can be used immediately.
    */

    var args = Array.prototype.slice.call(arguments);  //Put the arguments 
    //of the call to the Sandbox constructor in an array called args.

    var callback = args.pop(); //The last argument is the callback
    var requiredmodules = args;  //The remaining arguments are the require
    // modules

    //For each of the modules in 'requiredmodules', add the module's 
    //methods to 'this'
    for (i=0; i< requiredmodules.length; i++) {
        Sandbox.modules[requiredmodules[i]](this);
    }


    //'this' now has methods returnNumbers and returnLetters

    //Call the callback.  In the example below, 'this' will be called 
    //MYAPP, which within the callback will have all the methods from 
    //the required modules.

    callback(this);

}



//Finally here is an example of usage

new Sandbox('returnNumbers', 'returnLetters', function (MYAPP) {

    console.log(MYAPP.return100());
    console.log(MYAPP.returnABC());
});
like image 133
RobinL Avatar answered Oct 20 '22 23:10

RobinL


Stoyan Stefanov mentions in the same chapter that YUI version 3 implements the Sandbox pattern. The YUI add method (API) registers modules and the use method (API) loads the specified ones in the sandbox instance. There are links to the source js file in the API documentation. Virtually all YUI code examples use this pattern to work with the YUI library. Defining a module is rarely needed - YUI has many core ones and there is a page for custom modules added by the community.

like image 3
Dimitar Bonev Avatar answered Oct 20 '22 23:10

Dimitar Bonev


So I tried and came up with this solution:

function Sandbox() {
    // turning arguments into an array
    var args = Array.prototype.slice.call(arguments),
            // the last argument is the callback
            callback = args.pop(),
            // modules can be passed as an array or as individual parameters
            modules = (args[0] && "string" === typeof args[0]) ? args : args[0],
            i;

    // make sure the function is called
    // as a constructor
    if (!(this instanceof Sandbox)) {
        return new Sandbox(modules, callback);
    }

    // add properties to 'this' as needed:
    this.a = 1;
    this.b = 2;

    // now add modules to the core 'this' object
    // no modules or "*" both mean "use all modules"
    if (!modules || '*' === modules) {
        modules = [];
        for (i in Sandbox.modules) {
            if (Sandbox.modules.hasOwnProperty(i)) {
                modules.push(i);
            }
        }
    }

    // initialize the required modules
    for (i = 0; i < modules.length; i += 1) {
        Sandbox.modules[modules[i]](this);
    }

    // call the callback
    callback(this);

    // any prototype properties as needed
    Sandbox.prototype = {
        name: "Sandbox",
        version: "1.0",
        getName: function() {
            return this.name;
        }
    }
};

Sandbox.modules = {};

Sandbox.modules.color = function (box) {
    // private
    var initialColor = $('#main').css('color');    

    // set a red color
    box.setMainRed = function() {
        $('#main').css('color','red');
        return false;
    },

    // get the current color
    box.getInitialColor = function () {
        return initialColor;
    };
}

// another module
Sandbox.modules.style = function (box) {
    // set a red color
    box.setStyle = function() {
        $('#main').css('font-style','italic');
        return false;
    };
}

// page ready
$.ready(
    Sandbox(['color', 'style'], function (box) {
        console.log(box);
        box.setMainRed();
        box.setStyle();
        console.log('try access initialColor: ', box.initialColor);
        console.log('get initial color: ', box.getInitialColor());
    })
);

But I am really unsure weather this is what I should be doing. Especially adding the "modules" is somewhat confusing. Also earlier in the book he uses the namespace-pattern for this task, but not here. Why? Can't you do it here too? But I failed to combine these two patterns.

Namespace pattern example inspired by the book:

var APP = APP || {};

// namespace function
APP.namespace = function (nsString) {
    var parts = nsString.split('.'),
            parent = APP,
            i;

    // strip redundant leading global
    if ("APP" === parts[0]) {
        parts = parts.slice(1);
    }

    for (i = 0; i < parts.length; i += 1) {
        // create a property if it doesn't exist
        if ("undefined" === typeof parent[parts[i]]) {
            parent[parts[i]] = {};
        }
        parent = parent[parts[i]];
    }
    return parent;
}

// constructors
APP.namespace('modules.Color');

// immediate function
APP.modules.Color = (function () {
    var currentColor = $('#main').css('color'),    
    // set a red color
    setMainRed = function() {
        $('#main').css('color','red');
        return false;
    },
    // get the current color
    getCurrentColor = function () {
        return currentColor;
    };

    // revealing module pattern
    return {
        setMainRed: setMainRed,
        getCurrentColor: getCurrentColor
    };
}());

var doSomething = function () {
    var color = APP.modules.Color;

    color.setMainRed();
    console.log(color.currentColor);
    console.log(color.getCurrentColor());

    return false;
}

// page ready
$.ready(
    doSomething()
);
like image 1
steros Avatar answered Oct 20 '22 23:10

steros