I want to detect when text/value change in input field. Even if I change the value with js, I want to detect that changes.
Here's what I've tried so far in demo in fiddle.
HTML:
<input type="text" id="exNumber"/>
JavaScript:
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// console.log('Mutation type: ' + mutation.type);
if ( mutation.type == 'childList' ) {
if (mutation.addedNodes.length >= 1) {
if (mutation.addedNodes[0].nodeName != '#text') {
// console.log('Added ' + mutation.addedNodes[0].tagName + ' tag.');
}
}
else if (mutation.removedNodes.length >= 1) {
// console.log('Removed ' + mutation.removedNodes[0].tagName + ' tag.')
}
}
if (mutation.type == 'attributes') {
console.log('Modified ' + mutation.attributeName + ' attribute.')
}
});
});
var observerConfig = {
attributes: true,
childList: false,
characterData: false
};
// Listen to all changes to body and child nodes
var targetNode = document.getElementById("exNumber");
observer.observe(targetNode, observerConfig);
To understand what is going on is necessary to clear up the difference between attribute (content attribute) and property (IDL attribute). I won't expand on this as in SO there are already excellent answers covering the topic:
Properties and Attributes in HTML
.prop() vs .attr()
What is happening behind .setAttribute vs .attribute=?
When you change the content of a input element, by typing in or by JS:
targetNode.value="foo";
the browser updates the value property but not the value attribute (which reflects the defaultValue property instead).
Then, if we look at the spec of MutationObserver, we will see that attributes is one of the object members that can be used. So if you explicitly set the value attribute:
targetNode.setAttribute("value", "foo");
MutationObserver will notify an attribute modification. But there is nothing like properties in the list of the spec: the value property can not be observed.
If you want to detect when an user alters the content of your input element, the input event is the most straightforward way. If you need to catch JS modifications, go for setInterval and compare the new value with the old one.
Check this SO question to know about different alternatives and its limitations.
I've modified Shawn's method a little and wanted to share it. Can't believe there's actually a solution to this.
Type into the input box to see the default behavior. Now, open the DevTools and select the input element, then change its value, e.g. $0.value = "hello". Examine the UI vs. API difference. It seems UI interactions do not modify value property directly. If it were, it would also log "...changed via API...".
let inputBox = document.querySelector("#inputBox");
inputBox.addEventListener("input", function () {
console.log("Input value changed via UI. New value: '%s'", this.value);
});
observeElement(inputBox, "value", function (oldValue, newValue) {
console.log("Input value changed via API. Value changed from '%s' to '%s'", oldValue, newValue);
});
function observeElement(element, property, callback, delay = 0) {
let elementPrototype = Object.getPrototypeOf(element);
if (elementPrototype.hasOwnProperty(property)) {
let descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
Object.defineProperty(element, property, {
get: function() {
return descriptor.get.apply(this, arguments);
},
set: function () {
let oldValue = this[property];
descriptor.set.apply(this, arguments);
let newValue = this[property];
if (typeof callback == "function") {
setTimeout(callback.bind(this, oldValue, newValue), delay);
}
return newValue;
}
});
}
}
<input type="text" id="inputBox" placeholder="Enter something" />
the value property can be observed, Don't waste your time.
function changeValue (event, target) {
document.querySelector("#" + target).value = new Date().getTime();
}
function changeContentValue () {
document.querySelector("#content").value = new Date().getTime();
}
Object.defineProperty(document.querySelector("#content"), "value", {
set: function (t) {
alert('#changed content value');
var caller = arguments.callee
? (arguments.callee.caller ? arguments.callee.caller : arguments.callee)
: ''
console.log('this =>', this);
console.log('event => ', event || window.event);
console.log('caller => ', caller);
return this.textContent = t;
}
});
<form id="form" name="form" action="test.php" method="post">
<input id="writer" type="text" name="writer" value="" placeholder="writer" /> <br />
<textarea id="content" name="content" placeholder="content" ></textarea> <br />
<button type="button" >Submit (no action)</button>
</form>
<button type="button" onClick="changeValue(this, 'content')">Change Content</button>
This works and preserves and chains the original setter and getter so everything else about your field still works.
var registered = [];
var setDetectChangeHandler = function(field) {
if (!registered.includes(field)) {
var superProps = Object.getPrototypeOf(field);
var superSet = Object.getOwnPropertyDescriptor(superProps, "value").set;
var superGet = Object.getOwnPropertyDescriptor(superProps, "value").get;
var newProps = {
get: function() {
return superGet.apply(this, arguments);
},
set: function (t) {
var _this = this;
setTimeout( function() { _this.dispatchEvent(new Event("change")); }, 50);
return superSet.apply(this, arguments);
}
};
Object.defineProperty(field, "value", newProps);
registered.push(field);
}
}
Related
Environment:
Only JavaScript (no jquery :( )
Button click event handler is not available for change.
Is there a way to listen to textbox if its value is changed programmatically?
<input type="button" id="button1" value="button" />
<input type="text" id="text1" />
var btn1 = document.getElementById('button1');
var txt1 = document.getElementById('text1');
btn1.onclick=function(){ txt1.value = 'hello world'}
https://jsfiddle.net/yrt7e57w/
You can use element.dispatchEvent(event) when you change the text programmatically. In your example it would look like:
var btn1 = document.getElementById('button1');
var txt1 = document.getElementById('text1');
btn1.addEventListener("click", function() { // Add to OnClick of button.
txt1.dispatchEvent(new Event('change')); // force change event to run on textbox.
});
txt1.addEventListener("change", function(e){ // EventListener for OnChange of the element.
alert("changed");
});
Just the addEventListener on its own will not work as it isn't exactly being changed in the DOM.
Above is a simple solution which requires a few key presses but for a much more maintainable and reusable solution, check out Snowmonkey's answer (and probably most other answers to this question), https://stackoverflow.com/a/44708746/640263.
You can rely on the coder programmatically changing the element to know to trigger the onChange, but that's an iffy proposition. Looking through other posts, this looks very promising: for your text element, override the setters and getters, so that they automagically trigger for either keyed changes or programattic ones.
var btn1 = document.getElementById('button1');
var txt1 = document.getElementById('text1');
btn1.onclick = function() {
txt1.value = 'hello world'
}
txt1.addEventListener("change", function(e){
console.log(txt1.value);
})
//property mutation for hidden input
Object.defineProperty(txt1, "value", {
// Don't override the getter, but stub it in.
get: function() {
return this.getAttribute("value");
},
// In the setter, we want to set the value
// and also fire off the change event.
// By doing this, the coder changing the
// value never needs worry about it.
set: function(val) {
console.log("set");
// handle value change here
this.setAttribute("value", val);
//fire the event
if ("createEvent" in document) { //NON IE browsers
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
txt1.dispatchEvent(evt);
} else { //IE
var evt = document.createEventObject();
txt1.fireEvent("onchange", evt);
}
}
});
<input type="button" id="button1" value="button" />
<input type="text" id="text1" />
Or see it as a fiddle Here
So, to answer your question about why the click handler is showing the input as having a null value, it's because the getter/setter are overriding the default value behavior. The easiest way to work around this is to create a custom getter/setter to act as an interface to the value attribute:
var btn1 = document.getElementById('button1');
var txt1 = document.getElementById('text1');
btn1.onclick = function() {
console.log("in the button's click handler, ");
console.log("Value is: " + txt1.val);
console.log("--------------------------------")
txt1.val = 'hello world'
}
txt1.addEventListener("change", function(e) {
console.log("in txt1.onChange function...")
console.log(this.val);
console.log("--------------------------------")
})
//property mutation for hidden input
Object.defineProperty(txt1, "val", {
get: function() {
return this.value;
},
set: function(val) {
// handle value change here
this.value = val;
//fire the event
if ("createEvent" in document) { //NON IE browsers
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
txt1.dispatchEvent(evt);
} else { //IE
var evt = document.createEventObject();
txt1.fireEvent("onchange", evt);
}
}
});
<input type="button" id="button1" value="button" />
<input type="text" id="text1" />
What's happening here is, when the button is clicked, I get the val attribute of the input (which gets the value behind the scenes), then programmatically sets the val attribute of the same input (which, again, sets the value attribute). For some reason, you can't use get/set in object.defineProperty on an input's value attribute without completely breaking it. So in the console, you'll see THREE function calls: when you click the button, the input loses focus, triggering its change method, but then the button itself triggers its click handler, which then changes the value of the input and triggers the change handler one more time.
Hope this helps!
Again, to see this as a fiddle...
Maybe this is overkill,
but you could use the mutationObserver api to listen to an attribute change,
you would then have to change the value through setAttribute method:
var btn1 = document.getElementById('button1');
var txt1 = document.getElementById('text1');
btn1.onclick=function(){ txt1.setAttribute('value', 'hello world') }
// create an observer instance
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
console.log( mutation );
});
});
// configuration of the observer:
var config = { attributes:true };
// pass in the target node, as well as the observer options
observer.observe(txt1, config);
<input type="button" id="button1" value="button" />
<input type="text" id="text1" />
I have text field which is wired up with Keyup & change event, to trim the field length.
#Html.TextBoxFor(m => m.zipCode, new {data_bind = "textInput: zipcode, event: { keyup: trimField, change: trimField }", maxlength = "5"})
Trim function,
function trimField(data, event) {
var obj = event.target;
var maxlength = parseInt(obj.getAttribute('maxlength'), 10);
obj.value = obj.value.substring(0, maxlength);
obj.focus();
return true;
}
If I type "123456", on the UI it shows "12345", but the model has "123456".
How to get model updated after the keyup event?
You are not updating the observable variable which is bound to your element. It is better to make it generic as an observable extend so it can be used everywhere based on your max-length and to make sure it follows your rule for an initial value.
Example : https://jsfiddle.net/kyr6w2x3/55/
HTML:
<input data-bind='textInput: zipCode' />
<div>
zip code in Model:<span data-bind="text:zipCode"></span>
</div>
JS:
function AppViewModel(input) {
this.zipCode = ko.observable(input).extend({ maxLength:5});
this.phone = ko.observable(input).extend({ maxLength:11});
}
ko.extenders.maxLenght = function(target, characters) {
//you can use this to show an error message on view
// target.validationMessage = ko.observable();
//define a function to do validation for maxLength
function validate(newValue) {
var maxlength = parseInt(characters, 10);
if(newValue){
target(newValue.substring(0, maxlength) );
}
}
//initial validation
validate(target());
//validate whenever the value changes
target.subscribe(validate);
//return the original observable
return target;
};
ko.applyBindings(new AppViewModel("12345678910"));
change maxlength from 5 to 6:
#Html.TextBoxFor(m => m.zipCode, new {data_bind = "textInput: zipcode, event: { keyup: trimField, change: trimField }", maxlength = "6"})
Stop modifying the DOM. That's Knockout's job. You just modify the data item and Knockout will ensure that the UI is right.
function trimField(data, event) {
var obj = event.target;
var maxlength = parseInt(obj.getAttribute('maxlength'), 10);
data.zipcode(data.zipcode().substr(0, maxlength));
return true;
}
I'm trying to buld a custom directive that is actually a wrapper around input field (to simplify formatting, encapsulate animations, etc.).
One goal is to use ngModel so my directive would also be compatible with ng-maxlength, ng-required and similar directives depending on ng-Model.
I've created this plunkr with my current state:
http://embed.plnkr.co/xV8IRqTmQmKEBhRhCfBQ/
My problem is that ng-required seems to be working, but invalidates only the complete form (so that form.$invalid becomes true), but not the element itself form.element.$invalid remains false.
Also, ng-maxlength / ng-minlength does not seem to have any effect at all.
What am I missing here? Any hints welcome :)
Hi everyone and thanks a lot for your answers!
I finally figured out what the missing piece for me was: the name attribute which is used by the form to reference the element MUST NOT be on the inner input field.
It has to reside on the outer element that carries the mg-model that also gets the other directives (that interact with the ng-model).
So, to illustrate this in more detail, before my template looked like:
<span class="custom-input-element">
<label for="{{elementId}}-input">{{elementLabel}}<span class="required-marker" ng-if="elementRequired">*</span></label>
<input id="{{elementId}}-input" type="text" name="{{elementName}}" ng-trim ng-model="value" ng-init="focused = false" ng-focus="focused = true" ng-blur="focused = false"/>
</span>
Which was used like
<custom-input id="foldername" name="foldername" label="Folder Name:"
ng-model="folder.name" ng-maxlength="15" ng-required="true"> </custom-input>
Notice the name={{elementName}} that basically overlayed the name="foldername" on my directive's tag.
After removing it from the directives template, the form references my directive and the ngModel on my directive for validation - the input and the inner ng-model keeps hidden. Thus, the interaction with other directives like ng-maxlength and mg-minlength and also custom directives/validators works as expected.
So now, not only the form gets invalidated but also each element is validated in the expected way.
I updated my plunker where everything is working as desired now: http://embed.plnkr.co/i3SzV8H7tnkUk2K9Pq6m/
Thanks for your time and your very valuable input!
I have created one that works, i'll try to show you the relevant part of the code.
The one really annoying point was to reattach the input and the validation to the form of the parent controller.
For this i had to cc a bunch of private code from angular :
/**
* start cc from angular.js to modify $setValidity of ngModel to retrieve the parent form...
*/
var VALID_CLASS = 'data-ng-valid',
INVALID_CLASS = 'data-ng-invalid',
PRISTINE_CLASS = 'data-ng-pristine',
DIRTY_CLASS = 'data-ng-dirty',
UNTOUCHED_CLASS = 'data-ng-untouched',
TOUCHED_CLASS = 'data-ng-touched',
PENDING_CLASS = 'data-ng-pending';
function addSetValidityMethod(context) {
var ctrl = context.ctrl,
$element = context.$element,
classCache = {},
set = context.set,
unset = context.unset,
parentForm = context.parentForm,
$animate = context.$animate;
classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
ctrl.$setValidity = setValidity;
function setValidity(validationErrorKey, state, controller) {
if (state === undefined) {
createAndSet('$pending', validationErrorKey, controller);
} else {
unsetAndCleanup('$pending', validationErrorKey, controller);
}
if (!isBoolean(state)) {
unset(ctrl.$error, validationErrorKey, controller);
unset(ctrl.$$success, validationErrorKey, controller);
} else {
if (state) {
unset(ctrl.$error, validationErrorKey, controller);
set(ctrl.$$success, validationErrorKey, controller);
} else {
set(ctrl.$error, validationErrorKey, controller);
unset(ctrl.$$success, validationErrorKey, controller);
}
}
if (ctrl.$pending) {
cachedToggleClass(PENDING_CLASS, true);
ctrl.$valid = ctrl.$invalid = undefined;
toggleValidationCss('', null);
} else {
cachedToggleClass(PENDING_CLASS, false);
ctrl.$valid = isObjectEmpty(ctrl.$error);
ctrl.$invalid = !ctrl.$valid;
toggleValidationCss('', ctrl.$valid);
}
// re-read the state as the set/unset methods could have
// combined state in ctrl.$error[validationError] (used for forms),
// where setting/unsetting only increments/decrements the value,
// and does not replace it.
var combinedState;
if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
combinedState = undefined;
} else if (ctrl.$error[validationErrorKey]) {
combinedState = false;
} else if (ctrl.$$success[validationErrorKey]) {
combinedState = true;
} else {
combinedState = null;
}
toggleValidationCss(validationErrorKey, combinedState);
parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
}
function createAndSet(name, value, controller) {
if (!ctrl[name]) {
ctrl[name] = {};
}
set(ctrl[name], value, controller);
}
function unsetAndCleanup(name, value, controller) {
if (ctrl[name]) {
unset(ctrl[name], value, controller);
}
if (isObjectEmpty(ctrl[name])) {
ctrl[name] = undefined;
}
}
function cachedToggleClass(className, switchValue) {
if (switchValue && !classCache[className]) {
$animate.addClass($element, className);
classCache[className] = true;
} else if (!switchValue && classCache[className]) {
$animate.removeClass($element, className);
classCache[className] = false;
}
}
function toggleValidationCss(validationErrorKey, isValid) {
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
}
}
function arrayRemove(array, value) {
var index = array.indexOf(value);
if (index >= 0) {
array.splice(index, 1);
}
return index;
}
function isBoolean(value) {
return typeof value === 'boolean';
};
var SNAKE_CASE_REGEXP = /[A-Z]/g;
function snake_case(name, separator) {
separator = separator || '_';
return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
return (pos ? separator : '') + letter.toLowerCase();
});
}
function isObjectEmpty(obj) {
if (obj) {
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
return false;
}
}
}
return true;
};
/**
* end of cc
*/
Then in the link function :
function(scope, element, attrs, ctrl, transclude){
[...]
scope.form = element.parent().controller('form');
var transcludedContent = transclude(scope.$parent);
// find the input
var fieldContent = findFormField(transcludedContent);
var ngModelCtrl = angular.element(fieldContent).controller('ngModel');
if(!ngModelCtrl){
throw 'transcluded form field must have a ng-model';
}
addSetValidityMethod({
ctrl: ngModelCtrl,
$element: angular.element(fieldContent),
set: function(object, property, controller) {
var list = object[property];
if (!list) {
object[property] = [controller];
} else {
var index = list.indexOf(controller);
if (index === -1) {
list.push(controller);
}
}
},
unset: function(object, property, controller) {
var list = object[property];
if (!list) {
return;
}
arrayRemove(list, controller);
if (list.length === 0) {
delete object[property];
}
},
parentForm: scope.form,
$animate: $animate
});
scope.form.$addControl(ngModelCtrl);
element.html(template);
$compile(element.contents())(scope);
element.find('.ng-form-field-content').append(transcludedContent);
// remove the control from the form, otherwise an ng-if that hide an invalid input will block your form
scope.$on(
"$destroy",
function handleDestroyEvent() {
scope.form.$removeControl(ngModelCtrl);
});
The template is a variable containing the html of my wrapping around the input. (it generates the label, put a start if required, show a check or cross sign if field valid/invalid,...).
EDIT :
With my directive i can do :
<div my-directive>
<input/textarea/select ng-model="", required/ng-required, ng-pattern, <custom directive validation>...
</div>
And it will give something like
<div my-directive>
<label for=<input'sname>>Texte</label>
<input [the input will all his attrs]/>
[some extra content]
</div>
I can even put some intermediary nodes or have multiple input that point to the same ng-model (like with checkbox/Radio buttons), however it won't works with different ng-models. I didn't push it that far.
Currently I am unable to fire off a binding event that is dependent on the results of another binding event in knockout.
In the example below, provide a value in the 'available' input, when the 'condition1' input is filled with a value such as 22 the 'available' input should be cleared and disabled, all of this is done in the skip logic binding. This is happening properly.
However, the problem lies with the execution of the skiplogic binding on the chain1 input element. This isn't even being fired after the 'available' input is cleared of its value. How can I get it so the results of one binding fires off another binding?
Here is the js fiddle version of the code below: http://jsfiddle.net/gYNb8/2/
Here is the form I am using to test the concept out on:
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<span>Condition 1</span>
<input id="condition1" data-bind="value: condition1" />
<br/>
<span>Condition 2</span>
<input id="condition2" data-bind="value: condition2" />
<br/>
<span>Available?</span>
<input id="available" data-bind="value: available, skipLogic: condition1, skipLogic: condition2" />
<br/>
<span>Chain1</span>
<input id="chain1" data-bind="value: chain1, skiplogic: available" />
Here is the javascript:
// Overall viewmodel for this screen, along with initial state
function ReservationsViewModel() {
var self = this;
self.condition1 = ko.observable();
self.condition2 = ko.observable();
self.available = ko.observable();
self.chain1 = ko.observable();
}
//Here are the conditions which govern whether an element should be enabled or not
var elementConditions = {
'available': [{
'Condition': 'condition1() > 0',
'Type': 'evaluation'
}, {
'Condition': 'condition2() > 0',
'Type': 'evaluation'
}],
'chain1': [{
'Condition': 'available',
'Type': 'empty'
}]
};
ko.bindingHandlers.skipLogic = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var id = $(element).attr("id");
var conditions = elementConditions[id];
var isSkipped = false;
var conditionMet = false;
for (var i = 0; i < conditions.length; i++) {
conditionMet = false;
if (conditions[i].Type == "evaluation") {
conditionMet = eval('viewModel.' + conditions[i].Condition);
} else if (conditions[i].Type == "empty") {
if ($('#' + conditions[i].Condition).val().length == 0) {
conditionMet = true;
}
} else if (conditions[i].Type == "notempty") {
if ($('#' + conditions[i].Condition).val().length > 0) {
conditionMet = true;
}
}
if (conditionMet == true) {
isSkipped = true;
}
}
if (isSkipped) {
eval("viewModel." + id + "('');");
$(element).attr("disabled", "disabled");
} else {
if (elementSkipped[id] > 0) {
$(element).attr("disabled", "disabled");
} else {
$(element).removeAttr("disabled");
}
}
}
};
ko.applyBindings(new ReservationsViewModel());
Instead of trying to keep the conditions separately, can you use Boolean logic to string them together in the binding? this way you don't need to keep track of each bindings state. I have built the following binding:
ko.bindingHandlers.skipLogic = {
init: function(element, valueAccessor) {
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var valueOfCondition = ko.unwrap(valueAccessor());
var jqElement = $(element);
//update if the field is disabled if more than one condition is met
if(valueOfCondition){
jqElement.prop('disabled', true);
}
else{
jqElement.prop('disabled', false);
}
}
};
a working example is here: http://jsfiddle.net/M7vUV/3/
The update function for a binding will be executed when the element is first bound (after the init function) and then will run again whenever any of its dependencies change. You create dependencies by accessing observables within the function (like inside of a computed, because a computed is actually used to facilitate the binding updates).
So, you would want to ensure that you are accessing the retrieving whatever you passed to the binding by calling valueAccessor(), then if the value is observable you would want to call it as a function to retrieve the value. Otherwise, if you unsure whether you have been passed an observable you can call ko.unwrap (before 2.3 this was ko.utils.unwrapObservable - post 2.3 either can be used).
Additionally, you could access values passed to other bindings by using the allBindingsAccessor argument (3rd argument) or accessing values directly off of the data (4th arg) or context (5th arg).
It will not work to pass multiple bindings with the same name of the same element. You might want to consider structuring it differently like passing an array data-bind="skipLogic: [one, two]" and then accessing the values off of each.
I have aI need to validate the input by .onblur such that whenever a text input loses focus it gets validated by the same JS function.
My problem is with the JS function. I want to grab the value of the item that loses focus.
function valid(){
if (isNaN("this should be the value of the text item??")){
}
Thankss..
To grab the value of an item as you blur, you should add the onBlur trigger to the DOM element as follows:
<input type="text" name="validate_me" onBlur="valid(this);" />
That way you have access to the DOM element that triggered the onBlur event and can access its properties (such as value or innerHTML in the case of textarea elements.
Your valid function can then be something like:
function valid(element) {
if (element.value != '' && isNaN(element.value))
alert('This field is not valid!');
};
This javascript should do what you are asking for:
(function(){
var after = function(existing,after) {
if ( existing == null || typeof existing !== 'function' ) {
return after;
}
else {
return function() { existing(arguments); after(arguments); }
}
}
var validate = function(input) {
alert('validating ' + input.name);
}
window.onload = after(window.onload, function() {
var inputs = document.getElementsByTagName('input');
for (var i = 0; i < inputs.length; i++) {
if ( inputs[i].type === 'text' ) {
inputs[i].onblur = after(inputs[i].onblur, function() {
validate(this);
});
}
}
});
}());
Clearly you will have to replace the alert in the validate function with your validation logic, but this should do what you ask.
A couple notes, the immediately invoked function is to ensure you don't clobber any globals, and the after function is to ensure that if you there is already an attached listener that your new validate listener will be called after the existing one.