0

So close to getting this to work as expected. I am getting data from a JSON request and then using the mapping plugin to map it. I want to add new values to a nested array on a click binding.

Right now in my code I am getting an error that addPoint is not defined.

View:

<table>
    <tbody data-bind='template: {name: "statRowTemplate", foreach: stats }'></tbody>
</table>

<script id="statRowTemplate" type="text/html">
    {{if type() != "column"}}
    <tr>
            <td><label>Name: </label><input data-bind="value: name" /></td>
            <td>
                    <label>Plot Points: </label><ul class="list-plot-points" data-bind="template: {name: 'dataTemplate' , foreach: data}"></ul>
                    <button data-bind="click: addPoint">Add Point</button>
                </td>

    </tr>
    {{/if}}
</script>
<script type="text/html" id="dataTemplate">         
<li>
        <input data-bind="value: val" />
</li>
</script>

<button data-bind="click: saveChanges">Save Changes</button>

View Model

var viewModel = {};

$.getJSON('data-forecast-accuracy.json', function(result) {

    function mappedData(data) {
        this.val = data;
    }

    //override toJSON to turn it back into a single val
    mappedData.prototype.toJSON = function() {
       return parseInt(ko.utils.unwrapObservable(this.val), 10);  //parseInt if you want or not 
    };

    var mapping = {
        'data': {
            create: function(options) {
            return new mappedData(options.data)
            }
        }
    }

    viewModel.stats = ko.mapping.fromJS(result, mapping);

    viewModel.addPoint = function() {
    this.stats.data.push(new mappedData(0));
    }

    viewModel.saveChanges = function() {

        var unmapped = ko.mapping.toJSON(viewModel.stats);
        //console.log(unmapped);
        $.post('save-changes.php', {data: unmapped})
        .success(function(results) { console.log("success")})
        .error(function() {  console.log("error"); })
        .complete(function() {  console.log("complete"); });
    }

    ko.applyBindings(viewModel);
});

The JSON:

[{"type":"spline","marker":{"symbol":"diamond"},"name":"Cumulative","data":[10,17,18,18,16,17,18,19]},{"type":"spline","marker":{"symbol":"circle"},"name":"Monthly","data":[10,22,20,19,8,20,25,23]}]

I was able to adjust this jsfiddle from an earlier response to add points to the single array. I need to find a way to implement this across the generated mapping.

http://jsfiddle.net/vv2Wx/1/

2 Answers 2

1

Where you are binding against addPoint the context is an item in stats rather than your view model.

If viewModel has global scope, then you can do: click: viewModel.addPoint. You will want to make sure that this is set correctly in your addPoint method. Easiest way is to bind the method to your viewModel variable like:

viewModel.addPoint = function() {
    this.stats.data.push(new mappedData(0));
}.bind(viewModel);

If viewModel does not have global scope, then you can pass it in via templateOptions. This would be like:

<tbody data-bind='template: {name: "statRowTemplate", 
                             foreach: stats, templateOptions: { add: addPoints } }'></tbody>

Then, call it like: <button data-bind="click: $item.add">Add Point</button>

Finally, if you are using KO 1.3 beta, then you can do: <button data-bind="click: $root.addPoint">Add Point</button>

Sign up to request clarification or add additional context in comments.

2 Comments

I have tried both ways and I am getting the error Uncaught TypeError: Cannot call method 'push' of undefined The reason I placed the button in the stats template was because I want to be able to add new items to each array independently.
OK- you are really going to want your addPoint method to deal with a particular item in stats. You could add a mapping for stats and add an addPoint method there or have your addPoint on your viewModel take in a stat as the first parameter. Then, do: data-bind="click: function() { viewModel.addPoint($data); }. $data will be a particular item in stats and you can push to its data.
1

The reason you're getting "addPoint is not defined" error is because you're inside a template for a child property of the view model. Inside the template, the binding context is a stat object, not your view model.

Bottom line: Knockout is looking for a stat.addPoint function, which doesn't exist, so you get the error.

What you probably want to do is reference the parent binding context, your view model. In KO 1.3 beta, you can access parent binding contexts like this:

<button data-bind="click: $.parent.addPoint">Add Point</button>

If you're stuck on an old version of Knockout that doesn't provide access to the parent binding context, let me know, there are some additional options, such as making your view model globally accessible, then just binding to that fully qualified name.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.