0

A MVC create view has two sections for Invoice and InvoiceItem tables.

  1. Invoice details section is with default scaffolding code for create.

  2. Invoice item section has a button "Add Item" and a partial view. When the user clicks "Add Item", a jQuery UI pop will be shown to create Invoice items and it calls /Invoice/GetInvoiceItems action. This method appends the new invoice item with existing items(if any) and returns the partial view that is loaded in a Div(viewAPInvoiceIndex). This partial view has default scaffolding code for list.

The created invoice items are not stored to DB till the user creates a complete invoice. Also the user will be able to edit/delete invoice items.

Create.chtml:

<div class="panel-body">
   <div id="viewAPInvoiceIndex">
     @{ Html.RenderPartial("_APInvoiceIndex", (IEnumerable<TransportationModule.Models.APInvoiceItem>)@ViewBag.CurrInvItems); }
   </div>
</div>

Add invoice items code(for the button in the pop up):

$("#btnAddItem").click(function () {
   var currId = $('[id$=tableInvItems] tr').length;
   if (currId < 1)
      currId = 1;
   var _item = { Id: currId, InvoiceNo: $('#txtInvNo').val(), OrderQty: $('#txtOrderQty').val(), UnitPrice: $('#txtUnitPrice').val()
   };

   $("[id$=viewAPInvoiceIndex]").load("/Invoice/GetInvoiceItems", { InvItem: _item });
   $('#mdGrnList').dialog("close");
});

Action methods:

public ActionResult Create()
{
    IList<APInvoiceItem> existingItems = new List<APInvoiceItem>();
    if (Session != null && Session["ExistingItems"] != null)
       existingItems = (IList<APInvoiceItem>)Session["ExistingItems"];
    ViewBag.CurrInvItems = existingItems;
    return View();
}

// UPDATES THE MODEL WITH NEW ITEM AND RETURNS THE PARTIAL VIEW
public ActionResult GetInvoiceItems(APInvoiceItem InvItem)
{
   IList<APInvoiceItem> existingItems = new List<APInvoiceItem>();
   if (Session != null && Session["ExistingItems"] != null)
      existingItems = (IList<APInvoiceItem>)Session["ExistingItems"];
   existingItems.Add(InvItem);
   Session["ExistingItems"] = existingItems;
   return PartialView("_APInvoiceIndex", existingItems);
}

// REMOVES AN INVOICE ITEM 
public ActionResult DeleteInvItem(int id)
{
   IList<APInvoiceItem> existingItems = new List<APInvoiceItem>();
   if (Session != null && Session["ExistingItems"] != null)
     existingItems = (IList<APInvoiceItem>)Session["ExistingItems"];

   var itemToRemove = existingItems.SingleOrDefault(r => r.Id == id);
   if (itemToRemove != null)
      existingItems.Remove(itemToRemove);

   Session["ExistingItems"] = existingItems;
   return PartialView("_APInvoiceIndex", existingItems);
}

I am able to create invoice items and partial view is loaded correctly. But if I add a new item and press Edit/Delete nothing happens. Seems the corresponding jQuery methods are not binded till the page is refreshed. Once the page is refreshed, I am able to delete an item.

_APInvoiceIndex.chtml

<td>
   <button class="btn btn-danger btn-sm" id="btnDeleteItem" data-id="@item.Id" type="button"></button>
</td>

$('[id$=btnDeleteItem]').click(function () {
   var invId = $(this).attr("data-id");
   $.ajax({
     url: '/Invoice/DeleteInvItem',
     data: { id: invId },
     success: function (data) {
        $("[id$=viewAPInvoiceIndex]").html(data);
     },
     error: function (request, textStatus, errorThrown) {
        alert(textStatus + " " + errorThrown);
     }
    });
});

Also please advice whether this is the correct way to implement the above said scenario(creation of Invoice and InvoiceItems).

Thanks for your time.

Saravanan Sachi
  • 2,572
  • 5
  • 33
  • 42
  • `_APInvoiceIndex.cshtml` is a partial view and scripts should not be in a partial (put it in the main view and use event delegation (`.on()`) for handling the button. But is it the correct way - IMHO no it's not. Refer the answers [here](http://stackoverflow.com/questions/29161481/post-a-form-array-without-successful/29161796#29161796) and [here](http://stackoverflow.com/questions/28019793/submit-same-partial-view-called-multiple-times-data-to-controller/28081308#28081308) for some options for dynamically adding new collection items –  Feb 04 '16 at 04:08
  • 1
    Refer also this [DotNetFiddle](https://dotnetfiddle.net/UjxtUW) –  Feb 04 '16 at 04:09
  • Thanks for your suggestion and sorry, the script is in main cshtml file not in partial view. I will look into those links. – Saravanan Sachi Feb 04 '16 at 04:10
  • Also try `event delegation` here like `$(document).on('click','[id$=btnDeleteItem]',function(){....});` – Guruprasad J Rao Feb 04 '16 at 04:11
  • Thanks @GuruprasadRao. It works. Any idea on how I can accept it as answer? – Saravanan Sachi Feb 04 '16 at 04:16
  • 1
    @GuruprasadRao, Make it `$('#viewAPInvoiceIndex').on(....` - more efficient –  Feb 04 '16 at 04:20
  • Thanks @StephenMuecke changed it to $('#viewAPInvoiceIndex').on('click','[id$=btnDeleteItem]',function(){....}); – Saravanan Sachi Feb 04 '16 at 04:23
  • Yea @StephenMuecke.. mentioned it in answer.. :) Thank you.. – Guruprasad J Rao Feb 04 '16 at 04:26
  • @StephenMuecke, Your DotNetFiddle link works perfect for this scenario and it is much better than my approach. But if the page is refreshed(pressing F5), the role and percentage added are not retained. Please let me know if there is any way to retain the data. – Saravanan Sachi Feb 04 '16 at 08:16
  • Not sure what you mean. The reason a user would refresh the page is just that - to generate a new (original) view and doing otherwise is not the expected behavior –  Feb 04 '16 at 08:19

1 Answers1

0

Since the view is loaded to the DOM on later part [i.e. DOM content loaded] events will not get attached to the element unless you provide an option of event delegation. So you can provide event delegation as below:

$('#viewAPInvoiceIndex').on('click','[id$=btnDeleteItem]',function(){
   var invId = $(this).attr("data-id");
   $.ajax({
     url: '/Invoice/DeleteInvItem',
     data: { id: invId },
     success: function (data) {
        $("[id$=viewAPInvoiceIndex]").html(data);
     },
     error: function (request, textStatus, errorThrown) {
        alert(textStatus + " " + errorThrown);
     }
    });
});

As per @StephenMuecke's advice we can attach event to the nearest element which has been loaded on DOM load rather than attaching it to document, which will be more efficient.

Note : I also see that you might be creating duplicate id's with id=btnDeleteItem and this not a valid html then. So better change it to class then you can do it as $('#viewAPInvoiceIndex').on('click','.btnDeleteItem'///

Guruprasad J Rao
  • 29,410
  • 14
  • 101
  • 200