I am trying to use Remote Validation with an additional bool checkbox field
[Remote("IsStorageConnectionValid", "TenantManagement", AdditionalFields = "CreateStorage")]
public String StorageConnectionString { get; set; }
Validation code
public JsonResult IsStorageConnectionValid(string storageConnectionString, bool createStorage){
It works perfectly in terms of it hitting the validator. However createStorage is always true irrespective of the value of the checkbox. If I use additional fields that aren't check boxes they are supplied perfectly.
Checkbox created as standard:
#Html.CheckBoxFor(m => m.CreateStorage)
Is this a bug? Or am I doing it wrong?
Fiddle Is here (Is MVC4 I think but does the same thing)
It does appear that this is a bug when used with #Html.CheckBoxFor. The problem is that CheckBoxFor renders 2 elements, a checkbox with value="true" and a hidden input with value="false" (Unchecked checkboxes do not post back so the second hidden input ensures a value is posted back for use by the DefaultModelBinder)
Looking at the relevant section of the jquery.validate.unobtrusive.js file
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = appendModelPrefix(fieldName, prefix);
value.data[paramName] = function () {
return $(options.form).find(":input[name='" + escapeAttributeValue(paramName) + "']").val();
};
});
setValidationValues(options, "remote", value);
});
the return statement returns (in your case) .find(':input[name="CreateStorage"]').val(); which returns the value of the first input with the name="CreateStorage" which will always be true (the value of the checkbox)
As a test, if you render the value using HiddenFor rather that CheckBoxFor you will receive the correct value in your IsStorageConnectionValid method (but of course this does help since you cant change the value)
Not sure of the best solution, but the unobtrusive script should be first checking if .find(..) returns more than one element, then if the first is a checkbox which is unchecked, returning the value of the second element.
Edit
I have reported this as an issue at Codeplex
Edit 2
I have been advised the the issue has now been fixed here
Related
I'm using ngVal to decorate my inputs with AngularJS directives based on the ASP.NET MVC Data Annotations.
My model has the following annotations:
[Display(Name="Test Number")]
[Required]
[RegularExpression(#"^[\d]{6}$", ErrorMessage = "The value must be six numbers.")]
public string TestNumber { get; set; }
ngVal is then used to display the error messages associated with the input field.
From what I can determine (with a debug breakpoint in the $watch function) is that the function that should be called on modification of the value in scope is not being called unless the pattern is matched. Here's how the ngVal directive sets up the scope.$watch:
var ngval = angular.module('ngval', []);
ngval.directive('ngval', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, iElm, iAttrs, ngModel) {
var messages = angular.fromJson(iAttrs.ngval);
var getErrors = function() {
var errors = [];
for (var prop in messages) {
if (ngModel.$error[prop])
errors.push({ validator: prop, message: messages[prop] });
}
return errors;
};
scope.$watch(function() {
return ngModel.$modelValue;
}, function () {
ngModel.ngval = {
hasError: ngModel.$dirty && ngModel.$invalid,
errors: getErrors()
};
});
}
};
}]);
The result is that initially I see that the field is required but typing something invalid doesn't change the error to the one specified for the pattern - it still says the field is required. This is because the function given to the $watch isn't being invoked. If I enter 6 numbers, the field is valid and modifying the value after that shows the pattern mismatch message. The only exception to this is if I select the contents of the input box and erase it with one key press (del/backspace).
Is this normal behaviour; that scope.$watch isn't invoked if the value doesn't match the pattern?
EDIT: I've added a ng-change directive on the input and it behaves the same - it's not invoked if the input doesn't match the pattern. A demonstration of the behaviour can be seen in this plunker: http://plnkr.co/edit/krmQVk0giwCYGxdY5YNS?p=preview
The problem is that the watch is set up against the model, and the value of the property on the model is not set for input changes that do not match the ngPattern. The property on the model is only changed to the input value when it matches the pattern; otherwise the property is emptied (null, undefined or blank - I'm not sure.
The workaround I have in place is to watch the $error properties of the DOM element, which are updated by AngularJS. I added a formItem property to allow the name of the form and input element to be passed to the HTML Helper method so that it can add it to the markup. The watch is now set up as follows:
for (prop in messages) {
scope.$watch(iAttrs.formitem + ".$error."+ prop,
function() {
ngModel.ngval = {
hasError: ngModel.$dirty && ngModel.$invalid,
errors: getErrors()
};
});
}
I hope that helps someone out there with the same issue.
I am using the custom binding provided in How to create an auto-complete combobox?
I want to allow the user to either select a value from the list of suggestions or enter a value that is not in the list of suggestions. How can I get the value of the input into my observable field?
For example, if the user types 'smi' the autocomplete list will show Smith and other surnames beginning with 'smi', however, if they do not select an option from the list, I just want to set the value of my observable field to be 'smi'. At present, the only way the observable propety is set is when the user selects an item from the list of suggestions.
I have the following code (HTML):
<input type="text" data-bind="
value: surname,
jqAuto: { autoFocus: true },
jqAutoSource: surnames,
jqAutoQuery: surnameAutocomplete,
jqAutoValue: surname"
/>
JavaScript view model (simplified):
var vm = {
surnames: ko.observableArray(),
surname: ko.observable(),
surnameAutocomplete: function (searchTerm, result) {
repository.surnameAutocomplete(searchTerm, result);
};
Solution:
I amended the custom binding handler in two places:
init: function - added the following
// New setting to allow / disallow a user to enter values that are in the autocomplete list.
forceSelection = allBindings.jqAutoForceSelection;
options change function - amended to the following
//on a change, make sure that it is a valid value or clear out the model value
options.change = function (event, ui) {
var currentValue = $(element).val();
// Start: new code, check new setting on whether to force user to make a selection
if (!forceSelection) {
writeValueToModel(currentValue);
return;
}
// End: new code
var matchingItem = ko.utils.arrayFirst(unwrap(source), function (item) {
return unwrap(inputValueProp ? item[inputValueProp] : item) === currentValue;
});
if (!matchingItem) {
writeValueToModel(null);
}
}
I also found that the first item in the autocomplete list was being automatically selected, but then noticed by setting autofocus: false solved my issue, e.g.,
<input type="text" data-bind="
jqAuto: { autoFocus: false }, /* This fixes the auto select issue */
jqAutoSource: surnames,
jqAutoQuery: surnameAutocomplete,
jqAutoValue: surname,
jqAutoForceSelection: false"
/>
If you look closely at the binding handler you're using, you will notice this section:
//on a change, make sure that it is a valid value or clear out the model value
options.change = function(event, ui) {
var currentValue = $(element).val();
var matchingItem = ko.utils.arrayFirst(unwrap(source), function(item) {
return unwrap(item[inputValueProp]) === currentValue;
});
if (!matchingItem) {
writeValueToModel(null);
}
What this section of the binding handler essentially does is check if the text the user entered into the text field matches something in the autocomplete dropdown, and if it doesn't, it clears the model value (which it sounds like what you want to change).
You can either try deleting this section or extend it to suit your purposes.
I have multiselect jquery plagin (Choosen) and when I use it in 'Multiple Select' mode I expect in controller next values:
posted string = 'value1,value2...'
really have
posted string = 'value2'
only if I reffer directly to FormCollection I'll get expected values as below:
[HttpPost]
public ActionResult TagSearech(/*string tagSelect*/FormCollection c)
{
// only one value here
// string[] names = tagSelect.Split(',');
// as expected: value1,....
string expectedValue = c['tagSelect'];
return View();
}
I cant understand what might cause this behavior.
EDIT
Here is View:
#using (Html.BeginForm("TagSearech", "Tag"))
{
#Html.DropDownList("tagSelect", Model, new { #class = "chzn-select", data_placeholder = "tag names", multiple = "" })
<input type="submit"/>
}
MVC will attempt to bind the input data on the URL into the model. I haven't seen how Chosen.js posts the data back to the server, but essentially its coming in in the wrong format, so MVC binds the first element it sees to the string Model.
The FormsCollection retrieves all of the data that was posted in the URL, which is why all of your selected values can be seen there.
Did you try changing the incoming model from string to string[], and see if all of the items are bound to the array?
I have a Dropdownlistfor connected to my model. When the page is loaded the model is empty, but I have a button on the page that updates the model. My problem is that my dropdownlist doesn't update.
Markup:
#Html.DropDownList("ddlRaces", new SelectList(ViewBag.Races, "RaceId", "Name"))
<input type="button" id="btnChangeRace" class="btnGo" value=" " />
#Html.DropDownListFor(m => m.Timers, new SelectList(Model.Timers, "TimerId", "StartTime"), "Velg timer:")
Script:
btnChangeRace.click(function () {
url = "/TimeMerger/GetTimers/?raceId=" + ddlRaces.val();
$.get(url);
});
Codebehind:
[HttpGet]
public ActionResult GetTimers(int raceId)
{
var timeMergeModel = new TimeMergerModel();
timeMergeModel.Timers = TimerModel.GetTimers(raceId);
return View(timeMergeModel);
}
$.get(url);
Here you are sending an AJAX request but you are doing nothing in the success callback so nothing will update. There is not even a success callback. So you need to define a success callback:
$.get(url, function(result) {
// result here will represent the value returned by the server
// which in your case is HTML. So here you might need to update
// the relevant portion of your page.
});
Also your DropDownListFor definition is wrong. You are using the same model variable as first and second argument (Model.Timers). The first first argument of this helper should be a scalar property to which you are binding and not a list. It should be:
#Html.DropDownListFor(
m => m.SelectedTimerValue,
new SelectList(Model.Timers, "TimerId", "StartTime"),
"Velg timer:"
)
where SelectedTimerValue would be a property on your view model that will hold the selected value. Also why you are using DropDownList instead of DropDownListFor for the first drop down? The whole idea of a view model is that it will contain all the necessary information so that the view would simply display it.
I'm trying to create a form that change the validation of a field based on the select option from the html form field.
Ex: if user select a option 1 from drop down field "options", I want the field "metric" to validate as sfValidatorInteger. If user select option 2 from field "options", I want the field "metric" to validate as sfValidatorEmail, etc.
So inside the public function configure() { I have the switch statement to capture the value of "options", and create the validator based on that value returned from the "options".
1.) How do I capture the value of "options" ? I've tried:
$this->getObject()->options
$this->getTaintedValues()
The only thing that's currently working for me is but it's not really MVC:
$params = sfcontext::getInstance()->getRequest()->getParameter('options');
2.) Once I've captured that information, how can I assign the value of "metric" to a different field? ("metric" is not a real column in db). So I need to assign the value of "metric" to different field such as "email", "age" ... Currently I'm handling this at the post validator like this, just wondering if I can assign value within the configure():
$this->validatorSchema->setPostValidator(new sfValidatorCallback(array('callback' => array($this, 'checkMetric'))));
public function checkMetric($validator, $values) {
}
Thanks!
You want to use a post validator. Try doing something like this in your form:
public function configure()
{
$choices = array('email', 'integer');
$this->setWidget('option', new sfWidgetFormChoice(array('choices' => $choices))); //option determines how field "dynamic_validation" is validated
$this->setValidator('option', new sfValidatorChoice(array('choices' => array_keys($choices)));
$this->setValidator('dynamic_validation', new sfValidatorPass()); //we're doing validation in the post validator
$this->mergePostValidator(new sfValidatorCallback(array(
'callback' => array($this, 'postValidatorCallback')
)));
}
public function postValidatorCallback($validator, $values, $arguments)
{
if ($values['option'] == 'email')
{
$validator = new sfValidatorEmail();
}
else //we know it's one of email or integer at this point because it was already validated
{
$validator = new sfValidatorInteger();
}
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']); //clean will throw exception if not valid
return $values;
}
1) In a post validator, values can be accessed by using the $values parameter. Just use $values['options'] and it should be fine... or did you want to access this values from another part of you code? $this->getObject()->widgetSchema['options'] should work too I think, once your form is bound to an object.
2) The configure() method is called at the end of the form constructor, so values are not bound nor accessible yet, unless you are initializing your form with an object from the db (which does not require any validation). But if you want to initialize your form from $_POST, a post validator is definitely the way to go IMHO.
I got the validation error to appear alongside the field by throwing a sfValidatorErrorSchema instead of a sfValidatorError.
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']);
…becomes…
try
{
$values['dynamic_validation'] = $validator->clean($values['dynamic_validation']);
}
catch(sfValidatorError $e)
{
$this->getErrorSchema()->addError($e, 'dynamic_validation');
throw $this->getErrorSchema();
}
Not sure if this is the best way to get this result, but it seems to be working for me at the moment.