Parsley.js destroy and revalidate fields already validated - parsley.js

I have implemented parsley on a complicated form that uses on-blur validation.
I have run into an issue where i go through the form, the fields are validated one by one, then based on a dropdown change, I have to destroy the Parlsey object in order to modify the validation and the fields that were valid are no longer valid.
How can I retain the already validated fields.
For example say I have the following:
<input type="text" id="1">
<input type="text" id="2">
<select id="select">
<option id=1>1</option>
<option id=2>2</option>
<option id=3>3</option>
</select>
<input type="text" id="3">
<input type="text" id="4">
<input type="text" id="5">
###Scenario:
I enter data in ID=1 and ID=2 textboxes, success class is added as I tab out of the textbox.
I change select box to option 2
I destroy parsley
Success class is removed from ID=1 and ID=2 textboxes
Currently I am doing the following:
$("#form").parsley().destroy();
$('#form').parsley({
successClass: "valid",
errorClass: "invalid",
errorsWrapper: '<div></div>',
errorTemplate: '<p></p>'
}).validate('section');
###What this does
This revalidates all the fields
###What I am looking for
I need ID=1 and ID=2 to remain valid after I destroy.

When you call destroy() all things from Parsley are destroyed. This means that all the classes, messages (DOM), objects and events will be destroyed.
If you're looking for a way to maintain the UI aspect, you can do that with a not-so-pretty solution:
In your styles, where you have .valid { ...} add another class: .valid, .fake-valid { ... }. Do the same for invalid.
Before calling destroy(), navigate through all the fields and check if there is a class valid or invalid
If so, apply a class called fake-valid or fake-invalid.
Using the event parsley:form:init, loop through the fields again and change their classes from fake-... to the correct classes.
Parsley will validate the fields but the UI is maintained.
Check this JsFiddle.
<style>
.valid, .fake-valid {
color: #468847;
background-color: #DFF0D8;
border: 1px solid #D6E9C6;
}
.invalid, .fake-invalid {
color: #B94A48;
background-color: #F2DEDE;
border: 1px solid #EED3D7;
}
</style>
<form id="myForm" method="post">
<input type="text" id="1" data-parsley-trigger="focusout" required />
<input type="text" id="2" data-parsley-trigger="focusout" required />
<select id="select">
<option id=1>1</option>
<option id=2>2</option>
<option id=3>3</option>
</select>
</form>
<script>
$(document).ready(function() {
var parsleyOpts = {
successClass: "valid",
errorClass: "invalid",
errorsWrapper: '<div></div>',
errorTemplate: '<p></p>'
};
var ParsleyForm = $("#myForm").parsley(parsleyOpts);
$("#select").on('change', function() {
// before destroy, add fake class
for (var i in ParsleyForm.fields) {
var elem = ParsleyForm.fields[i].$element;
if (elem.hasClass('valid'))
elem.addClass('fake-valid');
else if(elem.hasClass('invalid'))
elem.addClass('fake-invalid');
}
ParsleyForm.destroy();
ParsleyForm = $("#myForm").parsley(parsleyOpts);
});
// when parlsey is initialized, lets see if the fields have fake classes
// if so, add parsley classes
$.listen('parsley:form:init', function(formInstance) {
for (var i in formInstance.fields) {
var elem = formInstance.fields[i].$element;
if (elem.hasClass('fake-valid'))
elem.toggleClass('fake-valid valid')
if (elem.hasClass('fake-invalid'))
elem.toggleClass('fake-invalid invalid');
}
});
});
</script>

Related

Angular validation issue

I've got an issue with the Angular validation classes. I've got three classes in my css with three different images, that shows if the field is required, if the user has filled in correct or not. E.g. I have one input field where the user types in a personal number, and I look it up in a service and fills the model with firstname, lastname, address etc. which also populates the input fields.
I made a simple example
HTML
<div ng-app="ebu" ng-controller="search">
<button id="search-apparentice" ng-click="addName()">Search apprentice</button>
<hr>
<input id="username" type="text" ng-model="user.name" required>
</div>
JS
angular.module('ebu', []).
controller('search', function($scope) {
$scope.user = {};
$scope.addName = function()
{
$scope.user.name = "Kasper";
}
})
CSS
input[required].ng-invalid
{
border: 1px solid red;
}
input[required].ng-valid
{
border: 1px solid green;
}
input[required].ng-pristine
{
border: 1px solid blue;
}
[FiddleJS][1][1]: http://jsfiddle.net/kaspergantzhorn/z1wg6tko/9/
It's still pristine because pristine means it has never had focus. The user is not clicking on it, therefore it never gets dirty. If this isn't a user-modified field, pristine is not the right thing to use.
Additionally, in the ng-change of the input, you should be able to use $setDirty.
<input type="text" ng-change="formName.inputName.$setDirty()" />
For you code above:
<div ng-app="ebu" ng-controller="search">
<button id="search-apparentice" ng-click="addName(); myForm.name.$setDirty()">Search apprentice</button>
<hr>
<form name="myForm">
<input name="name" id="username" type="text" ng-model="user.name" required>
</form>
</div>

How to validate combinations of values with Parsley?

I'm trying to write a validator for phonenumbers for Parsley. The phone-number form widget consists of two fields:
<div>
<select name="country">
<option value="DE">Germany</option>
<option value="FR">France</option>
</select>
<input type="tel" name="number" data-parsley-phonenumber>
</div>
The validation function that I use looks like this:
function validatePhoneNumber(country, number) {
// return true if valid else false.
}
I'm aware of the Custom Validator example in the documentation, but it seems to only work if I hardcode a global selector for the country select field into the validator attribute data-parsley-phonenumber='["global-selector-here"]'
Is there a way to solve this without such a global selector? or more specific: is there a way to access the ParsleyField.$element inside the validator function? What's the recommended way of doing multi-field validations with parsley?
I would do it the way you refered (with data-parsley-phonenumber="#country" and then access the value with the requirement parameter), like this:
<input type="tel" name="number" data-parsley-phonenumber="#country" />
<script>
$('#myForm').parsley();
window.ParsleyValidator
.addValidator('phonenumber', function (value, requirement) {
var select = $(requirement).parsley().$element;
// call your function with the text of the selected option
return validatePhoneNumber(select.find(":selected").text(), value);
}, 32)
.addMessage('en', 'phonenumber', 'En message');
</script>
If you don't want to do this, you can access the ParlseyField object inside your addValidator, like this:
<input type="tel" name="number" data-parsley-phonenumber />
<script>
$('#myForm').parsley();
window.ParsleyValidator
.addValidator('phonenumber', function (value, requirement) {
var select = $("select[name=country]").parsley().$element;
// call your function with the text of the selected option
return validatePhoneNumber(select.find(":selected").text(), value);
}, 32)
.addMessage('en', 'phonenumber', 'En message');
</script>
Note that you need to be sure to define the addValidator after you have $('#myForm').parsley();.

AngularJS compile rendered name value in dynamic form

I'm building a form dynamically in Angular.
All works great until I get to my validation directive.
This uses the form object to get the field name and apply styling to it to show errors in invalid fields.
However because it's dynamically set the name appears as {{field.name}} not the actual name rendered in the browser.
Here is my markup
<div class="form-group" ng-repeat="field in fields">
<label class="text control-label col-sm-2" for="{{field.name}}">{{field.label}}</label>
<div class="input-group col-sm-9" ng-switch="field.type">
<input ng-switch-when="email" type="email" name="{{field.name}}" class="form-control" id="{{field.name}}" placeholder="{{field.label}}" ng-model="field.value" required>
<input ng-switch-when="text" type="text" name="{{field.name}}" class="form-control" id="{{field.name}}" placeholder="{{field.label}}" ng-model="field.value" required>
<select ng-switch-when="select" id="{{field.name}}" name="{{field.name}}" id="{{field.name}}" ui-select2="" ng-model="field.value" placeholder="{{field.label}}" required>
<option></option>
<option ng-repeat="option in field.options" value="{{option.value}}">{{option.label}}</option>
</select>
<p ng-switch-default>Need template for {{field.type}}</p>
</div>
</div>
Here's where I check for errors in my directive
// go through every field
for(var errorType in form.$error)
{
var fields = form.$error[errorType];
// loop through all fields of this type
for(var j = 0 ; j < fields.length ; j++)
{
var field = fields[j];
console.log(field);
var $field = element.find('[name='+field.$name+']');
showErrors($field, field, errorType);
}
}
And here are the properties for one of the invalid fields when logged out
...
$invalid: true
$isEmpty: function (value) {
$modelValue: ""
$name: "{{field.name}}"
$parsers: Array[1]
$pristine: true
...
So when I try to get hold of the field using var $field = element.find('[name='+field.$name+']'); it obviously doesn't work and I get a error...
unrecognized expression: [name={{field.name}}]
How would I get around this and get the rendered name. I need this directive to work for forms that aren't dynamically created either (in which case it works fine).
You have to use a directive:
myApp.directive("dynamicName",function($compile){
return {
restrict:"A",
terminal:true,
priority:1000,
link:function(scope,element,attrs){
element.attr('name', scope.$eval(attrs.dynamicName));
element.removeAttr("dynamic-name");
$compile(element)(scope);
}
};
});
Here is a fiddle.
Full answer here.

Retrieve all ngModels within a transcluded directive

i'm trying to retrieve all ngModels within a transcluded directive. Is this the right way or is there a simpler solution to find the child model values?
Is there also a selector where i can use queries like this one ("input", "textarea", "select", ...)
The sample: http://plnkr.co/edit/tjjBEa1I1fIISvGbRz7e?p=preview
I don't know if this is the right approach. All your models are inside your $scope so why don't getting it from there directly?
For changes you shouldn't use jQuery like element.on('change', ...) style, instead bind an event listener to the model with $scope.$watch('model', ...) that would be the angular way.
Are you trying to disable all the inputs and clear their values when the 'Disable' checkbox is ticked?
I would recommend adding the ng-disabled directive to your inputs and binding it to a property on your model.
You can easily clear the input values by moving them onto an object on your model and then clearing that property when the controls are disabled.
Updated version of your plunkr: http://plnkr.co/edit/xKRF3rfAB8EcSKEBEeKd?p=preview
Here is the updated code based on your example:
app.js:
app.controller('MainCtrl', function($scope) {
// 1. Bind the 'Disable' checkbox's ng-model to this value.
$scope.disabled = false;
// 2. Move all your model data down one level onto the 'viewData' object.
// Now we can change all the input values just by changing the `$scope.viewData` object.
$scope.viewData = {
user: {
lastname: 'Doe',
firstname: 'John'
},
checker: true,
opt: 'Item 2'
};
// 3. Add a change callback on the 'Disable' checkbox to call this function.
// Replace the $scope.viewData to change the input values.
var originalData = null;
$scope.disabledChanged = function() {
if ($scope.disabled) {
// Clear the previous object.
originalData = $scope.viewData;
$scope.viewData = null;
} else {
// Revert back to the previous object.
$scope.viewData = originalData;
}
}
})
index.html:
<fieldset id="f">
<legend>
<label><input type="checkbox" child-disable child-disable-root="f" ng-model="disabled" ng-change="disabledChanged()" /> Disable</label>
</legend>
<hr />
<p><input type="checkbox" ng-model="viewData.checker" ng-disabled="disabled" /> Test</p>
<p><input type="text" ng-model="viewData.user.firstname" ng-disabled="disabled" />
<input type="text" ng-model="viewData.user.lastname" ng-disabled="disabled" /></p>
<p><textarea ng-model="viewData.multi" ng-disabled="disabled"></textarea></p>
<div>
<select ng-model="viewData.opt" ng-disabled="disabled">
<option>Item 1</option>
<option>Item 2</option>
<option>Item 3</option>
</select>
</div>
<div>
<button>Click</button>
</div>
</fieldset>

Angular clear subform data and reset validation

I'm trying to create a subform <div ng-form="vacancyForm"> with Angular.js
There is a type of data that has numerous fields
Headline
Date available
Price
All have required validation on them.
Once I submit that data I'll do what I need with it but I want to reset the subform so that all the fields are not dirty and the form is valid as at the moment clearing out the fields work but all fields are invalid as they are now dirty, but empty marking them as invalid.
An example field
<div class="control-group" ng-class="getErrorClasses(vacancyForm.headline)">
<label class="control-label" for="headline">Headline</label>
<div class="controls">
<input type="text" class="input-xlarge" id="headline" name="headline" required ng-model="new_vacancy.headline">
<span class="help-inline" ng-show="showError(vacancyForm.headline, 'required')">This field is required</span>
</div>
</div>
Here is the function that is called when submitted
$scope.addVacancy = function(){
// save the submitted data
$scope.school.vacancies.push($scope.new_vacancy);
// now clear it out
$scope.new_vacancy = {};
$scope.new_vacancy.date = new Date();
// this clears out all the fields and makes them all invalid
// as they are empty. how to reset the form???
}
Set the name attribute on the subform and then you can $scope.formName.$setPristine(); where formName is what the name attribute is. An element is no longer pristine when the value has been changed.
http://docs.angularjs.org/api/ng.directive:form.FormController#$setPristine
Update
The above answer was solely for 1.2, but in 1.3 angular introduced the concept of a "touched" input. Now when an element is blurred angular will mark the field as touched. Similar to $setPristine, you can set the input back by using $scope.formName.$setUntouched().
https://docs.angularjs.org/api/ng/type/form.FormController#$setUntouched
touched vs pristine: touched means the field has been blurred while pristine means the field's value has been modified. Angular's docs note that "Setting a form controls back to their untouched state is often useful when setting the form back to its pristine state."
Edit
Here is a fiddle demo: https://jsfiddle.net/TheSharpieOne/a30kdtmo/
angular.module('myApp', [])
.controller('myCtrl', myCtrl);
function myCtrl() {
var vm = this;
vm.reset = function() {
vm.myForm.$setPristine();
vm.myForm.$setUntouched();
vm.email = vm.password = '';
}
}
.ng-invalid.ng-touched {
outline: 2px solid blue;
}
.ng-invalid.ng-dirty {
outline: 2px solid red;
}
.ng-invalid.ng-dirty.ng-untouched {
outline: 2px solid green;
}
form,
form div {
padding: 5px 10px;
}
h3,
h4 {
margin-bottom: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl as ctrl">
<form name="ctrl.myForm">
<div>
<label for="email">Email</label>
<input name="myInput" type="email" ng-model="ctrl.email" id="email" required>
</div>
<div>
<label for="password">Password</label>
<input name="myPassword" type="password" minlength="8" ng-model="ctrl.password" id="password" required>
</div>
<div>
<button ng-click="ctrl.reset()" type="button">Reset</button>
</div>
</form>
<div>
<h4>Form Level</h4>
<div>$dirty: {{ctrl.myForm.$dirty}}</div>
<div>$pristine: {{ctrl.myForm.$pristine}}</div>
<h4>Input Level</h4>
<h5>Email Input</h5>
<div>$dirty: {{ctrl.myForm.myInput.$dirty}}</div>
<div>$pristine: {{ctrl.myForm.myInput.$pristine}}</div>
<div>$touched: {{ctrl.myForm.myInput.$touched}}</div>
<h5>Password Input</h5>
<div>$dirty: {{ctrl.myForm.myPassword.$dirty}}</div>
<div>$pristine: {{ctrl.myForm.myPassword.$pristine}}</div>
<div>$touched: {{ctrl.myForm.myPassword.$touched}}</div>
</div>
<div>
<h3>Color outlines for input</h3>
<div title="The form loads this way, it can still be invalid since required fields are empty to start with">untouched, pristine: no outline</div>
<div title="Such as in the middle of typing a valid email for the first time">invalid, untouched, dirty: green outline</div>
<div title="blurred with invalid input">invalid, touched, dirty: red outline</div>
<div title="focued and blurred without typing">invalid, touched: blue outline</div>
</div>
</div>

Resources