I'm trying to set up a Dropdown list in Kendo UI Grid using HTML helpers.
When I click to edit the grid, the dropdown appears and I can select a value. However, this does not save in the database when I click update (though the simple string WordName field does).
I would also like the CatId value from the WordViewModel to also be displayed as a word/dropdown when you're not editing the fields.
As far as I can tell, I have nothing which links the int CatId to the GetCategories list. How do I go about connecting those two? I've read a little about column.ForeignKey, but I don't understand it. Below is all my relevant code.
My WordViewModel (which loads from a similar, slightly more complex database model)
public class WordViewModel
{
public int WordId { get; set; }
[Required]
public string WordName { get; set; }
public Nullable<int> CatId { get; set; }
}
My Category model (generated by the database)
public partial class Category
{
public Category()
{
this.Words = new HashSet<Word>();
}
public int CatId { get; set; }
public string CategoryName { get; set; }
public bool IsActive { get; set; }
public virtual ICollection<Word> Words { get; set; }
}
Here's my razor code for the grid in Index.cshtml
@(Html.Kendo().Grid<WordViewModel>
()
.Name("wordGrid")
.DataSource(dataSource => dataSource
.Ajax()
.Model(model =>
{
model.Id(word => word.WordId); // Specify the property which is the unique identifier of the model
model.Field(word => word.WordId).Editable(false); // Make the ID property not editable
})
.Read(read => read.Action("Read", "Words")) //Populate the grid with Words
.Update(update => update.Action("Update", "Words")) // Action invoked when the user saves an updated data item
)
.Editable(editable => editable.Mode(GridEditMode.InLine)) // Use inline editing mode
.Columns(columns =>
{
columns.Bound(c => c.WordName);
columns.Bound(c => c.CatId).EditorTemplateName("_CategoryDropdown"); //link to EditorTemplate with the same name
columns.Command(commands =>
{
commands.Edit(); // The "edit" command will edit and update data items
}).Title("Commands").Width(200);
})
.Filterable()
)
Editor Template _CategoryDropdown.cshtml
@(
Html.Kendo().DropDownList()
.Name("Category") //is this Name important?
.DataTextField("CategoryName")
.DataValueField("CategoryId")
.DataSource(source =>
{
source.Read(read => { read.Action("GetCategories", "Words"); });
})
.OptionLabel("Select a category")
)
My function to get the drop down list from the database.
public JsonResult GetCategories()
{
var items = db.Categories.ToList().Select(c => new Category
{
CatId = c.CatId,
CategoryName = c.CategoryName
});
return Json(items, JsonRequestBehavior.AllowGet);
}
Here is a working solution. Rather than using column.ForeignKey, I ended up manually connecting the CatId with the CategoryName and including both in the WordViewModel.
My final files:
public class WordViewModel
{
public int WordId { get; set; }
[Required]
public string WordName { get; set; }
public string CategoryName { get; set; } //I added this field which is actually displayed on the grid
public Nullable<int> CatId { get; set; } //only used temporarily to transfer data
}
I did not end up referencing the Category model almost at all.
In my Grid I changed the binding on CategoryId to bind on CategoryName instead. Essentially with my solution, I only referenced Category Name in the view, and just matched up CategoryName with CategoryId in the Read/Update functions in the controller.
//The Title string below needs to be the same as the Name field in the EditorTemplate and possibly the same as the name in the model
columns.Bound(c => c.CategoryName).Title("CategoryName").EditorTemplateName("_CategoryDropdown");
The location of this file is important. Views/Shared/EditorTemplates/_CategoryDropdown.cshtml:
@(
Html.Kendo().DropDownList()
.Name("CategoryName") //This name has to be the same as the Title on the main grid page
.DataTextField("CategoryName")
.DataValueField("CategoryName")
.DataSource(source =>
{
source.Read(read => { read.Action("GetCategories", "Words"); });
})
.OptionLabel("Select a category")
)
The Words/GetCategories function was correct.
I had to do some work in Words/Read to get the category name from the category ID
public ActionResult Read([DataSourceRequest] DataSourceRequest request)
{
var items = db.Words.Select(w => new WordViewModel
{
WordId = w.WordId,
CatId = w.CatId,
CategoryName = "",
WordName = w.WordName
}).ToList(); //need .ToList to be able to iterate through it
//finish building the word
foreach(var item in items)
{
if(item.CatId!=null)
{
//add CategoryName corresponding to each CatId
//In my database I have a table for Categories which matches up CatId to CategoryName
Category cat = db.Categories.Select(c => c).Where(c => c.CatId == item.CatId).FirstOrDefault();
item.CategoryName = cat.CategoryName;
}
}
return Json(items.ToDataSourceResult(request), JsonRequestBehavior.AllowGet);
}
and some stuff in Words/Update to do the reverse Name->Id:
public ActionResult Update([DataSourceRequest]DataSourceRequest request, [Bind(Prefix = "models")] WordViewModel word)
{
if (ModelState.IsValid)
{
// Create a new Product entity and set its properties from the posted ProductViewModel
var entity = new Word
{
WordId = word.WordId,
CategoryName = word.CategoryName,
WordName = word.WordName
};
if (word.CategoryName != "")
{
//match CategoryWord to CatID
Category cat = db.Categories.Select(c => c).Where(c => c.CategoryName == word.CategoryName).FirstOrDefault();
entity.CatId = cat.CatId;
}
// Attach the entity
db.Words.Attach(entity);
// Change its state to Modified so Entity Framework can update the existing product instead of creating a new one
db.Entry(entity).State = EntityState.Modified;
// Update the entity in the database
db.SaveChanges();
}
// Return the updated product. Also return any validation errors.
return Json(new [] { word }.ToDataSourceResult(request, ModelState));
}
There might be some minor errors since this is a little simplified from my real code, but all the important pieces are there. Figuring out all the linkages and what I could depend on Kendo for vs what I had to manually was pretty difficult to figure out. Good luck to anyone else trying to use Kendo Grid, and I hope this example helps!