How to validate combinations of values with Parsley? - parsley.js

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();.

Related

Angular - Cannot read property '$invalid' of undefined

I am creating a login page using angular and typescript. When the submit button is clicked, I would like the login function in the controller to fire, but if the form is invalid then it just returns.
This is my first time using typescript, so every time I try to put in the if statement to check if the form is invalid it throws the error Cannot read property '$invalid' of undefined.
Here is the html:
<form class="login-form" name="loginForm" ng-submit="vm.login()" novalidate>
<input type="email" name="email" placeholder="Email" required ng-model="vm.email" ng-class="{true: 'input-error'}[submitted && loginForm.email.$invalid]"/>
<input type="password" name="password" placeholder="Password" required ng-model="vm.password" ng-class="{true: 'input-error'}[submitted && loginForm.password.$invalid]"/>
<input type="submit" id="submit" ng-click="submitted=true"/>
</form>
And here is the compiled javascript:
var LoginModule;
(function (LoginModule) {
var LoginController = (function () {
function LoginController() {
}
LoginController.prototype.login = function () {
if(this.loginForm.$invalid) {
return;
}
console.log("Login was clicked, email is " + this.email + " and password is " + this.password);
};
return LoginController;
})();
LoginModule.LoginController = LoginController;
})(LoginModule || (LoginModule = {}));
angular.module('loginModule', []).controller('LoginController', LoginModule.LoginController);
It looks like the common issue with this was that people were not specifying the form name, but that is not the case here. Does anyone know why I could be getting this error?
You need the controller alias in the form name
<form name="vm.loginForm" ng-submit="vm.login()" novalidate>
While #charlietfl's answer is correct for the OP's question, this error can also be thrown if you've missed the ng-model property on an input control and are using the ui.bootstrap.showErrors $scope.$broadcast('show-errors-check-validity') method.
As per the documentation (my emphasis):
.. an input control that has the ngModel directive holds an instance of NgModelController. Such a control instance can be published as a property of the form instance using the name attribute on the input control. The name attribute specifies the name of the property on the form instance.

Parsley.js destroy and revalidate fields already validated

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>

Have AngularJs update {{binding}} as the user types in input[email]

Angular only updates the model from an input[email] after the user has entered a valid email address. How can I add a {{binding}} somewhere on the page that will update with the email value as the user types -- even before the user has typed in a valid email address?
Here's what I've tried so far:
<div ng-app>
<div ng-controller="MyCtrl">
<form name="MyForm" novalidate>
Name: <input type="text" name="name" ng-model="contact.name" /><br/>
Name as you type: {{contact.name}}<br/>
Email: <input type="email" name="email" ng-model="contact.email" /><br/>
Email as you type: {{contact.email}} (doesn't work)<br/>
Also doesn't work: {{$document.forms.MyForm.elements.email.value}}
</form>
</div>
</div>
Controller:
function MyCtrl($scope) {
$scope.contact = {};
}
(fiddle)
The name updates in real-time like I want, but the email doesn't.
I'd like to leave the email validation enabled. I just need some way to bind the un-validated input[email] text, so it updates as the user types.
Update 2014/7/8
I'd like to add an explicit requirement that the type="email" remains unchanged. I do not want to change the semantics of the markup to workaround a limitation of the framework. If need be, I'd rather pull in a complementary dependency (such as jQuery) to shim in the needed functionality.
I'm not opposed to handling validation in the controller — as suggested by rageandqq and charlietfl — if it could be done easily. Looking around though, it looks like it could be tricky (given my requirements).
That is how angularjs works. If you use <input type="email" /> angular is not going to bind your input till input will be valid in this case value must be a proper e-mail address.
please read more here : https://github.com/angular/angular.js/issues/1426
The workaround I've come up with so far is to use jQuery to listen for the input change and update an object on $scope that I've called formRaw. It works. Still, I'm hoping someone will come along and show me a better way.
The updated example:
<div ng-app>
<div ng-controller="MyCtrl">
<form name="MyForm" novalidate>
Name: <input type="text" name="name" ng-model="contact.name" /><br/>
Name as you type: {{contact.name}}<br/>
Email: <input type="email" name="email" ng-model="contact.email" /><br/>
Email Model: {{contact.email}}<br/>
Email Form: {{formRaw.email}}
{{q}}
</form>
</div>
</div>
And controller:
function MyCtrl($scope) {
$scope.contact = {};
$scope.formRaw = {};
$('input[type=email]').on('keyup change', function () {
var input = $(this);
$scope.formRaw[input.attr('name')] = input.val();
$scope.$digest(); // FIXME: there's got to be a better way
});
}
(fiddle)
The type="email" attribute on your E-mail input is what is causing the DOM binding to mess up.
Changing it to type="text" works allows your {{contact.email}} to display correctly.
Edited JSFiddle.

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>

Don't record invalid values with ng-model

I really like how the ng-model attribute binds directly to my model and users get instant feedback on their changes. For my use case that's perfect. However, I don't want invalid values to be put into the model where they can throw a wrench into the calculations. I somehow want the model to only be updated if the value in the form control is valid. For invalid values, it's fine for the control value to change while the model value stays fixed.
If I change the source of angular (1.2rc) NgModelController's $setViewValue implementation:
this.$setViewValue = function(value) {
...
if (this.$modelValue !== value) {
this.$modelValue = value;
...
}
};
To this:
this.$setViewValue = function(value) {
...
if (this.$modelValue !== value && this.$valid) {
this.$modelValue = value;
...
}
};
It seems to do exactly what I want, however I don't know how to do this in a proper way. What's the right way to change this behavior? Or are my attempts doomed to failure for some reason?
Update: Added example.
For example look at http://jsfiddle.net/FJvgK/1/ HTML:
<div ng-controller="MyCtrl">
{{validNumber}}
<form>
<input
type="number"
ng-model="validNumber"
required
min="10"
max="20"
/>
</form>
</div>
And the JS:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.validNumber = 15;
}
The number shows properly for values between 10 and 20, but I want it so that if you suddenly type '8' into the box, or delete the second digit leaving '1' the last valid number still shows above. That is, the model always has a valid value, even if the control does not.
I believe the default behaviour of AnugularJS validators are not to update the model if the value passed is invalid. If you look at the developer guide and go through Custom Validation these samples also show that the model is not update or is cleared on invalid value provided in the UI
This is default behaviour, but, you can modify this using ngModelOptions directive
<input
type="number"
ng-model="validNumber"
required
min="10"
max="20"
ng-model-options="{ allowInvalid: true }"
/>
Documentation: https://docs.angularjs.org/api/ng/directive/ngModelOptions See the section 'Model updates and validation'
As Chandermani said, it is the default behavior, here is a example that shows it in action :
<form name="myform">
<input type="text" name="myinput" ng-model="myvalue" ng-minlength="4" required>
</form>
Is the input valid ? {{ myform.myinput.$valid }} <br />
Input's value : {{ myvalue }}
{{ myvalue }} doesn't show anything until you write at least 4 characters in the input.
Best Regards.
EDIT
If you need a default value, I guess you could break down your value into 2 values, using a computed value :
var validNumber = 15;
$scope.validNumber = function () {
if ($scope.myform.myNumber.$valid) return $scope.myNumber;
else return validNumber;
};
I set up an example here : http://jsfiddle.net/7vtew/1/

Resources