I'm using Kendo UI MVC and I have a view that contains details about an object. On that page I have a Kendo UI Grid that shows a list of notes about the object. I allow the user to create or edit the notes from the grid.
The problem I have is when the user clicks the add button I need to pass the id of the page's object. I'm using GridEditMode.PopUp.
Basically, this is what I have:
public class Item {
public int Id { get;set; }
...
}
public class Note {
public int ItemId {get;set;}
...
}
Here is the grid code:
#(Html.Kendo()
.Grid<NoteViewModel>()
.Name("kendo-grid")
.Columns(columns =>
{
columns.Bound(n => n.NoteDateTime).Title("Date").Format("{0:MM/dd/yyyy}");
columns.Bound(n => n.NoteDateTime).Title("Time").Format("{0:h:mm tt}").Sortable(false);
columns.Bound(n => n.NoteActivityType).Title("Activity Type");
columns.Bound(n => n.NoteDescription).Title("Description");
columns.Bound(n => n.NoteDetail).Title("Notes");
columns.Command(command => { command.Edit(); command.Destroy(); }).Width(200);
})
.ToolBar(toolbar => toolbar.Create())
.Editable(editable => editable.Mode(GridEditMode.PopUp).TemplateName("Note"))
.Mobile()
.Pageable()
.Sortable()
.Filterable()
.Reorderable(r => r.Columns(true))
.Resizable(r => r.Columns(true))
.DataSource(dataSource => dataSource.Ajax()
.Model(model => model.Id(note => note.Id))
.PageSize(25)
.Sort(sort =>
{
sort.Add(note => note.NoteDateTime);
})
.Read(read => read.Action("ReadNotes", "Case").Data("getCaseId"))
.Create(a => a.Action("CreateNote", "Case"))
.Update(u => u.Action("UpdateNote", "Case"))
.Destroy(d => d.Action("DeleteNote", "Case"))
)
)
I need to set Note.ItemId when the user clicks the add button on the grid. Or, is there a better way to do this, as in send the ItemId value on the post?
I ended up getting this to work by hooking into the edit event of the popup. I wasn't able to figure out how to do it on the initial setup, so I added this to doc ready handler to the edit popup. This kind of feels like a hack, so if someone has a better way I'd love to hear about it. The #ItemId input is already on the details page, so I figured I may as well use it.
$(function () {
function setItemId(event) {
var uid = $('.k-edit-form-container').closest('[data-role=window]').data('uid');
var model = $('#kendo-grid').data('kendoGrid').dataSource.getByUid(uid);
if (model.get('ItemId') === 0) {
model.set('ItemId', Number($('#ItemId').val()));
}
}
var grid = $('#kendo-grid').data('kendoGrid');
grid.bind('edit', setItemId);
});
I'm not sure if it's possible what you want, but to get you on the way this is how you'd start doing it.
You generally want a flat viewmodel, containing everything you want to use.
public class NoteViewModel {
public int ItemId { get;set; }
}
Then properly setup the use of your editor template. Since your ItemId is now part of the grid's model it'll be send to the controller on edit/create.
#(Html.Kendo()
.Grid<NoteViewModel>()
.Name("kendo-grid")
.Columns(columns =>
{
columns.Bound(n => n.ItemId).Hidden();
columns.Bound(n => n.NoteDateTime).Title("Date").Format("{0:MM/dd/yyyy}");
columns.Bound(n => n.NoteDateTime).Title("Time").Format("{0:h:mm tt}").Sortable(false);
columns.Bound(n => n.NoteActivityType).Title("Activity Type");
columns.Bound(n => n.NoteDescription).Title("Description");
columns.Bound(n => n.NoteDetail).Title("Notes");
columns.Command(command => { command.Edit(); command.Destroy(); }).Width(200);
})
.ToolBar(toolbar => toolbar.Create())
.Editable(editable => editable.Mode(GridEditMode.PopUp).TemplateName("NoteTemplate"))
Etc...
)
Finally create your template (called NoteTemplate.cshtml) and place it in Views/Shared/EditorTemplates so Kendo can find it.
#model NoteViewModel
Date: #Html.EditorFor(l => l.NoteDateTime)
Note Description: #Html.EditorFor(l => l.NoteDescription)
(Add all fields you need to edit here)
I had same problem
Problem is that some of fields of the model(viewmodel) is nullable
Model nullable field is not fully supported in Kendo UI
Related
I am trying to replicate exactly what is on this Kendo demo page with the Categories column. Simply a drop down list inside the grid. I copied all their source code exactly, but when I click the column with a drop down, the grid simply flashes. I can edit the FullName value fine. I tried to get an animation of the effect with a screen recorder, but for some reason it is not showing everything I see. In this image I am clicking the "Rol" drop down column multiple times.
What the gif does not show is that when I click the column, it briefly changes to a drop down control for a split second. I was able to capture this by adding a "debugger" line to the onDataBound event for the dropdown and taking a screenshot:
What is going on? Why doesn't it just pop open?
Here is some of the relevant code:
The EditorTemplates/RolList.cshtml file. Note that NONE of these events fire except the onDataBound event. That is probably a clue.
#model MyCompany.Web.CaseLink.Mvc.Admin.Models.RolDropDown
#(Html.Kendo().DropDownListFor(m => m)
.BindTo((System.Collections.IEnumerable)ViewData["roles"])
.DataValueField("RolId")
.DataTextField("RolDescription")
.Events(events => events
.Select("onSelect")
.Change("ddlChange")
.Open("onOpen")
.DataBound("onDatabound")
)
)
<script type="text/javascript">
function onSelect(e) {
console.log("ddl select" + e);
}
function ddlChange(e) {
console.log("ddl change" + e);
}
function onOpen(e) {
console.log("ddl open" + e);
}
function onDatabound(e) {
debugger;
console.log("ddl databound" + e);
}
</script>
Here is the partial view containing the MVC Grid itself. Note that the gridChange event was added just for debugging, and it does not fire when clicking in the drop down column.
#(Html.Kendo().Grid<CaseMemberGridRow>()
.Name("grid")
.Columns(columns =>
{
columns.Bound(c => c.FullName)
.Title("Name")
;
columns.Bound(p => p.Rol).ClientTemplate("#=Rol.RolDescription#").Width(180);
columns.Bound(c => c.Email)
.Title("Email")
;
columns.Bound(c => c.Telephone)
.Title("Phone")
;
columns.Command(command => command.Destroy()).Width(150);
})
.ToolBar(toolBar =>
{
toolBar.Create();
toolBar.Save();
})
.HtmlAttributes(new { style = "height: 550px;" })
.Scrollable()
.Sortable()
.Editable(editable => editable.Mode(GridEditMode.InCell))
.DataSource(dataSource => dataSource
.Ajax()
.Batch(true)
.ServerOperation(false)
.Events(events => events.Error("error_handler").Change("gridChange"))
.Model(m =>
{
m.Field(p => p.FullName).Editable(true);
m.Field(p => p.Rol).Editable(true);
m.Field(p => p.Rol).DefaultValue(
ViewData["defaultRol"] as MyCompany.Web.CaseLink.Mvc.Admin.Models.RolDropDown);
})
.Create(create => create.Action("AddCaseMemberNoCustomerRecord_Create", "AddMember"))
.Update(update => update.Action("AddCaseMemberNoCustomerRecord_Update", "AddMember"))
)
)
Controller is nothing special, as I said, I am doing exactly when Telerik did in their demo. A sample:
var roles = _rolService.GetAllRolesDTO()
.OrderBy(o => o.Description)
.Select(r => new RolDropDown
{
RolDescription = r.Description,
RolId = r.RolId
});
ViewData["roles"] = roles;
ViewData["defaultRol"] = roles.First();
If there is something to look for in the debugger on the "onDataBound" event, let me know. As you can see here, the datasource is definitely populated.
EDIT: I am aware that you can attempt to do the same thing using the ForeignKey type of column. When I try that, I can get a drop down to work once. After selecting a value, the column starts acting like before where you cannot change the value.
columns.ForeignKey(p => p.RolId, (System.Collections.IEnumerable)ViewData["roles"], "RolId", "Description")
.Title("Role")
.Width(150);
Kendo UI edit option showing some unexpected behaviour, as you can see in the image there is a text box below Server column and 2 below ServerIP column all containing the id of server "SQL" i selected. Problem is when ever i want to show Server IP column this behaviour occurs, both server and server IP are from the same table.
#(Html.Kendo().Grid<EnvironmentPOCO>()
.Name("Grid")
.Columns(columns =>
{
columns.Bound(d => d.EnvironmentName).Width(200).Title("EnvirontmentName");
columns.ForeignKey(d => d.EnvironmentTypeID, (List<EnvironmentTypePOCO>)ViewData["EnvironmentType"], "EnvironmentTypeID", "EnvironmentTypeCode").Width(150).Title("EnvironmentCode").EditorTemplateName("_EnvironmentCodeDropDown");
columns.ForeignKey(d => d.ServerID, (List<ServerPOCO>)ViewData["ServerDetails"], "ServerID", "ServerName").Width(200).Title("Server").EditorTemplateName("_ServerDropDown");
columns.ForeignKey(d => d.ServerID, (List<ServerPOCO>)ViewData["ServerDetails"], "ServerID", "ServerIP").Width(200).Title("ServerIP");
columns.ForeignKey(d => d.ProjectID, (List<ProjectPOCO>)ViewData["Projects"], "ProjectID", "ProjectName").Width(200).Title("ProjectName").EditorTemplateName("_ProjectNameDropDown");
// columns.ForeignKey(d => d.ProjectID, (List<ProjectPOCO>)ViewData["Projects"], "ProjectID", "ProjectDescription").Width(200).Title("ProjectDescription")/*.EditorTemplateName("_ProjectDescription")*/;
columns.Command(d =>
{
d.Edit();
d.Destroy();
}).Width(200).Title("Action");
})
.ToolBar(tools => tools.Create())
.Sortable()
.Pageable()
.Filterable()
.DataSource(dataSource => dataSource
.Ajax()
.Model(model =>
{
model.Id(m => m.EnvironmentID);
model.Field(m => m.EnvironmentName);
model.Field(m => m.EnvironmentTypeID);
model.Field(m => m.ProjectID);
model.Field(m => m.ServerID);
})
.Read(read => read.Url(ViewBag.ApiBaseUrl).Type(HttpVerbs.Get))
.Create(create => create.Url(ViewBag.ApiBaseUrl).Type(HttpVerbs.Post))
.Update(update => update.Url(ViewBag.ApiBaseUrl).Type(HttpVerbs.Put))
.Destroy(destroy => destroy.Url(ViewBag.ApiBaseUrl).Type(HttpVerbs.Delete))
)
)
I found a solution to this, actually if you want to show 2 fields of the foriegn key, You can make a property in your class.
public string ServerDetailsProperty
{
get
{
return string.Format(" Name: {0} || IP: {1}", ServerName, ServerIP);
}
}
then call it in your csHTML file like this.
columns.ForeignKey(d => d.ServerID, (List<ServerPOCO>)ViewData["ServerDetails"], "ServerID", "**ServerDetailsProperty**").Width(200).Title("ServerIP");
Now if you press Edit you want see the unexpected behiour as in the diagram.
Try Changing the id and name attribute of the Server IP columns as the Grid is not able to differentiate between the Server Column and Server IP column on Edit.
For your reference I have tried below:
columns.ForeignKey(d => d.ServerID, (List<ServerPOCO>)ViewData["ServerDetails"], "ServerID", "ServerIP").Width(200).Title("ServerIP").HtmlAttributes(new { #id="ServerIP_#=ServerIP#", #name="ServerIP_#=ServerIP#" })
Let me know if this doesn't solve your issue.
EDIT:
You can add the dropdown in client template as below:
columns.Bound(s => s.ServerID).ClientTemplate((#Html.Kendo().DropDownList()
.BindTo((List<ServerPOCO>)ViewData["ServerDetails"])
.Name("ServerIP#=ServerIP#")
.DataTextField("ServerIP")
.DataValueField("ServerID")
.ToClientTemplate()).ToHtmlString());
On Grid DataBound event set the grid scripts to load with document as below:
function onGridDataBound(e) {
$('#GridName script').appendTo(document.body);
}
Finally set the field to readonly in model meta:
model.Field(s => s.SensorID).Editable(false);
For further information have a look at the explaination: Dropdown in Column Client Template
I have a Kendo Grid which has a popup editable template,
If possible i would like to pass the model (the model of the row, or at least its Id) to the editable template
Grid
#(Html.Kendo().Grid<Client>()
.Name("grid")
.Columns(columns =>
{
columns.Bound(c => c.Name).Width(140);
columns.Bound(c => c.Status);
columns.Bound(c => c.ProcesingStyle);
columns.Bound(c => c.ArchiveDays);
columns.Command(command =>
{
command.Edit().Text(" ");
command.Destroy().Text(" "); ;
}).Width(90);
})
.ToolBar(toolbar => toolbar.Create().Text("New"))
.Editable(editable => editable
.Mode(GridEditMode.PopUp)
.TemplateName("Client").AdditionalViewData(new { Client = Model })
.Window(w => w.Title("Site")))
.HtmlAttributes(new { style = "height: 380px;" })
.Scrollable()
.Sortable()
.Selectable()
.Resizable(resize => resize.Columns(true))
.Reorderable(reorder => reorder.Columns(true))
.Events(events => events.Change("onChange"))
.Pageable(pageable => pageable
.Refresh(true)
.PageSizes(true)
.ButtonCount(5))
.DataSource(dataSource => dataSource
.Ajax()
.Read(read => read.Action("Get", "Clients"))
.Model(model => model.Id(p => p.Id))
.Create(update => update.Action("Create", "Clients"))
.Update(update => update.Action("Update", "Clients"))
.Destroy(update => update.Action("Destroy", "Clients"))
)
)
Template
#model Client
#(Html.Kendo().ComboBoxFor(m => m.Plan)
.DataTextField("Name")
.DataValueField("Id")
.Placeholder("Select Plan...")
.HtmlAttributes(new { style = "width:300px" })
.Filter(FilterType.Contains)
.MinLength(3)
.DataSource(source =>
source.Read(read =>
read.Action("GetPlans", "Plans",new {ClientId = Model.Id}))))
Everything works fine except i need to use the Id of the row/model inside the template, in particular , i need the to pass the model.Id (which is the id of the model of the row), to the action on the Combobox in the template, so i can filter the data correctly
This is the offending line in the grid,
.TemplateName("Client").AdditionalViewData(new { Client = Model })
The result is the Model inside the template is always null, im not sure how to pass the data i need to the template
Is there anyway i can do this, or should i be looking at a different approach?
The way I got around this was to put a JavaScript function in the original view as seen below:
function getClientId() {
var row = $(event.srcElement).closest("tr");
var grid = $(event.srcElement).closest("[data-role=grid]").data("kendoGrid");
var dataItem = grid.dataItem(row);
if (dataItem)
return { clientId: dataItem.Id }
else
return { clientId: null }
}
And reference it from my editor template:
.DataSource(source => source.Read(read => read.Action("GetPlans", "Plans").Data("getClientId"))))
Note : I'm pretty sure you cant run JavaScript from a EditorTemplate so it needed to be housed in the original view.
I know this is a really old question, but for those who are wondering why this doesn't work:
.TemplateName("Client").AdditionalViewData(new { Client = Model })
This code doesn't work because the only data you can pass through this method is static data. You can pass specific strings or numbers, like "Hello World", and that would work fine. For dynamic data with kendo, I've learned that it really depends on the situation, and your solution here works well.
I have a Kendo grid set up for client-side only. Whenever I add a row, then edit it, then cancel - it gets removed. Multiple questions have been asked here and on the Kendo forums about this same issue, and all the suggestions point to incorrect setup of the model's ID.
Well, in my case the ID seems to be set up correctly. I am assigning a new ID to the model in the onGridSave() javascript event, like this:
var _curId = 1;
function onGridSave(e) {
var newId = _curId++;
e.model.set('id', newId);
e.model.set('EncryptedIngredientId', newId);
}
And when I look at the data in the grid after having added multiple rows, all of their IDs are unique - from 1 to n.
But when I cancel an edit, in the onGridChange() event the action is "remove", and the cancelled row is removed. This happens for new rows as well as for edited rows, while it should only be the case for new rows.
The grid is set up as follows:
#(Html.Kendo().Grid<IngredientViewModel>(Model.ServerData)
.Name("IngredientsGrid")
.Editable(editable => editable.Mode(GridEditMode.InLine).Enabled(true))
.BindTo(Model.DataAfterEdit ?? Model.ServerData)
.DataSource(ds => ds
.Ajax()
.ServerOperation(false)
.Events(ev => ev.Change("onGridChange").Error("onGridError"))
.Model(m => {
m.Id(p => p.EncryptedIngredientId);
m.Field(p => p.EncryptedIngredientId).DefaultValue(Guid.NewGuid().ToString());
m.Field(p => p.PercentInfo).DefaultValue(new PercentInfoViewModel());
})
.Read("IngGrid_Read", "Company") // <-- dummy action that doesn't exist in controller
.Update("IngGrid_Update", "Company") // <-- dummy action that doesn't exist in controller
.Create("IngGrid_Create", "Company") // <-- dummy action that doesn't exist in controller
.Destroy("IngGrid_Destroy", "Company")) // <-- dummy action that doesn't exist in controller
.ToolBar(tbar => tbar.Create())
.Columns(c => {
c.AutoGenerate(false);
c.Bound(m => m.CasNumber);
c.Bound(m => m.IngredientName);
c.Bound(m => m.PercentInfo).ClientTemplate("#= makePercentageDisplayString(data.PercentInfo) #").Width(180);
c.Bound(m => m.ReachRegNumber);
c.Bound(m => m.ReachSvhc);
c.Bound(m => m.RohsSubstance);
c.Bound(m => m.Prop65Substance);
c.Command(command => {
command.Edit();
command.Destroy();
}).Width(200);
})
.Events(evt => {
evt.Save("onGridSave");
evt.Edit("onGridEdit");
})
)
What am I doing wrong?
When you declare this dummy actions, Kendo is trying to save data after you edit your cell. It perform create action but doesn't have correct response from the server so it believe that request was fail. Because of that it trying to remove row after you click cancel, cuz it can't find it in their dataSource.
If it gonna be just local grid as you said, the solution is add .Batch(true) on you grid dataSource config to prevent update action after you change cell.
If you wanna save your edited data on the server you should implement Update, Create and Destroy action correctly.
I also asked my question here:
http://www.telerik.com/forums/mvc---inline-client-side-grid---clicking-cancel-removes-row-probably-not-the-model-id-issue-#qn5VWKCX9kmpZnLuTzDveQ
It turns out this is not supported when using MVC wrappers, however it could be done using JavaScript. Since I didn't want to lose the type safety of C#, I implemented the CRUD actions on the controller and store rows in the session.
The only 2 things I had to change in my grid definition were: set .ServerOperation(true) instead of false; and remove the .BindTo(...) call. Resulting code looks like this:
#(Html.Kendo().Grid<IngredientViewModel>(Model.ServerData)
.Name("IngredientsGrid")
.Editable(editable => editable.Mode(GridEditMode.InLine).Enabled(true))
.DataSource(ds => ds
.Ajax()
.ServerOperation(true)
.Events(ev => ev.Change("onGridChange").Error("onGridError"))
.Model(m => {
m.Id(p => p.EncryptedIngredientId);
m.Field(p => p.EncryptedIngredientId).DefaultValue(Guid.NewGuid().ToString());
m.Field(p => p.PercentInfo).DefaultValue(new PercentInfoViewModel());
})
.Read("IngGrid_Read", "Company") // <-- dummy action that doesn't exist in controller
.Update("IngGrid_Update", "Company") // <-- dummy action that doesn't exist in controller
.Create("IngGrid_Create", "Company") // <-- dummy action that doesn't exist in controller
.Destroy("IngGrid_Destroy", "Company")) // <-- dummy action that doesn't exist in controller
.ToolBar(tbar => tbar.Create())
.Columns(c => {
c.AutoGenerate(false);
c.Bound(m => m.CasNumber);
c.Bound(m => m.IngredientName);
c.Bound(m => m.PercentInfo).ClientTemplate("#= makePercentageDisplayString(data.PercentInfo) #").Width(180);
c.Bound(m => m.ReachRegNumber);
c.Bound(m => m.ReachSvhc);
c.Bound(m => m.RohsSubstance);
c.Bound(m => m.Prop65Substance);
c.Command(command => {
command.Edit();
command.Destroy();
}).Width(200);
})
.Events(evt => {
evt.Save("onGridSave");
evt.Edit("onGridEdit");
})
)
I have a Grid with Employes. There is a Edit button and the edit mode is set to Popup. In the EditorTemplate of the entity I want to edit, there is another grid that has a history of Salary with a incell or inline edit mode.
Both grids uses Ajax datasources. The problem is with the inner grid binding. The controller action feeding a Json result to the ajax call requires the ID of the employe we are editing to return the appropriate Salary history. However, Kendo UI ASP.NET MVC wrapper will render some sort of template of the editor before knowing which employee we want to edit, then it will edit it when we are requesting the popup.
How can I feed the Employe ID in the Read Ajax call?
Main Grid
#(Html.Kendo().Grid<MyProject.Business.Models.EmployeDTO>().Name("EmployeGrid")
.ToolBar(toolbar => toolbar.Create())
.Columns(cols =>
{
cols.Bound(o => o.someData).Title("Some Data");
cols.Bound(o => o.moreData).Title("More Data");
cols.Command(o =>
{
o.Edit();
o.Destroy();
}).Title(" ");
})
.Editable(editor => editor
.Mode(GridEditMode.PopUp)
.Window(window => window.Draggable().Resizable().HtmlAttributes(new { #style = "width:700px;" })))
.Sortable()
.Filterable()
.Groupable()
.DataSource(datasource => datasource
.Ajax()
.Model(model => model.Id(o => o.id))
.Read(read => read.Action("GetAll", "EmployesAjax"))
.Update(update => update.Action("Update", "EmployesAjax"))
.Create(create => create.Action("Create", "EmployesAjax"))
.Destroy(destroy => destroy.Action("Destroy", "EmployesAjax"))
)
)
Inner Grid (In Views/Shared/EditorTemplates/EmployeDTO.cshtml)
#Html.Kendo().Grid<MyProject.Business.Models.SalairyDTO>().Name("SalaryGrid")
.Columns(cols =>
{
cols.Bound(o => o.someInfo).Title("Some Info");
})
.DataSource(datasource => datasource
.Ajax()
.Model(model =>
{
model.Id(o => o.id);
model.Field(o => o.employe_id).DefaultValue(Model.id);
})
// NEED THE ID HERE
.Read(read => read.Action("GetByEmployeId", "SalairyAjax", new { id = "" }))
.Update(update => update.Action("Update", "SalairyAjax"))
.Create(create => create.Action("Create", "SalairyAjax"))
.Destroy(destroy => destroy.Action("Destroy", "SalairyAjax"))));
Basically I would suggest you to Set the AutoBind option to false of the inner Grid and use the edit event of the outer Grid to perform a read request and pass the value as additional parameter.
Here is an example:
function onEditOfEmployeGrid(e){
$('#SalaryGrid').data().kendoGrid.dataSource.read({id:e.model.EmployeeID})
}
You could simply pass the value from the grid using addtionaldata parameter within the grid. the RouteID on the (left side) can then be referenced within the Grid Popup Editor using ViewData["RouteID"]. I hope that helps
.Editable(editable => editable.Mode(GridEditMode.PopUp)
.TemplateName("busStop")
.DisplayDeleteConfirmation(true)
.Window(window => window.Modal(true).Resizable().Draggable())
.AdditionalViewData(new { RouteID = Model.RouteID }))