Brian M. Hunt
Brian M. Hunt

Reputation: 83858

Add new item to a list created with the ko.mapping plugin for Knockout.js

I am trying to figure out the canonical way to add elements to a list in a model created with ko.mapping. I created a jsFiddle to tinker:

The javascript is something along the lines of the following:

var mapping, baseModel, view_model;

mapping = {
    outer: [{
        alpha: '',
        beta: [
            {
            carotine: '',
            blockers: ''}
        ]
    }]
};

baseModel = { // add in functionality
    append_outer: function() {
        this.outer.push({});
    },
    append_beta: function(xyz) {
        this.beta.push({});
    }
};

view_model = ko.mapping.fromJS(mapping, {}, baseModel);

ko.applyBindings(view_model, $("#mapped")[0]);​

The corresponding HTML is on the jsFiddle.

When I use append_outer I would expect to have a new element added to the view_model essentially identical to the first element, all with observables. When I use append_beta I would expect beta to have a new element with [carotine: ko.observable(), blockers: ko.observable]. I have not seen an obvious way to do this - but I would expect it to be a very common use-case.

For obvious reasons the sample code is not doing what I desire! It is of course just what I was tinkering in the hopes of finding something obvious, e.g. ko.mappingCreate or some such. Alas, no such luck.

It is perhaps mentioning that the data model is dynamic - though lists will always have items with data representations identical to that of their peers. One might say that the data used to create the view model (mapping) is a prototype.

I would be grateful for any thoughts and direction.

Upvotes: 0

Views: 1606

Answers (2)

Brian M. Hunt
Brian M. Hunt

Reputation: 83858

Working off Keith's suggestion, I did the following:

var mapping, baseModel, view_model;

mapping = {
    outer: [{
        alpha: '',
        beta: [
            {
            carotine: '',
            blockers: '',
            skittles: ''}
        ]}
    ]
};

baseModel = function(mapping) { // add in functionality
    var self = this;

    this._vm_prototype = mapping;

    console.log("Prototype: " + ko.mapping.toJSON(this._vm_prototype));

    self.append = function(target, proto_getter) {
        console.log("pg: " + proto_getter);
        var prototype = proto_getter(self._vm_prototype);

        console.log("Prototype: " + JSON.stringify(prototype));
        console.log("Target: " + ko.mapping.toJSON(target));

        this[target].push(ko.mapping.fromJS(prototype));
    };
};

view_model = ko.mapping.fromJS(mapping, {}, new baseModel(mapping));

ko.applyBindings(view_model, $("#mapped")[0]);​

Unfortunately this requires something of a hack on the HTML side, being e.g. to add a new beta one has to identify where in the prototype it exists, i.e.

<button data-bind='click: $root.append.bind($data, "beta", 
        function (proto) { return proto.outer[0].beta[0]; })'>
  Add another beta
</button>

and for an outer

<button data-bind='click: append.bind($data, "outer", 
        function (proto) { return proto.outer[0]; })'>
 Add another outer
</button>

The append function could be made cleaner if it were just passed outer[0].beta[0] in place of the function, but then one would have to parse or eval this.

As one can see from the jsFiddle A and B, the only thing that changes is the mapping, but the functionality works as expected for both.

Again, I am trying to avoid duplication of code, but this is ugly. I would love any thoughts on how to clean this up!

Upvotes: 0

Keith Nicholas
Keith Nicholas

Reputation: 44316

Not that I'd do it this way... but make a object for the types that takes aspects of the mapping as a template.

http://jsfiddle.net/keith_nicholas/EEE6J/

In general, I'd just define the structure with the objects and just use the json for data.

Upvotes: 1

Related Questions