Programatically add fields to parsley form - parsley.js

I'm using parsleyjs to validate my forms client-side. I have a scenario where, on click of a checkbox, some more form fields are exposed and need validating. If the checkbox is then unclicked the form fields are hidden and validation needs removing.
Is there functionality to achieve this in parsley? I've looked through the docs but can only find details of how to validate through attributes in the html. I'm looking for a method I can call in code to add and remove fields to be validated.

I ran into this exact situation a couple of weeks ago. You do need an extra bit of js to accomplish this. I dug around and found one bit of script that came close but needed some tweaking to suit my needs: it depended on predefined field definitions. No bueno. I wanted it to duplicate fields regardless of their names/ids/whatever. Then, of course, increment each new field name. Also without cloning whatever values had been entered by the user.
Since the fields being cloned already have the necessary parsley validation, that just went right along with them.
Here's the cloning code I came up with. I'm sure it can use improvement. And here's a fiddle with a working example.
this is my first contribution after years of lurking. be gentle. ;)
$('#btnDel').prop('disabled', true);
$('#btnAdd').prop('disabled', false);
$('#btnAdd').click(function() {
var num = $('.clonedInput').length;
var newNum = new Number(num + 1);
var newElem = $('#input1').clone().val('').attr('id', 'input' + newNum);
newElem.find(':input').attr('id', function () {
return this.id + '_' + newNum
});
newElem.find(':input').attr('name', function () {
return this.name + '_' + newNum
});
newElem.find(':input').val('');
$('#input' + num).after(newElem);
$('#btnDel').prop('disabled', false);
if (newNum == 5){
$('#btnAdd').prop('disabled', true);
}
});
$('#btnDel').click(function() {
var num = $('.clonedInput').length;
$('#input' + num).remove();
$('#btnAdd').prop('disabled', false);
if (num-1 == 1){
$('#btnDel').prop('disabled', true);
}
});
$('#btnDel').prop('disabled', true);
});

Parsley is great for most validation scenarios but in the past I've struggled to get it to do what I need - the lack of ability to programmatically interact with the validation lifecycle makes it less useful for more complex validations.
I wrote a library called Okjs that works in a very similar fashion to Parsley but with the added benefit that when you need to do something like add a new validator to a field based on user interaction there's a code API to allow you to do so:
https://github.com/jamesfiltness/Okjs
To add fields on click of a checkbox in Ok would go like so:
$('.my__checkbox').focus(function() {
Ok.Form.addField('checkbox1', ['required']);
Ok.Form.addField('checkbox2', ['required']);
})

Related

Ask to confirm when changing tabs in angular bootstrap

I have tabs with forms and I want ask the user to confirm or discard their changes when changing tabs. My current code works
<uib-tab heading="..." index="3" deselect="main.onDeselect($event)" ... >
this.onDeselect = function($event) {
if(...isDirty...) {
if($window.confirm("Do you want to discard?")) {
... discard (and go to new tab) ...
} else {
$event.preventDefault(); //stays on current tab
}
}
}
The problem is I want to change confirm to javascript dialog and I will get result in callback.
I planed to preventDefault() all and then switch manually, but I cannot figure out where to get new tab id.
Any solution is appreciated. Even if it is easier in other tab implementations.
I use AngularJS v1.4.7, ui-bootstrap-tpls-1.3.3.min.js
You can make use of $selectedIndex and the active property for that purpose. See this Plunk
One thing to be noted here is that when we manually change the active property, it again fires the deselect event which needed to be handled. Otherwise it seems to do what you wanted.
Edit
Indeed as noted in the comments, the deselect carries the HTML index rather than what is passed in in the tab index property. A workaround could be in this: Another Plunk. Here I'm pulling the actual index from the HTML index.
And a little research indicates that this issue might as well be fixed already with 3.0 bootstrap tpl See this.
I spent some time with different approaches and this one is stable for some time. What I do is to prevent deselect at the beginning and set the new tab in callback if confirmed to loose changes...
this.onDeselect = function($event, $selectedIndex) {
var me = this;
if(this.tabs.eventDirty || this.tabs.locationDirty || this.tabs.contractDirty) {
$event.preventDefault();
var alert = $mdDialog.confirm({
title: 'Upozornění',
textContent: 'Na záložce jsou neuložené změny. Přejete si tyto změny zrušit a přejít na novou záložku?',
ok: 'Přijít o změny',
cancel: 'Zůstat na záložce'
});
$mdDialog
.show( alert )
.then(function() {
$rootScope.$emit("discardChanges");
me.tabs.activeTab = $selectedIndex;
})
}
};

Is there a better way to get subcontrol name?

I am trying to get a reference in the "scrollbar-y" of the class qx.ui.list.List
Using the createChildControl event how can I check if the widget is the one named "scrollbar-y"?
So far I found two ways of which none seems elegant but both seem to get the job done
this.__list = new qx.ui.list.List()
this.__list.addListener("createChildControl", this.__onListCreateChildControl, this);
and later
__onListCreateChildControl: function (e){
debugger;
var child = e.getData();
if (child.constructor === qx.ui.core.scroll.ScrollBar && child.getOrientation() === "vertical") {
child.addListener("scroll", this.__onListScroll, this);
}
},
This checks implicitly. Apparently if it is a scrollbar and it is vertical it is our y scrollbar. Yeah it kinda looks like a duck but I have to check for both
if (quacks like one && walks like one)
The other way is
__onListCreateChildControl: function (e){
debugger;
var child = e.getData();
if (child.$$subcontrol === 'scrollbar-y') {
child.addListener("scroll", this.__onListScroll, this);
}
},
which uses the internal variable $$subcontrol. This works fine but it uses qooxdoo internals which seems like a hack.
P.S. I did try getChildControl('scrollbar-y') in various phases but since it is created in "as needed" basis I always get null.
You're right! There is no "straightforward" possibility to retrieve the ID (or name) of a widget created as a child of another widget in terms of child control creation.
Therefore I've submitted a PR to github which does exactly that: namely retrieving the id/name of a child control by exposing the internal $$subcontrol variable via a method getSubcontrolId https://github.com/qooxdoo/qooxdoo/pull/9140
The PR is currently in review state.

Warnings in AngularJs

Angularjs has great infrastructure for form validation and showing error messages. But, I am in a situation that I have to show a warning message to a user in a specific scenario. Here is the diagram of my simple form
The form has required and pattern validation applied on both fields. In addition to this validation I want a warning message to be displayed to the user if VatAmount is not 20 percent of the InvoiceAmount. The warning will differ from validation in following aspects
It will not prevent the form submission
It will only appear if both fields (InvoiceAmount and VATAmount) are
valid
The warning should have a button or link that would read "Change and
proceed". When user presses that button the warning message will
hide and focus will be set to VATAmount field.
I believe this is a prefect use case for creating a directive. Actually, I have given it a try and put my effort in the form of a plunker. But my directive does not handle following cases
It appears even if the fields involved in warning are invalid
The show and hide functionality is not implemented (have no idea how
to target it)
Here is the link to the plunker
Your plunkr demo was on the right track; really you just needed to check for the special cases of when one of the values was empty.
I'd suggest calculating the fraction and storing it in the scope, and then watching that to see whether you should display your tax rate warning. Here's how to calculate the fraction. If either invoice.Amount or invoice.VAT is empty, the fraction will be set to null.
if (amt == null || vat == null) {
$scope.warning.fraction = null;
return;
}
$scope.warning.fraction = vat / amt;
This works because those properties will be set to undefined if the user doesn't enter a valid number due to your use of ng-pattern.
However, while it's nice to encapsulate this in a directive, you don't need to compile the template yourself. Just use the built-in ng-transclude directive. Then you can include a button that references the current scope like this:
<vat-warning>
Vat Amount is not 20%.
<button ng-click="invoice.VAT = invoice.Amount / 5">change</button>
</vat-warning>
Your directive would contain this declaration:
transclude: true,
template: '<span class="alert-warning" ng-show="warning.show" ng-transclude></span>'
Plus a controller to update the directive's local scope to manipulate the warning object. Here's an updated demo.
You need to calculate visibility of vat-warning tag in controller on basis of $error.required and $error.pattern of invoiceAmount and vatAmount and then use it as below:
$scope.isInvoiceAmountInvalid = function () {
var error = $scope.invoiceForm.invoiceAmount.$error;
var required = error.hasOwnProperty("required") && error.required;
var pattern = error.hasOwnProperty("pattern") && error.pattern;
console.log("Inside isInvoiceAmountInvalid", error, required, pattern);
return (required || pattern);
};
$scope.isVatAmountInvalid = function () {
var error = $scope.invoiceForm.vatAmount.$error;
var required = error.hasOwnProperty("required") && error.required;
var pattern = error.hasOwnProperty("pattern") && error.pattern;
console.log("Inside isVatAmountInvalid", error, required, pattern);
return (required || pattern);
};
Here is an updated plunker for the same

how to access $error from nested forms

I have one main form and another subForm. How I can access $error of the child form but without having to directly reference the subForm name?
I want to be able to display that name is required(for example) instead of just knowing the name of the form.
Here's a demo of my problem: http://plnkr.co/edit/QWZArI1UFPpJdjoK8eVn?p=preview
Ok, I wasn't 100% sure I understood your question, but I think I do, so here's a stab at it:
There are a couple ways to access the errors of a child form, but all seem to need the name of the ngForm.
Assuming this structure:
<form name="parentForm">
<ng-form name="childForm"></ng-form>
</form>
you know that you can access it via $scope.childForm.$error, but less known is that it is also attached to the parent form. You can access it with $scope.parentForm.childForm.$error, but obviously, that's no good, since you still need the name.
You could get hacky and loop through the properties on the parent form and try to tell which one is the child form and go from there.
Lastly, as we've discussed on Twitter/GitHub, I have a directive that kinda does some of this magic for you. It did have a bug that didn't handle embedded forms correctly, but I fixed it. Check out the new version of my directive that tries to simplify handling errors with Angular:
https://github.com/CWSpear/angular-form-errors-directive
I added the ability to display all the errors of all the child ngForms with a flag in v1.3.0.
I think for this particular use case, my simple getErrors method on the scope is a more robust solution than the formErrors directive proposed in the accepted answer. Have a look at this comparison of the two solutions. The formErrors directive will clear errors from the child forms if the bottom child form is filled out. Play around with data entry and you will quickly see other bugs.
The getErrors solution is not pretty, but it is very simple and one can easily see how it might be improved to provide clearer messages.
The JavaScript:
var app = angular.module('app', ['FormErrors']);
app.controller('MainCtrl', ['$scope', function ($scope) {
$scope.people = [{name: ''}, {name: ''}];
$scope.allErrors = [];
$scope.getErrors = getErrors;
function getErrors(formObject){
$scope.allErrors = [];
extractErrors(formObject)
}
function extractErrors(formObject, parent) {
for (var e in formObject.$error) {
var path = parent ? parent + "." + e + "." + formObject.$name : formObject.$name + "." + e;
var err = formObject.$error[e];
if (err instanceof Array){ // this is an array of sub-forms
err.forEach(function (subForm, i) {
extractErrors(subForm, path + "[" + i + "]");
});
} else {
if (err === true) {
path = path.replace(new RegExp("." + e, "g"), "");
$scope.allErrors.push(path + ": " + e);
}
}
}
}
}]);

ExtJS findExact() and custom validator bug

I'm using a custom validator on my combobox's:
function(v) {
console.log(v === 'some value I know for SURE is in the store'); // (1)
var index = this.getStore().findExact(this.displayField, v);
return (index!==-1) ? true : 'Invalid selection';
}
Basically admits the same set as forceSelection but allows the user to type arbitrary text to attempt to auto-complete.
However; I'm having really odd results with findExact(). For example, if the combobox's value is currently valid, and a user does a space + backspace, the validator will fail, even though the output of (1) is true.
Any ideas what is causing the problem? The end-experience is currently very buggy-feeling..
When you type additional space, store is filtered. After you press backspace, and validator is fired, store is still empty.
If you have local store, then you could validate combo with some delay after each change. Example:
listeners: {
change: function() {
this.validate();
},
delay: 100
}
That should be enough.
On the other hand if you have remote store, try something like this:
validator: function(v) {
var store = this.getStore(),
index = store.findExact(this.displayField, v);
if (index === -1 && store.isLoading()) {
store.on('load', function() {
this.validate();
}, this, { single: true, delay: 100 });
}
return (index !== -1) ? true : 'Invalid selection';
}
I had a similar issue and found this entry. My problem was, that I reused the same store instance across multiple ComboBoxes. After giving each ComboBox an own store with cloned data, everything was fine.
See also:
https://www.sencha.com/forum/showthread.php?305182-ComboBox-forceSelection-clears-input&p=1127218&viewfull=1#post1127218
I just spent a few days on this issue and found a really great solution (by accident, really). You can - as the accepted answer suggests - utilize the provided validator function; however, from my understanding, there is a much simpler solution than the accepted answer provides: evaluating whether or not the user-provided input equates to a value in the store (which is the underlying question in the original post).
The advantage of thinking about the input in this way is that it enables us to handle the use case of an invalid value entered by the user (validated; no value) and - after the field loses focus - Ext JS sets the field back to its previous value (which remembers its store value).
This is an entirely different route than your thinking, but it should work, especially as .validate() runs regardless of whether you provide an implementation of the validator procedure:
validator : function(someParam) {
if(this.value === null) {
return "error message"; //falsy
} else {
return true;
}
}
If you enable forceSelection, the above works, very well, and gets rid of the buggy feeling. This allows you to rely on the .validate to do its magic elsewhere (notice I don't even call it; read the doc. to figure out when its called in relationship to validator) and not have to worry about what the user correctly explains in the accepted answer.
We were having trouble with forceSelection clearing user text before they were finished typing. We seemed to get what we needed by setting forceSelection false and just checking that they selected something.
validator: function(v) {
if (this.getSelection() === null) {
return 'invalid text';
}else{
return true;
}
}

Resources