custom in-line editing, sub-object not passed to update()

3 posts, 0 answers
  1. Paul
    Paul avatar
    11 posts
    Member since:
    May 2013

    Posted 09 Mar 2014 Link to this post

    I am missing something important regarding this example grid

        http://demos.telerik.com/kendo-ui/web/grid/editing-custom.html?mvc

    I based my shopProductAdmin view/controller closely on this example, making use of the custom editor used in the example for CategoryViewModel, for my priceStrategyViewModel.

    Other differences from the reference, 
    I am using InLine editing instead of InCell.  
    I am not using Batch mode.
    The read function has additional data it sends to get the products.
    The read/update/delete functions all have role requirement hints for security, but i tested without these security hints and the behavior is the same.

    There is something not right about the data being passed around though.  Some binding somewhere must be amiss and I can't seem to spot it.
    Bad behaviors I see..

    a) the default priceStrategy, when no record exists in the database, the PriceStrategyViewData.priceStrategyID/name are populated with the 99_none and the proper index.  That part works.
    b) when I edit a field, the customEditor comes up.. good
    c) the value of the priceStrategy changes from 99_none to the first value in the priceStrategies list -- bad!. it should still be 99_none.  This is the first indication that some data is not being passed around properly.
    d) when I update the record on the grid, the update function in the controller see's the new product and all fields are properly updated with the notable exception of the PriceStrategyViewModel.* fields.

    One piece of code in the example that I haven't gleaned why it is there.. in the example that I found on the installation package.. (this isn't shown on the web version, as the entities are not part of the sample code there), the entity not only has a 
      public CategoryViewModel Category { get; set; }
    member.. which I understand that.  But it also has this member right after it.. which seems to have no function anywhere in the grid.  Was this intentional?  I included it in my model also only to be consistent, but I haven't decided why it is there.  It seems like it isn't serving a purpose.

              public int? CategoryID { get; set; }

    Anyway, here is my grid in the Admin.cshtml


                    @(Html.Kendo().Grid<EveTools.WebUI.Models.shopProductsAdminViewModel>()
                        .Name("productsGrid")
                        .Columns(columns =>
                        {
                            columns.Bound(product => product.typeName).Width(200).Title("Shop");
                            columns.Bound(product => product.forSale).Title("ForSale?");
                            columns.Bound(product => product.PriceStrategy).ClientTemplate("#=PriceStrategy.name#").Width(150);
                            columns.Bound(product => product.markup).Title("markup").Width(50);
                            columns.Bound(product => product.price).Title("Price");
                            columns.Command(command => { command.Edit();  command.Destroy().Text("Clear"); }).Width(100);
                        })
                        .DataSource(dataBinding => dataBinding
                            .Ajax()
                            .ServerOperation(false)                        
                            .Model(model => model.Id(p => p.typeID))
                            .Read(read => read.Action("ProductListAdmin", "Shop").Data("additionalProductListData"))
                            .Destroy(update => update.Action("EditingInline_DeleteProduct", "Shop"))
                                    .Update(update => update.Action("EditingInline_UpdateProduct", "Shop"))
                            .Events(events => events.Error("error_handler"))
                            .Model(model =>
                            {
                                model.Id(p => p.typeID);
                                model.Field(p => p.typeName).Editable(false);
                                model.Field(p => p.price).Editable(false);
                                model.Field(p => p.PriceStrategy).DefaultValue(
                                    ViewData["defaultPriceStrategy"] as EveTools.WebUI.Models.PriceStrategyViewModel);
                            })
                        )
                        .Editable(editable => editable.Mode(GridEditMode.InLine))
                        .Pageable()
                        .Sortable()
                        .Scrollable()
                        .HtmlAttributes(new { style = "height:480px;" })

    And the critical controller functions.

    This function returns the data to the grid when requested.. by means of another list control of categories.

            public ActionResult ProductListAdmin([Kendo.Mvc.UI.DataSourceRequest]Kendo.Mvc.UI.DataSourceRequest request, int? categoryID)
            {
                // if there is no category, there are also no products
                if (!categoryID.HasValue)
                    return null;

                EveShop.Domain.Concrete.EFEveShopDbContext db = new EveShop.Domain.Concrete.EFEveShopDbContext();

                PriceStrategyViewModel defaultPriceStrategy = GetDefaultPriceStrategy();

                // extract data from db, and fill in shopProductsAdminViewModels
                var products = (from t in db.invTypes
                                join p in db.shopProducts
                                on t.invTypeID equals p.shopProductID into output
                                from o in output.DefaultIfEmpty()
                                select new shopProductsAdminViewModel
                                {
                                    typeID = t.invTypeID,
                                    groupID = t.groupID,
                                    typeName = t.typeName,
                                    volume = t.volume,
                                    basePrice = t.basePrice,
                                    published = t.published,
                                    marketGroupID = t.marketGroupID,
                                    iconID = t.iconID,
                                    markup = (t.shopProduct == null) ? 0 : t.shopProduct.markup,
                                    forSale = (t.shopProduct == null) ? false : t.shopProduct.forSale,
                                    price = (t.shopProduct == null) ? 0 : t.shopProduct.shopProductPrice.price,
                                    // not sure why i am filling this one in.. it was included in the original example though.. no binding to the grid?
                                    priceStrategyID = (t.shopProduct == null) ? defaultPriceStrategy.priceStrategyID : t.shopProduct.shopPriceStrategy.shopPriceStrategyID,
                                    // the actual price strategy data.. this one gets bound to the grid
                                    PriceStrategy = new PriceStrategyViewModel
                                    {
                                        priceStrategyID = (t.shopProduct == null) ? defaultPriceStrategy.priceStrategyID : t.shopProduct.shopPriceStrategy.shopPriceStrategyID,
                                        name = (t.shopProduct == null) ? defaultPriceStrategy.name : t.shopProduct.shopPriceStrategy.name
                                    }
                                }).Where(e => e.marketGroupID == categoryID && e.published != false)
                        .OrderBy(e => e.typeName)

                return Json(products.ToDataSourceResult(request));
            }


    This function receives the update record callback

            [AcceptVerbs(HttpVerbs.Post)]
            public ActionResult EditingInline_UpdateProduct([DataSourceRequest] DataSourceRequest request, shopProductsAdminViewModel product)
            {
                // do something with updated product
                // product.priceStrategy.* is always the default value, not the edited value.

                return Json(new[] { product }.ToDataSourceResult(request, ModelState));
            }

    The PriceStrategyViewModlEditor.cshtml

    @model EveTools.WebUI.Models.PriceStrategyViewModel
           
    @(Html.Kendo().DropDownListFor(m => m)
            .Name("PriceStrategyViewModel")
            .DataValueField("priceStrategyID")
            .DataTextField("name")
            .BindTo((System.Collections.IEnumerable)ViewData["priceStrategies"])
    )












  2. Paul
    Paul avatar
    11 posts
    Member since:
    May 2013

    Posted 09 Mar 2014 in reply to Paul Link to this post

    And the entity model... 

        public class shopProductsAdminViewModel
        {

            public int typeID { get; set; }
            public Nullable<int> groupID { get; set; }
            public string typeName { get; set; }
            public Nullable<double> volume { get; set; }
            public Nullable<decimal> basePrice { get; set; }
            public Nullable<bool> published { get; set; }
            public Nullable<int> marketGroupID { get; set; }
            public Nullable<int> iconID { get; set; }
            public bool forSale { get; set; }
            //public Nullable<int> priceStrategy { get; set; }
            public Nullable<double> markup { get; set; }
            public Nullable<decimal> price { get; set; }

            [UIHint("PriceStrategyViewModelEditor"), Required]
            public PriceStrategyViewModel PriceStrategy { get; set; }

            public int? priceStrategyID { get; set; }
       }
  3. Paul
    Paul avatar
    11 posts
    Member since:
    May 2013

    Posted 09 Mar 2014 Link to this post

    Solved the problem.
    Occured to me to look at the request being sent back to the application via Chromes debug console.
    Why did this did not occur to me before?  I had inspected the value received in VisualStudio, but had not viewed the data being sent as seen by the browser.

    So the problem was.. in the customeditor, the .Name field should match up with the field sending/receiving the data from the parent model.
    In this case I had used
      .Name("PriceStrategyViewModel")
    which is the class name, not the field name.

    It should have been
      .Name("PriceStrategy")
    like so:

    @model EveTools.WebUI.Models.PriceStrategyViewModel
           
    @(Html.Kendo().DropDownListFor(m => m)
            .Name("PriceStrategy")
            .DataValueField("priceStrategyID")
            .DataTextField("name")
            .BindTo((System.Collections.IEnumerable)ViewData["priceStrategies"])
    )
Back to Top