Search code examples
knockout.jsknockout-2.0knockout-mapping-pluginknockout-mvc

Knockout how do I set value in child observable


I am trying to implement inline editing using templates. On my viewmodel, I have an method : self.changeMode. I am triggering this from a button click event ( inside the template). The data parameter that is passed to the changeMode function contains is the expected data but I need to change the observable. How do I do this?

var MyViewModel = function (data) {
    var self = this;

    self.managingAgentId = ko.observable(data.managingAgentId);
    self.companyName = ko.observable(data.companyName);
    self.companyNumber = ko.observable(data.companyNumber);
    self.isActive = ko.observable(data.isActive);
    self.agents = ko.observableArray(data.agents);
    
    // Change to Edit or Display mode
    self.changeMode = function (data,event) {
        event.preventDefault();
        // => need to change mode here!
    };
}


$(function () {

        $.ajax({
            type: "GET",
            url: ma.Urls.LoadManagingAgent
        }).done(function (result) {
            $.each(result.agents, function (index, element) {
                element.mode = "display";
            });
            
            ko.applyBindings(new MyViewModel(result));
        }).error(function (response) {
            addMessage(response);
        });
    });
<script type="text/html" id="display">
    <td data-bind="text: managingAgentMemberId"></td>
    <td data-bind="text: applicationUser.userName"></td>
    <td data-bind="text: applicationUser.email"></td>
    <td data-bind="text: applicationUser.emailConfirmed"></td>
    <td data-bind="text: isActive"></td>
    <td>
        <button class="btn btn-success btn-sm" data-bind="click:$root.changeMode">
            <i class="fa fa-edit"></i>
            Edit
        </button>
    </td>
</script>

<script type="text/html" id="edit">
    <td data-bind="text: managingAgentMemberId"></td>
    <td data-bind="text: applicationUser.userName"></td>
    <td data-bind="text: applicationUser.email"></td>
    <td data-bind="text: applicationUser.emailConfirmed"></td>
    <td><input type="checkbox" data-bind="checked: isActive" /> </td>
    <td>
        <button class="btn btn-success btn-sm kout-update">
            <i class="fa fa-save"></i>
            Update
        </button>
        <button class="btn btn-danger btn-sm kout-cancel">
            <i class="fa fa-stop"></i>
            Cancel
        </button>
    </td>
</script>

enter image description here


Solution

  • Canonical solution:

    function Child(data) {
        var self = this;
        self.name = ko.observable();
        self.mode = ko.observable('display');
        ko.mapping.fromJS(data, Child.mapping, self);
    }
    Child.prototype.toggleMode = function () {
        this.mode(this.mode() === 'display' ? 'edit' : 'display');
    };
    Child.mapping = {
        // mapping rules, if applicable
    };
    function Parent(data) {
        var self = this;
        self.children = ko.observableArray();
        ko.mapping.fromJS(data, Parent.mapping, self);
    }
    Parent.mapping = {
        children: {
            create: function (options) {
                return new Child(options.data);
            }
        }
    };
    
    ko.applyBindings(new Parent({
        children: [
            {name: 'Child 1'}, {name: 'Child 2'}, {name: 'Child 3'}
        ]
    }));
    td:first-child {
        width: 200px;
    }
    button {
        width: 6em;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
    
    <table>
        <tbody data-bind="foreach: children">
            <tr data-bind="template: mode"></tr>
        </tbody>
    </table>
    
    <script type="text/html" id="display">
        <td data-bind="text: name"></td>
        <td>
            <button data-bind="click: toggleMode">Edit</button>
        </td>
    </script>
    
    <script type="text/html" id="edit">
        <td><input data-bind="value: name"></td>
        <td>
            <button data-bind="click: toggleMode">Save</button>
        </td>
    </script>