0
votes

I have a knockout viewmodel getting populated from a JSON call. In a select element in a form, I have a set of options (also coming from viewmodel) and the value, part of observableArray. The issue is only with the select element and not with input ones -- when submitting the form, only the values that have been assigned to in select contain proper values. So the ones that have been successfully loaded from JSON and presented in form, but left unchanged, will be sent back to server as the first value from the options array.

HTML Form:

<form>
    <table >
        <thead>
            ...
        </thead>
        <tbody data-bind='foreach: ScaledCostEntries'>
            <tr>
                <td><input data-bind='value: StartDateString' class="startdate" type="text"/></td>
                <td><select data-bind='value: InvoiceType, options: $root.InvoiceTypes'></select></td>                    
                <td><a href='#' data-bind='click: $root.removeCost'>Delete</a></td>
            </tr>
        </tbody>

    </table>
    <button data-bind='click: addCost'>Add New Row</button>
    <button data-bind='click: save' >Update</button>
</form>

In this code above the problem is with InvoiceType, part of the viewmodels ScaledCostEntries observableArray. (Also, if I swap the order of value and options, that will not put a selected value in the select element).

and the JS:

<script type="text/javascript">
    $(function () {
        var scaledCostModel = function () {
            var self = this;

            self.ScaledCostEntries = ko.observableArray([]);
            self.InvoiceTypes = ko.observableArray([]);

            self.addCost = function () {
                self.ScaledCostEntries.push({                    
                    StartDateString: ko.observable(),
                    InvoiceType: ko.observable()
                });
            };

            self.removeCost = function (cost) {
                cost.IsDeleted = true;
                self.ScaledCostEntries.destroy(cost);
            };

            self.save = function (form) {
                jQuery.ajax({
                    url: '@Request.Url.PathAndQuery',
                    type: "POST",
                    dataType: "json",
                    contentType: "application/json; charset=utf-8",
                    data: ko.toJSON(self.ScaledCostEntries)                    
                });
            };
        };

        jQuery.getJSON('@Request.Url.PathAndQuery', function (data) {
            ko.mapping.fromJS(data, {}, viewModel);
        });

        var viewModel = new scaledCostModel();

        ko.applyBindings(viewModel);
    });
</script>

So, to summarize, the issue is with viewmodel's property bound to a select element. When the select is left unchanged (not reselected), the viewmodel will have it's value as the first item from the options (InvoiceTypes) array, when posting to server. In the end, I might be forgetting something trivial and this is my first more serious knockout.js attempt.

Note: InvoiceType is part of the ScaledCostEntries, which is observableArray. InvoiceTypes is observableArray. Both InvoiceTypes and ScaledCostEntries, come from JSON and ScaledCostEntries is sent back.

1
Can you put a break in chrome or other browser debugger at the beginning of the ajax call and see what is in the in the self.ScalsedCostEntries array?zmanc
@zmanc: Yes, have done that. Values are fine, if I have reselected them. If I have not done anything to form or reselected another row's values, then the ones not edited will contain the first element from the select options -- InvoiceTypes[0].Aineislis Cole

1 Answers

3
votes

My assumption is that this is due to how the ScaledCostEntries are being handled on the server on the form submission.

I've run into the problem before (across various server-side frameworks) when I have a main model with a list of dependent models that are being added and removed, and a form submission is done against the main model to update everything.

The problem is that during form submission, when the values in the request are being mapped to the server-side model, blank values are taken to mean "no change" as opposed to "delete this". This works well with properties directly on a model, but doesn't work for lists of dependent models.

There are a couple of ways I've found of dealing with this: use Ajax to delete the underlying model and update the relationship with the main model when the 'remove' or 'delete' button is pressed in the view; or explicitly send the whole list of models each time and explicitly delete and rebuild the list on the server for each form submission. Each are applicable in different situations, and there are probably other approaches that may work well, too.