How to bind a model to knockout if the model is no

2019-09-15 15:19发布

问题:

I am learning knockout.js. I did one example earlier, which I am trying to modify in two ways:

  1. I organized the code to minimize global scope so there's just one top-level object inside the window object and I put everything including my model in there.

  2. I wanted to see if the text: binding changes the view when the model changes if the model is not an observable. For a finer elaboration of this idea, please see the comments in the HTML code below.

However, when I run this bit of code, I get a ReferenceError: friend is not defined.

I am assuming it is because of the fact that my model object is no longer a top-level member of the window object.

Is it possible to have a model hierarchy like this and have it work with knockout?

1a.html

<html>
    <head>
        <meta charset="utf-8"/>
        <script type='text/javascript' src='knockout-3.4.0.js'></script>
    </head>

    <!-- The purpose of this program is to find out whether just a 
        text: binding also changes the display / view if the model changes.
        Meaning if I explicitly do not create an observable out of a model property
        and use only a text: binding, does the property still behave like an observable?

        To do this, I am using the same code as 1.html and 1.js, 
        except for one change. I am adding another set of textboxes
        in which I will type in a new first name and last name.
        Then, I will click the "Update Name" button
        in whose listener, I will update the model properties friend.firstName
        and friend.lastName.

        Then, I'd like to see if the text box values for txtFirstName and txtLastName
        change.
        -->
    <body>

        <form id = "frm" name = "frm">
            <fieldset>
                <legend>Your friend's basic information:</legend>

                <div>
                    <label for = "FirstName">First Name</label>
                    <input type = "text" name = "FirstName" id = "txtFirstName" data-bind = "value: friend.firstName" />
                </div>

                <div>
                    <label for = "LastName">Last Name</label>
                    <input type = "text" name = "LastName" id = "txtLastName" data-bind = "value: friend.lastName" />
                </div>


                    <fieldset>
                        <legend>New Information:</legend>

                        <div>
                            <label for = "NewFirstName">New Value for First Name</label>
                            <input type = "text" name = "NewFirstName" id = "txtNewFirstName" />
                        </div>

                        <div>
                            <label for = "NewLastName">New Value for Last Name</label>
                            <input type = "text" name = "NewLastName" id = "txtNewLastName" />
                        </div>
                    </fieldset>

                <div>
                    <input type = "button" value = "Update Name" id = "btnUpdateName" name = "Save" />
                </div>
            </fieldset>

        </form>

        <script type='text/javascript' src='1a.js'></script>
    </body>
</html>

1a.js

var ThisDocument = function()
{
    this.model = 
    {
        friend : 
        {
            firstName : 'Sathyaish',
            lastName : 'Chakravarthy'
        }
    };

    this.init = function()
    {
        wire(window, "load", wireElementHandlers);
    };

    function wire(element, eventName, listener)
    {
        var ie = document.all ? true : false;

        if (!element) throw new Error("Element " + element + " not found.");

        if (ie)
        {
            element.attachEvent("on" + eventName, listener);
        }
        else
        {
            element.addEventListener(eventName, listener, false);
        }
    };


    function wireElementHandlers()
    {
        var btnUpdateName = document.getElementById('btnUpdateName');
        wire(btnUpdateName, "click", changeName);

        ko.applyBindings(this.model);
    };

    function changeName(event)
    {
        var firstName = document.getElementById('txtNewFirstName').value;
        var lastName = document.getElementById('txtNewLastName').value;

        this.model.friend.firstName = firstName;
        this.model.friend.lastName = lastName;
    };
};

var thisDocument = new ThisDocument();
thisDocument.init();

Making the Model a Top-Level Object Inside Window

If, however, I make the model a top-level member inside the window object, the code works fine and I get to know the answer to my question described in the HTML comments above and in the bullet point #2 above.

The answer is: no. Without having an observable, a simple text: binding does not update the view when the model changes.

Here is the code with the model as the top-level object:

1a.js

this.model = 
{
    friend : 
    {
        firstName : 'Sathyaish',
        lastName : 'Chakravarthy'
    }
};

var ThisDocument = function()
{
    this.init = function()
    {
        wire(window, "load", wireElementHandlers);
    };

    function wire(element, eventName, listener)
    {
        var ie = document.all ? true : false;

        if (!element) throw new Error("Element " + element + " not found.");

        if (ie)
        {
            element.attachEvent("on" + eventName, listener);
        }
        else
        {
            element.addEventListener(eventName, listener, false);
        }
    };


    function wireElementHandlers()
    {
        var btnUpdateName = document.getElementById('btnUpdateName');
        wire(btnUpdateName, "click", changeName);

        ko.applyBindings(model);
    };

    function changeName(event)
    {
        var firstName = document.getElementById('txtNewFirstName').value;
        var lastName = document.getElementById('txtNewLastName').value;

        model.friend.firstName = firstName;
        model.friend.lastName = lastName;
    };
};

var thisDocument = new ThisDocument();
thisDocument.init();

Therefore, my suspicion is that the this pointer is being messed up.

Is there an overload for the applyBindings method or may be another way if not an overload that allows me to tell knockout to use a particular object as the this parameter when binding a model?

For e.g.

var MyDocument = function()
{
  var model = 
  {
    friend : { firstName: 'Sathyaish', lastName: 'Chakravarthy' }
  };

  this.init : function()
  {
    ko.applyBindings(this.model /*, but this may lead knockout
    to not be able to discover my model, as my experience so far
    shows. I need to recalibrate the 'this' pointer so knockout
    knows to look for the model object inside whatever I tell it to, 
    i.e. inside an instance of the MyDocument class and not inside
    whatever the this pointer happens to reference when knockout 
    is performing the binding. */);
  };
}

var myDocument = new MyDocument();
myDocument.init();

回答1:

I think you really need the mapping plugin and to change/put a value will need to do:

model.friend.firstName(firstName)

I will try your code later and if is necessary i will edit my answer.