jqueryjsonajaxasp.net-mvcmodelbinders

Postback to controller action from ajax not working?


this is my ajax call:

$('#SaveItemButton').click(function () {   
        var tableData = { 'ItemViewModel': table.$('input, select').serialize() }; // here the data gets returned          
        //Add Data Function
        function Add() {                    
            $.ajax({
                url: "/Item/Add",
                data: tableData,
                type: "POST",
                contentType: "application/json;charset=utf-8",
                dataType: "json",
                success: function (result) {
                    alert("success: " + result);
                },
                error: function (errormessage) {
                    alert("error: " + errormessage);
                }
            });
        }       
    } );

And this is my controller action:

public JsonResult Add(List<ItemViewModel> ItemViewModel)
{
   var x = ItemViewModel;               
   return Json(1, JsonRequestBehavior.AllowGet);
}

actual data returned by tableData: "ItemType=Type1&Unit=select&Quantity=12&Price=1000&Total=&ItemType=Type2&Unit=//select&Quantity=11&Price=2000&Total="

my controller action is receiving ItemViewModel = null. Can anyone tell me what I need to do with the data received so I can successful postback to the server and bind to the model:

public class ItemViewModel
    {        
        public int Id { get; set; }
        public string ItemType { get; set; }
        public int Quantity { get; set; }
        public decimal Price { get; set; }
        public ICollection<ProductViewModel> Products { get; set; }
        public ICollection<ServiceViewModel> Services { get; set; }
        public ICollection<CustomerViewModel> Customers { get; set; }
        public InvoiceViewModel Invoice { get; set; }
        public string ApplicationUserID { get; set; }        
        public int ProductId { get; set; }       
        public int ServiceId { get; set; }       
        public int InvoiceId { get; set; }
        public int CustomerId { get; set; }
        public string InvoiceDate { get; set; }
        public string TransferDate { get; set; }
        public string TransferPlace { get; set; }
        public string InvoiceDescription { get; set; }        
    }

This is my view:

@using (Html.BeginForm("Add", "Item", FormMethod.Post, new { @class = "form-horizontal", id = "ItemForm", role = "form" }))
{
    <body>
        <h2>Invoice</h2>

        <table id="ItemTable" class="table table-hover table-secondary" style="width:100%">
            <thead>
                <tr>
                    <th></th>
                    <th>ItemType</th>
                    <th>Unit</th>
                    <th>Quantity</th>
                    <th>Price</th>
                    <th>Total</th>
                    <th></th>
                </tr>
            </thead>
        </table>

    </body>
}

And this is the jquery DataTable, javascript:

$(document).ready(function () {
    var table = $('#ItemTable').DataTable({
        "dom": '<"toolbar">frtip',
        "paging": true,
        "pagingType": "full_numbers",
        "searching": false,
        responsive: {
            details: {
                type: 'column'
            }
        },
        columnDefs: [{
            className: 'control',
            orderable: false,
            targets: 0
        }],
        order: [1, 'asc']
    });

    $("div.toolbar").html(
        '<button id="addRow" type="button" class="btn btn-outline-info fa fa-plus"> Add</button> <div class="divider"/>' +
        '<button id="SaveItemButton" type="submit" class="btn btn-outline-secondary fa fa-save"> Save</button> <div class="divider"/>' +
        '<button id="PreviewButton" type="button" class="btn btn-outline-info fa fa-eye"> Preview</button> <div class="divider"/>' +
        '<button id="PrintButton" type="button" class="btn btn-info fa fa-print"> Print</button> <div class="divider"/>' +
        '<button id="SendButton" type="button" class="btn btn-lump-sum fa fa-envelope"> Send</button> <div class="divider"/>' +
        '<button id="DeleteButton" type="button" class="btn btn-secondary fa fa-trash"> Delete </button> <div class="divider"/>'
    );

    var counter = 1;

    $('#addRow').on('click', function () {
        table.row.add([
            '',
            '<input name="ItemType" class="form-control" type="text">',

            '<select name="Unit" class="form-control defaultpicker">' +
                '<option value="select">dan</option>' + 
                '<option value="select">Komad</option>' +
                '<option value="select">Sat</option>' +
                '<option value="select">m</option>' +
                '<option value="select">m2</option>' +
                '<option value="select">m3</option>' +
                '<option value="select">kg</option>' +
                '<option value="select">lit</option>' +
                '<option value="select">pak</option>' +
                '<option value="select">reč</option>' +                               
            '</select > ',

            '<input name="Quantity" class="form-control" type="number">',
            '<input name="Price" class="form-control" type="text">',
            '<input name="Total" class="form-control" type="text" readonly>',
            '<button type="button" Id="DeleteButton" class="fa fa-times select-row btn btn-secondary btn-sm" data-id=""></button>',            
        ]).draw(false);

        counter++;
        $('#ItemTable').dataTable().fnPageChange('last');
    });

    $('#ItemTable').on("click", "#DeleteButton", function () {
        var table = $('#ItemTable').DataTable();
        var row;

        console.log($(this).closest('table'));
        if ($(this).closest('table').hasClass("collapsed")) {
            var child = $(this).parents("tr.child");
            row = $(child).prevAll(".parent");
        } else {
            row = $(this).parents('tr');
        }

        table.row(row).remove().draw();
    });

    // Automatically add a first row of data
    $('#addRow').click();    

    $('#SaveItemButton').click(function () {
        debugger;
        //Add Data Function
        function Add() {                    
            $.ajax({
                url: "/Item/Add",
                data: table.$('input, select').serialize(),
                type: "POST",                
                dataType: "json",
                success: function (result) {
                    alert("success: " + result);
                },
                error: function (errormessage) {
                    alert("error: " + errormessage);
                }
            });
        }       
    } );
});

Solution

  • You cannot use combine .serialize() inside a javascript object, and when using .serialize(), you need to use the default contentType ('application/x-www-form-urlencoded; charset=UTF-8') in any case.

    One option would be to include the collection indexer in the name attribute, so that the name attributes of you form controls are name="[#].ItemType" where # is a zero based, consecutive indexer, in which case the ajax call can be

    $.ajax({
        url: "/Item/Add",
        data: table.$('input, select').serialize(),
        type: "POST",
        ....
    

    however since you also appear to want to delete items, that also means you need to add an input for the collection indexer (e.g. <input name="Index" value="#">) in each row.

    Alternatively, you need to generate and array of objects based on the values of each form control in each <tr>, stringify it, and send it using contentType: 'application/json'

    To build the array

    var arr = [];
    // adjust following to exclude any tr elements in a thead or tfoot if applicable
    var rows = $('#ItemTable').find('tr');
    $.each(rows, function(index, item) {
        var controls = $(this).find('input, select');
        var item = {
            ItemType: controls.eq(0).val(),
            Unit: controls.eq(1).val(),
            Quantity: controls.eq(2).val(),
            Price controls.eq(3).val(),
        }
        arr.push(item);
    });
    

    And then to post it

    $.ajax({
        url: '@Url.Action("Add", "Item")', // don't hard code your urls
        data: JSON.stringify(arr),
        contentType: 'application/json',
        type: "POST",                
        dataType: "json",
        success: function (result) {
            ....
    

    As a side note, you are creating a <select> with name="Unit" but there does not appear to be a property in your model with that name. In addition, you have given each <option> the same value="select"