1

So I have this application in ASP MVC 3. My database has two tables: Comenzi and DetaliiComenzi with one-to-many relationship (and Link-to-Sql) - in my application I want my users to buy some products by making a oder(stored in table comenzi) and for that order a list of products he wants to buy (will be stored in DetaliiComenzi with Order.id as foreign key). Basically, after I create a new entry for Comenzi, I want to be able to make a list of products for that order (something like a shop chart but the user will choose his products in a view, adding how many products as he likes).

I have used Steve Sanderson’s method of editing (and creating) a variable length list.

-- Here is the model for which I create the list.

When I'm choosing a single product to oder I must first select the Group (Grupa) which he belongs to from a dropdownlist (using ListaGrupe) and then from a second dropdownlist (ListaProduse) a product from that particular group of products I selected in the first dropdownlist.

public class Comd
{
    public string Grupa { get; set; }

    public string Produs { get; set; }

    public string Cantitate { get; set; }

    public string Pret { get; set;}

    public string TVA { get; set; }

    public string Total { get; set; }

    public List<SelectListItem> ListaGrupe
    {
        get;
        set;
    }

    public List<SelectListItem> ListaProduse
    {
        get;
        set;
    }
}    

--The Controller:

    public ActionResult ComandaDetaliu(string id)
    {

        Comd model = new Comd();
        IProduseRepository _prod = new ProduseRepository();

        model.ListaGrupe = _listecomanda.GetGrupe();

        string first = model.ListaGrupe[0].Value;

        model.ListaProduse = _listecomanda.GetProduse(first);

        string pret = _prod.GetByDenumire(model.ListaProduse[0].Text).pret.ToString();
        model.Pret = pret;

        double fr = 0.24;
        model.TVA = fr.ToString();

        var data = new[] { model };

        return View(data);
    }

-- The View

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"       Inherits="System.Web.Mvc.ViewPage<IEnumerable<InMVC3.Models.Comd>>" %>
<%@ Import Namespace="InMVC3.Helpers"%>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

 <h2>Comanda numarul: <%: RouteData.Values["id"].ToString()%></h2>

<% using(Html.BeginForm()) { %>
    <div id="editorRows">
        <% foreach (var item in Model)
            Html.RenderPartial("ProduseEditor", item);
        %>
    </div>
    <%= Html.ActionLink("Adauga alt produs", "Add", null, new { id = "addItem" }) %>

    <input type="submit" value="Finished" />
<% } %>    

-- The Partial View "Produse Editor"

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<InMVC3.Models.Comd>" %>
<%@ Import Namespace="InMVC3.Helpers" %>

<div class="editorRow">

                   <script type="text/javascript">
                       $(document).ready(function () {
                           $("#Grupa").change(function () {

                               var url = '<%= Url.Content("~/") %>' + "Comenzi/ForProduse";
                               var ddlsource = "#Grupa";
                               var ddltarget = "#Produs";
                               $.getJSON(url, { id: $(ddlsource).val() }, function (data) {
                                   $(ddltarget).empty();
                                   $.each(data, function (index, optionData) {
                                       $(ddltarget).append("<option value='" + optionData.Value + "'>" + optionData.Text + "</option>");
                                   });

                               });
                           });
                       });

            </script>    


<% using(Html.BeginCollectionItem("comds")) { %>

    Grupa: <%= Html.DropDownListFor(x => x.Grupa, Model.ListaGrupe) %> 
    Produsul: <%= Html.DropDownListFor(x => x.Produs, Model.ListaProduse) %> 
    Cantitate: <%=Html.TextBoxFor(x=>x.Cantitate) %>
    Pret: <%=Html.DisplayFor(x => x.Pret, new { size = 4})%>
    TVA: <%= Html.DisplayFor(x=>x.TVA) %>
    Total: <%=Html.DisplayFor(x=>x.Total) %>

    <a href="#" class="deleteRow">Sterge</a>
<% } %>

-- And the JsonResult method

    public JsonResult ForProduse(string id)
    {
        throw new NotSupportedException();
        JsonResult result = new JsonResult();
        var produsele = _listecomanda.GetProduse(id);
        result.Data = produsele;
        result.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
        return result;
    }

All I need to know is how to make the call to the JsonResult action because this is what doesn't works so that when I change the selected value in the first dropdownlist to dynamically change the second too. Of course, I also need to change the other properties too but that after I get how to make getJson to work.

If you need more details please tell me.

UPDATE 1:

--The Helper

public static class HtmlPrefixScopeExtensions
{
    private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";

    public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
    {
        var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
        string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

        // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
        html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));

        return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
    }

    public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
    {
        return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
    }

    private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
    {
        // We need to use the same sequence of IDs following a server-side validation failure,  
        // otherwise the framework won't render the validation error messages next to each item.
        string key = idsToReuseKey + collectionName;
        var queue = (Queue<string>)httpContext.Items[key];
        if (queue == null) {
            httpContext.Items[key] = queue = new Queue<string>();
            var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
            if (!string.IsNullOrEmpty(previouslyUsedIds))
                foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
                    queue.Enqueue(previouslyUsedId);
        }
        return queue;
    }

    private class HtmlFieldPrefixScope : IDisposable
    {
        private readonly TemplateInfo templateInfo;
        private readonly string previousHtmlFieldPrefix;

        public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
        {
            this.templateInfo = templateInfo;

            previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
            templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
        }

        public void Dispose()
        {
            templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
        }
    }
}

UPDATE I now have another issue. When I Post that list to the actiont, I get the following error at the foreach statement inside the controller action: Object reference not set to an instance of an object.

-- The Controller Action

    [HttpPost]
    public ActionResult ComandaDetaliu(IEnumerable<Comd> comenzi)
    {
        if (ModelState.IsValid)
        {
            foreach (var item in comenzi)
            {
                detalii_comenzi det = new detalii_comenzi();

                det.id_comanda = Convert.ToInt32(RouteData.Values["id"].ToString());
                det.id_produs = Convert.ToInt32(item.Produs);
                det.cantitate_comandata = Convert.ToDecimal(item.Cantitate);
                det.cantitate_livrata = 0;
                det.pret =Convert.ToDecimal(item.Pret);
                det.tvap = Convert.ToDecimal(item.TVA);
            }
            return RedirectToAction("Index");
        }
        return View(comenzi);
    }
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
davvidd
  • 73
  • 2
  • 10

1 Answers1

1

Your problem is the duplicate IDs - Every row has a dropdown with ID "Grupa" so your jquery selector will match the dropdowns in every row.

You need to add a prefix to the controls - there are several ways to achieve that - a search for "mvc3 field prefix" brings up several other questions:

Most of those are focused on mapping when the form is posted, but the same issue applies with your javascript.

You could just update the ids in your script to something like "#@(ViewBag.Prefix)Grupa", but a better approach would be to use classes instead of ids in your selector and make the script reusable - something like:

ddlSource = this;
ddlDest = this.Parent().find(".produs");
Community
  • 1
  • 1
Tom Clarkson
  • 16,074
  • 2
  • 43
  • 51
  • I'm new to ASP MVC so I'll be looking into this for a while. Will it work if I only change the script as you pointed out? – davvidd Jun 16 '11 at 06:55
  • That should get you most of the way for the dynamic updates. You may still have problems with duplicates when submitting the form though - I think BeginCollectionItem will set prefixes for EditorFor calls but not DropDownListFor. – Tom Clarkson Jun 16 '11 at 07:04
  • I updated the question with the htmlhelper. I think the prefixes are set correctly. Are they? – davvidd Jun 16 '11 at 11:54
  • It's easiest do check stuff like that client side - look at the generated elements with firebug or chrome inspect element. As long as the ids aren't repeated and you can predict what they are it's all good. – Tom Clarkson Jun 16 '11 at 12:56
  • So the prefixes are alright.
    – davvidd Jun 16 '11 at 15:00
  • I'm not familiar enough with collection forms to know for sure, but shouldn't the prefix be "comenzi" to match your controller? Another thing to be aware of is that only properties that exist as form fields will be populated in the post method - the display only ones (pret & TVA) will be null unless you get the record from the db again or add hidden fields. – Tom Clarkson Jun 17 '11 at 03:19
  • Yes, that's right. The IEnumerable comds ..... that's how the parameter should be in the Controller Post Action. Thanks a lot. I'm kicking myself for that but I least I can move on.... – davvidd Jun 17 '11 at 06:14