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.

Khamidulla
  • 2,927
  • 6
  • 35
  • 59

7 Answers7

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.

plalx
  • 42,889
  • 6
  • 74
  • 90
3

You could make them functions, like this:

header:{
        title:undefined,
        type: function(){
           return Parameters.modal_window.type.normal.header;
        }
    }
pax162
  • 4,735
  • 2
  • 22
  • 28
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.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • That predates the advent of getter and setter definitions, doesn't it? – Pointy Nov 02 '13 at 17:41
  • @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? – jfriend00 Nov 02 '13 at 17:48
  • 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 Nov 02 '13 at 17:51
  • @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. – jfriend00 Nov 02 '13 at 17:52
  • Why the downvote? What's not accurate in this post and comments? – jfriend00 Nov 02 '13 at 18:18
  • @Pointy - added an additional option to the answer based on our comment discussion. – jfriend00 Nov 02 '13 at 18:24
  • @jfriend00 Haven't downvoted, but I could say that the words aren't accurate "read-only properties that can only by changed...". By definition a *read-only* property can never be changed. It certainly doesn't deserve a downvote however... – plalx Nov 02 '13 at 18:27
  • @plalx - that's what the OP is asking for. A property that is read-only to the outside world, but can be modified only by methods they define. I'll tweak the wording a bit. – jfriend00 Nov 02 '13 at 18:29
  • @jfriend00 I did not down voted your answer if it makes sense for you. However through the answers I saw methods to make it possible and statement like "Javascript doesn't have the ability to define read-only properties that can only be changed by certain methods." dose not make sense. – Khamidulla Nov 02 '13 at 18:33
  • @antindexer - OK, wording removed/changed again. I was hoping the focus could be more on the content of the actual suggestions. – jfriend00 Nov 02 '13 at 18:39
  • @jfriend00 you removed unnecessary party it is good. However you down voted my question. Thank you. – Khamidulla Nov 02 '13 at 18:51
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 }).

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I think you misunderstood the question like I first did. – plalx Nov 02 '13 at 18:11
  • @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. – Amadan Nov 02 '13 at 18:12
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.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • What do you think of enforcing true privacy vs using a naming convention such as `_myPrivateMember`? – plalx Nov 02 '13 at 18:25
  • Well this sample code gets pretty close to true privacy. I think naming conventions are not aesthetically satisfying ;) – Pointy Nov 03 '13 at 03:40
  • 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*. – plalx Nov 03 '13 at 03:55
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.

Ryan
  • 5,644
  • 3
  • 38
  • 66
  • 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 Nov 02 '13 at 17:43
  • @Pointy Well, he didn't say he wanted a private property, he said he wanted a readOnly property. Which this accomplishes. – Ryan Nov 02 '13 at 17:44
  • The last sentence talks about the properties being changeable but only via some explicit APIs. – Pointy Nov 02 '13 at 17:46
  • 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. – Khamidulla Nov 02 '13 at 17:47
  • @antindexer Right. You want control over how the property can be changed; for example, to check whether it's numeric or something like that. – Pointy Nov 02 '13 at 17:48
  • @antindexer that's what this will accomplish, and has a little more support then `object.defineProperty`, but if you don't need lots of support, then you should go with @plalx answer. – Ryan Nov 02 '13 at 17:49
  • @ryan it needs to be accessible and writable from other functions on the object. – Pointy Nov 02 '13 at 17:53
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
};
}();
Kalpers
  • 658
  • 5
  • 11