You have asked for   gift(s)


KnockOut / SignalR: Full Table IUD With Validation KnockOut / SignalR: Select For LookUp Table ModelViewController: Standard Razor Knockout Tablets MVC Entry Point NorthWind Category Verbal_ActionResult_Controller HomePolicy Verbal_ActionResult_Controller HomeAbout Detailed KnockOut Library Sample

The MVC / KnockOut GameSample loads the page via the Controller returning the DbContext/DbSet for the Gift Table View( db.Gifts.ToList() );
( remember this view is controlled by @model System.Collections.Generic.IEnumerable< project.Models.Gift > )
into var initialData = function () { return @Html.Raw(Json.Encode(Model)); }(); which is the constructor for the knockout view model to populate the ko.observableArray([]).
The Sample adds the Database interaction via JSON as suggested in the following article:
Posted on July 12, 2010 Steven Sanderson's blog Editing a variable-length list, Knockout-style

ko.utils.postJson() - Could not get this to call the Controller [HttpPost] IEnumerable with a list so used jQuery ajax to POST the JSON.
Should Try: ko.utils.postJson(top.location.href + "/Update", ko.toJSON(this.gifts));


jQuery Validation: Probably need to add international locale; No doubt need to pull the form data-bind or the form will call save twice.
Do not know the result of ko data-bind "uniqueName: true" and the jQuery "required" class in the article.

Personally I prefer KnockOut extenders as field managers for typed input ( numbers, numbers with places, email, phone, post code etc ) KnockOut bindingHandlers are cool as well,
ko.bindingHandlers.dateString = {
      init : function( ...  
	 update: function ( ... 
};
For example bind-a-date data-bind="dateString: Birthday, valueUpdate: 'afterkeydown'" and your update by keystroke could be a date.js input which tries to date guess as you type.
  • Below are the Controller, Models, View and Knockout observable Model View View Model javascript.       
  • You might find it easier to understand if you read the code from thebottomof the file back up to here.
A Plain Old Class Object. Note the Holding (table) part of the data model is not used in this view (model)
Gift Class Object
namespace project.Models
{    
    public class Gift
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public double Price { get; set; }
    }
}
    
using System.Web.Configuration;
using System.Data.Entity;

namespace project.Models
{    
    public class HoldingContext : DbContext
    {
        // If you want Entity Framework to drop and regenerate your database
        // automatically whenever you change your model schema, add the following
        // code to the Application_Start method in your Global.asax file.
        // Note: this will destroy and re-create your database with every model change.
        // 
        // System.Data.Entity.Database.SetInitializer(
        // new System.Data.Entity.DropCreateDatabaseIfModelChanges<project.Models.HoldingContext>());

        public HoldingContext()
            : base(WebConfigurationManager.ConnectionStrings["ProjectConnect"].ConnectionString)
        {
        }

        public DbSet<Holding> Holding { get; set; }
        public DbSet<Gift> Gifts { get; set; }
    }
}
This is the HTML for the KnockOut Grid (Shopping Cart) Form
HTML Page Coding
@model  System.Collections.Generic.IEnumerable< project.Models.Gift >
@{ ViewBag.Title = "Index"; }

<script type="text/javascript">
    var initialData = function () { return @Html.Raw(Json.Encode( Model )); }();
</script>

<p>You have asked for <span data-bind="text: gifts().length"> </span> gift(s) <span id="ChatBack"></span></p>

<script type="text/html" id="giftRowTemplate">
<tr>
    <td>Gift name: <input class="required" maxlength="10" data-bind="value: Title, uniqueName: true"/></td>
    <td>Price: € ¥ <input class="required number" data-bind="value: Price, uniqueName: true"/></td>
    <td><a href="#" data-bind="click: function () { $parent.removeGift($data) }">Delete</a></td>
</tr>
</script>

<form id="jQVerify" class="giftListEditor"  data-bind="submit: save" >
    <table>
        <tbody data-bind="template: { name: 'giftRowTemplate', foreach: gifts }"></tbody>
    </table>
    <button data-bind="click: addGift">Add Gift</button>
 
    <button data-bind="enable: gifts().length > 0" type="submit" id="idsave">Submit</button>
</form>
It is possible to write the @Html.Raw() to a <div> that has style visiblility hidden display none.
The Binding
@model System.Collections.Generic.IEnumerable<montegodata.Models.Gift>

<script type="text/javascript" src="~/Scripts/knockout-3.3.0.js"></script>

<script type="text/javascript">
    var initialData = function () { return @Html.Raw(Json.Encode( Model )); }();
</script>
The KnockOut View Model ( ko.observableArray( [] ) ) makes jQuery ajax calls to the Controller ActionResult ( IEnumerable< POCO > )
The KnockOut View Model
<script type="text/javascript">
$(document).ready(function () {
    function MyViewModel(initialData) {
        this.gifts = ko.observableArray(initialData);
        this.addGift = function () { this.gifts.push({ Title: "", Price: "" }); };
        this.removeGift = function (gift) {
            var callback = this.gifts;
            var url = top.location.href + "/Delete?id=" + gift.Id;
            $.ajax({
                url: url,
                type: 'POST',
                contentType: 'application/json; charset=utf-8',
                success: function (status) {
                    callback.remove(gift);
                }
            });
        };
        this.save = function () {
            var url = top.location.href + "/Update";
            $.ajax({
                url: url,
                type: 'POST',
                contentType: 'application/json; charset=utf-8',
                data: ko.toJSON(this.gifts),
                success: function (status) {
                    var now = new Date();
                    $("#ChatBack").html('Saved ' + now.toString("DD-MMM-YYYY hh:mm:ss")); //alert(status);
                }
            });
        };
    }
    var vm = new MyViewModel( initialData );
    ko.applyBindings( vm, document.body );
});
</script>
The Gift Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

using project.Models;
    
namespace project.Controllers
{    
    public class GiftShopController : Controller
    {
        private HoldingContext db = new HoldingContext();   // DbContext,DbSet : Plain Old Class Object's
                                                            // [Views/]GiftShop/   GET: /Gift/
        public ActionResult Index()
        {
            var initialState = new [] {
                new Gift { Id=1, Title = "Tall Hat", Price = 49.95 },
                new Gift { Id=2, Title = "Long Cloak", Price = 78.25 }
            };   
            return View( db.Gifts.ToList() );  // return View( db.Gifts.ToArray() );
        }
        [HttpPost]
        public ActionResult Update( IEnumerable< Gift > gifts )
        {
            foreach (Gift g in gifts)
            {
                var record = db.Gifts.FirstOrDefault(x => x.Id == g.Id);
                if (record == null) 
                {
                    record = db.Gifts.Create();
                    record.Title = g.Title;
                    record.Price = g.Price;
                    db.Gifts.Add(record);
                }
                else
                {
                    record.Title = g.Title;
                    record.Price = g.Price;
                }
            }
            db.SaveChanges();
            return View("Index");   //return View("Saved", gifts);//return View(db.Gifts.ToList()); 
        }
        [HttpPost]
        public ActionResult Delete(int id)
        {
            try {
                using (var context = new HoldingContext()) {
                    var record = context.Gifts.FirstOrDefault(x => x.Id == id);
                    if (record != null)
                    {
                        context.Gifts.Remove(record);
                        context.SaveChanges();
                    }
                }
            }
            catch (Exception) {
            }
            return View("Index");
        }
    }
}