I'm new to using Telerik, and I am currently working on a ASP.Net Core Grid
So I have it working, but now I want to add the search bar toolbar, so I added it as:
#(Html.Kendo().Grid(Model)
.Name("grid")
.ToolBar(t => t.Search())
.Search(s =>
{
s.Field(o => o.PrimaryContact.FirstName, "contains");
})
The problem is that PrimaryContact.FirstName can be null, so when I try to search for something, it throws a console error:
Uncaught TypeError: Cannot read properties of null (reading
'FirstName')
This same thing happen on the columns and I solve it adding ClientTemplate as:
columns.Bound(x => x.PrimaryContact.FirstName)
.ClientTemplate("#= PrimaryContact ? PrimaryContact.FirstName : '' #")
But Search has no a client template to handle this
I tried to change the null value for the string empty with jQuery as:
function onDataBound(e) {
for (let i = 0; i < rows.length; i++)
{
const row = $(rows[i]);
const dt = e.sender.dataItem(row);
const firstName = dt.get("PrimaryContact.FirstName");
if (firstName === null)
{
row[0].cells[2].innerHTML = "";
}
}
}
But it does not work.
How can I avoid the null values from the search? Regards
FullCode:
#(Html.Kendo().Grid(Model)
.Name("grid")
.ToolBar(t =>
{
t.Excel();
t.Pdf();
t.Search();
})
.DataSource(dataSource => dataSource
.Custom()
.PageSize(20)
.Schema(schema =>
{
schema.Data(x => "function(d) { return validateData(d); }");
})
)
.Pageable(pager => pager
.Numeric(true)
.Info(true)
.PreviousNext(true)
.PageSizes(new [] { 10 ,25 ,50 })
.Position(GridPagerPosition.Bottom)
.Messages(m =>
{
m.ItemsPerPage("entries");
m.Display("Showing {0} - {1} of {2} entries");
})
)
.Sortable()
.Search(s =>
{
s.Field(o => o.Name, "contains");
s.Field(o => o.PrimaryContact.FirstName, "contains");
s.Field(o => o.PrimaryContact.PhoneNumber, "contains");
s.Field(o => o.PrimaryContact.EmailAddress, "contains");
s.Field(o => o.IsActive, "contains");
})
.Events(events => events.DataBound("onDataBound"))
.Columns(columns =>
{
columns.Bound(x => x.AdvertiserId)
.Hidden();
columns.Bound(x => x.Name)
.Title("Advertiser Name");
columns.Bound(x => x.PrimaryContact.FirstName)
.ClientTemplate("#= PrimaryContact ? PrimaryContact.FirstName : '' #")
.Title("Contact Name");
columns.Bound(x => x.PrimaryContact.PhoneNumber)
.ClientTemplate("#= PrimaryContact ? PrimaryContact.PhoneNumber : '' #")
.Title("Contact Phone");
columns.Bound(x => x.PrimaryContact.EmailAddress)
.ClientTemplate("#= PrimaryContact ? PrimaryContact.EmailAddress : '' #")
.Title("Contact Email");
columns.Bound(x => x.IsActive)
.Title("Status")
.ClientTemplate("<span class='badgeTemplate'></span>")
.Sortable(false);
columns.Template("<div class='btn-group'></div>")
.Title("").Width(100);
})
)
<script type="text/javascript">
function validateData(data)
{
for (const item of data) {
if (!item.hasOwnProperty("PrimaryContact")) {
item["PrimaryContact.FirstName"] = { "FirstName": ""};
}
}
return data;
}
</script>
I also tried using BeforeEdit event
.Events(events => events.DataBound("onDataBound").BeforeEdit("onBeforeEdit")
function onBeforeEdit(e) {
if(e.model.isNew()){
e.PrimaryContact.FirstName = "";
}
}
But it does not work as well
Probably avoid using nested properties whenever possible. You should have a custom ViewModel that flattens everything (whatever your Model class is). Second, the asp.net API is not as full featured as the native javascript library. It's just a passthrough to generate javascript on the page anyway, so don't be afraid to add javascript if you need to.
What you're trying to do isn't possible. You can build a proper flat ViewModel, or you can intercept data and mutate before loading it into the grid. You do that using the schema property on the datasource. You'll define a custom javascript method.
One more thing, I think your problem is not that FirstName is missing, it's that PrimaryContact is null. You can probably fix this server side without too much hassle. But here's how to fix it client side, if, for example, you're getting this from a 3rd party API.
.DataSource(ds =>
{
ds
.Custom()
.Transport(t =>
{
// ...
})
.Schema(x => .Data(x => "function(d) { return validateData(d); }"))
// ...
;
})
Or if you just use the javascript definition
dataSource: {
// ...
schema: { data: validateData }
}
Then here is the javascript method to cleanup your model with whatever properties you need to fix:
function validateData(data)
{
for (const item of data) {
if (!item.hasOwnProperty("PrimaryContact")) {
item["PrimaryContact"] = { "FirstName": ""};
}
}
return data;
}
Full dojo example here
Related
I have the initial values for the grid in my ViewModel. But if the user wants to update values, I want to update my database, then send back the updated values for the grid from the backend. The problem is that the Ajax Read is always called. But it shouldn't because the (initial) values are there in the ViewModel and bound to Grid.
I tried setting the AutoBind to false, but it does not work, I get an error. (Cannot set AutoBind if widget is populated during initialization)
#(Html.Kendo().Grid<MyClass>()
.Name("MyClassGrid")
.BindTo(Model.MyClassList)
.Columns(column =>
{
column.Bound(c => c.SomeProp).Title("Some Property");
})
.Scrollable()
.DataSource(ds => ds
.Ajax()
.Read(read => read.Action("GetMyData", "CheckBar", new { param1 = Model.ParamFirst}))))
I want to display the already stored values for my grid, and only use the read operation if I want to update the values in my database too.
#(Html.Kendo().Grid<SQDCDashboard.Core.ViewModels.SafetyIncidentViewModel>()
.Name("safetyincident-grid")
.Columns(columns =>
{
columns.Bound(c => c.CreatedAt).HtmlAttributes(new { style = "width: 22%" }).Format("{0:MM/dd/yyyy}");
columns.Bound(c => c.Type).HtmlAttributes(new { style = "width: 22%" });
columns.Bound(c => c.Description).HtmlAttributes(new { style = "width: 22%" });
columns.ForeignKey(c => c.ProductionLineId, (System.Collections.IEnumerable)ViewData["ProductionLines"], "Id", "Name").HtmlAttributes(new { style = "width: 22%" });
columns.Command(command => { command.Edit(); }).HtmlAttributes(new { style = "width: 12%" });
})
.ToolBar(toolbar => toolbar.Create().Text("New Safety Incident"))
.Editable(editable => editable.Mode(GridEditMode.InLine))
.DataSource(ds =>
ds.Ajax()
.ServerOperation(false)
.Model(model =>
{
model.Id(p => p.Id);
model.Field(p => p.ProductionLineId).DefaultValue(Model.DefaultProdLine);
})
.Read(read => read.Action("GetSafetyIncidentList", "Safety"))
.Update(update => update.Action("EditSafetyIncidentInLine", "Safety"))
.Create(create => create.Action("CreateNewSafetyIncident", "Safety"))
)
.HtmlAttributes(new { style = "height: 100%" })
.Sortable()
.Filterable()
.Pageable()
.Mobile()
)
Here is an example of my grid. The Update(update => update.Action("Action" , "Controller"))
is the method you are wondering about. For my code I did not have to pass a parameter in with my read, and I do not need to pass one in with my Update.
[HttpPost]
public ActionResult EditSafetyIncidentInLine([DataSourceRequest] DataSourceRequest request, SafetyIncidentViewModel sivm)
{
if (sivm != null && ModelState.IsValid)
{
SafetyIncident si = _safetyIncidentService.Find(sivm.Id);
si.Description = sivm.Description;
si.ProductionLineId = sivm.ProductionLineId;
si.ProdLine = _productionLineService.Find(sivm.ProductionLineId);
si.Type = sivm.Type;
_safetyIncidentService.Update(si);
sivm.Id = si.Id;
sivm.CreatedAt = si.CreatedAt;
}
return this.Json(new[] { sivm }.ToDataSourceResult(request, ModelState));
}
This method is the one that is called when the Update button is pressed. The update button is created with the columns.Command(command => { command.Edit(); }; and the Editable(editable => editable.Mode(GridEditMode.InLine)). If you want more clarification or if your question is different let me know. I never got an error with having my grid already initialized when I was updating values.
When I set the datasource to WebAPI binding, the HTTP GET is being invoked on the controller and it returns the correct data. The problem is that my Kendo Grid is not binding the data correctly, instead I get an empty grid as a result.
#(Html.Kendo().Grid(Model)
.Name("Accounts")
.Columns(columns =>
{
columns.Bound(c => c.Description).Title("Account Name");
columns.ForeignKey(c => c.Type, (System.Collections.IEnumerable)ViewData["accountTypes"], "Id", "Name").Title("Account Type");
columns.ForeignKey(c => c.Currency, (System.Collections.IEnumerable)ViewData["currencyTypes"], "Id", "Name").Title("Account Currency");
columns.Command(command => { command.Edit(); command.Destroy(); }).Width(210);
})
.Sortable(sortable =>
{
sortable.SortMode(GridSortMode.SingleColumn);
sortable.AllowUnsort(false);
})
.Pageable(pageable => pageable
.Refresh(true)
.PageSizes(true)
.ButtonCount(5)
)
.ToolBar(toolbar => { toolbar.Create(); })
.Editable(editable => editable.Mode(GridEditMode.InLine))
.DataSource(dataSource => dataSource
.WebApi()
.Model(model =>
{
model.Id(p => p.Id);
model.Field(p => p.Currency).DefaultValue(0).Editable(true);
model.Field(p => p.Description).Editable(true);
model.Field(p => p.Type).Editable(true);
})
.ServerOperation(true)
.Read(read => read.Action("Get", "Accounts"))
.Create(create => create.Action("Post", "Accounts"))
.Update(update => update.Action("Put", "Accounts", new { id = "{0}" }))
.Destroy(destroy => destroy.Action("Delete", "Accounts", new { id = "{0}" }))
.PageSize(10)
)
)
Controller
// GET: api/values
[HttpGet]
public DataSourceResult Get([DataSourceRequest]DataSourceRequest request)
{
return _context.Accounts.ToDataSourceResult(request);
}
I always get an HTTP 200 OK as a result of the paging or sorting command, but the grid is empty afterwards. The URL generated by Kendo is:
http://localhost:58829/Accounts/Get?sort=&page=1&pageSize=10&group=&filter=
Which actually returns JSON when I open it with a browser.
The problem seems to be that you are mixing two different ways to load the data. On the one hand you are passing the Model by param to the grid (this approach is used when ServerOperation = false) and on the other hand you are setting ServerOperation = true and specifying a read action.
The reason why the grid is empty in this case is probably because either the Model is empty.
Take a look at this demo example for a reference on how to implement the remote source databinding: http://demos.telerik.com/aspnet-core/grid/remote-data-binding
Example View:
Example controller:
Hope this helps KendoGrid is an awesome library but like many other libraries it could definitely use better exception handling and more user friendly exceptions :) In my opinion the grid should have shown an exception in this case.
The problem was this: when declaring the Kendo Grid and passing the Model as a parameter like so:
#(Html.Kendo().Grid(Model)
You need to remove the .Read() action, and make sure to use .ServerOperation(false). This works either with WebApi binding or Ajax binding:
.DataSource(dataSource => dataSource
.WebApi() // also works with .Ajax()
.Model(model =>
{
model.Id(p => p.Id);
}
)
.ServerOperation(false)
.Create(create => create.Action("Post", "Invoices"))
.Update(update => update.Action("Put", "Invoices", new { id = "{0}" }))
.Destroy(destroy => destroy.Action("Delete", "Invoices", new { id = "{0}" }))
.PageSize(10)
)
Also, the DataSourceResult Get() method can be removed.
I have a sample like this popup editing mode
I want to store my model while first inserting, and set default values.
#(Html.Kendo().Grid<License>()
.Name("popupGrid")
.Columns(columns =>
{
columns.Bound(p => p.LicenseId).Width(20).Hidden().HeaderHtmlAttributes(new { #title = "License" });
columns.ForeignKey(p => p.CustomerId, (System.Collections.IEnumerable)ViewData["customers"], "CustomerID", "CustomerName")
.Title("Customer").Width(200);
columns.Bound(p => p.VendorId).Width(20).HeaderHtmlAttributes(new { #title = "Vendor" });
columns.Bound(p => p.ProductId).Width(20).HeaderHtmlAttributes(new { #title = "Product" });
columns.Command(p => p.Edit().Text("Edit").HtmlAttributes(new { #title = "Edit" })).Width(80);
})
.ToolBar(toolbar => toolbar.Create().HtmlAttributes(new {#id ="MyAddButton" ,#title = "Add" }))
.Editable(editable => editable.Mode(GridEditMode.PopUp).TemplateName("PopupEditView"))
.Events(e => e.Edit("onGridEdit").Save("onGridSave"))
.DataSource(dataSource => dataSource
.Ajax()
.Model(model => model.Id(p => p.LicenseId))
.Create(create => create.Action("Create", "Home").Type(HttpVerbs.Post))
.Read(read => read.Action("Read", "Home").Type(HttpVerbs.Post))
.Update(update => update.Action("Update", "Home").Type(HttpVerbs.Post))
)
)
<script>
var myModel;
function onGridSave(e) {
if (e.model.isNew()) {
myModel = e.model;
}
}
function onGridEdit(e) {
if (e.model.isNew()&&myModel!=null) {
e.model.set("CustomerId", myModel.CustomerId);
e.model.set("VendorId", myModel.VendorId);
e.model.set("ProductId", myModel.ProductId);
}
}
</script>
But it's not working.
following code is not working too :
$(e.container).find('input[name="CustomerId"]').data("kendoDropDownList").value(myModel.CustomerId);//it's not giving error. but CustomerId is null in model of create method
Actually I want to this. But I don't know how to update datasource while prevent closing window.
You might try configuring your dropdownlist with valuePrimitive: true
http://docs.telerik.com/kendo-ui/api/javascript/ui/dropdownlist#configuration-valuePrimitive
I have a Kendo Grid with some environments data. One of the fields of the grid is "isDefault" wich recieve 1 or 0 (for true or false). In the database I have a trigger that when some record is set to isDefault = 1 any other record is update to isDefault = 0, just to make sure there is only one default environment.
The Kendo grid is working fine, it binds the data and updates the records just fine but after the update, the grid is not refreshing all the records and if there was, lets say, record 1 with isDefault =1 and I update record 4 to isDefault = 1 the trigger is fired and updates all others records to isDefault = 0 but the grid still showing record 1 with isDefault = 1 and now record 4 with isDefault = 1
This is the code on my view:
Html.Kendo().Grid<Models.Environment>()
.Name("environmentGrid")
.Sortable()
.ToolBar(tb => tb.Create())
.Editable(editable => editable.Mode(GridEditMode.PopUp))
.Columns(cols =>
{
cols.Bound(c => c.Name).Width(150).Sortable(true);
cols.Bound(c => c.ConnectionString).Width(150).Sortable(true);
cols.Bound(c => c.Template).Width(150).Sortable(true);
cols.Bound(c => c.isDefault).Width(150).Sortable(true);
cols.Bound(c => c.StatusID).Width(150).Sortable(true);
cols.Command(command => { command.Edit();}).Width(60);
})
.DataSource(ds => ds
.Ajax()
.Model(model =>
{
model.Id(m => m.EnvironmentID);
})
.Read(r => r.Action("GetEnvironments", "Admin"))
.Update(update => update.Action("UpdateEnvironments", "Admin"))
.Create(update => update.Action("UpdateEnvironments", "Admin"))
)
and this is the code on my controller:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateEnvironments([DataSourceRequest] DataSourceRequest dsRequest, Environment environment)
{
environment.ModifiedBy = userName;
if (environment != null && ModelState.IsValid)
{
if (environment.EnvironmentID != 0)
{
var toUpdate = xgr.EnviromentRepository.ListAll().FirstOrDefault(p => p.EnvironmentID == environment.EnvironmentID);
TryUpdateModel(toUpdate);
}
xgr.EnviromentRepository.Save(environment);
}
return Json(ModelState.ToDataSourceResult());
}
Thank you in advance for your answers.
I finally get it to work. Added an event handler:
Html.Kendo().Grid<Models.Environment>()
.Name("environmentGrid")
.Sortable()
.ToolBar(tb => tb.Create())
.Editable(editable => editable.Mode(GridEditMode.PopUp))
.Columns(cols =>
{
cols.Bound(c => c.Name).Width(150).Sortable(true);
cols.Bound(c => c.ConnectionString).Width(150).Sortable(true);
cols.Bound(c => c.Template).Width(150).Sortable(true);
cols.Bound(c => c.isDefault).Width(150).Sortable(true);
cols.Bound(c => c.StatusID).Width(150).Sortable(true);
cols.Command(command => { command.Edit();}).Width(60);
})
.DataSource(ds => ds
.Ajax()
.Model(model =>
{
model.Id(m => m.EnvironmentID);
})
.Events(events =>
{
events.RequestEnd("onRequestEnd"); //I've added this
})
.Read(r => r.Action("GetEnvironments", "Admin"))
.Update(update => update.Action("UpdateEnvironments", "Admin"))
.Create(update => update.Action("UpdateEnvironments", "Admin"))
)
And a Javascript Function:
function onRequestEnd(e) {
if (e.type == "update") {
$("#environmentGrid").data("kendoGrid").dataSource.read();
}
}
Also I needed to modify my ListAll() Method on the EnvironmentRepository to be like this:
public List<XML_Environment> ListAll()
{
_dataContext = new XMLGenEntitiesDataContext(); //I've to add this line. so the context is instantiated every time I call the ListAll Method.
return _dataContext.XML_Environments.OrderBy<XML_Environment, string>(c => c.EnvironmentName).ToList<XML_Environment>();
}
You are returning the wrong object. I don't really know how you get your data, because you did not posted the GET controller, so I'm going to try to guess it. After you updated your data, you need to send them back to the grid. The ModelState does not contain the data you want. Try this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult UpdateEnvironments([DataSourceRequest] DataSourceRequest dsRequest, Environment environment)
{
environment.ModifiedBy = userName;
var updatedRecords = null;//1
if (environment != null && ModelState.IsValid)
{
if (environment.EnvironmentID != 0)
{
var toUpdate = xgr.EnviromentRepository.ListAll().FirstOrDefault(p => p.EnvironmentID == environment.EnvironmentID);
TryUpdateModel(toUpdate);
updatedRecords = xgr.EnviromentRepository.ListAll();//2 -- you might need to add "ToList()", depending on your implementation
}
xgr.EnviromentRepository.Save(environment);
}
return Json(updatedRecords.ToDataSourceResult(request, ModelState));//3
}
Please see this link for a complete example.
I have such grid defined
#(Html.Kendo().Grid<FieldViewModel>(Model.Fields)
.HtmlAttributes(new { #class = "fullScreen" })
.Name("formFields")
.ClientDetailTemplateId("formFieldsTemplate")
.Columns(columns =>
{
columns.Bound(e => e.Key);
columns.Bound(e => e.DisplayName);
columns.Bound(e => e.FieldTypeName);
columns.Bound(e => e.Order);
columns.Bound(e => e.IsMandatory);
columns.Bound(e => e.Type);
})
.Pageable()
.Sortable()
.Scrollable()
.Selectable()
.Resizable(resize => resize.Columns(true))
.Groupable()
.Filterable()
.DataSource(dataSource => dataSource.Ajax().ServerOperation(false).Model(model => model.Id(e => e.Key))))
and details template
<script id="formFieldsTemplate" type="text/kendo-tmpl">
#(Html.Kendo().Grid<FieldViewModel>()
.Name("FormField_#=Key#")
.ClientDetailTemplateId("formFieldsTemplate")
.Columns(columns =>
{
columns.Bound(e => e.Key);
columns.Bound(e => e.DisplayName);
columns.Bound(e => e.FieldTypeName);
columns.Bound(e => e.Order);
columns.Bound(e => e.IsMandatory);
columns.Bound(e => e.Type);
})
.DataSource(dataSource => dataSource.Ajax().Read(read => read.Action("LoadFieldDetails", "Forms", new { formPath = Model.Schema, rootElementName = Model.Root ,fieldKey = "#=Key#" })))
.Pageable()
.Sortable()
.Selectable()
.ToClientTemplate())
</script>
As you can see I have Type property (of type int), so what I want to do is not to show any details view and no arrow on the entire row when Type property is set to the specific value. How can I achieve it?
When you define your template like this, then it's used on client side. It's rendered by the #(Html.Kendo().Grid<FieldViewModel>() command but actually then used on client side. But you cannot compare a Type on clientside. but for example, when constructing the model you do:
if (myType is MyDataType) // Do the type check
{
myRow.UseTemplate = 1; // Define ID for template
}
else // ...and so on, can do a 'switch` or whatever
{
myRow.UseTemplate = 2;
}
And here is your template where you switch by Template ID:
<script id="formFieldsTemplate" type="text/kendo-tmpl">
# if (UseTemplate == 1) { #
<div>Template 1</div>
# } else { #
<div>Template 2</div>
# } #
</script>
Not sure if it will work properly if you have different data to display within your data rows... Hope you get the idea though.