Example: Read-Only and Write-Once Attributes

Attributes can be configured to be readOnly, stopping them from being modified by the end user, or writeOnce allowing them to be set by the end user, but only once. This example demonstrates how to setup attributes for your class as readOnly or writeOnce attributes, and shows how their behavior differs when the end user attempts to set their values.

Construct o1, setting initial values for both foo and bar in the constructor:

Try setting values again, after they've been set once:

Call a MyClass method, which sets foo internally (using _set):

ReadOnly And WriteOnce

Attribute supports the ability to configure attributes to be readOnly or writeOnce. readOnly attributes cannot be set by the end user, either through initial values passed to the constructor, or by invoking the set method. writeOnce attributes on the other hand, can be set by the user, but only once, either during initialization or through a call to set. Once a value is established for a writeOnce attribute, it cannot be reset to another value by the user.

Configuring ReadOnly And WriteOnce Attributes

This example sets up a custom class, MyClass, with two attributes, foo and bar. foo is configured to be a readOnly attribute, and bar is configured to be a writeOnce attribute:

// Setup a custom class with attribute support
function MyClass(cfg) {

    // Attribute configuration
    var attrs = {
        "foo" : {
            value: "Default Foo",
            readOnly: true
        },

        "bar" : {
            value: "Default Bar",
            writeOnce: true
        }
    }

    this.addAttrs(attrs, cfg);
}

Attempting To Set Values

We first attempt to set values for both attributes in the constructor (used to intialize the attributes) and see that only bar, the writeOnce attribute, gets set to the user provided value:

var fooVal = Y.one("#writeInitial .fooVal").get("value");
var barVal = Y.one("#writeInitial .barVal").get("value");

o1 = new MyClass({
    foo: fooVal,
    bar: barVal
});

displayValues(o1, "Attempt to set initial values in constructor", 
                "#writeInitial .example-out");

We then attempt to set values for both attributes again, using set, and see that niether of the values are modified:

var fooVal = Y.one("#writeAgain .fooVal").get("value");
var barVal = Y.one("#writeAgain .barVal").get("value");

// Attempt to reset values:
o1.set("foo", fooVal);
o1.set("bar", barVal);

displayValues(o1, "Attempt to set values again, using set", 
                "#writeAgain .example-out");

Setting The State Of ReadOnly Values Internally

Although the user cannot update the value of readOnly attributes, it maybe neccessary for the host object to update it's value internally. The example shows how this can be done, using the private _set property on the host:

MyClass.prototype.doSomething = function(val) {
    // ... Do something which requires
    // MyClass to change the value
    // of foo ...

    // Host code can reset value of 
    // readOnly attributes interally,
    // by working with the private _set
    // property

    this._set("foo", val);
};

...

var val = Y.one("#writeInternally .fooVal").get("value");

// Call method, which sets foo internally...
o1.doSomething(val);

displayValues(o1, "Set value of foo (readOnly) interally", 
                "#writeInternally .example-out");

Complete Example Source

<form id="writeInitial" action="#">
    <p>Construct o1, setting initial values for both foo and bar in the constructor: </p>
    <label>foo: <input type="text" value="Initial Foo" class="fooVal"></label>
    <label>bar: <input type="text" value="Initial Bar" class="barVal"></label>
    <button type="submit" class="do">Initial Values</button>
    <div class="example-out"></div>
</form>
<form id="writeAgain" action="#">
    <p>Try setting values again, after they've been set once: </p>
    <label>foo: <input type="text" value="Set Foo Again" class="fooVal"></label>
    <label>bar: <input type="text" value="Set Bar Again" class="barVal"></label>
    <button type="submit" class="do">Set Again</button>
    <div class="example-out"></div>
</form>
<form id="writeInternally" action="#">
    <p>Call a MyClass method, which sets foo internally (using _set): </p>
    <label>foo: <input type="text" value="Set Foo Internally" class="fooVal"></label>
    <button type="submit" class="do">Set Internal</button>
    <div class="example-out"></div>
</form>

<script type="text/javascript">
YUI().use("node", "attribute", "escape", function(Y) {

    // Setup a custom class with attribute support
    function MyClass(cfg) {

        var attrs = {
            "foo" : {
                value: "Default Foo",
                readOnly: true
            },
     
            "bar" : {
                value: "Default Bar",
                writeOnce: true
            }
        }

        this.addAttrs(attrs, cfg);
    }

    MyClass.prototype.doSomething = function(val) {
        // ... Do something which requires
        // MyClass to change the value
        // of foo ...

        // Host code can reset value of 
        // readOnly attributes interally,
        // by working with the private _set
        // property

        this._set("foo", val);
    };

    Y.augment(MyClass, Y.Attribute);

    function displayValues(o, title, node) {
        var str = 
            '<div class="myclass"><div class="myclass-title">' 
            + title + 
            '</div><ul class="myclass-attrs"><li>foo (readOnly): ' 
            + Y.Escape.html(o.get("foo")) 
            + '</li><li>bar (writeOnce): '
            + Y.Escape.html(o.get("bar"))
            + '</li></ul></div>';

        Y.one(node).set("innerHTML", str);
    }

    var o1;

    Y.on("submit", function(e) {

        e.preventDefault();
    
        var fooVal = Y.one("#writeInitial .fooVal").get("value");
        var barVal = Y.one("#writeInitial .barVal").get("value");

        o1 = new MyClass({
            foo: fooVal,
            bar: barVal
        });

        displayValues(o1, "Attempt to set initial values in constructor", "#writeInitial .example-out");

    }, "#writeInitial");

    Y.on("submit", function(e) {

        e.preventDefault();

        if (o1) {
            var fooVal = Y.one("#writeAgain .fooVal").get("value");
            var barVal = Y.one("#writeAgain .barVal").get("value");

            // Attempt to reset values:
            o1.set("foo", fooVal);
            o1.set("bar", barVal);
    
            displayValues(o1, "Attempt to set values again, using set", "#writeAgain .example-out");
        }

    }, "#writeAgain");

    Y.on("submit", function(e) {

        e.preventDefault();

        if (o1) {
            var val = Y.one("#writeInternally .fooVal").get("value");
            // Call method, which sets foo internally...
            o1.doSomething(val);

            displayValues(o1, "Set the value of foo (readOnly) internally", "#writeInternally .example-out");
        }

    }, "#writeInternally");
});
</script>