In Tapestry 5, in a form, I need to log (via google analytics) when each individual form field has been completed and passed the client-side validation successfully.
I need to do this for each individual field in real-time, even if the form is not submitted, so waiting until form submission and doing this on the server is not an option.
Is there a way (out of the box) to hook into the success/failure of the javascript client-side validation provided by Tapestry 5?
The two possibilities I can think of would be:
A Tapestry api to provide a callback function to the success/failure events.
Listening for some Tapestry custom event on success/failure.
But I cannot find anything in the documentation about either of these existing. Is either of these options possible, or is there another way to achieve this?
You might be able to attach clientside event listeners to achieve what you want. Take a look at Tapestry.FormEventManager.handleSubmit in tapestry.js. Form submission fires the following clientside events:
/**
* Event that allows observers to perform cross-form validation after
* individual fields have performed their validation. The form element is
* passed as the event memo. Observers may set the validationError property
* of the Form's Tapestry object to true (which will prevent form
* submission).
*/
FORM_VALIDATE_EVENT: "tapestry:formvalidate",
/**
* Event fired just before the form submits, to allow observers to make
* final preparations for the submission, such as updating hidden form
* fields. The form element is passed as the event memo.
*/
FORM_PREPARE_FOR_SUBMIT_EVENT: "tapestry:formprepareforsubmit",
/**
* Form event fired after prepare.
*/
FORM_PROCESS_SUBMIT_EVENT: "tapestry:formprocesssubmit",
/**
* Event, fired on a field element, to cause observers to validate the
* input. Passes a memo object with two keys: "value" (the raw input value)
* and "translated" (the parsed value, usually meaning a number parsed from
* a string). Observers may invoke Element.showValidationMessage() to
* identify that the field is in error (and decorate the field and show a
* popup error message).
*/
FIELD_VALIDATE_EVENT: "tapestry:fieldvalidate",
/**
* Event notification, on a form object, that is used to trigger validation
* on all fields within the form (observed by each field's
* Tapestry.FieldEventManager).
*/
FORM_VALIDATE_FIELDS_EVENT: "tapestry:validatefields",
A colleague suggested overriding the Tapestry.FieldEventManager.validateInput function using ProptypeJS Class.create.
Tapestry.FieldEventManager = Class.create(Tapestry.FieldEventManager, {
validateInput : function($super) {
var isValidationFailure = $super();
// can use this.field inside this function to access the field being validated
if(!isValidationFailure) {
// LOGIC ON VALIDATION SUCCESS
} else {
// LOGIC ON VALIDATION FAILURE
}
return isValidationFailure;
}
});
This solves the problem nicely, allowing for clean readable code and easy access to both the super implementation and the actual field itself, which I needed to do my tracking logic. And the best part is it just works, with validateInput being called on each individual form field when it loses focus. No detailed knowledge of, or messy code around, the events that drive the validation engine required!
FYI validateInput will also run for each form field on form submission - that can be worked around by overriding Tapestry.FormEventManager.handleSubmit to hook into form submission logic... but that is a little beyond the scope of this answer.
Related
I have a web form using AngularJS form validation, however when I use the address lookup (using api/script from loqate.com) the inputs are filled in from selecting an address but the AngularJS can't see the values. The form responds as if the inputs are empty.
The form validation all works fine, but when the inputs have values set via the address lookup the ng-model sees nothing, ng-change isn't triggered etc.
I've tried calling $scope.$apply() but whenever I do I get $rootScope:inprog console error.
The script the address lookup calls appears to be this, but I can't make head nor tail of it
Here is the form in action. Address is on page 2, start entereing a postcode eg. 'PE' for address options.
According to the Loqate documentation you can use event listeners so you could update your model by adapting this code:
control.listen("populate", function(address, variations) {
// I don't know what fields the received address variable has but
// you just have to check it and apply the necessary changes.
$scope.Contact.address1_postalcode = address.PostalCode;
$scope.Contact.address1_line1 = address.Street;
$scope.Contact.address1_city = address.City;
$scope.Contact.address1_country = address.Country;
});
Just the background:
(not really related to the question)
I am using SharePoint and creating a Sharepoint Hosted App.
I have some fields which provide a peoplepicker with which I can select Users out of a user pool. The functioninality is provide by the SharePoint internal scripts.
If I input a name of a user via jquery nothing happens. It has no built-in change handler I guess.
What I have done is a separat script which just resolve the user names on my jquery input via trigger("change") and then the SharePoint internal Scripts doing the job. This "job" is asynchronous and then if the data arrives from the backend the SharePoint Scripts apply the data to the Peoplepicker fields.
But this functions also doesn't support jQuery's trigger because I cannot alter the functions as they're pre-defined...
So want I want to do is to monitor when the text inside the Peoplepicker fields has been changed but I am a little bit lost now.
Question:
I have tried it with the following code (which doesn't work):
$('.peoplePickerDiv').bind("DOMSubtreeModified", function () {
alert("HAS BEEN CHANGED!");
});
Is there another way to react on lets say background editing of input fields in my DOM?
For People Picker control manipulation SPClientPeoplePicker object (clientpeoplepicker.js) is intended, which provides methods for getting information from the picker or to perform other operation.
SPClientPeoplePicker class exposes the following events:
OnControlValidateClientScript - triggers after a server error is set or cleared
OnUserResolvedClientScript - triggers once the resolved user is added or removed in client
OnValueChangedClientScript - triggers after text input or users change in client
OnValueChangedClientScript event probably suits your scenario, here is an example on how to attach it:
var picker = SPClientPeoplePicker.SPClientPeoplePickerDict[peoplePickerId];
picker.OnValueChangedClientScript = function (elementId, userInfo) {
if(userInfo.length > 0){ // once the value is resolved it could be retrieved via `userInfo` object
console.log(userInfo[0]);
}
console.log('value changed');
};
References
Use the client-side People Picker control in SharePoint-hosted SharePoint Add-ins
In Dynamics CRM 2013 is it possible to revert a field changed on a form when a business process error occurs?
For example:
1. User changes a text field on a form from 'abc' to 'xyz'
2. User clicks save
3. CRM pre-operation plugin validates field, 'xyz' not allowed, exception thrown
4. CRM displays business process error to user, 'xyz' is not allowed
5. The value 'xyz' is still shown in the form
The desired behavior we want is 'xyz' to revert to 'abc' in step 5.
You will need to cache the data first. You can do this OnLoad, e.g. by memorizing the entity's attribute values:
function GetInitialAttributeState() {
var preImage = {};
Xrm.Page.data.entity.attributes.forEach(function(field) {
// TODO: for lookup attributes you need to do extra work in order to avoid making merely a copy of an object reference.
preImage[field.getName()] = field.getValue();
});
return preImage;
}
window.preImage = GetInitialAttributeState();
Then you need to perform the save operation through the Xrm.Page.data.save method. Pass a callback function handling errors and resetting the fields, e.g.
Xrm.Page.data.save().then(
function() {
/* Handle success here. */
window.preImage = getInitialAttributeState();
},
function() {
/* Handle errors here. */
Xrm.Page.data.entity.attributes.forEach(function(field) {
if (field.getIsDirty()) {
field.setValue(preImage[field.getName()]);
}
});
});
It is not possible to reset the form's fields this way using the save event, because it kicks in before the actual save operation, never after it.
Why let the user save the record at all?
You could use a Business Ruel to validate the field, and set an error condition against the field for values you don't "like". The error condition will persist and prevent them saving the record until they change the value. The error message can give them some explanation as to why their value is not valid.
Obviously the validation you can do in a Business Rule is limited, but your example does not make it clear on what basis we match "xyz" as a "bad" value.
I have an input field with a value binding. The binding references a writable computed observable.
The intended behavior is:
a user types some letters: The writable computed should recognize the user is still typing
a user finishes typing: The writable should save the value
However, I only can choose updateValue: 'input' or updateValue: 'keydown', but not both. In the JavaScript code, the writable computed is not able to check which event occurred.
I need this behavior for this reason: if the user is just typing I want to fill an auto-suggestion box and want to bold the already typed letters. For this I need the keydown event. But if the user finishes typing (losing focus) I want to do some verification and then save the entered value.
If I use input the verification is working but the auto-suggestion feature is broken. If I use keydown the user isn't able to type something, because the verification always fails and the user cannot finish typing.
Suppose you have computed named self.inputValue which references to the input field.
You can apply rateLimit extender on perticular computed..
e.g.
self.inputValue = ko.computed(function(){
//perform the required logic
}).extend({ rateLimit: { method: "notifyWhenChangesStop", timeout: 400 } });;
where timeout is in miliseconds. It will hold the updation or notification of value for 400 miliseconds.
This should solve your problem. A small JSFiddle I've made for you : http://jsfiddle.net/rahulrulez/x8jmcpLh/
rateLimit was introduced in KO 3.1.0, if you are using old library then use throttle instead.
I've been putting together a sign-up form that is essentially broken up into 3 parts; you fill out your name, email, username, etc. then your chosen options (colour, intro text, etc.), then your address details. Each of these steps has at least one field that utilises ajax to check its validity; i.e. checking for duplicate emails or usernames, suggesting alternate page titles, etc.
These ajax functions are triggered (after a brief delay) on keyup input blur, but it has only just occurred to me that I also want to validate the inputs when someone clicks to move on to the next step of the sign-up. The method I'd usually use is to trigger a blur on all fields of the current sign-up step and, if any have an .error attached, don't allow the user to move on; however triggering a blur on the fields with ajax listeners attached to them means that they don't immediately return an error.
Handily, I'm not using any kind of plugin - I've coded everything myself. When the ajax fields are checking the validity of their input, they do have a .loading class attached that will eventually turn into either an .okay or .error class, but on some screens there are 2-4 ajax-checked fields at once, so I need to wait for all of them to validate before moving on.
My current validation function is uber-simple:
$(document).on('click', '.step button.validate', function() {
var currentStep = $(this).closest('.step');
// Let's try to trigger those errors ON PORPOISE
currentStep.find('input, select, textarea').trigger('blur');
if (currentStep.find('.error').length) {
// No dice.
alert('This is bad, yo.');
return false;
}
// Everything looks hunky-dory; the form should submit as normal
gotoNextStep();
});
TL;DR I'm trying to figure out how to 'wait' for all of my ajaxified fields to return a response before doing anything.
Worth noting:
The whole thing is wrapped inside one big <form> element. This doesn't get submitted with each step; only at the end, when each step has been provisionally validated. It (very basically) looks like this: http://jsfiddle.net/zVAxs/ (HTML markup only for illustration of structure).
Any help or pointers in the right direction would be greatly appreciated.
I'm not really sure that it is what you are looking for, but $.ajaxSetup({async: false}) is what I can think of. Try using it at the start of your code and this way the code will wait for the answer of the request
If you can call the ajax validation functions explicitly (not by triggering blur) then jQuery's
when() function may help you: http://api.jquery.com/jQuery.when/
From jQuery sample:
$.when( $.ajax( "/validation1.php" ), $.ajax( "/validation2.php" ) ).then(function( a1, a2 ) {
// a1 and a2 are arguments resolved for the validation1 and validation2 ajax requests, respectively.
// Each argument is an array with the following structure: [ data, statusText, jqXHR ]
gotoNextStep();
});
then() function is only executed if all ajax request have completed successfully.