0

I have the following HTML code that binds a list of members from a JSON call. This works fine, however I am now want to find a row based on a MemberId and change the name in code.

<ul data-bind="foreach: { data: members}">
    <li>
        <span data-bind="text: $index"></span>
        <span data-bind="text: $data.Name"></span>
        <span data-bind="text: $data.MemberId"></span>
    </li>
</ul>

<button id="btnChange">Change</button>

<script>
    $(function () {
        $.getJSON("/home/memberlist", function(data) {
            var viewModel = {
                members: ko.observableArray(data),
            };
            ko.applyBindings(viewModel);
        });

        $('#btnChange').click(function() {
          //I would like to find the row that is a match for a MemberId
          // and change the name....
        });

    });
</script>
Rippo
  • 22,117
  • 14
  • 78
  • 117
  • if you store the results of your getJSON call in a global variable you can probably reference it in your #btnChange listener function and access the MemberId by parsing it as json if it is properly formatted, i.e. proper_json = JSON.parse(data); proper_json.MemeberId, etc.. – Carl Carlson Jan 15 '14 at 20:21

3 Answers3

2

If you want data changes to reflect on the page, you must make that data observable. This means mapping raw server data to objects with observable properties.

The following piece of code does this:

// define a Member class
function Member(data) {
    this.MemberId = ko.observable(data.MemberId);
    this.Name = ko.observable(data.Name);
}
Member.prototype.changeName = function () {
    var newName = prompt("New name?", this.Name());
    if (newName) {
        this.Name(newName);
    }
};

$(function () {
    $.getJSON("/home/memberlist")
    .then(function(rawData) {
        return ko.utils.arrayMap(rawData, function (instanceData) {
            return new Member(instanceData);
        });
    })
    .done(function(mappedData) {
        ko.applyBindings({
            members: observableArray(mappedData),
            changeRandomName: function () {
                var members = this.members(),
                    random = getRandomInt(0, members.length - 1);

                members[random].changeName();
            }
        });
    });
});

// courtesy of http://stackoverflow.com/a/1527820
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

and

<ul data-bind="foreach: members">
    <li>
        <span data-bind="text: $index"></span>
        <span data-bind="text: Name"></span>
        <span data-bind="text: MemberId"></span>
    </li>
</ul>
<button data-bind="click: changeRandomName">Change</button>

Note...

  • the simpler foreach binding
  • the simpler text bindings (There is no need to use $data in most cases. It is implicit.)
  • the use of the click binding instead of jQuery event handlers (A thing you generally should try to do in Knockout applications. Do as much as you can within Knockout.)
  • the use of jQuery delegate functions (then() and done()) to transform and use an Ajax result - this is more powerful (and more legible) than using the success callback model
Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • OK, getting there, however the button in my world is outside of the loop. – Rippo Jan 15 '14 at 20:38
  • And how do you envision selecting *which* item needs changing? (And before you answer think if you really should do it that way - from a UX perspective, as a user, I directly want to click the thing I want to change.) – Tomalak Jan 15 '14 at 20:40
  • Think of a lotto, list of members, on button click I get a random row and change a span tag to "Won". Its not `CRUD or row select` I am doing else your answer would be spot on. – Rippo Jan 15 '14 at 20:44
  • @Rippo I see. I admit I thought of CRUD. Okay so let's assume there's a button that selects a random item for changing. See modified answer. – Tomalak Jan 15 '14 at 21:03
  • 1
    From questions I asked this has helped me no end and got me to where I need to be. In actual fact my button click fires a request to server and returns a `MemberId` which I use to find and update something on the model. I am using `ko.utils.arrayFirst` to find the relevant record to update. – Rippo Jan 16 '14 at 06:55
  • @Rippo Way to go, that's the right approach. Good to hear you got it working. BTW For more complex mapping requirements that you don't want to do all manually check out the [mapping plugin](http://knockoutjs.com/documentation/plugins-mapping.html) or [knockout.viewmodel](http://coderenaissance.github.io/knockout.viewmodel/). – Tomalak Jan 16 '14 at 07:38
1

First of all, since you're using knockout, I think you should use the click binding handler on the button element. The code you wrote is exactly what knockout tries to prevent i.e. binding events by id in a generic script section (not scoped).

@BenjaminDavid is right. To find html element, you'll have to create something you can search for (ex. id attribute on the row element). Then again, there is a knockout binding handler to apply values to attributes.

The code to apply the id to the row element would be something like this:

<ul data-bind="foreach: { data: members}">
    <li data-bind="attr: {id: $data.MemberId}">
        <span data-bind="text: $index"></span>
        <span data-bind="text: $data.Name"></span>
        <span data-bind="text: $data.MemberId"></span>
    </li>
</ul>

If all you want is changing the name of a member, then you should not work with html at all. You should find the member in the "in-memory" collection, and change the value directly. The data will be updated in the UI (if the property is observable).

W3Max
  • 3,328
  • 5
  • 35
  • 61
  • Yes I understand, the click binds to a function inside the viewmodel, aha the light comes on – Rippo Jan 15 '14 at 20:36
0

You could do something like this

<li id="$data.MemberID">
  ...
</li>

$('#btnChange').click(function() {

  $("#memberID")
  //Then get the second child and or set a class of name to the span and do 
  // $("#memberID > .name") 
  // to get the html node that you want 
});
BenDavidJamin
  • 169
  • 1
  • 10