Search code examples
c#javascriptasp.net-mvcknockout.jsknockout-mapping-plugin

Knockout view model with mapping plugin


I am trying to figure out how to use the Knockout mapping engine with my view model.

If I have a server side view model that looks like this:

public class InventoryIndexViewModel
{
    public bool IsDeleteReceiverButtonVisible { get; set; }
    public IList<Receiver> Receivers { get; set; }

    public string ReceiversAsJson
    {
        get
        {
            var jsonSerializerSettings = new JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            };

            var receivers = JsonConvert.SerializeObject(this.Receivers, jsonSerializerSettings);

            return receivers;
        }
    }

}

And the Receiver property is an object that looks like this:

public class Receiver
{
    public Cooperative Cooperative { get; set; }
}

And the Cooperative property is an object that looks like this:

public class Cooperative
{
    public int Id { get; set; }
    public string Name { get; set; }
}

My view looks like this:

@model InventoryIndexViewModel

@{
    Layout = "~/Views/Shared/_Layout.cshtml";
    ViewBag.Title = "Inventory List";
}

@section scripts{
    <script>
        $(function() {
            var inventoryViewModel = function() {

                var self = this;

                self.isDeleteButtonVisible = function() { return @Model.IsDeleteReceiverButtonVisible.ToString().ToLower(); }();
                self.receivers = ko.observableArray(@Html.Raw(Model.ReceiversAsJson));
            };

            ko.applyBindings(new inventoryViewModel());
        });
    </script>
}

<div class="col-md-10">
    <br />
    <table class="table table-striped">
        <tbody data-bind="foreach: receivers">
            <tr>
                <td data-bind="text: serialNumber"></td>
                <td data-bind="text: cooperative.Name"></td>
            </tr>
        </tbody>
    </table>
</div>

So the question is, how to work with the complex Receiver object. In my view code, I've tried to data-bind to cooperative.Name, but that does not work. I am guessing that I need to use the Knockout mapping plugin, but I've tried the syntax at the Knockout page along with several different syntax variations I've found by Googling, but nothing I have tried seems to work.

On a side note, the code shown here is vastly simplified from my real code. So, while there probably is a very easy way in this simplified code to get to the cooperative.Name through data-binding, just consider that the Receiver object is a complex object with many properties that are themselves, complex objects, so that is where the Knockout mapping engine would do the grunt work of turning everything into observables throughout the entire object hierarchy.

Or maybe it can't and I am way off base. Thoughts?

Edit: Updated server side view model to correctly include the ReceiversAsJson method that I forgot to include originally.


Solution

  • If your properties are defined in the model you can serialize the model as json using mapping as follows:

    Javascript

        var data = @Html.Raw(Json.Encode(Model));
        var viewModel = ko.mapping.fromJS(data);
        ko.applyBindings(viewModel);
    

    View

    <table class="table table-striped">
            <tbody data-bind="foreach: Receivers">
                <tr>
    
                    <td data-bind="text: Cooperative.Name"></td>
                </tr>
            </tbody>
    </table>
    

    JsFiddle

    http://jsfiddle.net/wr5W7/

    Controller with example data

            public ActionResult Index()
            {
                var model = new InventoryIndexViewModel();
                model.IsDeleteReceiverButtonVisible = false;
                model.Receivers.Add(new Receiver { Cooperative = new Cooperative { Id = 1, Name = "aName" } });
    
                return View(model);
            }
    

    Models

    ps I had to add an initializer to the collection i.e.

        public class InventoryIndexViewModel
        {
            public InventoryIndexViewModel()
            {
                Receivers = new List<Receiver>();
            }
    
            public bool IsDeleteReceiverButtonVisible { get; set; }
            public IList<Receiver> Receivers { get; set; }
        }
    
        public class Receiver
        {
            public Cooperative Cooperative { get; set; }
        }
    
        public class Cooperative
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
    

    Update

    As per comments, this is a way of using the mapping plugin with knockout computed functions:

    Javascript

    var aViewModel = function(data)
    {
       self = this;
       ko.mapping.fromJS(data, {}, self);
    
       self.firstNameAndLastName = ko.computed(function() {
            return self.FirstName() + " " + self.LastName();
        }); 
    
    }
    
    
    var data = {"IsDeleteReceiverButtonVisible":false, "FirstName": "First", "LastName": "Last", "Receivers":[{"Cooperative":{"Id":1,"Name":"aName"}}]};
            var viewModel = new aViewModel(data);
            ko.applyBindings(viewModel);
    

    Html

    <table class="table table-striped">
            <tbody data-bind="foreach: Receivers">
                <tr>
    
                    <td data-bind="text: Cooperative.Name"></td>
                </tr>
            </tbody>
        </table>
    <span data-bind="text:firstNameAndLastName()"></span>
    

    jsFiddle

    http://jsfiddle.net/wr5W7/3/