1
votes

I put together a Contacts prototype MVC application that uses Knockoutjs. I'm fairly new to Knockout and was wondering if my design is correct in reaching my end goal. My end goal is basically to take an MVC Model that is passed to my Contacts view to start and achieve the following:

  • Mapping it to my KO Viewmodel.
  • Use Bootstrap Modal Popup to input my contact data.
  • Upon Clicking Add in Bootstrap Modal call template after posting JSON data to controller successfully and have it display under
  • Edit button on each template rendered under div if clicked brings up same Modal Popup to edit that templates data.

Here's the code breakdown of what I have currently in place.

View Code

    <h2>Contacts List</h2>

    <div class="row">
        <div class="col-lg-2"></div>
        <div class="col-lg-10"><h3>KO Results</h3></div>
    </div>
    <br />
    <div class="row">
        <div class="col-lg-2"></div>
        <div class="col-lg-10"><div id="koResults" data-bind="template: { name: 'contactSectionTmp', foreach:Contacts }"></div></div>
    </div>
    <div class="row">
        <div class="col-lg-2"></div>
        <div class="col-lg-10"><a href="#" id="addContact" class="btn btn-sm btn-success" data-toggle="modal" data-target="#contactModal" data-bind="click: addContact"><strong>Add</strong></a></div>
    </div>

     @*I enter data in my bootstrap modal shown below and when I click "Add" the Template below appears
        in div element koResults with the data I just entered. This is the desired effect I'm looking for. *@

 <div class="modal" id="contactModal" tabindex="-1" role="dialog" aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header" style="background-color:#B8E28D; border-color: black">
                <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
                <h4 class="modal-title" id="myModalLabel">Add Contact</h4>
            </div>
            <div class="form-horizontal">
                <form id="contactModalForm" data-bind="with:newContact,submit:add">
                    <div class="modal-body">
                        <h4>Contact</h4>
                        <div class="form-group">
                            <label class="col-sm-4 control-label">Name:</label>
                            <div class="col-sm-8">
                                <input type="text" name="Name" class="form-control" data-bind="value: Name" />
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-4 control-label">Address:</label>
                            <div class="col-sm-8">
                                <textarea rows="4" cols="50" name="Address" class="form-control" data-bind="value: Address"></textarea>
                            </div>
                        </div>
                        <div class="form-group">
                            <label class="col-sm-4 control-label">Phone:</label>
                            <div class="col-sm-8">
                                <input type="text" name="Phone" class="form-control" data-bind="value: Phone" />
                            </div>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <button type="submit" id="formSubmitContact" class="btn btn-success">Add</button>
                        <button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>


@section scripts

    <script type="text/javascript" src="~/Scripts/knockout-3.4.0.debug.js"></script>
    <script type="text/javascript" src="~/Scripts/knockout.mapping-latest.debug.js"></script>


    @* Knockout Template *@
    <script id="contactSectionTmp" type="text/html">
        <div class="row">
            <div class="col-lg-3">Name:</div>
            <div class="col-lg-9" data-bind="text: name"></div>
        </div>
        <div class="row">
            <div class="col-lg-3">Address:</div>
            <div class="col-lg-9" data-bind="text: address"></div>
        </div>
        <div class="row">
            <div class="col-lg-3">Phone:</div>
            <div class="col-lg-9" data-bind="text: phone"></div>
        </div>
    </script>

End Section

Controller Code

    Pass in model to view here.
    public ActionResult ContactsList()
    {
        ContactsVM mData = new ContactsVM();

        mData.Contacts = new List<Contact>(){ new Contact { ID = 1, Name="Drew Lucacca", Address="782 Select St.", Phone="421-821-9101"},
            new Contact {ID = 2, Name="Kevin Rosassa", Address = "222 Potter Lane", Phone="421-982-5222" },
            new Contact {ID = 3, Name="Tim Kropp", Address = "440 PPG Place", Phone="725-434-8989"} };

        return View(mData);
    }

    [HttpPost]
    public ActionResult ContactCreate(Contact newContact)
    {

        var res = newContact;

        ContactsVM myContacts = new ContactsVM();
        myContacts.Contacts = new List<Contact>();

        myContacts.Contacts.Add(new Contact { ID = 4, Name = "Santa Claus", Address = "440 Trump Plaza", Phone = "774-489-8989" });

        return Json(myContacts);
    }

Javascript Code

`        //Main ViewModel
        function ContactsVM(data) {

            var self = this;

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

            ko.mapping.fromJS(data, mapping, self);

            self.newContact = ko.observable();

            self.addContact = function() {
                debugger;
                self.newContact(new Contact({Name: '', Address: '', Phone: ''}));
            }

            self.add = function () {
                debugger;
                var jsData = data;
                var jsData1 = ko.mapping.toJSON(self.newContact());

                $.ajax({
                    url: '@Url.Action("ContactCreate", "Home")',
                    type: 'POST',
                    data: ko.mapping.toJSON(self.newContact()),
                    dataType: 'json',
                    contentType: 'application/json; charset=utf-8',
                    success: function (jsonObject) {
                        self.contacts.push(new Contact(jsonObject));
                    }
                });

                // Close the modal.
                $('#contactModal').modal('toggle');
            };


            self.cancel = function () {

                // Close the modal.
                $('#contactModal').modal('toggle');
            };

            //self.resetForm = function (formId) {
            //    var form = $('#' + formId);
            //    form.validate().resetForm();
            //    form.get(0).reset();
            //};



};

    function Contact(data) {
        ko.mapping.fromJS(data, {}, this);
        this.isEdit = ko.observable(false);
    };


$(function () {

    var jsonModel = @Html.Raw(Newtonsoft.Json.JsonConvert.SerializeObject(this.Model));

    var vm = new ContactsVM(jsonModel);
    ko.applyBindings(vm);

    });

Contact Entity

public class Contact
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string Phone { get; set; }
}

ContactsVM Entity

public class ContactsVM
{
    public List<Contact>  Contacts { get; set; }
}

EDIT #1 See here's the thing I know that the javascript isn't correct and note that in javascript comments asking if you can help me identify if it isn't correct how should it be.

I moved the code to new location at bottom of MVC view for the Javascript code didn't seem to find model.

Javascript - Knockout - Mapping Error

JavaScript runtime error: 'push' is undefined

self.contacts.push(new Contact(jsonObject)); < --- Error happens here.

Any help here would be greatly appreciated and I'm sure would help others as well.

1
Have you run the code and checked what issues you have? I can see a number of potential problems with your current JavaScript, but you may be able to resolve some of the issues by doing some debug work. - Travis Schettler
@TravisSchettler Yes I have done some debug work - I have cleaned up some code and now get to ko.applybindings(vms) where it errors out. See changes mentioned in Javascript. - DotNetDude

1 Answers

0
votes

I think it might be best for you to take an iterative approach at getting this working, based on the steps you have listed. Do one thing at a time, get it working, then move on to the next item. Trying to work out everything at once and test it altogether is really tough.

First Recommendation: Make your client-side models reflect your server-side models. After that, things just get easier. Since you are using ko mapping, your client-side model setup gets easier:

function ContactsVM(data) {
  var mapping = {
    'Contacts': {
      create: function(options) {
        return new Contact(options.data);
      }
    }
  };

  ko.mapping.fromJS(data, mapping, this);
  this.newContact = ko.observable();
}

function Contact(data) {
  ko.mapping.fromJS(data, {}, this);
  this.isEdit = ko.observable(false);
}

Then you can create and apply the top-level view model fairly easily as well:

  var vm = new ContactsVM(jsonModel);
  ko.applyBindings(vm);

This gives you a top-level viewmodel with a fully filled Contacts observable array property. You can use the newContact property for your modal to add a new contact, just fill it with a new Contact instance.

new Contact({Name:'', Address:'', Phone:''})

When you push this new contact to the array of contacts, the DOM will automatically update to display the new contact, so you won't need to use the "ko.renderTemplate" logic that you specified. I imagine you could also show/hide the modal based on if this observable has a value if you wanted to.

Second recommendation: Try doing things with knockout first, and if you can't then use jQuery. I would not recommend using jQuery to serialize form values. Remember, you have direct access to client-side models, so you are no longer dependent upon the DOM. The ko mapping plugin has a method to unmap back to a regular JS object.