0

Select/Input Button Object as Value

I was running into issues using a SelectButton. For the options that I passed to the button, I used a string as the label, and an object as the value. This seemed to me to be a perfectly valid use for the value of the option.

Turns out the getValueDisplay method compares the values of each option value until it finds one equal. When using an object, the condition never occurs, and the last option value always gets returned, as it is the last one set in the loop.

It was not entirely clear from the documentation, that an object could not be used as a value. Thought I would post this for anyone running into the same issue.

CODE
 190         getDisplayValue: function (value) {
191 KONtx_automation_log("function","KONtx.control.InputButton","getDisplayValue");
192
193 value = value || this.getValue();
194
195 var label = value;
196 this.getOptions().forEach(function(option){
197 if (option.value == value) label = option.label;
198 });
199
200 return label;
201 }

by
7 Replies
  • If I stringify the object using JSON.stringify, that appears to work fine. It is just a long string, which would be sufficient for comparison.
    0
  • QUOTE (Steve @ Apr 12 2011, 05:16 AM) <{POST_SNAPBACK}>
    It was not entirely clear from the documentation, that an object could not be used as a value. Thought I would post this for anyone running into the same issue.

    Actually, this is the nature of objects. Observe:

    CODE
    ({champions: "Phillies"}) === ({champions: "Phillies"}); //false;


    QUOTE (Steve @ Apr 12 2011, 05:16 AM) <{POST_SNAPBACK}>
    Turns out the getValueDisplay method compares the values of each option value until it finds one equal. When using an object, the condition never occurs, and the last option value always gets returned, as it is the last one set in the loop.

    If an equal match isn't found, it doesn't return the last option value but the parameter you passed to the method.

    Here's a way for you to do what you want:
    CODE
    KONtx.control.SelectButton.implement({
    getDisplayValue: function (value) {
    value = value || this.getValue();
    if (typeof value === "object") {
    value = JSON.stringify(value);
    }
    var label = value;
    this.getOptions().forEach(function(option){
    var val = (typeof option.value === "object") ? JSON.stringify(option.value) : option.value;
    if (val == value) label = option.label;
    });
    return label;
    }
    });
    0
  • QUOTE
    Actually, this is the nature of objects. Observe:

    CODE
    ({champions: "Phillies"}) === ({champions: "Phillies"}); //false;

    Thanks. I understand they are technically different references and therefore not equal, but what confused me was why the value was being used at all for comparison. I would have thought the select button would use some type of internal unique index, apart from any labels or values. Then anything could be used as a value.

    QUOTE
    If an equal match isn't found, it doesn't return the last option value but the parameter you passed to the method.

    It was actually returning the last value. I saw it with my own eyes. :bThanks!
    0
  • QUOTE (Steve @ Apr 15 2011, 03:37 AM) <{POST_SNAPBACK}>
    Thanks. I understand they are technically different references and therefore not equal, but what confused me was why the value was being used at all for comparison. I would have thought the select button would use some type of internal unique index, apart from any labels or values. Then anything could be used as a value.

    Ah, I see. Yes, that would make sense.
    0
  • QUOTE (Steve @ Apr 15 2011, 05:02 AM) <{POST_SNAPBACK}>
    Turns out it wasn't quite that easy. The setValue and getValue methods also need to be modified to allow an object as a value.

    Yes, you're right. I was approaching this from the perspective of calling the public getDisplayValue api. As is, it's definitely an over-simplification and something more robust needs to be in place like your idea. My take is at the bottom of this post.

    QUOTE (Steve @ Apr 15 2011, 05:02 AM) <{POST_SNAPBACK}>
    What I did, was modify (set|get)Value to leave the value as is. If it's an object then leave it as an object. Only when a comparison needs to be made do I convert everything to a String, or Stringify if it is an object. This appears to be working fine so far. Can you verify the code above appears safe?

    I agree. If I hand a value to the control, I don't want it "cast" and stored as another value unbeknownst to me. My take is like yours except for that I added a helper method that would do the conversion when a comparison is necessary, and I also support the values undefined and null.

    CODE
    KONtx.control.SelectButton.implement({
    getValue : function() {
    return this.stringify(this._value) || "";
    },
    stringify : function(value) {
    switch ($type(value)) {
    case "object":
    case "array":
    return JSON.stringify(value);
    default:
    return String(value) || "";
    }
    },
    setValue : function(value) {
    var firstblush = (this._value == null),
    stringvalue = value;

    // Same value
    if ((this._value||"") == stringvalue) return "";

    this._value = value;
    this.fire(firstblush ? 'onValueInitialized' : 'onValueChanged', {
    value : value
    });

    return value;
    },
    getDisplayValue: function (value) {
    /*
    - this is a total hack, using function decompilation to determine if the getDisplayValue method was called from within a view;
    - it should probably work as per the docs a SelectButton control must be bound to a view and accessible within the this.controls object, but still...;

    - the reason this is necessary is b/c of the problem that getDisplayValue is called internally as well as externally, and when it's called internally there are no function parameters as the value to operate on is in this._value;
    - however, to be able to pass undefined or null from the public getDisplayValue api, it's problematic as the api expects to always receive a truthy value as a parameter when called this way;
    - as is, when a falsy parameter is received from the public api, the framework will use the value in the internal this._value, which in most cases will be the value assigned in the constructor when the control was instantiated;
    - so, again, it's necessary to know from which context the api is called;
    - also, i can't just override the getDisplayValue api as the option.value property in the forEach iterator needs to be stringified;

    - of course, if you don't care about supporting undefined and null than it can be removed;
    */

    var label = value = (/this.controls/.test(arguments.callee.caller)) ? this.stringify(value) : this.getValue();

    this.getOptions().forEach(function(option){
    if (this.stringify(option.value) == value) label = option.label;
    }.bindTo(this));
    return label;
    }
    });
    0
  • QUOTE
    My take is like yours except for that I added a helper method that would do the conversion when a comparison is necessary

    Good point....much cleaner.

    QUOTE
    ...I also support the values undefined and null.

    Luckily we don't need to support undefined and null. We will always have a valid object reference.

    Thanks for taking the time to look into this.
    0
  • QUOTE (Benjamin Toll @ Apr 14 2011, 03:19 PM) <{POST_SNAPBACK}>
    Here's a way for you to do what you want:
    CODE
    KONtx.control.SelectButton.implement({
    getDisplayValue: function (value) {
    value = value || this.getValue();
    if (typeof value === "object") {
    value = JSON.stringify(value);
    }
    var label = value;
    this.getOptions().forEach(function(option){
    var val = (typeof option.value === "object") ? JSON.stringify(option.value) : option.value;
    if (val == value) label = option.label;
    });
    return label;
    }
    });


    Turns out it wasn't quite that easy. The setValue and getValue methods also need to be modified to allow an object as a value.

    The original getValue method converts the current value to a String. When a value is an object, we get the string '[Object object]'.

    CODE
    setValue: function (value) {
    var firstblush = this._value == null,
    stringvalue = value == null ? "" : String(value);

    if ((this._value||"") == stringvalue) return "";

    // this._value is set to the string value here, giving us 'Object object]'
    this._value = stringvalue;
    this.fire( firstblush ? 'onValueInitialized' : 'onValueChanged', { value: stringvalue } );
    return this.getValue();
    }


    Then getValue also converts the value to a String as well, so still the same issue:

    CODE
    getValue: function () {
    return String(this._value||'');
    }


    What I did, was modify (set|get)Value to leave the value as is. If it's an object then leave it as an object. Only when a comparison needs to be made do I convert everything to a String, or Stringify if it is an object. This appears to be working fine so far. Can you verify the code above appears safe?

    CODE
    KONtx.control.SelectButton.implement({
    getValue : function() {
    return this._value || '';
    },

    setValue : function(value) {
    var firstblush = (this._value == null);
    var stringvalue = (typeof value === "object" ? JSON.stringify(value) : String(value));

    // Same value
    if ((this._value||"") == stringvalue) return "";

    this._value = value;
    this.fire(firstblush ? 'onValueInitialized' : 'onValueChanged', {
    value : value
    });

    return value;
    },

    getDisplayValue : function(value) {
    value = value || this.getValue();

    if (typeof value === "object") {
    value = JSON.stringify(value);
    }

    var label = value;
    this.getOptions().forEach(function(option) {
    var val = (typeof option.value === "object") ? JSON.stringify(option.value) : option.value;
    if (val == value) {
    label = option.label;
    }
    });

    return label;
    }
    }
    0

Recent Posts

in General - Yahoo! TV Widgets