1
votes

I have the following viewModel:

var ViewModel = function(setData, dummyCard) {
    var self = this;
    ko.mapping.fromJS(setData, {}, self);

    self.cardCount = ko.computed(function() {
        //debugger;
        return self.cards().length;
    });

    self.editing = ko.observable(false);

    self.edit = function () {
        debugger;
        self.editing(true);
    };

};

This viewModel is used to display a list of Cards which belong to a Set. I'm trying to allow the user to edit the Sides of those Cards (both sides at the same time) via the following:

<!-- ko foreach: cards -->
      <!-- ko foreach: sides -->
         <div data-bind="visible: !$root.editing()" class="span5 side-study-box">
             <p data-bind="text: content">SIDE 1</p>
         </div>
      <!-- /ko -->
<!-- /ko -->

I added the Editing functionality per the example here (see the hasFocus example with Bert Bertington): http://knockoutjs.com/documentation/hasfocus-binding.html

This isn't quite working though because the Edit properties are attached to the $root (Set) object, and not the $parent (Card) object. I think that the way to make this work involves the "create" method as seen here: Adding properties to the view model created by using the Knockout JS mapping plugin

What syntax is required to bring those properties to the side's parent?

Edit: So far I have:

var ViewModel = function(setData, dummyCard) {
    var self = this;

    var cardModel = function(data) {
        debugger;
        ko.mapping.fromJS(data, {}, this);

        this.editing = ko.observable(false);

        this.editing = function() {
            debugger;
            this.editing(true);
        };
    };

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

self.cardCount = ko.computed(function() {
        //debugger;
        return self.cards().length;
    });

It's not quite working with the rest of the js though - now "cards()" is undefined. Digging into it now but if anyone has any tips, I'm all ears!

1

1 Answers

0
votes

Although you have defined the "cardModel" and invoked KO mapping inside this model, the function body (i.e. the model definition) is not getting executed by default when you apply KO bindings. Also, your code maps the "cards" to the "cardModel" and therefore, the "cards" array is not available in the root model view. These together are the reason why you see the cards not being defined.

I also noticed an issue the "editing" property. It seems you have an observable and a function with the same name "editing" which will cause an non-ending recursion.

With the description you have given, I've assumed that you are trying to map a JSON array containing cards elements and defined one way of getting what you want done. The code is available in this fiddle.

The code in the fiddle is mentioned below for reader convenience

JavaScript

var cardsJSON = {
    "cards" : [{
        "cardName" : "Card1",
        "sides" : ["Side1", "Side2"]
    }, {
        "cardName" : "Card2",
        "sides" : ["Side1", "Side2"]
    }, {
        "cardName" : "Card3",
        "sides" : ["Side1", "Side2"]
    }]
};

function ViewModel() {
    var self = this;

    var testFcn = function() {
        alert("In Function");
    };

    self.cards = ko.observableArray([]);
    self.cardCount = ko.computed(function() {
        return self.cards().length;
    });
};

function Card() {
    var self = this;
    self.editing = ko.observable(false);

    self.edit = function() {
        self.editing(true);
    };

    self.doneEdit = function() {
        self.editing(false);
    };
};

function createCard(data) {
    var card = new Card();
    ko.mapping.fromJS(data, {}, card);

    return card;
};

var viewModel = new ViewModel();
function init() {
    var mapping = {
        "cards" : {
            create : function(options) {
                return createCard(options.data);
            }
        }
    };

    ko.mapping.fromJS(cardsJSON, mapping, viewModel);
};

init();
ko.applyBindings(viewModel);

HTML

<table border="1">
    <thead>
        <tr>
            <th>Card</th>
            <th colspan="2">Sides</th>
            <th></th>
        </tr>
    </thead>
    <tbody>
        <!-- ko foreach: cards -->
        <tr>
            <td data-bind="text: cardName"></td>
            <!-- ko foreach: sides -->
            <td>
                <span  data-bind="text: $data, visible: !$parent.editing(), click: $parent.edit"></span>
                <input data-bind="value: $data, visible:$parent.editing"></input>
            </td>
            <!-- /ko -->
            <td><input value="Done" type="button" data-bind="click: doneEdit"></input></td>
        </tr>
        <!-- /ko -->
    </tbody>
</table>

The code might be a bit elaborate than it should, but serves the purpose of better explaining.

As you can see, there's a separate view model to represent a Card which contains its own properties such as "editing", "edit" etc. When we map the JSON via the mapping plugin, we override how a Card is created. What we basically do is create a Card view model and apply the mappings, so that it contains both the pre-defined properties as well as the properties in the JSON.

This has to be done explicitly (as per my understanding). Once you are done with the mapping, the root view model contains an observable array of Cards, and each Card instance contains its own "edit" function and "editing" property which you can use in the data binding.

Hope this helps.