0

I have following JavaScript Object Literal Notiation object

var Parameters= {
    modal_window:{
        backdrop:true,
        keyboard:true,
        show:true,
        remote:false,
        type:{
            normal:function(){
                this.footer.button.accept.type='btn btn-primary';
                this.header.type='modal-header';
            },
            success:function(){
                this.footer.button.accept.type='btn btn-success';
                this.header.type='modal-header alert alert-success';
            },
            info:function(){
                this.footer.button.accept.type='btn btn-info';
                this.header.type='modal-header alert alert-info';
            },
            error:function(){
                this.footer.button.accept.type='btn btn-danger';
                this.header.type='modal-header alert alert-error';
            },
            warning:function(){
                this.footer.button.accept.type='btn btn-warning';
                this.header.type='modal-header alert';
            }
        }
    },
    header:{
        title:undefined,
        type:this.window.type.normal.header
    },
    footer:{
        button:
        {
            accept:{
                title:'Accept',
                click:undefined,
                type:undefined
            },
            cancel:{
                title:'Cancel',
                click:undefined
            }
        }
    }
};

Is it possible to make header.type and footer.button.accept.type read only variables which can be changed only through window.type.normal, window.type.success and etc.?

Clarifications: I want to make some clarifications here. My Parameters.header.type should be read only and should have default value. And when user selects for example Parameters.modal_window.type.normal Parameters.header.type must be changed.

12
  • 1
    no, read-only is not possible in javascript. Commented Nov 2, 2013 at 17:29
  • 2
    Read only is possible with Object.defineProperty, or my using functions with closures. Commented Nov 2, 2013 at 17:32
  • 1
    Well I would if your code made sense, but when this.window.type.normal.header is called, this is the global context (window). Commented Nov 2, 2013 at 17:35
  • 1
    And another thing, how is this a prototype design pattern? Commented Nov 2, 2013 at 17:36
  • 1
    @andy I think it actually is possible in modern JavaScript runtimes (with getter/setter support). Commented Nov 2, 2013 at 17:43

7 Answers 7

5

Despite what everyone says, you can create read-only properties in modern browsers that supports Object.defineProperty.

var obj = {};

Object.defineProperty(obj, 'someProp', {
    configurable: false,
    writable: false,
    value: 'initial value'
});

obj.someProp = 'some other value';

console.log(obj.someProp); //initial value

EDIT:

After reading your question again, I understand that you meant true private members or private variables. That can be accomplished by making use of closures and custom getters/setters.

Note: I simplified your object's structure for the sake of the example.

var Parameters = (function () {
    var headerType = 'some value'; //private variable

    return {
        modal_window: {
            type: {
                normal: function () {
                    //custom logic
                    headerType = 'some new value'; //set private variable
                }
            }
        },
        header: {
            get type() { return headerType; } //define a getter only

            //for older browsers, you could just define a normal function
            //which you would have to access like Parameters.header.type()
            //type: function () { return headerType; }
        }
    };

})();

var header = Parameters.header;

console.log(header.type); //some value
header.type = 'some other val';
console.log(header.type); //some value
Parameters.modal_window.type.normal();
console.log(header.type); //some new value

Now that we know it is possible to enforce true privacy, I am not sure it's really worth it. Enforcing true privacy complicates the design and reduces testability (depending on the case). An approach that is far popular as well is to simply identify private members using a naming convention such as _myPrivateVar. This clearly indicates the itention and tells the programmers that they should treat that member like a private one.

Sign up to request clarification or add additional context in comments.

5 Comments

This is working perfectly i up voted your answer. I just trying others answers also. So please wait for my decision.
@antindexer Did you forget? ;)
The problem with this is that non-writable (read-only) properties cannot be written by any code. The OP isn't looking for that kind of solution, the way I read it.
@Pointy, The second part of the answer allows the variable to be modified.
@plalx yes, ok I see that. That's more-or-less what my answer did too.
3

You could make them functions, like this:

header:{
        title:undefined,
        type: function(){
           return Parameters.modal_window.type.normal.header;
        }
    }

2 Comments

Well, it doesn't make them read-only, but I think this is in the spirit of what the op is trying to achieve.
@pax162 How i can change it than from other function?
2

You can create a property and set it as non-writable. Your constructor would have to replace the values with the properties. If the variable that the property is returning is captured in a closure and not exposed to anything else, it will be as good as read-only. If it is not changed, you don't even need a closure, just use the value configuration option.

EDIT: As per your demand,

var Properties = function(obj) {
    var makePropRecursive = function(prop) {
        var old_prop = obj[prop];
        delete obj[prop];
        var prop_obj = {};
        for (var attr in old_prop) {
            if (old_prop.hasOwnProperty(attr)) {
                Object.defineProperty(prop_obj, attr, {
                    value: old_prop[attr],
                    writable: false,
                    enumerable: true
                });
            }
        }
        Object.defineProperty(obj, prop, {
            value: prop_obj,
            writable: false,
            enumerable: true
        });
    };
    makePropRecursive('header');
    makePropRecursive('footer');
    return obj;
};

var props = new Properties({
    modal_window:{
        backdrop:true,
        keyboard:true,
        show:true,
        remote:false,
        type:{
            normal:function(){
                this.footer.button.accept.type='btn btn-primary';
                this.header.type='modal-header';
            },
            success:function(){
                this.footer.button.accept.type='btn btn-success';
                this.header.type='modal-header alert alert-success';
            },
            info:function(){
                this.footer.button.accept.type='btn btn-info';
                this.header.type='modal-header alert alert-info';
            },
            error:function(){
                this.footer.button.accept.type='btn btn-danger';
                this.header.type='modal-header alert alert-error';
            },
            warning:function(){
                this.footer.button.accept.type='btn btn-warning';
                this.header.type='modal-header alert';
            }
        }
    },
    header:{
        title:"Whatever",
        type:"Type"
    },
    footer:{
        button:
        {
            accept:{
                title:'Accept',
                click:undefined,
                type:undefined
            },
            cancel:{
                title:'Cancel',
                click:undefined
            }
        }
    }
});

console.log(props.header);
props.header = 17;
props.header.type = 18;
props.header.title = 19;
console.log(props.header);

props.header is unchanged: output shows

Object {title: "Whatever", type: "Type"}
Object {title: "Whatever", type: "Type"} 

It's 3am and the recursive function isn't, so you can only "fix" one level of one object; also, it would be better if the values were copied onto this rather than returning obj; but it should not be too hard to polish it up.

If you need to have the values changeable, you can set up a private copy of the whole object inside the constructor, then make a getter (get: function(name) { return stuff.from.the.original.object }).

2 Comments

I think you misunderstood the question like I first did.
@plalx: Yeah, went with static readonly attributes. Last paragraph briefly explains how to do it for privately writeable readonly attributes, but I really need my sleep :p You got an upvote from me as soon as I read it, if it means anything.
2

If you need to support IE 8 or earlier, you could create an accessor method that retrieves the value and then use a private variable to store the actual data. If you define your methods appropriately, the private variable could be set from them, but not set by the outside world. In IE8, there is no ability to define a read-only property so you'd have to use an accessor instead.

See Crockford's treatise on private member data: http://javascript.crockford.com/private.html for details on how to set up the private data that your accessor could be an interface for.

If you're willing to require IE9 or greater, then you could use a getter via Object.defineProperty() combined with a private variable in a closure. If there was no setter, then it couldn't be set from the outside world, but methods defined within the closure (described in Crockford's article) could still set the value of the private variable. You would have a read-only property that could also be set by a few of your own methods.

11 Comments

That predates the advent of getter and setter definitions, doesn't it?
@Pointy - how do you store something privately and then access it only from certain methods without a closure concept as described in crockford's article?
You can do it with a closure, so of course that part is accurate. With getters and setters you can make it look like there's a property there by making the getter fetch the closure value. The setter can either do nothing, or do some validation, or whatever.
@Pointy - sounds right. Closure plus getter would give you a read-only property that you could modify from other methods defined within the closure. As I understand it, using these features in Object.defineProperty() forces you to IE9 or greater so one would have to be OK with that too.
Why the downvote? What's not accurate in this post and comments?
|
1

How about: Object.freeze()

You can find out more here: MDN Object.freeze

So:

Object.freeze(Parameters.modal_window.header);
...

Then in your your function that you want to be able to set them, you unfreeze them, change them, and re-freeze them.

You absolutely won't be able to change the frozen objects, by mistake anywhere else in your program.

This works in IE9+ chrome, firefox, and safari.

7 Comments

Then nothing can alter the property. That's not quite the same as having a private property that can be set only under the control of a setter function (which might apply validation rules or whatever).
@Pointy Well, he didn't say he wanted a private property, he said he wanted a readOnly property. Which this accomplishes.
The last sentence talks about the properties being changeable but only via some explicit APIs.
I want to make this parameter read only from outside. I do not need private member. It should be accessible from outside to read not for write.
@antindexer Right. You want control over how the property can be changed; for example, to check whether it's numeric or something like that.
|
1

In newer versions of JavaScript, you can define how property access works:

var yourObj = function() {
  var readOnly = "cannot be changed";
  return {
    get readOnly() { return readOnly; },
    set readOnly(v) { return; },

    specialSetter: function(something) {
      if (something == "magic number") {
        readOnly = "oops maybe it can";
      }
    }
  };
}();

Now code can get the value like this:

var theValue = yourObj.readOnly;

without having to make a function call. However, if it tries to change the value:

yourObj.readOnly = "hello world";

then nothing will happen.

Either the setter or any other function can, when it wants to, still update the value that'll be returned when accessing the "readOnly" property. However, any attempt to just set the property directly will do nothing (unless the setter function decides it likes the value).

edit you'd want to make "specialSetter" be read-only probably, though nothing will be able to "break in" to the closure. Also, you might want to use Object.defineProperty to make "readOnly" not be writable, but I don't know whether that'd work out properly.

3 Comments

What do you think of enforcing true privacy vs using a naming convention such as _myPrivateMember?
Well this sample code gets pretty close to true privacy. I think naming conventions are not aesthetically satisfying ;)
Yeah I kinda feel the same, however I have the feeling that most libraries out there wont bother implementing true private members, at least from what i've seen so far. It's too sad we have to make the choice between writing memory-efficient code and real encapsulated code. I am actually just enforcing privacy for private module members, but never for classes.
1

You can use the following revealing module pattern to hide variables and keep them from being changed but this wont stop anyone from changing the accessible "type" function.

Below in the code, the header property was changed to _header and made into a function. The property type was changed to _type and hidden by wrapping a return with an object notation to return "type" as a function instead of the property. Someone can change the type function to anything they want by over writing it, but they can't change the value of _type.

var Parameters = function () {
var _modal_window = function modal_window() {
    var backdrop = true,
    keyboard = true,
    show = true,
    remote = false;
    return {
        type: {
            normal: function () {
                this.footer.button.accept.type = 'btn btn-primary';
                this.header.type = 'modal-header';
            },
            success: function () {
                this.footer.button.accept.type = 'btn btn-success';
                this.header.type = 'modal-header alert alert-success';
            },
            info: function () {
                this.footer.button.accept.type = 'btn btn-info';
                this.header.type = 'modal-header alert alert-info';
            },
            error: function () {
                this.footer.button.accept.type = 'btn btn-danger';
                this.header.type = 'modal-header alert alert-error';
            },
            warning: function () {
                this.footer.button.accept.type = 'btn btn-warning';
                this.header.type = 'modal-header alert';
            }
        }
    };
}();
var _header = function header() {
    var _type = 'This causes error';//this.window.type.normal.header;
    return {
        title: undefined, type: function () { return _type; }
    };
}();
var _footer = function footer() {
    return {
        button:
    {
        accept: {
            title: 'Accept',
            click: undefined,
            type: undefined
        },
        cancel: {
            title: 'Cancel',
            click: undefined
        }
    }
    };
}();
return {
    modal_window: _modal_window,
    header: _header,
    footer: _footer
};
}();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.