Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ko.toJSON seems to be ignoring fields

Tags:

knockout.js

I am coding a dynamic query editor with knockoutjs, but ko.toJSON is not outputting the fields until they have been modified by a data-bound form element. See this jsfiddle with the following view:

<span data-bind="template: {name: 'filterGroupTemplate', data: viewModel}"></span>

<script type="text/x-jquery-tmpl" id="filterGroupTemplate">
    <div class="filterGroup">
        <div class="filterGroupParams">
            Match
            <select name="join" data-bind="value: join, options: joins"></select>
            of the following rules:
            <button class="addFilter" data-bind="click: addFilter">+</button>
            <button class="addGroup" data-bind="click: addGroup">{...}</button>
            <button class="removeGroup">x</button>
        </div>
        <span data-bind='template: {name: "filterTemplate", foreach: filters }'></span>
    </div>
</script> 

<script type="text/x-jquery-tmpl" id="filterTemplate"> 
    <div class="filter">
        {{if $data.filters }}       
            <div data-bind='template: "filterGroupTemplate"'> 
            </div>                 
        {{else}}
            <select data-bind="value: field, options: fields"></select>       
            <select data-bind="value: modifier, options: modifiers"></select>
            <input type="text" data-bind="value: criteria" />
            <button>x</button>
        {{/if}}
    </div>
</script>

<h2>ViewModel JSON</h2>
<div data-bind="text: dev()"></div>

And this code:

// filter class
var Filter = function() {
    this.field = ko.observable();
    this.modifier = ko.observable();
    this.criteria = ko.observable();
};

// filter group class
var FilterGroup = function() {
    // Include a blank filter in every group
    this.join = ko.observable('All');
    this.filters = ko.observableArray([new Filter()]);
    this.addFilter = function() {
        var filter = new Filter();
        this.filters().push(filter);
        this.filters.valueHasMutated();
    };
    this.addGroup = function() {
        var group = new FilterGroup();
        this.filters().push(group);
        this.filters.valueHasMutated();
    };
};

// Data
var joins = ['All', 'Any'];
var modifiers = [
    'equals',
    'not equal to',
    'less than',
    'greater than',
    'contains',
    'does not contain',
    'starts with'
];
var fields = ['f1','f2','f3'];

var viewModel = new FilterGroup();
function dev(){
    return ko.toJSON(viewModel);
}

ko.applyBindings(viewModel);

Though the view model clearly has fields pre-initialized (such as the join property), they're not showing up in the JSON object until the user changes them in the UI.

Can someone please explain what I'm doing wrong and how to fix it? This actually seems like a bug with knockoutjs itself. If it comes down to it, I'll just use defaults when building the query if the values aren't there, but this seems like a crappy solution

like image 937
daedalus28 Avatar asked Feb 24 '23 01:02

daedalus28


1 Answers

There is a subtle issue in your code that has caused many people to tear their hair out. When you use the options binding with the value binding on a select element, you need to specify options before value.

The options binding builds the available options and then the value binding enforces that the selected option and view model value are in sync. If you have them in the wrong order, then the value binding runs first and sees that there is not a matching option to select, so it sets the value to null.

Here is your fiddle with the order switched: http://jsfiddle.net/rniemeyer/32fYk/

This has been logged on github a couple of times, most recently here: https://github.com/SteveSanderson/knockout/issues/58. There is currently not a simple way to enforce that a binding runs before another one in the same data-bind. Hopefully, at some point this will be addressed. I can think of a fix that would specifically handle this case by having the two bindings check if the other one is listed.

like image 149
RP Niemeyer Avatar answered Mar 05 '23 15:03

RP Niemeyer