Search code examples
javascriptknockout.jsknockout-mapping-plugin

In Javascript, is there a way to evaluate a variable that may or may not be a function


I have a complex javascript variable that has numerous nested variables inside of it, which each can contain their own nested variables... When I do my knockout mapping sometimes the nested variables are set as objects and other times those exact same variables are functions. So when my data bindings to those nested variables is expecting an object the next time around when the variable is now a function the binding does not update.

The below fiddle gives an idea of what I am seeing:

http://jsfiddle.net/Eves/AUcGM/2/

html:

<p> <span>Name:</span>
 <span data-bind="text: TempData().objectA.Name"></span>

    <button id="update" data-bind="click: Update">Update!</button>
</p>

Javascript:

var ViewModel = function (data) {
    var me = this;
    me.TempData = ko.observable(data);

    me.Update = function () {


        ko.mapping.fromJS(temp2, {}, me);
    };

    return me;
};

var temp1 = {
    objectA: {
        Name: 'temp1.objectA.Name'
    }
};
var temp2 = {
    objectA: function () {
        this.Name = 'temp2.objectA.Name';
        return this;
    }
};
window.viewModel = ko.mapping.fromJS(new ViewModel(temp1));
ko.applyBindings(window.viewModel);

Initially the span text shows "temp1.objectA.Name". However, if you click the update button and the binding switches from the temp1 object to the temp2 object because "objectA" is now a function the data binding never updates. If I were to change the span's data binding to be "TempData().objectA().Name" then temp2 will work fine...but then that breaks temp1.

So the question is:

Is there a way to always evaluate a variable as either an object or function?

I suspect I could use ko.computed to always get the appropriate value regardless of function or object. But that would get really messy considering the complexity of the object I am really dealing with.


Solution

  • I didn't read your entire question but there is a quick answer -

    var returnedValue = ko.unwrap(possibleFunction);
    

    (In older versions of ko this is ko.utils.unwrapObservable)

    A good way that you could use this would be in a custom binding handler to unwrap a value, regardless of whether it is a function or not.

    ko.bindingHandlers.returnAction = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
            var value = ko.unwrap(valueAccessor());
            // now value is a value no matter what the element's value was set as
    
            $(element).keydown(function (e) {
                if (e.which === 13) {
                    value(viewModel);
                }
            });
        }
    };
    

    Edit

    Here is a working fiddle example of passing in various objects - http://jsfiddle.net/5udhU/3/

    That demonstrates passing a string, an observable, a computed, and a function that all evaluate properly.