I have a Telerik MVC grid, in an MVC 3 application with Razor, which is being Ajax-bound. I am now trying to add a drop list column to it, so that users can use it in edit mode, but can't figure out how. The grid displays a list of Products, and I want the drop list to contain a collection of ProductCategories, to which the Product can be associated. I've been at this for hours now and I'm out of ideas. I really hope someone here can help :)
I have been referencing a Telerik demo, which is located here.
I think the part that is hanging me up is in a help view that the demo uses. In the demo, this is called "ClientEmployee(Editor)." In my case, I've placed the helper in a file called "ProductCategoryDropList.cshtml". In this helper, I'm having a tough time getting the DropDownList to bind correctly. I think this might be because I'm not setting up the BindTo() method with the right data somehow. I've marked this point of confusion in the sample DropDownList Helper code, below, with "SomeCollectionReference," as the first parameter in a "new SelectList()" constructor call. When I try to put "Model" in that spot, I get a NullReferecne exception. When I try to access ViewBag data containing the list, I get a message similar to "the SelectList doesn't have a ProductCategoryID column," or something like that. So, I'm not sure what else to try.
I'm not sure how clear this description of my problem is, but in an effort to be complete, I've included the code that I think is relevant below.
Controller:
public ActionResult Index()
{
ViewBag.ProductCategories = new SelectList(_productCategoryService.GetActiveProductCategories(), "ProductCategoryID", "ProductcategoryName");
var products = _productService.GetProducts().ToList();
var presentationModel = _mapper.MapAsList(products);
return View(presentationModel);
}
//
// GET: /Product/
[GridAction]
public ViewResult _Index()
{
ViewBag.ProductCategories = new SelectList(_productCategoryService.GetActiveProductCategories(), "ProductCategoryID", "ProductcategoryName");
return View(new GridModel<ProductPresentationModel>
{
Data = _mapper.MapAsList(_productService.GetProducts().ToList())
});
}
View:
This is a bit long, but I've tried to simplify it by placing "// <--- DropList Here" next to the column that I am trying to work with.
@model IEnumerable<Models.PresentationModels.ProductPresentationModel>
@(Html.Telerik().Grid(Model).HtmlAttributes(new { style = "width: 100%;" })
// Give the Grid an HTML id attribute value
.Name("ProductGrid")
// Establish the promiry key, to be used for Insert, Update, and Delete commands
.DataKeys(dataKeys => dataKeys.Add(p => p.ProductID))
// Add an Insert command to the Grid Toolbar
.ToolBar(commands => commands.Insert().ButtonType(GridButtonType.ImageAndText))
// Using Ajax Data Binding to bind data to the grid
.DataBinding(dataBinding => dataBinding
// Ajax Binding
.Ajax()
.Select("_Index", "Product")
// Home.Insert inserts a new data record
.Insert("Create", "Product")
// Home.Update updates an existing data record
.Update("Edit", "Product")
// Home.Delete deletes an existing data record
.Delete("Delete", "Product")
)
.Columns(columns =>
{
columns.Bound(p => p.ProductName).Width(120);
columns.Bound(p => p.ProductDescription).Width(150);
columns.Bound(p => p.PricePerMonth).Width(120);
columns.Bound(p => p.ProductImagePath).Width(150)
columns.Bound(p => p.ProductActive).Width(120)
.ClientTemplate("<input type='checkbox' disabled='disabled' name='Active' <#= ProductActive ? checked='checked' : '' #> />");
columns.Bound(p => p.ProductCategoryName); // <--- DropList Here
columns.Command(commands =>
{
commands.Edit().ButtonType(GridButtonType.Image);
commands.Delete().ButtonType(GridButtonType.Image);
});
})
.Editable(editing => editing.Mode(GridEditMode.PopUp))
.ClientEvents(events => events.OnEdit("onEdit"))
.Pageable()
.Scrollable()
.Sortable()
.Filterable()
)
@section HeadContent {
<script type="text/javascript">
function onEdit(e) {
$(e.form).find('#ProductCategoryName').data('tDropDownList').select(function (dataItem) {
return dataItem.Text == e.dataItem['ProductCategoryName'];
});
}
</script>
}
Model:
[DisplayName(@"Category Name")]
[UIHint("ProductCategoryDropList"), Required]
[StringLength(255, ErrorMessage = @"Product Category Name cannot be more than 255 characters in length")]
public string ProductCategoryName
{
get
{
string name = string.Empty;
if (_model.ProductCategory != null)
{
name = _model.ProductCategory.ProductCategoryName;
}
return name;
}
set
{
if (_model.ProductCategory != null)
{
_model.ProductCategory.ProductCategoryName = value;
}
}
}
DropList Helper:
@model Models.PresentationModels.ProductPresentationModel
@(Html.Telerik().DropDownList()
.Name("ProductCategoryName")
.BindTo(new SelectList(<SomeCollectionReference>, "ProductCategoryID", "ProductCategoryName"))
)
ProductMapper:
public List<ProductPresentationModel> MapAsList(List<Product> products)
{
//var categoryList = new SelectList(_productCategoryService.GetProductCategories().ToList(), "ProductCategoryID", "ProductCategoryName");
var presentationModels = products
.Select(x => new ProductPresentationModel()
{
ProductID = x.ProductID,
ProductCategoryID = ((x.ProductCategory != null) ? x.ProductCategory.ProductCategoryID : 0),
ProductCategoryName = ((x.ProductCategory != null) ? x.ProductCategory.ProductCategoryName : String.Empty),
ProductName = x.ProductName,
ProductDescription = x.ProductDescription,
PricePerMonth = x.PricePerMonth,
ProductImagePath = x.ProductImagePath,
ProductActive = x.ProductActive,
ProductCategories = new SelectList(_productCategoryService.GetProductCategories().ToList(), "ProductCategoryID", "ProductCategoryName")//categoryList
}).ToList();
return presentationModels;
}
I asked the good support folks at Telerik about this. Here is the answer they gave me:
The Model is null because the DropDownList partial view is rendered for ajax editing. In that case the grid prerenders all partial view editor templates so it can use them on the client-side. In that case Model will be null. If you used server binding and editing the Model would be set to the right value.
At this point, I'm going to accept this post as the answer to my question. It's unfortunate that I have to accept my own answer in this case, but ... well, I didn't get any others to choose from :)