Kendo UI Grid - How to save current radio button state - asp.net-mvc

I am currently able to submit all records on my Kendo Grid back to the controller, but it does not reflect the latest state of the radio button. I dont want to have to click on a cell multiple times, to go into edit mode, nor use an edit button. I want to simply be able to make a radio button selection, click save and have the current state be reflected on my controller.
Razor View
#model IEnumerable<ECM.DAL.ViewModels.Roles.AcctAppUserRoles>
#using ECM.DAL.Infrastructure.Managers
#using GridEditMode = Kendo.Mvc.UI.GridEditMode
#{
ViewBag.applicationType = (int)SystemWideConfiguration.ApplicationTypes.ECM;
ViewBag.acctId = RightsManager.AccountId;
}
#(Html.Kendo().Grid(Model)
.Name("EcmRolesGrid")
.Columns(columns =>
{
columns.Bound(x => x.IsActive).Width(46)
.ClientTemplate("<input type='radio' name='isActive' value='#= IsActive #' # if (IsActive) { # checked='checked' # } # />")
.Title("Select Role")
.HtmlAttributes(new {style = "text-align:center"});
columns.Bound(r => r.RoleName).Width(75);
columns.Bound(r => r.RoleDescription).Width(125);
})
.HtmlAttributes(new { style = "height:340px;" })
.Editable(editable => editable.Mode(GridEditMode.InLine))
.DataSource(dataSource => dataSource
.Ajax()
.ServerOperation(false)
.Model(model => model.Id(p => p.UserId))
.Read(read => read.Action("GetApplicationAndAccountRoles", "User", new { applicationType = ViewBag.applicationType, acctId = ViewBag.acctId, userId = ViewBag.EcmRoleUserId.ToString() }))
.Update(update => update.Action("SaveApplicationRoles", "User")))
)
Controller:
[AcceptVerbs(HttpVerbs.Post)]
[HandleAjaxReturnableException]
public void SaveApplicationRoles(IEnumerable<AcctAppUserRoles> updatedRoles)
{
var userRoleDal = new UserRoleDal();
var roleDal = new RoleDal();
var applicationId = Guid.Empty;
var userId = Guid.Empty;
if (updatedRoles != null)
...
Javascript (I am using this because I have several tabs in this view with several grids)
function EcmRolesGridHasChanges() {
$.ajax({
url: 'user/SaveApplicationRoles',
cache: false,
type: 'POST',
contentType: 'application/json;',
charset:'utf-8',
data: JSON.stringify($("#EcmRolesGrid").data().kendoGrid._data)
});
}
Let me give you a visual as well....
When the grid is initially loaded the "Order Entry" Role is selected.
(Not showed)
I'll now choose "Limited User"
When I click save this is what I get on the controller:
I was expecting the first one to be false, second true and third false.
Any ideas?
Thank you.

You need to set the model on radio button click to let the grid know that particular row has been set as dirty therefore fire update event with updated model values
so add this on radio button click..
***************************Grid*******************
columns.Bound(x => x.IsActive).Width(46)
.ClientTemplate("<input type='radio' name='isActive' value='#= IsActive #' # if (IsActive) { # checked='checked' # } # onclick='radioBoxClick(this)' />")
**********************Script********************
//checkbox click function
function radioBoxClick(e) {
grid = $("kendoGrid").data().kendoGrid;
var dataItem = grid.dataItem($(e).closest('tr'));
var model = grid.dataSource.get(dataItem.UserId);
model.set("isActive", e.checked ? 1 : 0);
}

Minor modification to the answer that Shaz and Jayesh Goyani provided.
The function below goes over the other radio buttons and sets it to false.
function radioBoxClick(e) {
var grid = $("#EcmRolesGrid").data().kendoGrid;
var elementCount = grid.dataSource._data.length;
var dataItem = grid.dataItem($(e).closest('tr'));
var model = grid.dataSource.get(dataItem.RoleId);
model.set("IsActive", e.checked ? 1 : 0);
for (var i = 0; i < elementCount; i++) {
var elem = grid.dataSource._data[i];
if (elem.RoleId != model.RoleId)
elem.set("IsActive", 0);
}
}

Related

Multiselect list in Kendo Grid inline editing

I need to use multiselect list in kendo grid (inline editing) so that user can select multiple values from the list per row.
Following are my requirements:
At the time of display, kendo grid should show comma separated list of all the selected values.
At the time of Add, kendo grid should show multiselect list and allow to select multiple values.
At the time of Edit, kendo grid should show multiselect list with already selected values. User should be able to modify the select and add/remove items from the list.
When user clicks on update/save button, selected values from multiselect list should be available in code behind (in update ajax action) along with id of row.
Following what I do as of now:
I am taking an approach similar to using a drop down list in kendo inline grid.
I have created an Editor Template for displaying multiselect at the time of add/edit.
Following is the code:
#model List<Namespace.CompanyConnector>
#using Kendo.Mvc.UI
#(Html.Kendo().MultiSelectFor(c=>c)
.Name("company_connector_id")
.DataTextField("connector_name")
.DataValueField("company_connector_id")
.Placeholder("Select connector...")
.AutoBind(false)
.Value((List<int>)ViewData["SelectedValues"])
.DataSource(source =>
{
source.Read(read =>
{
read.Action("GetCompanyConnectors", "BrandConnector");
})
.ServerFiltering(true);
})
)
#Html.ValidationMessageFor(m => m)
Explanation: I bind a list of model class to the multiselect and set data source in the read action. For selecting the selected values at the time of edit, I have created a function that returns the ids of selected values and put that in View Data in the read action.
I've used this Editor template in my Index page as following code:
#{Html.Kendo().Grid<Cee.DomainObjects.DomainObjects.BrandConnector>()
.Name("BrandConnectorGrid")
.Filterable()
.Events(e => e.Edit("onEdit"))
.DataSource(dataSource => dataSource
.Ajax()
.Events(e => e.Error("error_handler").RequestEnd("onRequestEnd"))
.ServerOperation(false)
.Model(model =>
{
model.Id(p => p.brand_id);
model.Field(e => e.CompanyConnectorList).DefaultValue(new
List<Cee.DomainObjects.DomainObjects.CompanyConnector>());
})
.Read(read => read.Action("_AjaxBinding", "BrandConnector",new{companyID = 0 }).Type(HttpVerbs.Post))
.Update(update => update.Action("_UpdateBinding", "BrandConnector").Type(HttpVerbs.Post)))
.Columns(columns =>
{
columns.Bound(c => c.brand_connector_id).Width(0).Hidden(true);
columns.Bound(c => c.company_id).Width(0).Hidden(true);
columns.Bound(c => c.brand_id).Width(0).Hidden(true);
columns.Bound(u => u.brand_name).Title("Brand").Width("18%").HtmlAttributes(new { #class = "brkWord", #readonly = "readonly" });
columns.ForeignKey(u => u.connector_name, Model.CompanyConnectorList, "company_connector_id", "connector_name").Title("Connector").Width
("16%").HtmlAttributes(new { #class = "brkWord" }).EditorTemplateName("company_connector_id");
columns.Command(p => p.Edit().Text("Edit").HtmlAttributes(new { #title = "Edit" })).Width("16%").Title("Edit");
})
.Editable(editable => editable.Mode(GridEditMode.InLine).CreateAt(GridInsertRowPosition.Top))
.Pageable(pageable => pageable.Refresh(true).PageSizes(GlobalCode.recordPerPageList).ButtonCount(GlobalCode.PageSize).Input(true).Numeric(true))
.HtmlAttributes(new { #class = "dynamicWidth" })
.Sortable(sorting => sorting.Enabled(true))
.Render();
}
Explanation: I've used ForeignKey. Bound it to the string column "connector_name". Connector_name is a comma separated list of IDs that I send from controller. Editor template is used here.
Issue: It works fine at the time of View/Display in Index but Edit does not show selected value. Also we do not get updated value in code behind on update click.
Is this correct way of implementing multiselect list or do I need to bind a collection property as a column in grid?
If I bind a collection property as a column then how would I be able to show comma separated string at the time of display?
Try below code:
function onEdit(e) {
var multiselect = $("#YourMutliselectDropdown").data("kendoMultiSelect");
var IDArray = [];
$(e.model.propertyName).each(function (index) {
var ID = e.model.propertyName[index].id;
IDArray.push(ID);
});
multiselect.value(IDArray);
}
I assume that propertyName is List of your collection and it contains id as property.
try it:
c.Bound(p => p.CompanyConnectorList).ClientTemplate("#= connectorsToString(data)#").EditorTemplateName("company_connector_id");
and js:
function connectorsToString(data) {
var list = data.company_connector_id;
var result = "";
for (var i = 0; i < list.length; i++) {
result += list[i].Name + ';';
}
return result;
}

Kendo UI Grid Only Expand One Row at a Time

I have a Kendo Grid which I would like to only be able to expand one row at a time for detail editing. What is the simplest way to do this?
#(Html.Kendo().Grid<MyModel>()
.Name("MyGrid")
.ClientDetailTemplateId("MyTemplate")
.Columns(columns =>
{
columns.Bound(b => b.Code);
columns.Bound(b => b.Name);
columns.Bound(b => b.Description);
...
columns.Command(cmd => { cmd.Edit(); cmd.Destroy(); });
})
.ToolBar(toolbar => toolbar.Create())
.Editable(editable => editable.Mode(GridEditMode.InLine))
.DataSource(dataSource => dataSource
.Ajax()
.Model(model => model.Id(a => a.Id))
.Create(create => create.Action("Create", "SysMaint", new { id = Model.ProjectId }))
.Read(read => read.Action("Read", "SysMaint", new { projectId = Model.ProjectId }))
.Update(update => update.Action("Update", "SysMaint"))
.Destroy(destroy => destroy.Action("Destroy", "SysMaint"))
)
)
<script id="MyTemplate" type="text/kendo-tmpl">
#(Html.Kendo().TabStrip()
.Name("TabStrip_#=Id#")
.SelectedIndex(0)
.Items(items =>
{
items.Add().Text("A").LoadContentFrom("MyPartialA", "SysMaint", new { id = "#=Id#" });
items.Add().Text("B").LoadContentFrom("MyPartialB", "SysMaint", new { id = "#=Id#" });
})
.ToClientTemplate()
)
</script>
Ends up this is really simple. Just add these few lines.
...
.Update(update => update.Action("Update", "SysMaint"))
.Destroy(destroy => destroy.Action("Destroy", "SysMaint"))
)
.Events(events => events.DetailExpand("detailExpand"))
)
<script type="text/javascript">
var expandedRow;
function detailExpand(e) {
// Only one open at a time
if (expandedRow != null && expandedRow[0] != e.masterRow[0]) {
var grid = $('#MyGrid').data('kendoGrid');
grid.collapseRow(expandedRow);
}
expandedRow = e.masterRow;
}
</script>
I hope this helps somebody.
That works except it does not remove the old detail row. Add the bit marked NEW to remove each previously opened detail row.
if (expandedRow != null && expandedRow != e.masterRow[0]) {
var grid = $('#RequestsGrid').data('kendoGrid');
grid.collapseRow(expandedRow);
expandedRow[0].nextElementSibling.remove(); //NEW
}
expandedRow = e.masterRow;
Building on Trey's answer, this version will work generically for any grid (using #vikasde's suggestion), and will also work when you have nested grids, so that the child grid when invoking the detailExpand, won't collapse its parent grid row as a side effect.
<script type="text/javascript">
function detailExpand(ev) {
var expandedRow = $(ev.sender.wrapper).data('expandedRow');
// Only one open at a time
if (expandedRow && expandedRow[0] != ev.masterRow[0]) {
var grid = $(ev.sender.wrapper).data('kendoGrid');
grid.collapseRow(expandedRow);
}
$(ev.sender.wrapper).data('expandedRow', ev.masterRow);
}
</script>
Further to the answers already here, I found that by combining hatchet's and Danny Blue's answers and using the DetailCollapse event works nicely, and will remove the underlying detail content if a row is collapsed manually.
MVC Grid configuration:
.Events(ev =>
{
ev.DetailExpand("detailExpand");
ev.DetailCollapse("detailCollapse");
})
Page scripts:
function detailExpand(ev) { // Credit hatchet
var expandedRow = $(ev.sender.wrapper).data('expandedRow');
// Only one open at a time
if (expandedRow && expandedRow[0] !== ev.masterRow[0]) {
var $grid = $(ev.sender.wrapper).data('kendoGrid');
$grid.collapseRow(expandedRow);
}
$(ev.sender.wrapper).data('expandedRow', ev.masterRow);
}
function detailCollapse(ev) {
var expandedRow = $(ev.sender.wrapper).data('expandedRow');
expandedRow.next().remove(); // Credit Danny Blue
}

Telerik mvc grid tab order issue

I am working in an MVC 3.0 application. In my project I used Telerk mvc grid for data listing. I made grid edit model as "InCell" and provided key board navigation in my question answer area. Each question will have 2 answer options such as "Facts" and "Actions" and will be numeric value. I have used "integer" editor template for this purpose. My requirement is that when the user presses tab key from "Facts" integer textbook, focus will move it to "Actions" integer textbox and if the user presses tab from "Actions" it will move to next row "Facts" text box. But currently key board navigation goes through all cells in the grid. There are few columns between "Facts" and "Actions". My code for grid listing looks like.
#(Html.Telerik().Grid<AnswerQuestionVM>()
.Name("GridQuestions")
.DataKeys(keys =>
{
keys.Add(p => p.QuestionID);
})
.Columns(columns =>
{
columns.Bound(p => p.QuestionNumber).Format("{0:0.00}").HtmlAttributes(new { style = "text-align:center;" }).Title("#").Width("5%").ReadOnly();
columns.Bound(p => p.QuestionName).Title("Question Name").Width("43%").ReadOnly();
columns.Bound(p => p.Facts).Width("8%");
columns.Template(#<text></text>).ClientTemplate("<img src='" + #Url.Content("~/images/help.gif") + "' name='help' alt='help' title='<#=FactOptions#>' />");
columns.Bound(p => p.Actions).Width("8%");
columns.Template(#<text></text>).Width("2%").ClientTemplate("<img src='" + #Url.Content("~/images/help.gif") + "' name='help' alt='help' title='<#=ActionOptions#>' />");
columns.Template(#<text></text>).Title("Skip").Width("3%").ClientTemplate(
"<# if(Skip==false) {#>" +
"<input type='checkbox' style='cursor:pointer;' class='skipClass' />" +
"<#} else{#>" +
"<input type='checkbox' style='cursor:pointer;' class='skipClass' checked='checked' />" +
"<# } #>"
);
columns.Bound(p => p.Note).Title("Note").Width("26%");
})
.Editable(editing => editing.Mode(Telerik.Web.Mvc.UI.GridEditMode.InCell))
.KeyboardNavigation( navigation => navigation.EditOnTab(true))
.ClientEvents(e => e.OnSave("OnSave"))
.DataBinding(dataBinding =>
{
dataBinding.Ajax().Select("GetQuestion", "AnswerQuestion", new { Area = "question", ajax = true }).Enabled(true);
})
.Scrollable(scrolling => scrolling.Enabled(false))
.Sortable(sorting => sorting.Enabled(true))
.Pageable(paging => paging.Enabled(true))
.Filterable(filtering => filtering.Enabled(true))
.Groupable(grouping => grouping.Enabled(false))
.Footer(true)
)
Code for Integer editor template for "Facts" & "Actions" columns bellow.
#(Html.Telerik().IntegerTextBox()
.Name(ViewData.TemplateInfo.GetFullHtmlFieldName(string.Empty))
.InputHtmlAttributes(new { style = "width:100%", pattern = "[0-9]*" })
.MinValue(1)
.MaxValue(5)
.Value(Model)
)
Please provide a solution. Any help is appreciated.
Here is what I can suggest you as a work-around because your goal is not supported out of the box.
Feel free to optimize/change the implementation or to make it more generic (e.g. use classes to find cells and not to rely on indexes)
$(function () {
$('#persons tbody').on('keydown', function (e) {
if (e.keyCode == 9) {
var currentCell = $(e.target).closest('td');
var cellIndex = currentCell.index();
if (cellIndex == 2 || cellIndex == 4) { //if editing cell in third or fifth column, use different indexes if needed
e.stopPropagation();
e.preventDefault();
var grid = $('#persons').data().kendoGrid;
if (cellIndex == 2) {
var cellToEdit = currentCell.parent().find('td:eq(4)');
grid._handleEditing(currentCell, cellToEdit);
}
if (cellIndex == 4) {
var cellToEdit = currentCell.parent().find('td:eq(2)');
grid._handleEditing(currentCell, cellToEdit);
}
setTimeout(function () {
cellToEdit.find('input').focus();
})
}
}
})
})
I´m not really sure how to do this, but have you tried using something like tabindex, perhaps by using it within the htmlAttributes? Anyway, I have never done anything like this but maybe you can try something in that area:
columns.Bound(p => p.Facts).Width("8%").HtmlAttributes(new { tabindex = 1 });
columns.Bound(p => p.Actions).Title("Practice").Width("8%").HtmlAttributes(new { tabindex = 2 });
Since you are using Telerik Grid, you might have to mix this somehow within each item. Then again, maybe I´m misunderstanding your point (I´ve been known to do that) :)

How to format the row based on condition in kendo ui mvc grid

I am working on asp.net mvc. I am trying to display list of messages in a Kendo mvc ui grid.
I have written the code like,
Html.Kendo().Grid((List<messages>)ViewBag.Messages))
.Name("grdIndox")
.Sortable(m => m.Enabled(true).SortMode(GridSortMode.MultipleColumn))
.HtmlAttributes(new { style = "" })
.Columns(
col =>
{
col.Bound(o => o.RecNo).HtmlAttributes(new { style = "display:none" }).Title("").HeaderHtmlAttributes(new { style = "display:none" });
col.Bound(o => o.NoteDate).Title("Date").Format("{0:MMM d, yyyy}");
col.Bound(o => o.PatName).Title("Patient");
col.Bound(o => o.NoteType).Title("Type");
col.Bound(o => o.Subject);
}
)
.Pageable()
.Selectable(sel => sel.Mode(GridSelectionMode.Single).Type(GridSelectionType.Row))
.DataSource(
ds => ds.Ajax().ServerOperation(false).Model(m => m.Id(modelid => modelid.RecNo))
.PageSize(10)
//.Read(read => read.Action("Messages_Read", "Msg"))
)
.Events(ev => ev.Change("onSelectingGirdRow"))
)
and i have the field in the table like IsRead-boolean type. so if the message is unread message then i need to format that record with bold font. I have used clientTemplates but with that i am able to format only particular cells i want format entire row. Please guide me.
as Sanja suggested you can use the dataBound event but it will be better to cycle through the tr elements (the rows). Also I assume that you will need the related dataItem to check if the property that indicates if the message is read.
e.g.
dataBound: function ()
{
var grid = this;
grid.tbody.find('>tr').each(function(){
var dataItem = grid.dataItem(this);
if(!dataItem.IsMessageRead)
{
$(this).addClass('someBoldClass');
}
})
}
You can use dataBound event to change your rows.
dataBound: function ()
{
$('td').each(function(){
if(some condition...)
{
$(this).addClass('someBoldClass')}
}
})
}
There is another way to do that.It's called RowAction.
.RowAction(row =>
{
if (condition)
{
row.HtmlAttributes["class"] = "someBoldClass";
}
})
Please note that the RowAction is available only for rows that are rendered on server side. So in your case, you can use RowAction as an alternative to DataBound.

How to pass values from telerik mvc ajax grid to controller

I have 2 telerik mvc ajax grids which needs to be populated based on the selected row on the first grid.
below is my code:
#(Html.Telerik().Grid<PurchaseRequest>()
.Name("grdPurchaseRequest")
.DataBinding(binding => binding.Ajax()
.Select("GetPurchaseRequests", "PurchaseOrder"))
.DataKeys(keys => keys
.Add(o => o.PurchaseRequestID))
.Columns(cols =>
{
cols.Bound(c => c.PurchaseRequestID).ReadOnly().Hidden();
cols.ForeignKey(c => c.ProjectID,(System.Collections.IEnumerable)ViewData["prProjects"],
"ProjectID", "ProjectName");
cols.Bound(c => c.FullName);
cols.Bound(c => c.RequestDate).Format("{0:MM/dd/yyyy}");
cols.Bound(c => c.Remarks);
cols.Bound(c => c.CheckedBy);
cols.Bound(c => c.ApprovedBy);
})
.ClientEvents(clientEvents => clientEvents.OnRowSelect("onRowSelected"))
.RowAction(row =>
{
row.Selected = row.DataItem.PurchaseRequestID.Equals(ViewData["id"]);
})
.Pageable()
.Sortable()
.Filterable()
.Selectable()
)
====Second GRID=====
This the the second Grid that will be populated based on the selected record on the first grid
#(Html.Telerik().Grid<PurchaseRequestDetail>().HtmlAttributes(new { style = "width: 50%" })
.Name("grdDetails")
.DataKeys(keys => keys
.Add(o => o.PurchaseRequestDetailID)
.RouteKey("PurchaseRequestDetailID"))
.Columns(cols =>
{
cols.ForeignKey(c => c.ItemID, (System.Collections.IEnumerable)ViewData["prItems"],
"ItemID", "ItemName").Width(200).Title("Description");
cols.Bound(d => d.ItemQuantity).Width(100).Title("Quantity");
cols.Bound(d => d.ItemValue).Width(100).Title("Value per quantity").Format("Php {0:###,###.00}");
cols.Bound(d => d.TotalPrice).Width(500).Format("Php {0:###,###.00}")
.Aggregate(aggs => aggs.Sum())
.ClientFooterTemplate("Php <#= $.telerik.formatString('{0:n}', Sum) #>");
})
.DataBinding(binding => binding.Ajax()
.Select("GetPurchaseRequestDetails", "PurchaseOrder", new { purchaseRequestID = "<#= PurchaseRequestID #>" }))
.ClientEvents(clientEvents => clientEvents.OnDataBinding("onDataBinding"))
.Pageable()
.Sortable()
)
the Script code
<script type="text/javascript">
var purchaseRequestID;
var purchaseRequestName;
function onRowSelected(e) {
var detailsGrid = $('#grdDetails').data('tGrid');
purchaseRequestID = e.row.cells[0].innerHTML;
purchaseRequestName = e.row.cells[1].innerHTML;
// update ui text
$('#purchaseRequestName').text(purchaseRequestName);
// rebind the related grid
//alert(purchaseRequestID);
//location.href = "/PurchaseOrder/Index/" + purchaseRequestID;
detailsGrid.rebind();
}
function onDataBinding(e) {
e.data = $.extend(e.data, { purchaseRequestID: purchaseRequestID });
}
below is the code in the controller:
[HttpPost]
public ActionResult Create(PurchaseOrder purchaseorder)
{
if (ModelState.IsValid)
{
HERE, I WANT TO BE ABLE TO GET AND SAVE THE SELECTED ROW IN THE VIEW
//purchaseorder.PurchaseRequestID = ViewBag.SelectedID;
db.PurchaseOrders.Add(purchaseorder);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "Name", purchaseorder.SupplierID);
ViewBag.OfficeID = new SelectList(db.Offices, "OfficeID", "OfficeName", purchaseorder.OfficeID);
return View(purchaseorder);
}
Thanks
In case some of you might encounter the same problem,
I used the HiddenFor to solve this. Please suggest if you have a better way than this.
#Html.HiddenFor(model => model.PurchaseRequestID)
and then i updated the script
<script type="text/javascript">
var purchaseRequestID;
var purchaseRequestName;
function onRowSelected(e) {
var detailsGrid = $('#grdDetails').data('tGrid');
purchaseRequestID = e.row.cells[0].innerHTML;
purchaseRequestName = e.row.cells[1].innerHTML;
// update ui text
$('#purchaseRequestName').text(purchaseRequestName);
$('#PurchaseRequestID').val(purchaseRequestID);
detailsGrid.rebind();
}
function onDataBinding(e) {
e.data = $.extend(e.data, { purchaseRequestID: purchaseRequestID });
}

Resources