Comparing Selected DropDown with condition - javascript

i am creating a form which can be accessed based on condition in MVC. I have first view with dropdownlist and submit button, i want when the submit button is clicked, the value in dropdownlist is passed and compared with condition set for that value, and if the condition is not ok, it shows alert rather than processing to the form.
Here is my code:
public ActionResult ChooseType()
{
var x = DataAccess.GetEmployee(#User.Identity.Name);
var lists = new SelectList(RuleAccess.GetAllRule(), "ID", "TypeDetail");
ViewBag.CategoryId = lists;
/*rule*/
ViewBag.comp1 = Logic.AnnualToogle(#User.Identity.Name);
if (x.EmpSex == "F" && x.EmpMaritalSt == "NIKAH")
{ ViewBag.comp2 = 1; }
else ViewBag.comp2 = 0;
return View();
}
[HttpGet]
public ActionResult Create(int lv_type)
{
var type = RuleAccess.GetTypeByID(lv_type);
ViewBag.type = type;
var model = new LeaveApplicationViewModels();
model.X = DataAccess.GetEmployee(#User.Identity.Name);
model.C = DataAccess.GetLeaveApp(#User.Identity.Name);
/*disable*/
ViewBag.dis = DataAccess.GetDisabledDate(#User.Identity.Name);
/*max*/
var max= RuleAccess.GetMaxByID(lv_type);
ViewBag.c = max;
if (lv_type == 1)
{
var used = RuleAccess.CountUsedAnnual(#User.Identity.Name);
var rem = max - used;
ViewBag.a = used;
ViewBag.b = rem;
}
else
{
ViewBag.b = max;
}
return View(model);
}
I used the Viewbag.comp 1 & 2 in my view:
<script type="text/javascript">
var x = #ViewBag.comp1;
var y = #ViewBag.comp2;
function validatecreate()
{
var value= document.getElementById("lv_type").value;
if (value==1)
{
if(x==1)
document.getElementById('validatecreate').submit();
else { alert('Action cant be done. You either have another annual leave application in pending status or you have reach the limit of annual leave'); }
}
else if(value==2)
{
if(y==1)
document.getElementById('validatecreate').submit();
else { alert('Action cant be done. You either are Male or Not Married Yet'); }
}
else if(value==3)
{
document.getElementById('validatecreate').submit();
}
else {
document.getElementById('validatecreate').submit();
//alert('Http Not Found');
}
}
#Html.DropDownList(
"lv_type", (SelectList) ViewBag.CategoryId,
"--Select One--",
new{ //anonymous type
#class = "form-control input-sm"
}
)
I feel like im doing it wrong especially because if someone manually put the url with ?lv_type=2, they not validate and can go to the form directly. But i need the value of lv_type bcs i use that in my view. Please Helpp :(

Validation must always be done on the server, and client side validation should only be considered a nice bonus that minimizes the need for to a call to the server. And presenting options in a dropdownlist to a user and then telling them thay can't select that option is an awful user experience. Instead, you should be presenting only those options which are applicable to the user (and delete all the scripts you have shown).
Create an additional method in your RuleAccess class, say GetEmployeeRules(Employee employee) which returns only the rules that are applicable to that employee, for example
public static List<Rule> GetEmployeeRules(Employee employee)
{
// Get the list of all rules
if (employee.EmpSex == "F" && employee.EmpMaritalSt == "NIKAH")
{
// Remove the appropriate Rule from the list
}
.....
// Return the filtered list
}
In addition, you should be using a view model in the view
public class LeaveTypeVM
{
[Required(ErrorMessage = "Please select a leave type")]
public int SelectedLeaveType { get; set; }
public IEnumerable<SelectListItem> LeaveTypeList { get; set; }
}
Then in the ChooseType() method
public ActionResult ChooseType()
{
var employee = DataAccess.GetEmployee(#User.Identity.Name);
var rules = RuleAccess.GetEmployeeRules(employee);
var model = new LeaveTypeVM()
{
LeaveTypeList = new SelectList(rules, "ID", "TypeDetail")
};
return View(model);
}
and in the view
#model LeaveTypeVM
#using (Html.BeginForm())
{
#Html.DropDownListFor(m => m.SelectedLeaveType, Model.LeaveTypeList, "--Select One--", new { #class = "form-control input-sm" }
#Html.ValidationMessageFor(m => m.SelectedLeaveType)
<input type="submit" value="Submit" />
}
and submit to a POST method which allows you to easily return the view if its invalid, or to redirect to the Create method.
[HttpPost]
public ActionResult ChooseType(LeaveTypeVM model)
{
if (!ModelState.IsValid)
{
model.LeaveTypeList = .... // as per GET method
}
return RedirectToAction("Create", new { leaveType = model.SelectedLeaveType });
and in the Create() method
public ActionResult Create(int leaveType)
{
var employee = DataAccess.GetEmployee(#User.Identity.Name);
var rule = RuleAccess.GetEmployeeRules(employee).Where(x => x.ID == leaveType).FirstOrDefault();
if (rule == null)
{
// Throw exception or redirect to an error page
}
var model = new LeaveApplicationViewModels();
....
return View(model);
}
Note your LeaveApplicationViewModels should contain additional properties so that you can avoid all those ViewBag properties and generate a strongly typed view.

Related

Modal not working properly after validating it from POST Action

I am using a Modal Partial View in my app. I am validating my model if it has errors I am returning the model using ModelState.AddModelError() but it is not working fine. Also, I could not load the SelectLists.
public ActionResult StockOut(StockOut model)
{
if (ModelState.IsValid)
{
var stock = (from s in db.Stocks where s.ProductId == model.ProductId select s).FirstOrDefault();
if (stock.Quantity > 0 && model.Quantity <= stock.Quantity)
{
var weight = ((from p in db.Products where p.Id == model.ProductId select p).FirstOrDefault().NetWeight) * model.Quantity;
stock.Quantity -= model.Quantity;
stock.TotalWeight -= weight;
StockOut entity = new StockOut()
{
DriverId = model.DriverId,
LastUpdated = DateTime.Now,
ProductId = model.ProductId,
Quantity = model.Quantity,
TotalWeight = weight
};
db.StockOut.Add(entity);
db.SaveChanges();
return RedirectToAction("Index");
}
else
{
ModelState.AddModelError("Quantity", "Please Enter A Valid Quantity");
ViewBag.ProductId = new SelectList(db.Products.ToList(), model.ProductId);
ViewBag.DriverId = new SelectList(db.Products.ToList(), model.DriverId);
}
}
ViewBag.ProductId = new SelectList(db.Products.ToList(), model.ProductId);
ViewBag.DriverId = new SelectList(db.Users.ToList(), model.DriverId);
return PartialView(model);
}
Before Submitting:
After Getting an Error
fix you select lists, you are creating them twice, remove one set (inside of if)
ViewBag.ProductId = new SelectList(db.Products.ToList(),"Id", "Name" , model.ProductId);
///Are you sure that you have have to use products again?
ViewBag.DriverId = new SelectList(db.Products.ToList(),"Id",
"Name" , model.DriverId);
and if you want to use AddModelError you have to add to form
#Html.ValidationSummary(false)

Get ID from all checked checkboxes

On a page in a Razor MVC app a bunch of lines are written from a model, like this:
#foreach (var item in Model)
{
// write lots of data
#Html.CheckBoxFor(item.Id) // incorrect syntax, I know
}
<input type="button" value="Update" onclick="Update();" />
The javascript that is run from the button is supposed to get the item.Id (which is a unique integer) from each checkbox that has been checked, put the ids in an array and pass the array to another function. This is what I'm working with so far:
function Update() {
var ids = document.getElementsByName("myCheckbox");
var idList = new Array(ids.length);
for (var i = 0; i < ids.length; i++) {
if (ids[i].checked {
idList.push(ids[i]);
}
}
$.ajax({
type: "POST",
url: "#Url.Action("Update", "Home")",
data: idList,
success: function (data) {
},
error: function (data) {
console.log(data);
}
});
}
How do I add the same name to all the checkboxes and have their ID be that of item.Id?
Edit
I tried C Murphy's suggestion by making a class for the checkboxes:
public class ToBeUpdated
{
public int ID { get; set; }
public bool IsSelected { get; set; }
}
And then adding a List to the class that's in the model:
public List<ToBeUpdated> ToBeUpdatedList { get; set; }
However, I can't figure out how to write the CheckBoxFor. His/her suggestion was:
#Html.CheckboxFor(modelItem => modelItem.SomeNameList[i].IsSelected)
But I'm not doing a for loop but a foreach:
foreach (var item in Model)
So I had to do this instead:
#Html.CheckBoxFor(modelItem => item.ToBeUpdatedList.Find(m => m.ID == item.Id).IsSelected)
Then I have my button that sends me to the function in my controller:
<input type="button" value="Update" onclick="location.href='#Url.Action("UpdateStatuses", "Home")'" />
public IActionResult UpdateStatuses(IEnumerable<FileData> model)
{
// iterate through the items in the model
}
The problem is that when I get to UpdateStatuses the model is empty even though I had a ton of checkboxes that I checked before clicking the button. The model in the View is
#model IEnumerable<MyData>
And MyData is a simple class:
public class MyData
{
public int Id { get; set; }
public List<ToBeUpdated> ToBeUpdatedList { get; set; }
}
Edit 2: Finally found a solution
I made my checkboxes like this:
<input type="checkbox" name="chkUpdateThis" id="#item.Id" />
And then I could, with the help of T.J's code, edit my javascript to do this:
function UpdateStatuses() {
const ids = [...document.querySelectorAll("[name=chkUpdateThis]")].filter(({checked}) => checked).map(({id}) => id)
var idList = new Array();
for (var i = 0; i < ids.length; i++) {
idList.push(parseInt(ids[i]));
}
$.ajax({
type: "POST",
url: "#Url.Action("UpdateStatuses", "Home")",
data: ({ ids: idList }),
success: function (data) {
},
error: function (data) {
console.log(data);
}
});
}
Then I can call the UpdateStatuses function in my Home controller:
public IActionResult UpdateStatuses(List<int> ids)
Since T.J didn't post a suggestion that I can give a checkbox to, I'll give it to C Murphy who was also helpful. I didn't use his suggestion, but I don't want to give myself a checkbox even though the solution I went for is right here.
#Html.CheckBoxFor(model => model.Id, new { Name = "myCheckbox" })
On a similar note to what Heretic Monkey commented, you should probably set up a class. Without knowing what your Update ActionResult is doing in your controller, I can't give you a complete answer, but you could follow this general structure:
Model:
public class SomeNameViewModel
{
public List<SomeName> SomeNameList { get; set; }
}
public class SomeName
{
public int Id { get; set; }
public bool IsSelected { get; set; }
}
View:
#model ProjectName.Models.SomeNameViewModel
#for (var i = 0; i < Model.Count(); i++)
{
<table>
<tr>
<td>#Html.HiddenFor(modelItem => modelItem.SomeNameList[i].Id)</td>
<td>#Html.CheckboxFor(modelItem => modelItem.SomeNameList[i].IsSelected)</td>
</tr>
</table>
}
Edit (due to information provided via comment):
(note I also adjusted the previous section to use a ViewModel instead of a list of the new class)
You would then adjust your ActionResult to instead accept the ViewModel class as an argument and perform the database update as follows:
Controller:
public ActionResult Update(SomeNameViewModel viewModel)
{
List<int> selectedIds = new List<int>();
foreach(var row in viewModel.SomeNameList)
{
if(row.IsSelected == true)
{
selectedIds.Add(row.Id);
}
else
{
// wasn't selected, so do nothing
}
}
// Now you have a list of Ids (selectedIds) that were selected
// and can then be passed to your other function
}
Second Edit:
I think there is still some confusion surrounding the business requirement around what you are trying to accomplish.
The reason for the error was I assumed you would be passing those IDs (in the form of the ToBeUpdated view model class) to the ActionResult, instead of passing in the IEnumerable<FileData>. That is why it is empty when it reaches your controller. The structure of MVC (Model View Controller) dictates that you pass information in the form of a model (class) to the controller from the view (and vice versa).
Please provide your full model list along with a more detailed explanation of what you are trying to achieve and why.
The reason why I utilized a for loop instead of a foreach loop is to ensure that the list is serialized with unique indexes for each item in the list. The only reason you need to use checkboxes is to identify which IDs are to be updated. The logic to determine which are going to be updated should be used on the controller side. Think of a checkbox as an input field for a boolean (true/false, 0/1) property. The goal of a unique ID is just to identify your unique data records. The boolean (checkbox field on the view side) is just to have your end user designate a specific record(s) as needing to be updated.Whatever your model may be, you should add a boolean property (like I gave as an example) to have the user determine which record(s) to update, and call the SQL update command from your controller ONLY on the records from your list/IEnumerable that have a value of true for your IsSelected property.

How to create dependant dropndown/selection cell In GWT celltable 2.3?

I am using gwt2.3 celltable.
In my celltable there will be multiple column out of that few columns are dependent.
ex. Name and address columns are dependent
Name and address columns contains my custom selection cells.
In Name column : 1 cell contains jon,tom,steve
when Name cell contains jon then I want to set US & UK in address cell
if user changes Name cell to tom then I want to set india & china in address cell
if user changes Name cell to steve then I want to set japana & bhutan in address cell
I want to change dependent data from address cell when name cells selection changes.
How I can achieve this thing? Any sample code or pointers to do this?
This solution is for GWT 2.5, but it should probably work in 2.3.
I think the best is that you modify your RecordInfo element when you change the selection on the first column. You can do it similar to this in your CustomSelectionCell:
#Override
public void onBrowserEvent(Context context, Element parent, C value, NativeEvent event, ValueUpdater<C> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if (BrowserEvents.CHANGE.equals(event.getType())) {
Xxxxx newValue = getSelectedValueXxxxx();
valueUpdater.update(newValue);
}
}
Then where you use your cell add a fieldUpdater like this, that will update the RecordInfo with the new value and ask to redraw the row:
column.setFieldUpdater(new FieldUpdater<.....>() {
....
recordInfo.setXxxxx(newValue);
cellTable.redrawRow(index);
....
});
This will call the render of the other CustomSelectionCell, in there you will be able to check if the value of the RecordInfo has changed and update the seletion values as needed. Example:
#Override
public void render(Context context, C value, SafeHtmlBuilder sb) {
if (!value.getXxxxx().equals(this.lastValue)) {
this.items = loadItemsForValueXxxx(value.getXxxxx());
}
.... // Your usual render.
}
Be careful when you changed the items to set a default selected item too.
This is my implementation of DynamicSelectionCell.
DynamicSelectionCell allows you to render different options for different rows in the same GWT table.
Use a single DynamicSelectionCell object and use the addOption method to add options for each row. Options are stored in a Map with the Key being the row number.
For each row $i in the table the options stored in the Map for key $i are rendered.
Works on DataGrid, CellTable.
CODE
public class DynamicSelectionCell extends AbstractInputCell<String, String> {
public TreeMap<Integer, List<String>> optionsMap = new TreeMap<Integer, List<String>>();
interface Template extends SafeHtmlTemplates {
#Template("<option value=\"{0}\">{0}</option>")
SafeHtml deselected(String option);
#Template("<option value=\"{0}\" selected=\"selected\">{0}</option>")
SafeHtml selected(String option);
}
private static Template template;
private TreeMap<Integer, HashMap<String, Integer>> indexForOption = new TreeMap<Integer, HashMap<String, Integer>>();
/**
* Construct a new {#link SelectionCell} with the specified options.
*
* #param options the options in the cell
*/
public DynamicSelectionCell() {
super("change");
if (template == null) {
template = GWT.create(Template.class);
}
}
public void addOption(List<String> newOps, int key){
optionsMap.put(key, newOps);
HashMap<String, Integer> localIndexForOption = new HashMap<String, Integer>();
indexForOption.put(ind, localIndexForOption);
refreshIndexes();
}
public void removeOption(int index){
optionsMap.remove(index);
refreshIndexes();
}
private void refreshIndexes(){
int ind=0;
for (List<String> options : optionsMap.values()){
HashMap<String, Integer> localIndexForOption = new HashMap<String, Integer>();
indexForOption.put(ind, localIndexForOption);
int index = 0;
for (String option : options) {
localIndexForOption.put(option, index++);
}
ind++;
}
}
#Override
public void onBrowserEvent(Context context, Element parent, String value,
NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
String type = event.getType();
if ("change".equals(type)) {
Object key = context.getKey();
SelectElement select = parent.getFirstChild().cast();
String newValue = optionsMap.get(context.getIndex()).get(select.getSelectedIndex());
setViewData(key, newValue);
finishEditing(parent, newValue, key, valueUpdater);
if (valueUpdater != null) {
valueUpdater.update(newValue);
}
}
}
#Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
// Get the view data.
Object key = context.getKey();
String viewData = getViewData(key);
if (viewData != null && viewData.equals(value)) {
clearViewData(key);
viewData = null;
}
int selectedIndex = getSelectedIndex(viewData == null ? value : viewData, context.getIndex());
sb.appendHtmlConstant("<select tabindex=\"-1\">");
int index = 0;
try{
for (String option : optionsMap.get(context.getIndex())) {
if (index++ == selectedIndex) {
sb.append(template.selected(option));
} else {
sb.append(template.deselected(option));
}
}
}catch(Exception e){
System.out.println("error");
}
sb.appendHtmlConstant("</select>");
}
private int getSelectedIndex(String value, int ind) {
Integer index = indexForOption.get(ind).get(value);
if (index == null) {
return -1;
}
return index.intValue();
}
}
Varun Tulsian's answer is very good, but the code is incomplete.
The DynamicSelectionCell stores each rows' options in a map. When the cell updates or renders itself, it matches the row index from your Context to its matching row list in your map.
For posterity, see the simplified and updated version below:
public class DynamicSelectionCell extends AbstractInputCell<String, String> {
interface Template extends SafeHtmlTemplates {
#Template("<option value=\"{0}\">{0}</option>")
SafeHtml deselected(String option);
#Template("<option value=\"{0}\" selected=\"selected\">{0}</option>")
SafeHtml selected(String option);
}
private static Template template;
/**
* key: rowIndex
* value: List of options to show for this row
*/
public TreeMap<Integer, List<String>> optionsMap = new TreeMap<Integer, List<String>>();
/**
* Construct a new {#link SelectionCell} with the specified options.
*
*/
public DynamicSelectionCell() {
super("change");
if (template == null) {
template = GWT.create(Template.class);
}
}
public void addOptions(List<String> newOps, int rowIndex) {
optionsMap.put(rowIndex, newOps);
}
public void removeOptions(int rowIndex) {
optionsMap.remove(rowIndex);
}
#Override
public void onBrowserEvent(Context context, Element parent, String value,
NativeEvent event, ValueUpdater<String> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
String type = event.getType();
if ("change".equals(type)) {
Object key = context.getKey();
SelectElement select = parent.getFirstChild().cast();
String newValue = optionsMap.get(context.getIndex()).get(select.getSelectedIndex());
setViewData(key, newValue);
finishEditing(parent, newValue, key, valueUpdater);
if (valueUpdater != null) {
valueUpdater.update(newValue);
}
}
}
#Override
public void render(Context context, String value, SafeHtmlBuilder sb) {
// Get the view data.
Object key = context.getKey();
String viewData = getViewData(key);
if (viewData != null && viewData.equals(value)) {
clearViewData(key);
viewData = null;
}
int selectedIndex = getSelectedIndex(viewData == null ? value : viewData, context.getIndex());
sb.appendHtmlConstant("<select tabindex=\"-1\">");
int index = 0;
try {
for (String option : optionsMap.get(context.getIndex())) {
if (index++ == selectedIndex) {
sb.append(template.selected(option));
} else {
sb.append(template.deselected(option));
}
}
} catch (Exception e) {
System.out.println("error");
}
sb.appendHtmlConstant("</select>");
}
private int getSelectedIndex(String value, int rowIndex) {
if (optionsMap.get(rowIndex) == null) {
return -1;
}
return optionsMap.get(rowIndex).indexOf(value);
}
}
Or you could do something like creating a custom cell, which has methods you can call in the getValue of that particular column
say
final DynamicSelectionCell selection = new DynamicSelectionCell("...");
Column<GraphFilterCondition, String> operandColumn=new Column<GraphFilterCondition, String>(selection) {
#Override
public String getValue(FilterCondition object) {
if(object.getWhereCol()!=null){
((DynamicSelectionCell)this.getCell()).addOptions(new String[]{">","<",">="});
}
if(object.getWhereCondition()!=null){
return object.getWhereCondition().getGuiName();
}
return "";
}
};
This should work I guess.
Also check this other question

mvc3 validate input 'not-equal-to'

My forms have inputs with default helper text that guides the user on what to enter (rather than using labels). This makes validation tricky because the input value is never null.
How can I extend unobtrusive validation to handle this? The form shouldn't be valid if the Name input is equal to "Please enter your name..."
I started reading Brad Wilson's blog post on validation adapters, but I'm not sure if this is the right way to go? I need to be able to validate against different default values depending on the field.
Thanks
Here's a sample illustrating how you could proceed to implement a custom validation attribute:
public class NotEqualAttribute : ValidationAttribute, IClientValidatable
{
public string OtherProperty { get; private set; }
public NotEqualAttribute(string otherProperty)
{
OtherProperty = otherProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(OtherProperty);
if (property == null)
{
return new ValidationResult(
string.Format(
CultureInfo.CurrentCulture,
"{0} is unknown property",
OtherProperty
)
);
}
var otherValue = property.GetValue(validationContext.ObjectInstance, null);
if (object.Equals(value, otherValue))
{
return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
}
return null;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessage,
ValidationType = "notequalto",
};
rule.ValidationParameters["other"] = OtherProperty;
yield return rule;
}
}
and then on the model:
public class MyViewModel
{
public string Prop1 { get; set; }
[NotEqual("Prop1", ErrorMessage = "should be different than Prop1")]
public string Prop2 { get; set; }
}
controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel
{
Prop1 = "foo",
Prop2 = "foo"
});
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and view:
#model MyViewModel
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
jQuery.validator.unobtrusive.adapters.add(
'notequalto', ['other'], function (options) {
options.rules['notEqualTo'] = '#' + options.params.other;
if (options.message) {
options.messages['notEqualTo'] = options.message;
}
});
jQuery.validator.addMethod('notEqualTo', function(value, element, param) {
return this.optional(element) || value != $(param).val();
}, '');
</script>
#using (Html.BeginForm())
{
<div>
#Html.LabelFor(x => x.Prop1)
#Html.EditorFor(x => x.Prop1)
#Html.ValidationMessageFor(x => x.Prop1)
</div>
<div>
#Html.LabelFor(x => x.Prop2)
#Html.EditorFor(x => x.Prop2)
#Html.ValidationMessageFor(x => x.Prop2)
</div>
<input type="submit" value="OK" />
}
Yes thats the right way to go. You should implement your own atribute and implement IClientValidatable.
You could also have a required boolean value set initially to false as a hidden form field. When the user changes the textbox, set it to true.
You could make your ViewModel implement IValidatableObject and when implementing the Validate method (from IValidatableObject) add some logic to check the values of the properties e.g.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
var results = new List<ValidationResult>();
if (Name == "Please enter your name...")
results.Add(new ValidationResult("You must enter a name");
...
Enter other validation here
...
return results;
}
Now, when Model.IsValid is called in your controller, this bit of logic will be ran and will return validation errors as normal.
It took a little while since your question was asked, but if you still like data annotations, this problem can be easily solved using this library:
[Required]
[AssertThat("FieldA != 'some text'")]
public string FieldA { get; set; }
Above, the field value is compared with some pre-defined text. Alternatively, you can compare fields values with each other:
[AssertThat("FieldA != FieldB")]
...and when the case of the strings being compared does not matter:
[AssertThat("CompareOrdinalIgnoreCase(FieldA, FieldB) != 0")]
To improve a little bit of #Darin Dimitrov answer, if you want to add messages from the resources using ErrorMessageResourceName and ErrorMessageResourceType, just add this to the to the Error message ErrorMessage = ErrorMessage ?? ErrorMessageString
The ErrorMessageString will look for the localized version of error message that you set in the model using those parameters (ErrorMessageResourceName and ErrorMessageResourceType)
The ideal solutions is a custom Attribute where you specify minimum and maximum lengths as well as MustNotContain="Please enter your name...".

Using a custom validator in a variable length list in Microsoft MVC 2 (client-side validation issues)

I have created a variable length list according to the many great posts by Steve Sanderson on how to do this in MVC 2. His blog has a lot of great tutorials.
I then created a custom "requiredif" conditional validator following this overview http://blogs.msdn.com/b/simonince/archive/2010/06/11/adding-client-side-script-to-an-mvc-conditional-validator.aspx
I used the JQuery validation handler from the MSDN blog entry which adds the following to a conditional-validators.js I include on my page's scripts:
(function ($) {
$.validator.addMethod('requiredif', function (value, element, parameters) {
var id = '#' + parameters['dependentProperty'];
// Get the target value (as a string, as that's what actual value will be)
var targetvalue = parameters['targetValue'];
targetvalue = (targetvalue == null ? '' : targetvalue).toString().toLowerCase();
// Get the actual value of the target control
var actualvalue = ($(id).val() == null ? '' : $(id).val()).toLowerCase();
// If the condition is true, reuse the existing required field validator functionality
if (targetvalue === actualvalue)
return $.validator.methods.required.call(this, value, element, parameters);
return true;
});
})(jQuery);
Alas, this does not cause a client-side validation to fire ... only the server-side validation fires. The inherent "required" validators DO fire client-side, meaning I have my script includes set-up correctly for basic validation. Has anyone accomplished custom validators in a variable length list in MVC 2 using JQuery as the client-side validation method?
NOTE that this same custom validator works client-side using the exact same set-up on a non-variable length list.
Turns out that it was a field ID naming issue with the way that collection IDs render in a variable length list. The validator was attempting to name the element ID of the dependent property with the expected statement of:
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty);
I analyzed the HTML viewsource (posted in my comment, above), and actually, [ and ] characters are not output in the HTML of the collection-index elements... they're replaced with _... so, when I changed my CustomValidator.cs to have the dependent property set to:
string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty).Replace("[", "_").Replace("]", "_");
... then the client-side validator works since the name matches. I'll have to dig deeper to see WHY the ID is getting renamed in Sanderson's collection index method, below...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace Sendz.WebUI.Helpers
{
public static class HtmlPrefixScopeExtensions
{
private const string IdsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
var itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
// autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
html.ViewContext.Writer.WriteLine(
string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />",
collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
// We need to use the same sequence of IDs following a server-side validation failure,
// otherwise the framework won't render the validation error messages next to each item.
var key = IdsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (var previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
#region Nested type: HtmlFieldPrefixScope
private class HtmlFieldPrefixScope : IDisposable
{
private readonly string _previousHtmlFieldPrefix;
private readonly TemplateInfo _templateInfo;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
_templateInfo = templateInfo;
_previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
#region IDisposable Members
public void Dispose()
{
_templateInfo.HtmlFieldPrefix = _previousHtmlFieldPrefix;
}
#endregion
}
#endregion
}
}
A complete validator / attribute reference...
public class RequiredIfAttribute : ValidationAttribute
{
private RequiredAttribute innerAttribute = new RequiredAttribute();
public string DependentProperty { get; set; }
public object TargetValue { get; set; }
public RequiredIfAttribute(string dependentProperty, object targetValue)
{
this.DependentProperty = dependentProperty;
this.TargetValue = targetValue;
}
public override bool IsValid(object value)
{
return innerAttribute.IsValid(value);
}
}
public RequiredIfValidator(ModelMetadata metadata, ControllerContext context, RequiredIfAttribute attribute)
: base(metadata, context, attribute) { }
public override IEnumerable<ModelValidationResult> Validate(object container)
{
// Get a reference to the property this validation depends upon
var field = Metadata.ContainerType.GetProperty(Attribute.DependentProperty);
if (field != null)
{
// Get the value of the dependent property
var value = field.GetValue(container, null);
// Compare the value against the target value
if ((value == null && Attribute.TargetValue == null) ||
(value != null && value.ToString().ToLowerInvariant().Equals(Attribute.TargetValue.ToString().ToLowerInvariant())))
{
// A match => means we should try validating this field
if (!Attribute.IsValid(Metadata.Model))
// Validation failed - return an error
yield return new ModelValidationResult { Message = ErrorMessage };
}
}
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
ValidationType = "requiredif"
};
var viewContext = (ControllerContext as ViewContext);
var depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(Attribute.DependentProperty).Replace("[", "_").Replace("]", "_");
rule.ValidationParameters.Add("dependentProperty", depProp);
rule.ValidationParameters.Add("targetValue", Attribute.TargetValue.ToString());
yield return rule;
}

Categories

Resources