Kendo DropDownList FilterText always null - asp.net-mvc

I have a Kendo DropdownList that I can't get to filter - on looking at the data received in the controller to the following function I notice that the "string text" is always null:
[OutputCache(NoStore = true, Duration = 0)]
public JsonResult GetAssemblys(string text, long site)
{
return Json(CreateFilteredList(text, site, (long)AssetTypeEnum.Assembly), JsonRequestBehavior.AllowGet);
}
This is the code for the DropDownList:
<div>#(Html.Kendo().DropDownList()
.Name("AssemblySelector")
.DataTextField("AssetName")
.DataValueField("AssetId")
.HtmlAttributes(new { style = "width: 570px;" })
.OptionLabel("Select assembly...")
.DataSource(s =>
{
s.Read(r => r.Action("GetAssemblys", "Form707B").Data("getsite"));
s.ServerFiltering(true);
})
.Filter(FilterType.Contains)
.Height(300)
.SelectedIndex(0))
This worked before I added the .Data("getsite")) part to to the read method. getsite() returns a long called site (this is successfully received in the controller).

What is often not known by people using the MVC Builder is that fluent builder puts a default read data handler to send the filter text to their controller.
if you override the data handler you need to send the text filter yourself or call the method it usually calls which is the following
if (DataSource.ServerFiltering && !DataSource.Transport.Read.Data.HasValue() && DataSource.Type != DataSourceType.Custom) {
DataSource.Transport.Read.Data = new ClientHandlerDescriptor {
HandlerName = "function() { return kendo.ui.DropDownList.requestData(jQuery(\"" + EscapeRegex.Replace(Selector, #"\\$1") + "\")); }"
};
}
to do so your getsite function should look like this.
function getsite() {
// drop down element
var $dd = $('#AssemblySelector');
// widget
var dd = dd.data('kendoDropDownList');
var filterText = dd.input.text();
var site = null; // do your logic for this.
return {
site: site,
text: filterText
};
}
or
function getsite() {
// drop down element
var $dd = $('#AssemblySelector');
// widget
var dd = dd.data('kendoDropDownList');
var ret = kendo.ui.DropDownList.requestData($dd);
ret['site'] = site;
return ret;
}

Related

MVCGrid.net - Grid doesn't page results, shows all records and pager also

The grid is being displayed which has pager enabled but displays all records without paging. Also the pager is displayed. Code for RegisterGrid() method is as below:-
public static void RegisterGrids()
{
GridDefaults defaultSet1 = new GridDefaults()
{
Paging = true,
ItemsPerPage = 5,
NoResultsMessage = "Sorry, no results were found"
};
MVCGridDefinitionTable.Add("grdFiles", new MVCGridBuilder<FilesModel>(defaultSet1)
.WithAuthorizationType(AuthorizationType.AllowAnonymous)
.AddColumns(cols =>
{
cols.Add("FileName")
.WithValueExpression(p => p.Name.ToString());
cols.Add("LastModified").WithHeaderText("LastModified")
.WithValueExpression(p => p.LastModified.ToString());
cols.Add("Size").WithHeaderText("Size")
.WithValueExpression(p => p.Size.ToString());
})
.WithRetrieveDataMethod((context) =>
{
var res = DisplayFiles();
return new QueryResult<FilesModel>()
{
Items = res,
TotalRecords = res.Count // if paging is enabled, return the total number of records of all pages
};
})
);
}
View Code is:
#model IEnumerable<FileViewerWeb.Models.FilesModel>
#using MVCGrid.Web;
#{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<div class="well well-lg" style="padding-top:10px">
<h2>Explore Uploaded Files</h2>
</div>
#Html.MVCGrid("grdFiles")
Controller Code :-
public ActionResult Index()
{
return View();
}
Change your way of generating the list to a query and then at the end pass the query via a ToList()
var result = new QueryResult<FilesModel>();
var query = db.FilesModel.AsQueryable();
You are missing the code
if (options.GetLimitOffset().HasValue)
{
var limitOffset = options.GetLimitOffset().Value;
var limitRowCount = options.GetLimitRowcount().Value;
query = query
.Skip(limitOffset)
.Take(limitRowCount);
}
Finally, at the point of returning use
result.Items = query.ToList();
result.TotalRecords = result.Items.Count();
return result;
When you are using skip you have firstly to use orderby.
if (options.GetLimitOffset().HasValue)
{
var limitOffset = options.GetLimitOffset().Value;
var limitRowCount = options.GetLimitRowcount().Value;
query = query.OrderBy(c=>c.name) //or add any column you prefer to order the list
.Skip(limitOffset)
.Take(limitRowCount);
}

Remove/Add asterisk for requiredif when dependent property changes value

I have the following scenario:
Multiple requiredif DataAnnotation attributes. I made a custom label helper that renders a "*" aside for the properties that are decorated if requiredif attribute.
Now on the clientside I want to be able to hide/show if the dependent property changes value.
For a more precise example I have a model
public class Document {
public bool IsFake {get; set; }
[RequiredIf("IsFake",false,ValueComparison.IsEqual)]
public string Number{ get; set; }
}
Based on the label helper that I made I have the corresponding label for Number with a red * in the UI. When I change on the client side from the is fake to the is not fake radio button I want to hide the *.
I want to do be able to make this changes automatic and not make a script that for the known fields does that, as I have multiple cases like this.
I was thinking maybe I could write a javascript code that attaches dynamically a change event to the dependent property input and a handler that would show/hide the required mark.
This is pretty much the solution which I came up with.
It is the best I could do, but still needs some customization meaning that the span that contains the "*" it's custom for my pages' DOM.
<div class="editor-label">
<label ui-documentLabel" for="Number">Number<span class="span-required">*</span></label>
</div>
<div class="editor-field">
<input class="k-textbox ui-textbox" data-val="true" data-val-requiredif="The field Number is required!" data-val-requiredif-dependentproperty="IsFake" data-val-requiredif-dependentvalue="False" data-val-requiredif-operator="IsEqual" id="Number" name="Number" value="" type="text">
<span class="field-validation-valid" data-valmsg-for="Number" data-valmsg-replace="true"></span>
</div>
and the javascript
var clietsidevalidation = function () { };
clietsidevalidation.is = function (value1, operator, value2) {
//function that verifies that the comparison between value1 and value2 is true or not
};
clietsidevalidation.handleRequirefIf = function (container) {
$('input', container).filter(function () {
var attr = $(this).attr('data-val-requiredif');
return (typeof attr !== 'undefined' && attr !== false);
}).each(function (index, item) {
var params = new Array();
params["operator"] = $(this).attr('data-val-requiredif-operator');
params["dependentvalue"] = $(this).attr('data-val-requiredif-dependentvalue');
params["dependentproperty"] = $(this).attr('data-val-requiredif-dependentproperty');
var dependentProperty = clietsidevalidation.getName(this, params["dependentproperty"]);
var dependentTestValue = params["dependentvalue"];
var operator = params["operator"];
var dependentPropertyElement = document.getElementsByName(dependentProperty);
params["element"] = this;
$(dependentPropertyElement).on('change', { params: params }, function (e) {
var input = e.data.params.element;
var inputName = $(input).attr("name");
var $span = $('label[for=' + inputName + '] span', '.editor-label');
var dependentProperty = this;
var dependentTestValue = e.data.params["dependentvalue"];
var operator = e.data.params["operator"];
var dependentPropertyElement = $(this);
var dependentValue = null;
if (dependentPropertyElement.length > 1) {
for (var index = 0; index != dependentPropertyElement.length; index++)
if (dependentPropertyElement[index]["checked"]) {
dependentValue = dependentPropertyElement[index].value;
break;
}
if (dependentValue == null)
dependentValue = false
}
else
dependentValue = dependentPropertyElement[0].value;
if (clietsidevalidation.is(dependentValue, operator, dependentTestValue) == false) {
$span.addClass('hidden');
var $form = $span.closest("form");
// get validator object
var $validator = $form.validate();
var $errors = $form.find("span.field-validation-error[data-valmsg-for='" + inputName + "']");
// trick unobtrusive to think the elements were succesfully validated
// this removes the validation messages
//custom form our solution as the validation messages are differently configured DOM
$errors.each(function () { $validator.settings.success($('label',this)); })
// clear errors from validation
$validator.resetForm();
}
else {
$span.removeClass('hidden')
}
});
});
};
This is inspired by another post which I can't find right now, but when I do I will post a link.

ASP.NET MVC Validation form with AngularJS

I'm with a project in MVC 4 and AngularJS (+ twitter bootstrap). I usually use in my MVC projects "jQuery.Validate", "DataAnnotations" and "Razor". Then I enable these keys in my web.config to validate properties of model on the client:
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
For example if I have in my model this:
[Required]
[Display(Name = "Your name")]
public string Name { get; set; }
With this Cshtml:
#Html.LabelFor(model => model.Name)
#Html.TextBoxFor(model => model.Name)
#Html.ValidationMessageFor(model => model.Name)
The html result would:
<label for="Name">Your name</label>
<input data-val="true" data-val-required="The field Your name is required." id="Name" name="Name" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Name" data-valmsg-replace="true"></span>
But now when I use AngularJS, I want to render maybe like this:
<label for="Name">Your name</label>
<input type="text" ng-model="Name" id="Name" name="Name" required />
<div ng-show="form.Name.$invalid">
<span ng-show="form.Name.$error.required">The field Your name is required</span>
</div>
I do not know if there are any helper or "Data Annotation" to resolve this. I understand that AngularJS has many more features like:
<div ng-show="form.uEmail.$dirty && form.uEmail.$invalid">Invalid:
<span ng-show="form.uEmail.$error.required">Tell us your email.</span>
<span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
</div>
Well, specifically. I need some helper or "Data Annotation" to resolve the attributes (Data Annotation) for display on the client with AngularJS.
If it still does not exist, perhaps it is time to do, like RazorForAngularJS
Edit
I think perhaps the best way to work with ASP.NET MVC and AngularJS is do it (front-end) by hand (writing all the HTML by hand)
As someone that's authored an ASP.Net/Angular website, I can tell you that you're going to be way better off stepping away from using Razor to render your HTML where you can.
In my projects I've set up one razor view to render my main page (I'm using a single page app written in Angular), then I have a folder of straight .html files that I use as my templates for Angular.
The rest is done in ASP.Net Web API calls in my case, but you can also use MVC action with JSON results.
As soon as I switched to this architecture, things went a lot more smoothly for me, development wise.
I agree with blesh idea about stepping away from razor, but you can create some tools for creating pages more rapid. IMHO it is better to use razor features where they needed instead of removing it from out toolset.
BTW have a look at ngval. It brings data annotations to client side as angularjs validators. It has an html helper and an angular module. I have to mention that project is in early development stages.
I wrote a directive to smooth out the transition from MVC to AngularJs. The markup looks like:
<validated-input name="username" display="User Name" ng-model="model.username" required>
Which behaves identically to Razor conventions, including delaying validation until after a field is modified. Over time, I've found maintaining my markup pretty intuitive and simple.
My article on the subject
Plinkr
I think there are probably half a dozen ways to do what you want. Probably the easiest is to use an Angular directive that recognizes jquery.validation markup.
Here is such a project: https://github.com/mdekrey/unobtrusive-angular-validation
And here is another: https://github.com/danicomas/angular-jquery-validate
I haven't tried either because personally, I solved this problem by writing code to make MVC output angular validation attributes instead of jquery.validation.unobtrusive attributes.
A 3rd option is to rely only on server side validation. Though this is obviously slower, it may be your only option sometimes for more complex validation scenarios. In this case, you just have to write javascript to parse the ModelStateDictionary object that Web API controllers usually return. There are some examples out there on how to do that and integrate it into AngularJS's native validation model.
Here is some incomplete code to parse the ModelStateDictionary:
````
angular.module('app')
.directive('joshServerValidate', ['$http', function ($http) {
return {
require: 'ngModel',
link: function (scope, ele, attrs, c) {
console.info('wiring up ' + attrs.ngModel + ' to controller ' + c.$name);
scope.$watch('modelState', function () {
if (scope.modelState == null) return;
var modelStateKey = attrs.joshServerValidate || attrs.ngModel;
modelStateKey = modelStateKey.replace(attrs.joshServerValidatePrefix, '');
modelStateKey = modelStateKey.replace('$index', scope.$index);
modelStateKey = modelStateKey.replace('model.', '');
console.info('validation for ' + modelStateKey);
if (scope.modelState[modelStateKey]) {
c.$setValidity('server', false);
c.$error.server = scope.modelState[modelStateKey];
} else {
c.$setValidity('server', true);
}
});
}
};
}]);
````
I'm rather disappointed with the other answers provided here. "Don't do it" isn't such a great suggestion when you're trying to validate something a little more difficult than an email address.
I solved this in a slightly different way. I modified my MVC application to response to the application/json content type via a filter and a custom view engine which injects a Json serializer razor template into the view locations to search.
This was done to allow skinning of our website with jQuery UI, Bootstrap & Json responses for the same controllers/actions.
Here is a sample json result:
{
"sid": "33b336e5-733a-435d-ad11-a79fdc1e25df",
"form": {
"id": 293021,
"disableValidation": false,
"phone": null,
"zipCode": "60610",
"firstName": null,
"lastName": null,
"address": null,
"unit": null,
"state": "IL",
"email": null,
"yearsAtAddress": null,
"monthsAtAddress": null,
"howHeard": null
},
"errors": [
"The first name is required",
"The last name is required",
"Please enter a phone number",
"Please enter an email address"
],
"viewdata": {
"cities": [
{
"selected": false,
"text": "CHICAGO",
"value": "CHICAGO"
}
],
"counties": [
{
"selected": false,
"text": "COOK"
}
]
}
}
The filter is used to translate redirect results into a json object which passes the next url onto the calling program:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
// if the request was application.json and the response is not json, return the current data session.
if (filterContext.HttpContext.Request.ContentType.StartsWith("application/json") &&
!(filterContext.Result is JsonResult || filterContext.Result is ContentResult))
{
if (!(filterContext.Controller is BaseController controller)) return;
string url = filterContext.HttpContext.Request.RawUrl ?? "";
if (filterContext.Result is RedirectResult redirectResult)
{
// It was a RedirectResult => we need to calculate the url
url = UrlHelper.GenerateContentUrl(redirectResult.Url, filterContext.HttpContext);
}
else if (filterContext.Result is RedirectToRouteResult routeResult)
{
// It was a RedirectToRouteResult => we need to calculate
// the target url
url = UrlHelper.GenerateUrl(routeResult.RouteName, null, null, routeResult.RouteValues, RouteTable.Routes,
filterContext.RequestContext, false);
}
else
{
return;
}
var absolute = url;
var currentUri = filterContext.HttpContext.Request.Url;
if (url != null && currentUri != null && url.StartsWith("/"))
{
absolute = currentUri.Scheme + "://" + currentUri.Host + url;
}
var data = new {
nextUrl = absolute,
uid = controller.UniqueSessionId(),
errors = GetFlashMessage(filterContext.HttpContext.Session)
};
var settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
Formatting = Formatting.Indented,
NullValueHandling = NullValueHandling.Ignore
};
filterContext.Result = new ContentResult
{
ContentType = "application/json",
Content = JsonConvert.SerializeObject(data,settings)
};
}
Here is the Views\Json\Serializer.cshml, with using statements excluded for brevity and security of our codebase. This does three attempts at returning a response. The first is to read the original View{controller}{action}.cshtml, parsing out the html helpers and placing those into forms and fields. The second attempt looks for and elements from our built-in blogging system (PostContent below) and failing that we just use the Model.
#model dynamic
#{
Response.ContentType = "application/json";
Layout = "";
var session = new Object(); // removed for security purposes
var messages = ViewBag.Messages as List<string>() ?? new List<string>();
var className = "";
if (!ViewData.ModelState.IsValid)
{
messages.AddRange(ViewData.ModelState.Values.SelectMany(val => val.Errors).Select(error => error.ErrorMessage));
}
dynamic result;
string serial;
try
{
Type tModel = Model == null ? typeof(Object) : Model.GetType();
dynamic form = new ExpandoObject();
dynamic fields = new ExpandoObject();
var controller = ViewContext.RouteData.Values["controller"] as string ?? "";
var action = ViewContext.RouteData.Values["action"] as string;
var viewPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Views", controller, action + ".cshtml");
if (File.Exists(viewPath))
{
string contents = File.ReadAllText(viewPath);
var extracted = false;
var patterns = new[]
{
#"#Html\.\w+For\(\w+ => \w+\.(.*?)[,\)]",
#"#Html\.(\w+)For\(\w+ => \w+\.([\w\.]+)[, ]*(\(SelectList\))*(ViewBag\.\w+)*[^\)]*",
"name=\"(.*?)\""
};
for (var i = 0; i < 3 && !extracted; i++)
{
switch (i)
{
case 0:
form = contents.ExtractFields(patterns[0], Model as object, out extracted);
fields = contents.ExtractElements(patterns[1], Model as object, out extracted, ViewData);
break;
case 1:
form = Model as mvcApp.Models.Blog == null ? null : (Model.PostContent as string).ExtractFields(patterns[2], Model as object, out extracted);
break;
default:
form = Model;
break;
}
}
}
else if (Model == null)
{
// nothing to do here - safeModel will serialize to an empty object
}
else if (Model is IEnumerable)
{
form = new List<object>();
foreach (var element in ((IEnumerable) Model).AsQueryable()
.Cast<dynamic>())
{
form.Add(CustomExtensions.SafeClone(element));
}
} else {
form = Activator.CreateInstance(tModel);
CustomExtensions.CloneMatching(form, Model);
}
// remove any data models from the viewbag to prevent
// recursive serialization
foreach (var key in ViewData.Keys.ToArray())
{
var value = ViewData[key];
if (value is IEnumerable)
{
var enumerator = (value as IEnumerable).GetEnumerator();
value = enumerator.MoveNext() ? enumerator.Current : null;
}
if (value != null)
{
var vtype = value.GetType();
if (vtype.Namespace != null && (vtype.Namespace == "System.Data.Entity.DynamicProxies" || vtype.Namespace.EndsWith("Models")))
{
ViewData[key] = null;
}
}
}
result = new
{
uid = session.UniqueId,
form,
fields,
errors = messages.Count == 0 ? null : messages,
viewdata = ViewBag
};
var setting = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.None,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Formatting = Formatting.Indented
};
if (form is IEnumerable)
{
setting.NullValueHandling = NullValueHandling.Ignore;
}
serial = JsonConvert.SerializeObject(result, setting);
}
catch (Exception e)
{
result = new {
uid = session.UniqueId,
error = e.Message.Split('|')
};
serial = JsonConvert.SerializeObject(result);
}
#Html.Raw(serial)
}
For the clone methods see Best way to clone properties of disparate objects
public static dynamic ExtractFields(this string html, string pattern, object model, out bool extracted)
{
if (html == null || model == null)
{
extracted = false;
return null;
}
dynamic safeModel = new ExpandoObject();
var safeDict = (IDictionary<string, Object>)safeModel;
var matches = new Regex(pattern).Matches(html);
extracted = matches.Count > 0;
if ( extracted )
{
foreach (Match match in matches)
{
var name = match.Groups[1].Value;
var value = CustomExtensions.ValueForKey(model, name);
var segments = name.Split('.');
var obj = safeDict;
for (var i = 0; i < segments.Length; i++)
{
name = segments[i];
if (i == segments.Length - 1)
{
if (obj.ContainsKey(name))
{
obj[name] = value;
}
else
{
obj.Add(name, value);
}
continue;
}
if (!obj.ContainsKey(name))
{
obj.Add(name, new ExpandoObject());
}
obj = (IDictionary<string, Object>)obj[name];
}
}
}
return safeModel;
}
And here is an implementation of key value coding to make dealing with property chains a bit easier:
/// <summary>
/// This borrows KeyValueCoding from Objective-C and makes working with long chains of properties more convenient.
/// KeyValueCoding is null tolerant, and will stop if any element in the chain returns null instead of throwing a NullReferenceException.
/// Additionally, the following Linq methods are supported: First, Last, Sum & Average.
/// <br/>
/// KeyValueCoding flattens nested enumerable types, but will only aggregate the last element: "children.grandchildren.first" will return
/// the first grandchild for each child. If you want to return a single grandchild, use "first.children.grandchildren". The same applies to
/// Sum and Average.
/// </summary>
/// <param name="source">any object</param>
/// <param name="keyPath">the path to a descendant property or method "child.grandchild.greatgrandchild".</param>
/// <param name="throwErrors">optional - defaults to supressing errors</param>
/// <returns>returns the specified descendant. If intermediate properties are IEnumerable (Lists, Arrays, Collections), the result *should be* IEnumerable</returns>
public static object ValueForKey(this object source, string keyPath, bool throwErrors = false)
{
try
{
while (true)
{
if (source == null || keyPath == null) return null;
if (keyPath == "") return source;
var segments = keyPath.Split('.');
var type = source.GetType();
var first = segments.First();
var property = type.GetProperty(first);
object value = null;
if (property == null)
{
var method = type.GetMethod(first);
if (method != null)
{
value = method.Invoke(source, null);
}
}
else
{
value = property.GetValue(source, null);
}
if (segments.Length == 1) return value;
var children = string.Join(".", segments.Skip(1));
if (value is IEnumerable || "First|Last|Sum|Average".IndexOf(first, StringComparison.OrdinalIgnoreCase) > -1)
{
var firstChild = children.Split('.').First();
var grandchildren = string.Join(".", children.Split('.').Skip(1));
if (value == null) {
var childValue = source.ValueForKey(children);
value = childValue as IEnumerable<object>;
switch (first.Proper())
{
case "First":
return value == null ? childValue : ((IEnumerable<object>)value).FirstOrDefault();
case "Last":
return value == null ? childValue : ((IEnumerable<object>)value).LastOrDefault();
case "Count":
return value == null ? (childValue == null ? 0 : 1) : (int?)((IEnumerable<object>)value).Count();
case "Sum":
return value == null
? Convert.ToDecimal(childValue ?? "0")
: ((IEnumerable<object>) value).Sum(obj => Convert.ToDecimal(obj ?? "0"));
case "Average":
return value == null
? Convert.ToDecimal(childValue ?? "0")
: ((IEnumerable<object>) value).Average(obj => Convert.ToDecimal(obj ?? "0"));
}
} else {
switch (firstChild.Proper())
{
case "First":
return ((IEnumerable<object>)value).FirstOrDefault().ValueForKey(grandchildren);
case "Last":
return ((IEnumerable<object>)value).LastOrDefault().ValueForKey(grandchildren);
case "Count":
if (!string.IsNullOrWhiteSpace(grandchildren))
{
value = value.ValueForKey(grandchildren);
if (value != null && ! (value is IEnumerable<object>))
{
return 1;
}
}
return value == null ? 0 : ((IEnumerable<object>)value).Count();
case "Sum":
return ((IEnumerable<object>)value).Sum(obj => Convert.ToDecimal(obj.ValueForKey(grandchildren)??"0"));
case "Average":
return ((IEnumerable<object>)value).Average(obj => Convert.ToDecimal(obj.ValueForKey(grandchildren) ?? "0"));
}
}
if (value == null) return null;
var flat = new List<object>();
foreach (var element in (IEnumerable<object>)value)
{
var child = element.ValueForKey(children);
if (child == null)
{
continue;
}
if (child is IEnumerable && !(child is string))
{
flat.AddRange((IEnumerable<object>) child);
}
else
{
flat.Add(child);
}
}
return flat.Count == 0? null: flat;
}
source = value;
keyPath = children;
}
}
catch (Exception)
{
if (throwErrors) throw;
}
return null;
}

Persist CheckBox State in Telerik MVC Grid While Paging in ASP.NET MVC Application

I am using Telerik MVC Grid where one of the columns is checkboxes. If I select checkboxes and then go to page 2 and then come back to page 1 all the checkboxes are gone. Which is of course the way HTTP works. Now, I put all the selected checkboxes inside the hidden field but since the grid does some sort of postback my hidden field is cleared next time.
If you're using Client Side data binding you can use the javascript/jquery below to maintain checkbox state.
Maintain checkbox state:
var selectedIds = [];
$(document).ready(function () {
//wire up checkboxes.
$('#YOUR_GRID_ID :checkbox').live('change', function (e) {
var $check = $(this);
console.log($check);
if ($check.is(':checked')) {
//add id to selectedIds.
selectedIds.push($check.val());
}
else {
//remove id from selectedIds.
selectedIds = $.grep(selectedIds, function (item, index) {
return item != $check.val();
});
}
});
});
Restore checkbox state after data binding:
function onDataBound(e) {
//restore selected checkboxes.
$('#YOUR_GRID_ID :checkbox').each(function () {
//set checked based on if current checkbox's value is in selectedIds.
$(this).attr('checked', jQuery.inArray($(this).val(), selectedIds) > -1);
});
}
A more verbose explanation available on my blog:
http://blog.cdeutsch.com/2011/02/preserve-telerik-mvc-grid-checkboxes.html
You need to save the state of the checkboxes to your database, and then retrieve them again from the database when you reload the page.
During paging, you need to reload only those records that pertain to a particular page. You can do that using the Skip() and Take() methods from Linq.
to preserve checked /unchecked checkbox state using telerik grid clientemplate across postbacks and async postbacks and in refreshing grid and (auto)paging, I tried the solution above with no avail and so went up with a bit harder solution; as I could not save the state in db, I used a session variable and an hiddenfield:
first, a way to do ajax postback (see function DoAjaxPostAndMore , courtesy of somebody herearound), where in success we take care of client values of selections, adding and removing as checked /unchecked
I also had to manually check / uncheck the checkboxes inside the manual ajax post
second, an hidden field (see 'hidSelectedRefs') to preserve clientactions, as the Session variable I am using will not be seen clientside in partial rendering
#model IEnumerable<yourInterfaceOrClass>
#{
ViewBag.Title = "Select Something via checkboxes";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Select Something via checkboxes</h2>
<!-- we need a form with an id, action must be there but can be an empty string -->
<form id="frm" name ="frm" action="">
<p>
<!--we need this as Session new values will not be takein in aajax requests clientisde, so it is easier to mange this field, which, in first and subsequent complete postbacks can have the value of the Session variable -->
<input type="hidden" name="hidSelectedRefs" id="hidSelectedRefs" value= '#Session["SelectedReferencesToPrint"]' />
</p>
<br />
<script type="text/javascript">
//ajax manual post to a custom action of controller, passing the id of record and the state of the checkbox
//to adjust the Session value
//data: $form.serialize() will have the single checbox value
//but only if checked. To make my life eaasier, I added the value (is id ) and the checked/unchecked
//state of checkbox (is the $(chkClicked).attr('checked'))
function DoAjaxPostAndMore(chkClicked) {
var $form = $("#frm");
$.ajax({
type: "POST",
url: 'SelectReferences',
data: $form.serialize() + '&id=' + $(chkClicked).val() + '&checked=' + $(chkClicked).attr('checked'),
error: function (xhr, status, error) {
//do something about the error
alert("Sorry, we were not able to get your selection...");
},
success: function (response) {
//I also needed to check / uncheck manually the checkboxes:
$(chkClicked).attr('checked', !$(chkClicked).attr('checked'));
//and now put correct values in hidSelectedRefs hidden field:
if ($(chkClicked).attr('checked')) {
$('input[name=hidSelectedRefs]').val($('input[name=hidSelectedRefs]').val() + '|' + $(chkClicked).val() + '|');
} else {
var tmp = $('input[name=hidSelectedRefs]').val();
$('input[name=hidSelectedRefs]').val(tmp.toString().replace('|' + $(chkClicked).val() + '|', ''));
}
}
});
return false; // if it's a link to prevent post
}
Then I handled the OnRowDataBound, to ensure the checboxes would be correctly checked on postbacks,
function onRowDataBound(e) {
var itemsChecked = $('input[name=hidSelectedRefs]').val();
if (itemsChecked)
{
if (itemsChecked.indexOf('|' + $(e.row).find('input[name=checkedRecords]').val() + '|') >= 0)
{
$(e.row).find('input[name=checkedRecords]').attr('checked', true);
}
}
}
</script>
The telerik mvc Grid is as follows:
(you can see I also handled OnDataBinding and OnDataBound, but thats's only to show a
"Loading" gif. The controller is named "Output" and the action that normally would be called "Index" here is callled "PrintReferences". The correspondenting Ajax action is called "_PrintReferences")
Of interest here is the ClientTemplate for checkbox (cortuesy of someone else herearound, where onclick
we call our custom ajax action (named "SelectReferences") on our Output controller via a call to the
DoAjaxPostAndMore() javascript/jquery function
#(Html.Telerik().Grid<yourInterfaceOrClass>()
.Name("Grid")
.ClientEvents(e => e.OnDataBinding("showProgress").OnDataBound("hideProgress").OnRowDataBound("onRowDataBound"))
.DataBinding(dataBinding =>
{
dataBinding.Server().Select("PrintReferences", "Output", new { ajax = ViewData["ajax"]});
dataBinding.Ajax().Select("_PrintReferences", "Output").Enabled((bool)ViewData["ajax"]);
})
.Columns( columns =>
{
columns.Bound(o => o.ID);
columns.Bound(o => o.ID)
.ClientTemplate(
"<input type='checkbox' name='checkedRecords' value='<#= ID #>' onclick='return DoAjaxPostAndMore(this)' />"
)
.Width(30)
.Title("")
.HtmlAttributes(new { style = "text-align:center; padding: 0px; margin: 0px;" });
columns.Bound(o => o.TITLE);
columns.Bound(o => o.JOBCODE);
columns.Bound(o => o.ORDERCODE );
//columns.Bound(o => o.AUTHOR);
columns.Bound(o => o.STATE);
columns.Command(commands =>
commands
.Custom("Details")
.ButtonType(GridButtonType.Image)
.HtmlAttributes(new { #class = "t-icon-details" })
.DataRouteValues(route => route.Add(o => o.ID)
.RouteKey("ID"))
.Ajax(false)
.Action("Details", "Referenza")
);
})
.Pageable(paging =>
paging.PageSize(10)
.Style(GridPagerStyles.NextPreviousAndNumeric)
.Position(GridPagerPosition.Bottom))
.Sortable()
.Filterable()
.Resizable(resizing => resizing.Columns(true))
.Reorderable(reorder => reorder.Columns(true))
.NoRecordsTemplate("No Reference found. Please review your filters...")
.ColumnContextMenu()
)
</form>
that's all for the View. Now, to the controller:
//Get : this is the usally called "Index" action
//here we can load data, but we also must ensure the Session variable is fine
public ActionResult PrintReferences(bool? ajax, string msgInfo, string selectedRef)
{
if (Session["SelectedReferencesToPrint"] == null)
{
Session["SelectedReferencesToPrint"] = string.Empty;
}
if (string.IsNullOrEmpty(selectedRef))
{
selectedRef = "|0|";
}
string msgOut = string.Empty;
//this is where I get data to show
List<yourInterfaceOrClass> ret = LoadData(out msgOut);
if (!string.IsNullOrEmpty(msgInfo) && !string.IsNullOrEmpty(msgInfo.Trim()))
{
msgOut = msgInfo + ' ' + msgOut;
}
ViewBag.msgOut = msgOut;
ViewData["ajax"] = ajax ?? true;
return View(ret);
}
//GridAction: here is telerik grid Ajax get request for your "_Index"
[GridAction]
public ActionResult _PrintReferences(string msgInfo)
{
//again, we must take care of Session variable
if (Session["SelectedReferencesToPrint"] == null)
{
Session["SelectedReferencesToPrint"] = string.Empty;
}
string msgOut = string.Empty;
List<yourInterfaceOrClass> ret = LoadData(out msgOut);
return View(new GridModel(ret));
}
//Post: this is where our custom ajax post goes
//we are here if a checkbox is cheched or unchecked
//in the FormCollection parameter we get the checkbox value only if checked, and also
//(and always) the parms we passed (id of record and state of checkbox: we cannot simply add,
//we must also subtract unchecked)
[HttpPost]
public ActionResult SelectReferences(FormCollection collection)
{
//we need a session variable
if (Session["SelectedReferencesToPrint"] == null)
{
Session["SelectedReferencesToPrint"] = string.Empty;
}
//use a local variable for calculations
string wholeSelectionToPrint = Session["SelectedReferencesToPrint"].ToString();
//get value passed: id
string selectedRefId = collection["id"];
if (!string.IsNullOrEmpty(selectedRefId))
{
selectedRefId = "|" + selectedRefId + "|";
}
bool cheked = (collection["checked"].ToString()=="checked");
//get vcalue passed :checked or unchecked
if (cheked)
{
//the element is to add
wholeSelectionToPrint += selectedRefId;
}
else
{
//the element is to remove
wholeSelectionToPrint = wholeSelectionToPrint.Replace(selectedRefId, "");
}
//set session variable final value
Session["SelectedReferencesToPrint"] = wholeSelectionToPrint;
return null;
}
//normal postback:
//we will be here if we add a button type submit in our page,
//here we can collect all data from session variable to do
//something with selection
[HttpPost]
public ActionResult PrintReferences(FormCollection collection)
{
//get selected references id
if (Session["SelectedReferencesToPrint"] == null)
{
Session["SelectedReferencesToPrint"] = string.Empty;
}
//use a local variable for calculations
string wholeSelectionToPrint = Session["SelectedReferencesToPrint"].ToString();
wholeSelectionToPrint = wholeSelectionToPrint.Replace("||", "|");
string[] selectdIDs = wholeSelectionToPrint.Split(new char[] { '|' });
foreach (string id in selectdIDs)
{
if (!string.IsNullOrEmpty(id))
{
//do something with single selected record ID
System.Diagnostics.Debug.WriteLine(id);
}
}
//omitted [....]
ViewData["ajax"] = true;
return View(ret);
}

How to redirect to a controller action from a JSONResult method in ASP.NET MVC?

I am fetching records for a user based on his UserId as a JsonResult...
public JsonResult GetClients(int currentPage, int pageSize)
{
if (Session["UserId"] != "")
{
var clients = clirep.FindAllClients().AsQueryable();
var count = clients.Count();
var results = new PagedList<ClientBO>(clients, currentPage - 1, pageSize);
var genericResult = new { Count = count, Results = results };
return Json(genericResult);
}
else
{
//return RedirectToAction("Index","Home");
}
}
How to redirect to a controller action from a JsonResult method in asp.net mvc?Any suggestion...
EDIT:
This doesn't seem to work...
if (Session["UserId"] != "")
{
var clients = clirep.FindAllClients().AsQueryable();
var count = clients.Count();
var results = new PagedList<ClientBO>(clients, currentPage - 1, pageSize);
var genericResult = new { Count = count, Results = results ,isRedirect=false};
return Json(genericResult);
}
else
{
return Json({redirectUrl = Url.Action("Index", "Home"), isRedirect = true });
}
This will depend on how you are invoking this controller action. As you are using JSON I suppose that you are calling it in AJAX. If this is the case you cannot redirect from the controller action. You will need to do this in the success callback of the AJAX script. One way to achieve it is the following:
return Json(new
{
redirectUrl = Url.Action("Index", "Home"),
isRedirect = true
});
And in the success callback:
success: function(json) {
if (json.isRedirect) {
window.location.href = json.redirectUrl;
}
}
Remark: Make sure to include isRedirect = false in the JSON in case you don't want to redirect which is the first case in your controller action.
Adding to Darin Dimitrov's answer. For C#.NET MVC - If you want to redirect to a different page/controller and want to send an Object/Model to the new controller, You can do something like this.
In the JsonResult Method (in the controller):
ErrorModel e = new ErrorModel();
e.ErrorTitle = "Error";
e.ErrorHeading = "Oops ! Something went wrong.";
e.ErrorMessage = "Unable to open Something";
return Json(new
{
redirectUrl = Url.Action("Index", "Home",e),
isRedirect = true
});
And in the success callback:
success: function(json) {
if (json.isRedirect) {
window.location.href = json.redirectUrl;
}
}
And if the new controller can accept the model/object like below.. you can pass the object to the new controller/page
public ActionResult Index(ErrorModel e)
{
return View(e);
}
Hope this helps.
What to do you think about trying to call:
return (new YourOtherController()).JSONResultAction();
instead of using redirects?
And if you work with areas ...
Controller:
return Json(new
{
redirectUrl = Url.Action("Index", "/DisparadorProgSaude/", new { area = "AreaComum" }),
isRedirect = true
});
View:
success: function (json) {
if (json.isRedirect) {
window.location.href = json.redirectUrl;
}
},
No way to do this, the client is executing an AJAX script so will not be able to handle anything else.
I suggest you redirect in the client script based on the returned data in the callback function.
Take a look at a similar question here: http://bytes.com/topic/javascript/answers/533023-ajax-redirect

Resources