single requirement in parsley error message %s1 - parsley.js

Hi I have a custom validator with 2 requirements(arguments/paramaters) in parsley.js
For the error message I only want to display one of the requirements but I cannot work out how
messages: {en: 'You must choose at least one language with %s'},
I thought %s1 or %s0 might work but they don't
%s results in this message:
You must choose at least one language as [native,enrolment_form_new_instruction_languages_proficiency]
but I just want
You must choose at least one language as native
here is my full validator in case that helps you answer:
Parsley.addValidator('oneChildEquals', {
requirementType: ['string', 'string'],
validateString: function(_value, requirement, requirement2, instance) {
var $inputs = $(instance.element).find("select.language-proficiency");
var valid = false;
$inputs.each(function(i){
if($(this).val() == requirement){
valid = true; // one input has the target value (requirement2)
return false; //break out of the loop
}
});
// no input has the target value (requirement2)
return valid;
},
messages: {en: 'You must choose at least one language with %s'},
});

I have written a solution, probably could be improved, but I don't even know if I need this now. Anyway in case anyone else does, you can just add this code:
if(typeof(string) === 'string' && typeof(parameters) === 'string' && parameters.match(/^\[.*\]$/) && string.match(/\%s\d/)){
//parameters are an array of values and string is trying to access them individually with %s1 to get first etc
var paramsArray = parameters.slice(1, parameters.length-1).split(',');
var interpolations = string.match(/\%s\d/g);
for(var j = 0; j < interpolations.length ; j++){
var interpolation = interpolations[j];
var number = parseInt(interpolation.replace("%s", ''));
if(isNaN(number)){
string = string.split(interpolation).join(interpolation + '[not a valid interpolation]');
}else {
var val = paramsArray[number-1];
if(typeof(val) === 'undefined'){
val = interpolation + '[not a valid interpolation]';
}
string = string.split(interpolation).join(val);
}
}
return string
}
after this code in the source
formatMessage: function formatMessage(string, parameters) {
if ('object' === _typeof(parameters)) {
for (var i in parameters) {
string = this.formatMessage(string, parameters[i]);
}
return string;
}
and before
return 'string' === typeof string ? string.replace(/%s/i, parameters) : '';
Then it supports things like
Parsley.addValidator('testParsley', {
requirementType: ['string','string'],
validateString: function(_value, requirement, requirement2) {
return (_value === requirement || _value === requirement2);
},
messages: {en: 'test parsley this field must equal %s1 or %s2'}
});

Related

Parsley.js Customer Validator Pass Variable to message

window.Parsley.addValidator('passwordValidChars', {
requirementType: ['integer', 'integer'],
validateString: function(value, min, max) {
var invalidChars = [];
for (var i = 0; i < value.length; i++) {
if(value.charCodeAt(i) < min || value.charCodeAt(i) > max) {
invalidChars.push(value.charAt(i));
}
}
return (invalidChars.length === 0)
},
messages: {
en: 'These characters are not allowed %s'
}
});
I have this custom validator which validates the password entered between specific char codes e.g [33, 126].
If the user entered any invalid characters I collect them to an array invalidChars and then I want to pass the invalidChars array to the error message but how can I do that? The only values I can pass to the message are the min and max.
There's no super easy way, but you can return a dynamic error message. It's not well documented, but instead of returning false return $.Deferred().reject('your dynamic error message').

Angular ng-repeat filtering

I have a deeply nested object. I have some records which contain 2 fields that show keys of object properties. I also have select needed to search records by property of object and input to search by key of object. So if I choose option1 and type in input some text, it will be shown the matches in the first field (not second!). And it's similar for second field.
How I try to realize:
I wrote a filter http://plnkr.co/edit/z9DEmfYz2grW9UonLcFK?p=preview
.filter('appFilter', function() {
return function(value, select, input) {
var result = [];
input = input.toLowerCase();
var reg = new RegExp(input,'g');
if (angular.isArray(value)) {
if (input === '' || $scope.isFiltering) {
return value;
} else if (select.value === 'Sequence') {
for (let i = 0; i < value.length; i++) {
if (value[i].Sequence.toLowerCase().match(reg)) {
result.push(value[i]);
}
}
return result;
} else if (select.value === 'ID') {
for (let i = 0; i < value.length; i++) {
if (angular.isArray(value[i].Document)) {
for (let j = 0; j < value[i].Document.length; j++) {
if (value[i].Document[j].ID.toLowerCase().match(reg)) {
result.push(value[i]);
}
}
}
}
return result;
} else {
console.log('error');
}
}
}
})
In controller I set to select's ng-model first option: $scope.selectParameter = $scope.parameter[0];
In debug I set to input parameter some value (123 for example).
So I searching record by first field that contains 123 value. And result finds and pushes the object. But in browser shows anything.
What's the problem? And I can't avoid the empty option with '?' value in my select :(
UPDATED
Nearly solve my problem: http://plnkr.co/edit/z9DEmfYz2grW9UonLcFK?p=preview
It filters by appropriate field and input value. But I faced with another troubles.
When input is empty it doesn't show any record. And second is when I choose second option (ID) filter duplicates some records.
Also I try to switch off filter without clearing the input text by clicking on checkbox.
It's what I want to do but it doesn't work:
else if (input === '' || $scope.isFiltering) {
return value;
}
$scope.isFiltering is ng-model for checkbox input
I tried using angulars default filter. I'm not sure if this is exactly what you want, but maybe it helps a little.
.filter('appFilter', function($filter) {
return function(value, select, input) {
if( !angular.isDefined(input) || input.length < 1) {
return value;
}
// Angulars "filter" lets you pass in a object-structure to search for nested fields.
var query =
(select.value === 'Sequence') ?
{Sequence:input} : {Document:{ID:input}};
return $filter('filter')(value, query);
}
})
http://plnkr.co/edit/Egkw9bUvTPgooc0u2w7C?p=preview

AngularJS concatenate two functions

I have two factories (because it is more readable) and one controller.
I do not know if it is possible, but I want to sent the result of the two factories in one variable.
code
vm.result = [];
vm.validate = validate;
function validate(password1, password2) {
vm.result = comparePasswordFactory.compare(password1, password2) + validNumberFactory.valid(password1);
return vm.result;
}
compare returns:
return {
color: 'green',
text: 'Passwords match',
validate1: 'true'
};
valid returns:
return {
text: 'Number should be between 12 and 45',
validate2: 'false'
};
I do not think what I wrote is correct.
But, is this possible and/or is it a good option ?
in order to validate using both functions you have to read and compare its validate1 / validate2 properties
var passwordOK = comparePasswordFactory.compare(password1, password2).validate1 === 'true';
var numberOK = validNumberFactory.valid(password1).validate2 === 'true';
vm.result = passwordOK && numberOK;
tip: better way would be to use boolean values instead of string, eg.
return {
validate: false
}
pro way: consider using consistent api in your factories, then you could do something like:
var validators = [comparePasswordFactory, validNumberFactory];
var isValid = true;
for(var i=0;i<validators.length;i++){
if(!validators[i].validate.call(this, password1, password2)){
isValid = false;
break;
}
}
vm.result = isValid;
then you could easily add another validator (notice that all factories must implement validate method)

AngularJS number input formatted view

I want to use a formatted number input to show thousand seperator dots to user when he types big numbers. Here is the directive code that I used: http://jsfiddle.net/LCZfd/3/
When I use input type="text" it works, but when I want to use input type="number" it's weirdly cleaning by something when user typing big numbers.
What is problem about input[number]?
As written in the comments, input type="number" doesn't support anything but digits, a decimal separator (usually , or . depending on the locale) and - or e. You may still enter whatever you want, but the browser will discard any unknown / incorrect character.
This leaves you with 2 options:
Use type="text" and pattern validation like pattern="[0-9]+([\.,][0-9]+)*" to limit what the user may enter while automatically formatting the value as you do in your example.
Put an overlay on top of the input field that renders the numbers how you want and still allows the user to use the custom type="number" input controls, like demonstrated here.
The latter solution uses an additional <label> tag that contains the current value and is hidden via CSS when you focus the input field.
All these years later, there still isn't an HTML5 solution out of the box for this.
I am using <input type="tel"> or <input type="text"> ("tel" brings up a numeric keyboard in Android and iOS, which in some cases is a bonus.)
Then I needed a directive to:
filter out non-numeric characters
add thousand-separator commas as the user types
use $parsers and keyup to set elem.val() and $formatters to set the display...
...while behind the scenes, assign ng-model a floating point number
The directive example below does this, and it accepts negatives and floating point numbers unless you specify you want only positive or integers.
It's not the full solution I would like, but I think it bridges the gap.
HTML
<input type="text" ng-model="someNumber" number-input />
JAVASCRIPT
myApp.directive('numberInput', function($filter) {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ngModelCtrl) {
ngModelCtrl.$formatters.push(function(modelValue) {
return setDisplayNumber(modelValue, true);
});
// it's best to change the displayed text using elem.val() rather than
// ngModelCtrl.$setViewValue because the latter will re-trigger the parser
// and not necessarily in the correct order with the changed value last.
// see http://radify.io/blog/understanding-ngmodelcontroller-by-example-part-1/
// for an explanation of how ngModelCtrl works.
ngModelCtrl.$parsers.push(function(viewValue) {
setDisplayNumber(viewValue);
return setModelNumber(viewValue);
});
// occasionally the parser chain doesn't run (when the user repeatedly
// types the same non-numeric character)
// for these cases, clean up again half a second later using "keyup"
// (the parser runs much sooner than keyup, so it's better UX to also do it within parser
// to give the feeling that the comma is added as they type)
elem.bind('keyup focus', function() {
setDisplayNumber(elem.val());
});
function setDisplayNumber(val, formatter) {
var valStr, displayValue;
if (typeof val === 'undefined') {
return 0;
}
valStr = val.toString();
displayValue = valStr.replace(/,/g, '').replace(/[A-Za-z]/g, '');
displayValue = parseFloat(displayValue);
displayValue = (!isNaN(displayValue)) ? displayValue.toString() : '';
// handle leading character -/0
if (valStr.length === 1 && valStr[0] === '-') {
displayValue = valStr[0];
} else if (valStr.length === 1 && valStr[0] === '0') {
displayValue = '';
} else {
displayValue = $filter('number')(displayValue);
}
// handle decimal
if (!attrs.integer) {
if (displayValue.indexOf('.') === -1) {
if (valStr.slice(-1) === '.') {
displayValue += '.';
} else if (valStr.slice(-2) === '.0') {
displayValue += '.0';
} else if (valStr.slice(-3) === '.00') {
displayValue += '.00';
}
} // handle last character 0 after decimal and another number
else {
if (valStr.slice(-1) === '0') {
displayValue += '0';
}
}
}
if (attrs.positive && displayValue[0] === '-') {
displayValue = displayValue.substring(1);
}
if (typeof formatter !== 'undefined') {
return (displayValue === '') ? 0 : displayValue;
} else {
elem.val((displayValue === '0') ? '' : displayValue);
}
}
function setModelNumber(val) {
var modelNum = val.toString().replace(/,/g, '').replace(/[A-Za-z]/g, '');
modelNum = parseFloat(modelNum);
modelNum = (!isNaN(modelNum)) ? modelNum : 0;
if (modelNum.toString().indexOf('.') !== -1) {
modelNum = Math.round((modelNum + 0.00001) * 100) / 100;
}
if (attrs.positive) {
modelNum = Math.abs(modelNum);
}
return modelNum;
}
}
};
});
https://jsfiddle.net/benlk/4dto9738/
You need to add the step attribute to your number input.
<input type="number" step="0.01" />
This will allow floating points.
http://jsfiddle.net/LCZfd/1/
Also, I'd recommend reviewing the bug thread on number inputs in Firefox. You may want to consider not using this input type, as it was just finally supported in this release of FF.
https://bugzilla.mozilla.org/show_bug.cgi?id=344616
http://caniuse.com/input-number
You cannot use values with , because type=number only takes numbers, adding a comma makes it a string.
See http://jsfiddle.net/LCZfd/5
You're better off making your own controls if you want commas. One with a true value (the number) and a display value (the string).
you can try this, I modified the directive I saw here...
How do I restrict an input to only accept numbers? ...
here's the modified directive I made... This directive uses the keyup event to modify the input on the fly...
.directive('numericOnly', function($filter) {
return {
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
element.bind('keyup', function (inputValue, e) {
var strinput = modelCtrl.$$rawModelValue;
//filter user input
var transformedInput = strinput ? strinput.replace(/[^,\d.-]/g,'') : null;
//remove trailing 0
if(transformedInput.charAt(0) <= '0'){
transformedInput = null;
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}else{
var decimalSplit = transformedInput.split(".")
var intPart = decimalSplit[0];
var decPart = decimalSplit[1];
//remove previously formated number
intPart = intPart.replace(/,/g, "");
//split whole number into array of 3 digits
if(intPart.length > 3){
var intDiv = Math.floor(intPart.length / 3);
var strfraction = [];
var i = intDiv,
j = 3;
while(intDiv > 0){
strfraction[intDiv] = intPart.slice(intPart.length-j,intPart.length - (j - 3));
j=j+3;
intDiv--;
}
var k = j-3;
if((intPart.length-k) > 0){
strfraction[0] = intPart.slice(0,intPart.length-k);
}
}
//join arrays
if(strfraction == undefined){ return;}
var currencyformat = strfraction.join(',');
//check for leading comma
if(currencyformat.charAt(0)==','){
currencyformat = currencyformat.slice(1);
}
if(decPart == undefined){
modelCtrl.$setViewValue(currencyformat);
modelCtrl.$render();
return;
}else{
currencyformat = currencyformat + "." + decPart.slice(0,2);
modelCtrl.$setViewValue(currencyformat);
modelCtrl.$render();
}
}
});
}
};
you use it like this ...
<input type="text" ng-model="amountallocated" id="amountallocated" numeric-only />

Determine attribute being tested in backbone.js

I have a massive validate function in my backbone model that could be simplified if only I could detect the attribute I'm testing against. There are a few ideas of how I can think to approach this problem, but they all rely on knowing the attribute name I'm testing, without hardcoding it as I am in the id variable below.
Here is an example:
validate : function(attr){
var t = this;
if(attr.user1DobMonth && attr.user1DobMonth != t.get('user1DobMonth')){
var val = jQuery.trim(attr.user1DobMonth.toLowerCase()),
id = 'user1DobMonth',
error = {
attr : id
};
if(val === 'select'){
return error;
}
}
if(attr.user2DobMonth && attr.user2DobMonth != t.get('user2DobMonth')){
var val = jQuery.trim(attr.user2DobMonth.toLowerCase()),
id = 'user2DobMonth',
error = {
attr : id
};
if(val === 'select'){
return error;
}
}
}

Resources