Javascript Revealing Module pattern - exposing initialization variables after function has returned
Asked Answered
A

3

6

I’ve been using the Javascript Revealing Module pattern a lot and I like the clear separation it gives between the public interface and the internals. However I keep running into a situation which makes me wonder if my overall usage pattern is correct, or if I should use some variant of the pattern.

The problem is when something passed into the init function of a module and stored privately for internal use also needs to be publicly exposed, either in a Knockout binding expression or some other module. The return statement of the module executes immediately and sometime later the init function is called, typically being passed some dynamic parameters such as Ajax URLs or raw JSON rendered in a script block within a Razor view. Because the module's return statement just returns a copy of the private variable rather than a reference, my setting that private variable in the init function can’t change what has already been returned.

var productsModule = function() {

    var urls;

    var init = function(ajaxUrls) {
        urls = ajaxUrls;
    };

    return {
        init: init,
        urls: urls,
        getUrls: function() { return urls; }
    };

}();

var customersModule = function() {

    var doSomethingWithProductsModule = function() {
        alert(productsModule.urls); // undefined
        alert(productsModule.getUrls()); // object
    } ;

    return {
        doSomethingWithProductsModule: doSomethingWithProductsModule
    };

}();

var urls = {
    getProduct: '/Product/'
};

productsModule.init(urls);

customersModule.doSomethingWithProductsModule();

My workaround is just to wrap objects such as “urls” in a function and then access them via productsModule.getUrls(). However that becomes very messy, especially if the variable is a Knockout observable which is itself a function, and hence to evaluate it I need to use double brackets like productsModule.getMyObservable()().

Is there a nicer way to get at the up-to-date internal values using something which at least approximates the revealing module pattern?

Ashkhabad answered 26/10, 2012 at 3:8 Comment(0)
A
2

Although I don't completely like the idea of having to iterate through all the possible levels of my objects to merge them like that, El Yobo's answer got me thinking about making the result of the module function itself a local variable whose properties I could update.

var productsModule = function() {

var urls;

var init = function(ajaxUrls) {
urls = ajaxUrls;
result.urls = urls;
};

var result = {
init: init,
urls: urls
};

return result;

}();


// Before init
alert(productsModule.urls); // undefined

var urls = {
getProduct: '/Product/'
};

productsModule.init(urls);

alert(productsModule.urls.getProduct); // /Product/
Ashkhabad answered 26/10, 2012 at 7:19 Comment(3)
If you're only going to be passing in simple objects, then the overhead to merge is minimal, but this does seem better.Giulia
Cheeky of me to accept my own answer, but thanks for pointing me in the right direction.Ashkhabad
@TomHall: Accepting your own answer is fine. Writing a question in an answer is not.Clairvoyant
G
2

Basic types are passed by value while objects are passed by reference; you could exploit this so that instead over overwriting urls in productsModule you just update it. This way the reference returned in the initial module invocation remains up to date. I've updated your code to show what I mean.

var productsModule = function() {

var urls = {};

var init = function(ajaxUrls) {
    // Merge properties into the original object instead; more robust approach
    // may be needed
    for ( name in ajaxUrls ) {
        if (ajaxUrls.hasOwnProperty(name)) {
            urls[name] = ajaxUrls[name];
        }
    }
};

return {
    init: init,
    urls: urls,
    getUrls: function() { return urls; }
};

}();

var customersModule = function() {

var doSomethingWithProductsModule = function() {
    alert(productsModule.urls); // undefined
    alert(productsModule.getUrls()); // object
} ;   

return {
    doSomethingWithProductsModule: doSomethingWithProductsModule
};

}();

var urls = {
    getProduct: '/Product/'
};

productsModule.init(urls);

customersModule.doSomethingWithProductsModule();
Giulia answered 26/10, 2012 at 3:26 Comment(5)
objects are passed by reference, would it be better to say all are passed by value, but an object's value is a reference?Lelandleler
Aww, now you're making my head hurt ;) I'm not sure that that's actually saying anything different.Giulia
If it were a reference, would you expect this jsfiddle to output "not a value"? I'm not 100% sure, but I'm sure someone will pop in and give a definite answer.Lelandleler
This question is excellent on this point: #518500 "the item passed in is passed by value. But the item that is passed by value is itself a reference."Ashkhabad
Thanks @TomHall. The behaviour is pretty common; PHP does exactly the same (but has a third option to explicitly pass by reference, which would solve this problem). It is commonly described as passing objects by reference, but apparently that's not quite true either; php.net/manual/en/language.oop5.references.php.Giulia
A
2

Although I don't completely like the idea of having to iterate through all the possible levels of my objects to merge them like that, El Yobo's answer got me thinking about making the result of the module function itself a local variable whose properties I could update.

var productsModule = function() {

var urls;

var init = function(ajaxUrls) {
urls = ajaxUrls;
result.urls = urls;
};

var result = {
init: init,
urls: urls
};

return result;

}();


// Before init
alert(productsModule.urls); // undefined

var urls = {
getProduct: '/Product/'
};

productsModule.init(urls);

alert(productsModule.urls.getProduct); // /Product/
Ashkhabad answered 26/10, 2012 at 7:19 Comment(3)
If you're only going to be passing in simple objects, then the overhead to merge is minimal, but this does seem better.Giulia
Cheeky of me to accept my own answer, but thanks for pointing me in the right direction.Ashkhabad
@TomHall: Accepting your own answer is fine. Writing a question in an answer is not.Clairvoyant
E
1

Why don't you make urls an observable property ?

Look at my example:

http://jsfiddle.net/Razaz/zkXYC/1/

var productsModule = function() {

    var urls=ko.observable();

    var init = function(ajaxUrls) {
        urls(ajaxUrls);
    };

    return {
        init: init,
        urls: urls,
        getUrls: function() { return urls(); }
    };

}();

var customersModule = function() {

    var doSomethingWithProductsModule = function() {
        alert(productsModule.urls()); // undefined
        alert(productsModule.getUrls()); // object   
    };       

    return {
        doSomethingWithProductsModule: doSomethingWithProductsModule
    };

}();

var urls = {
    getProduct: '/Product/'
};

productsModule.init(urls);

customersModule.doSomethingWithProductsModule();​

Greetings.

Elegy answered 26/10, 2012 at 10:22 Comment(1)
In a Knockout.js world that's probably a reasonable option for some properties although it seems a bit like unnecessary overhead (subscriptions) and brackets just to get around the awkward flow of the RMP. Often my module will expose a heavy top-level view model which I don't want to be observable itself, only its properties and those of its child view models. But good thinking nonetheless.Ashkhabad

© 2022 - 2024 — McMap. All rights reserved.