Context
In a view, I've created one form for my view model. I've separated the form over multiple sections using the Twitter Bootstrap Wizard plugin. The user can access the next section by clicking a "Next" button. Each section has a Fluent Validation RuleSet defined for the model properties on that section. The rules I've defined in each RuleSet are compatible with Fluent Validaiton's client-side validation.
Question
Upon clicking the next button, what's the best way:
To get the validation state for only the current section's RuleSet on the client-side with Fluent Validation?
To get Fluent Validation to display client-side validation for only the current section's RuleSet?
What I've Tried
I've read the Fluent Validation start guide and this question. While they demonstrate how to achieve what I'm looking for on the server-side, they don't seem to address my client-side questions. I bold "seem" because I'm reasonably new to Fluent Validation and MVC, so, I may have misunderstood the links' content.
You can use commands from the jQuery validation library, which Fluent Validation uses.
In an element in your form, define an attribute that will help you recognise a validation group. For example
#Html.EditorFor(model => model.GroupA_TextA, new { htmlAttributes = new { #class = "form-control", data_validation_group = "GroupA" }})
Use the .settings.ignore syntax from the jQuery validation library to control which groups to validate.
I've made a class that exposes functionality to validate a group in, and the entirety of, a Fluent Validation validated form. I've included the TypeScript and transpiled JavaScript below.
TypeScript
/**
* For validating a form when Fluent Validation is used for model valdiation.
*/
interface IFluentValidationFormValidator {
/**
* The form to validate.
*/
form: JQuery<HTMLElement>;
/**
* The name of the validation group to validate.
*/
group: string;
/**
* Validate the entire form.
*/
validate(): boolean;
/**
* Validate a validation group in the form.
* #param group The name of the validation group to validate.
*/
validateGroup(): boolean;
}
/**
*
*/
class StandardFluentValidationFormValidator implements IFluentValidationFormValidator {
/**
* #inheritdoc
*/
form: JQuery<HTMLElement>;
/**
* #inheritdoc
*/
group: string;
/**
* #inheritdoc
*/
validate(): boolean {
const formValidator = this.form.validate();
formValidator.form();
return formValidator.valid();
}
/**
* #inheritdoc
*/
validateGroup(): boolean {
// The form validator.
const formValidator = this.form.validate();
// Perform standard validation on form if the validation group is undefined.
if (this.group === undefined) {
formValidator.form();
return formValidator.valid();
}
// Current group validation settings.
const initialValidateIgnoreSetting = formValidator.settings.ignore;
// Ignore all elements but the group.
formValidator.settings.ignore += `,:not([data-validation-group=${this.group}])`;
// Valdiate the form.
formValidator.form();
// Reset group validation settings.
formValidator.settings.ignore = initialValidateIgnoreSetting;
// Return the validation state.
return formValidator.valid();
}
}
JavaScript
"use strict";
var StandardFluentValidationFormValidator = (function () {
function StandardFluentValidationFormValidator() {
}
StandardFluentValidationFormValidator.prototype.validate = function () {
var formValidator = this.form.validate();
formValidator.form();
return formValidator.valid();
};
StandardFluentValidationFormValidator.prototype.validateGroup = function () {
var formValidator = this.form.validate();
if (this.group === undefined) {
formValidator.form();
return formValidator.valid();
}
var initialValidateIgnoreSetting = formValidator.settings.ignore;
formValidator.settings.ignore += ",:not([data-validation-group=" + this.group + "])";
formValidator.form();
formValidator.settings.ignore = initialValidateIgnoreSetting;
return formValidator.valid();
};
return StandardFluentValidationFormValidator;
}());
Related
In a ZF2 project i am using the AuthenticationService to validate a users log in credentials. This is working fine, except it only stores in the session a string containing the users name.
What i would like would be for subsequent calls to AuthenticationService::getIdentity to return a custom Identity object, that is populated with the users database id, roles and permissions (popualted from an RBAC service), so that the object in the session is a bit more useful.
I am able to create this object, but am unsure of the best way to keep it in the session; ideally i would like to override the entry with the key Zend_Auth, but this does not seem to be working.
My code so far:
<?php
namespace Authentication\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Authentication\AuthenticationService;
use Authentication\Form\Login\LoginForm;
use Zend\Form\Form;
use Authentication\Model\Identity\AuthenticatedIdentity;
class AuthenticationController extends AbstractActionController
{
/**
*
* #var AuthenticationService
*/
protected $authenticationService;
/**
*
* #var LoginForm
*/
protected $loginForm;
/**
*
* #param AuthenticationService $authenticationService
* #param LoginForm $loginForm
*/
public function __construct(AuthenticationService $authenticationService, LoginForm $loginForm){
$this->authenticationService = $authenticationService;
$this->loginForm = $loginForm;
}
public function indexAction(){
$form = $this->loginForm;
$viewModel = new ViewModel();
$viewModel->setVariables([
'loginForm' => $form
]);
if($this->getRequest()->isPost() === false){
return $viewModel;
}
$form->setData($this->getRequest()->getPost());
if($form->isValid() === false){
return $viewModel;
}
$data = $form->getData();
$authenticationAdapter = $this->authenticationService->getAdapter();
$authenticationAdapter->setIdentity($data['credentials']['username'])
->setCredential($data['credentials']['password']);
$authenticationResult = $this->authenticationService->authenticate($authenticationAdapter);
if($authenticationResult->isValid() === false){
$viewModel->setVariable('validCredentials', false);
return $viewModel;
}
/**
* Create a user model and save it to the session.
*/
$authenticationResultRow = $authenticationAdapter->getResultRowObject(null, ['password']);
$permissions = $this->rbacService->getPermissionsForUser($authenticationResultRow->user_id);
$roles = $this->rbacService->getRolesForUser($authenticationResultRow->user_id);
$identity = new AuthenticatedIdentity(
$authenticationResult->getIdentity(),
'admin',
$permissions,
$roles
);
$identity->setUserId($authenticationResultRow->user_id);
//how to store this Identity object in session so AuthenticationService will return it?
return $this->redirect()->toRoute('dashboard');
}
}
Check out https://github.com/zendframework/zend-authentication/blob/master/src/AuthenticationService.php#L75 and https://github.com/zendframework/zend-authentication/blob/master/src/Storage/StorageInterface.php
You can write the AuthenticatedIdentity object directly to the storage like so:
$this->authenticationService->getStorage()->write($identity);
However, I would advice against doing so because:
If the user's permissions/roles change during the session he/she would have to log out and back in to see any changes which is not very user-friendly.
Your AuthenticatedIdentity object and all objects it contains need to be serializable, which can become problematic to maintain.
I would (and do) fetch the user object and/or roles when needed, either from DB or some form of cache but don't store it in the session.
I have a complex nested (order) Zend\Form, that can be edited multiple times. The user first creates an order, but doesn't need to place it immediately. He can just save the order (or more exact: its data) and edit it later. In this case the application loads an Order object (with all its nested structure) and binds it to the form. The important steps are:
get ID of the order from the request
get the Order object by ID
$orderForm->bind($orderObject)
...
Now I want to catch the data and serialize it to JSON. (The background: Forms cloning -- in the next step a empty new form should created and the should be passed to it; after saving we'll get a clone.) It should happen between 2 and 3. So I'm trying
$formData = $this->orderForm->getData();
$formJson = json_encode($formData, JSON_UNESCAPED_SLASHES);
and getting the error:
Zend\Form\Form::getData cannot return data as validation has not yet occurred
Well, I could try to work around it and validate the form:
$formIsValid = $this->orderForm->isValid();
but it only leads to further troubles:
Zend\InputFilter\BaseInputFilter::setData expects an array or Traversable argument; received NULL
Is there a way to get the form data before the validation?
Okay, the comment space is way too small to say everything about what you try to archive. Let 's refactor every single step you mentioned in the starting post. This will lead us to your goal. It 's all about hydration.
This will be a small example, how an order entity with products in it could look like. After the order entity follows the product entity, which we need for this example.
namespace Application\Entity;
class Order implements \JsonSerializable
{
/**
* ID of the order
* #var integer
*/
protected $orderID;
/**
* Array of \Application\Entity\Product
* #var array
*/
protected $products;
public function getOrderID() : integer
{
return $this->orderID;
}
public function setOrderID(integer $orderID) : Order
{
$this->orderID = $orderID;
return $this;
}
public function getProducts()
{
if ($this->products == null) {
$this->products = [];
}
return $this->products;
}
public function setProducts(array $products) : Order
{
$this->products = $products;
return $this;
}
/**
* #see \JsonSerializable::jsonSerialize()
*/
public function jsonSerialize()
{
return get_object_vars($this);
}
}
The following entity represents a product.
class Product implements \JsonSerializable
{
protected $productID;
protected $name;
public function getProductID() : integer
{
return $this->productID;
}
public function setProductID(integer $productID) : Product
{
$this->productID = $productID;
return $this;
}
public function getName() : string
{
return $this->name;
}
public function setName(string $name) : Product
{
$this->name = $name;
return $this;
}
/**
* #see \JsonSerializable::jsonSerialize()
*/
public function jsonSerialize()
{
return get_object_vars($this);
}
}
Above you see our entity, wich represents a single order with several possible products in it. The second member products can be an array with Product entities. This entity represents the data structure of our simple order.
At this point we need a form, which uses this entites as objects for the data it contains. A possible factory for our form could look like this.
namespace Application\Form\Factory;
class OrderFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$parentLocator = $serviceLocator->getServiceLocator();
$inputFilter = $parentLocator->get('InputFilterManager')->get(OrderInputFiler::class);
$hydrator = new ClassMethods(false);
$entity = new OrderEntity();
return (new OrderForm())
->setInputFilter($inputFilter)
->setHydrator($hydrator)
->setObject($entity);
}
}
This is the factory for our form. We set a hydrator, an input filter and an entity for the form. So you don 't have to bind something. The following code shows, how to handle data with this form.
// retrieve an order from database by id
// This returns a order entity as result
$order = $this->getServiceLocator()->get(OrderTableGateway::class)->fetchById($id);
// Extract the order data from object to array assumed the
// retrieved data from data base is an OrderEntity object
// the hydrator will use the get* methods of the entity and returns an array
$data = (new ClassMethods(false))->extract($order);
// fill the form with the extracted data
$form = $this->getServiceLocator()->get('FormElementManager')->get(OrderForm::class);
$form->setData($data);
if ($form->isValid()) {
// returns a validated order entity
$order = $form->getData();
}
It is absolutely not possible to get data from a form, that is not validated yet. You have to validate the form data and after that you can get the filtered / validated data from the form. Hydrators and entities will help you a lot when you have to handle a lot of data.
I have fields in a Window, some with validators and all bound to properties.
The validation works as expected.
But -
I do not want to proceed when any field is invalid. What would be the best way to determine if any validation went wrong?
There are several ways of dealing with validation in Vaadin, all supported by Vaadin (no need for custom boolean afterValidationFlag).
One possible way (preffered by me) shown below:
public class CustomWindow extends Window {
DateField customBeanFirstPropertyName = new DateField("Caption1");
ComboBox customBeanSecondPropertyName = new ComboBox("Caption2");
TextArea customBeanThirdPropertyName = new TextArea("Caption3");
BeanFieldGroup<CustomBean> binder = new BeanFieldGroup<>(CustomBean.class);
public CustomWindow(CustomBean customBean) {
buildLayout();
binder.buildAndBindMemberFields(this);
binder.setItemDataSource(new BeanItem<>(customBean));
//add validators
customBeanFirstPropertyName.addValidator((Validator) value -> {
if (value == null) throw new Validator.InvalidValueException("nonnull required");
});
customBeanThirdPropertyName.addValidator(
new RegexpValidator(".{3,20}", "length between 3-20 required")
);
/*
or have basic validators on #Entity level with e.g. javax.validation.constraints.Size
example:
#Size(min = 3, max = 20)
#Column(name = "customBeanThirdPropertyName", unique = true)
private String customBeanThirdPropertyName;
*/
}
void commit(Button.ClickEvent event) { //method called by "save" button
try {
binder.commit(); //internally calls valid() method on each field, which could throw exception
CustomBean customBeanAfterValidation = binder.getItemDataSource().getBean(); //custom actions with validated bean from binder
this.close();
} catch (FieldGroup.CommitException e) {
Map<Field<?>, Validator.InvalidValueException> invalidFields = e.getInvalidFields(); //do sth with invalid fields
}
}
}
If you use a FieldGroup instance to bind your fields with the properties, which is the recommended way, you can write:
fieldGroup.isValid();
That checks on all field validations of the fields managed by the field group.
Maintain a flag. Before proceeding, check if the flag is set. In the validation code, set the flag if the validation fails.
I have a form displayed in a jquery ui dialog. I load the form with ajax and I submit the form using ajax.
I now have validation that checks that a field is unique and that can only happen server-side.
So in my Controller I add any validation errors to ModelState:
public ActionResult CreateOrEdit(Property p)
{
_Uow.PropertyRepository.InsertOrUpdate(p);
//My unit of work has a method that returns any data layer validation errors
foreach (var error in _Uow.GetValidationErrors())
ModelState.AddModelError(error.Key, error.Value);
if (ModelState.IsValid)
_Uow.Save();
return GetCreateOrEditView(p, "Property");
}
EDIT
I return the same view that is used in the get, so the behaviour is the same as if the field failed client side ie. the dialog contains a form and the validation error is displayed next to the field just like other errors.
Now, I don't want to close the dialog, and at the top of the dialog I have a validation legend created with this helper:
public static MvcHtmlString ValidationLegend(this HtmlHelper htmlHelper
, string message = "The form is invalid.")
{
string cssClass = htmlHelper.ViewData.ModelState.IsValid ?
"validation-summary-valid" : "validation-summary-errors";
var tagBuilder = new TagBuilder("div");
tagBuilder.Attributes.Add("id", "validation-legend");
tagBuilder.Attributes.Add("data-valmsg-summary", "true");
tagBuilder.SetInnerText(message);
tagBuilder.AddCssClass(cssClass);
return new MvcHtmlString(tagBuilder.ToString());
}
So this helper will display a legend with this html if there is a server error:
<div class="validation-summary-errors" data-valmsg-summary="true"
id="validation-legend">The form is invalid.</div>
So it's like a validation summary without all the extra messages - and I use the presence of the summary in my javascript to stop the dialog from closing.
I'd like to two things to happen:
1. When the user changes the field with the server-side error message, the validation error next to the field should clear (whatever it is changed to since I cannot check it server-side)
2. The validation legend should show when any validation fails and should hide when all invalid fields are cleared.
Currently the legend only displays for server validation errors and both the legend and the field error remain on screen until the submit button is clicked - which is inconsistent with the client-side validation.
Are there other data attributes that I can add to make this happen as part of the unobtrusive validation? Or is there another solution?
Here is my javascript:
function submitDialogForm(successCallback) {
var $dialog = $(this);
var $form = $dialog.find("form").filter(':visible:first');
if ($form.find("input:first").length > 0) {
if ($form.updateValidation().valid()) {
var $target = getTarget($dialog);
$form.ajaxSubmit({
target: $target,
success: function () {
$target.find(".fadein").show();
//Check if server side validation has failed before closing
$form = $dialog.find("form").filter(':visible:first');
if ($form.find("#validation-legend.validation-summary-errors").length > 0) {
return;
}
if (successCallback && typeof (successCallback) === "function") {
successCallback();
}
else {
$dialog.dialog("close");
}
}
});
}
}
else {
$dialog.dialog("close");
}
}
Included for completeness - code for validating form after it's been loaded using Ajax. Based on this answer: Update unobtrusive validation after ajax call
//jQuery plugin for updating unobtrusive validation when forms are loaded using Ajax
(function ($) {
$.fn.updateValidation = function () {
//If the form has been loaded via ajax, then we have to update the
//unobtrusive validation
var $this = $(this);
var form = $this.closest("form")
.removeData("validator")
.removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse(form);
return $this;
};
})(jQuery);
I'm not really sure I understand your two goals:
When the user changes the field with the server-side error message, the validation error next to the field should clear (whatever it is
changed to since I cannot check it server-side)
The validation legend should show when any validation fails and should hide when all
invalid fields are cleared.
However, as long as your server-side action is actually returning a server error code (presumably an HTTP 500 Internal Server Error message), what you want to do is add an error callback to your $.ajax call, just as you've done with the success callback. You could also use ajaxError to attach an error handler for all the page's AJAX activity.
If you're trying to clear existing validation errors (question 1 sounds like you're trying to do this), you want the reset or resetForm methods:
$form.ajaxSubmit({
target: $target,
success: function () {
//...
},
error: function() {
$form.validate().resetForm();
}
});
This is just a rough guess at the code you need, but hopefully you can extrapolate it from here. If you explain a little more what you want to do, I'll try to be more specific.
Following on from this post Perform client side validation for custom attribute
I am trying to get my head around how to do this, passing additional parameters to the client-side script
As I understand it so far to implement custom validation with MVC 3 the following is required
Create a custom validation attribute
Based on ValidationAttribute and implementing IClientValidatable. I have also see some examples deriving from ModelValidator, which seems to implement the functionality of both ValidationAttribute and IClientValidatable. So this is my first point of confusion as to what the diffirences are or whether ModelValidator was used in MVC 2 but is now deprecated or what ?
An instance of ModelClientValidationRule must be returned from GetClientValidationRules() to specify details such as the error message, ValidationType (which I understand to be the name of the Javascript function that will perform the client-side validation) and any additional custom parameters that the attribute may have, and that need to be passed to the Javascript validation.
I assume that the runtime (not sure which part of it) then use the ModelClientValidationRule to generate html attribute in the tag elements as follows:
data-val="true" (to indicate that the element requires validation)
data-val-[ValidationType]=[ErrorMessage]
data-val-[ValidationType].[ValidationParameters(n).Key]=[ValidationParameters(n).Value]
Implement the client-side validation logic
A Javascript function must be created and added to jQuery.validators with jQuery.validators.addmethod() so that JQuery is aware of it when it need to be executed. Something like:
jQuery.validator.addMethod(
'greaterThan',
function (value, element, params) {
/.../
return /* true or false */ ;
},
''
);
My question here is whether the signature 'function (value, element, params)' is standard for methods that will handle validation and I assume it will be called by some jQuery functionality at the appropriate time such as before a form is submitted or when an element looses fuces or on keyUp events. I just don't undertand how you can controll this i.e. choose which event is appropriete for yout custom validation.
Implement an unobtrusive adapter
This translates unobtrusive attributes to; something I am not very clear on, but assume it to be a jQuery Rule, but I am not clear on how those work. Something like
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
{ },
function (options) {
options.rules['greaterThan'] = true;
options.messages['greaterThan'] = options.message;
}
);
My question here is about 'function (options)'. Is this the function that will be called before 'function (value, element, params)' and is responsible for extracting the unobtrusive tags into a data structure that can be understood by jQuery.Validation. From the code example it seems to me that options is an object that contains both, the attribute values from the tag (such as options.message) and the jQuery relevant properties it must map to (such as options.messages['ClientSideValidationFunctionName']. If so how are custom parameters retrieved and mapped.
I hope I have not added any additional confusion.
You could use the ValidationParameters property to add custom parameters to the rule:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = this.ErrorMessage,
ValidationType = "futuredate",
};
rule.ValidationParameters.Add("param1", "value1");
rule.ValidationParameters.Add("param2", "value2");
yield return rule;
}
which could be used in the adapter:
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
[ 'param1', 'param2' ],
function (options) {
var param1 = options.params.param1; // shall equal 'value1'
var param2 = options.params.param2; // shall equal 'value2'
// TODO: use those custom parameters to define the client rules
}
);
UPDATE:
As requested in the comments section here's how you could pass those parameters to the custom validator rule function:
jQuery.validator.unobtrusive.adapters.add(
'futuredate',
[ 'param1', 'param2' ],
function (options) {
// simply pass the options.params here
options.rules['greaterThan'] = options.params;
options.messages['greaterThan'] = options.message;
}
);
jQuery.validator.addMethod('greaterThan', function (value, element, params) {
// params here will equal { param1: 'value1', param2: 'value2' }
return ...
}, '');