Skip to content

Commit

Permalink
feat: focusout trigger + changeOrX behavior change
Browse files Browse the repository at this point in the history
BREAKING CHANGE: This commit changes the default behavior for the
changeOrBlur trigger.
- The change trigger is ineffective till the
  associated property is validated once, either by manual validation or
  by blur-triggered validation. This prevents showing validation failure
  immediately in case of an incomplete input. Note the distinction made
  between *incomplete* and *invalid* input.
- The blur trigger is ineffective until the property is dirty; i.e. any
  changes were made to the property. This prevents showing a failure
  message when there is a blur event w/o changing the property.
This closes #509.

Additionally, this commit also adds 2 new validateTriggers namely
focusout, and changeOrFocusout and respective binding behaviors.
As it is quite evident from the name the triggers are based on focusout
DOM event. The difference between blur and focusout is that the later
bubbles. It is useful for custom elements, containing input elements.
The changeOrFocusout is similar in nature of changeOrBlur, as described
above.
This closes #543.
  • Loading branch information
Sayan751 committed Mar 6, 2020
1 parent d45fe85 commit 62f0579
Show file tree
Hide file tree
Showing 20 changed files with 1,124 additions and 206 deletions.
92 changes: 80 additions & 12 deletions dist/amd/aurelia-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,16 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
* when it updates the model due to a change in the view.
*/
validateTrigger[validateTrigger["changeOrBlur"] = 3] = "changeOrBlur";
/**
* Validate the binding when the binding's target element fires a DOM "focusout" event.
* Unlike "blur", this event bubbles.
*/
validateTrigger[validateTrigger["focusout"] = 4] = "focusout";
/**
* Validate the binding when the binding's target element fires a DOM "focusout" event or
* when it updates the model due to a change in the view.
*/
validateTrigger[validateTrigger["changeOrFocusout"] = 6] = "changeOrFocusout";
})(exports.validateTrigger || (exports.validateTrigger = {}));

/**
Expand Down Expand Up @@ -1041,6 +1051,7 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
return ValidationController;
}());

// tslint:disable:no-bitwise
/**
* Binding behavior. Indicates the bound property should be validated.
*/
Expand All @@ -1067,23 +1078,44 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
controller.registerBinding(binding, target, rules);
binding.validationController = controller;
var trigger = this.getValidateTrigger(controller);
// tslint:disable-next-line:no-bitwise
if (trigger & exports.validateTrigger.change) {
var event = (trigger & exports.validateTrigger.blur) === exports.validateTrigger.blur ? 'blur'
: (trigger & exports.validateTrigger.focusout) === exports.validateTrigger.focusout ? 'focusout'
: null;
var hasChangeTrigger = (trigger & exports.validateTrigger.change) === exports.validateTrigger.change;
binding.isDirty = !hasChangeTrigger;
binding.validatedOnce = hasChangeTrigger && event === null;
if (hasChangeTrigger) {
binding.vbbUpdateSource = binding.updateSource;
// tslint:disable-next-line:only-arrow-functions
// tslint:disable-next-line:space-before-function-paren
binding.updateSource = function (value) {
this.vbbUpdateSource(value);
this.validationController.validateBinding(this);
this.isDirty = true;
if (this.validatedOnce) {
this.validationController.validateBinding(this);
}
};
}
// tslint:disable-next-line:no-bitwise
if (trigger & exports.validateTrigger.blur) {
binding.validateBlurHandler = function () {
_this.taskQueue.queueMicroTask(function () { return controller.validateBinding(binding); });
if (event !== null) {
binding.blurOrFocusoutValidationHandler = function () {
_this.taskQueue.queueMicroTask(function () {
if (binding.isDirty) {
controller.validateBinding(binding);
binding.validatedOnce = true;
}
});
};
binding.validationTriggerEvent = event;
binding.validateTarget = target;
target.addEventListener('blur', binding.validateBlurHandler);
target.addEventListener(event, binding.blurOrFocusoutValidationHandler);
if (hasChangeTrigger) {
var propertyName_1 = getPropertyInfo(binding.sourceExpression, binding.source).propertyName;
binding.validationSubscription = controller.subscribe(function (event) {
if (!binding.validatedOnce && event.type === 'validate') {
binding.validatedOnce = event.errors.findIndex(function (e) { return e.propertyName === propertyName_1; }) > -1;
}
});
}
}
if (trigger !== exports.validateTrigger.manual) {
binding.standardUpdateTarget = binding.updateTarget;
Expand All @@ -1105,13 +1137,19 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
binding.updateTarget = binding.standardUpdateTarget;
binding.standardUpdateTarget = null;
}
if (binding.validateBlurHandler) {
binding.validateTarget.removeEventListener('blur', binding.validateBlurHandler);
binding.validateBlurHandler = null;
if (binding.blurOrFocusoutValidationHandler) {
binding.validateTarget.removeEventListener(binding.validationTriggerEvent, binding.blurOrFocusoutValidationHandler);
binding.blurOrFocusoutValidationHandler = null;
binding.validateTarget = null;
}
if (binding.validationSubscription) {
binding.validationSubscription.dispose();
binding.validationSubscription = null;
}
binding.validationController.unregisterBinding(binding);
binding.validationController = null;
binding.isDirty = null;
binding.validatedOnce = null;
};
return ValidateBindingBehaviorBase;
}());
Expand Down Expand Up @@ -1209,6 +1247,34 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
aureliaBinding.bindingBehavior('validateOnChangeOrBlur')
], ValidateOnChangeOrBlurBindingBehavior);
return ValidateOnChangeOrBlurBindingBehavior;
}(ValidateBindingBehaviorBase));
var ValidateOnFocusoutBindingBehavior = /** @class */ (function (_super) {
__extends(ValidateOnFocusoutBindingBehavior, _super);
function ValidateOnFocusoutBindingBehavior() {
return _super !== null && _super.apply(this, arguments) || this;
}
ValidateOnFocusoutBindingBehavior.prototype.getValidateTrigger = function () {
return exports.validateTrigger.focusout;
};
ValidateOnFocusoutBindingBehavior.inject = [aureliaTaskQueue.TaskQueue];
ValidateOnFocusoutBindingBehavior = __decorate([
aureliaBinding.bindingBehavior('validateOnFocusout')
], ValidateOnFocusoutBindingBehavior);
return ValidateOnFocusoutBindingBehavior;
}(ValidateBindingBehaviorBase));
var ValidateOnChangeOrFocusoutBindingBehavior = /** @class */ (function (_super) {
__extends(ValidateOnChangeOrFocusoutBindingBehavior, _super);
function ValidateOnChangeOrFocusoutBindingBehavior() {
return _super !== null && _super.apply(this, arguments) || this;
}
ValidateOnChangeOrFocusoutBindingBehavior.prototype.getValidateTrigger = function () {
return exports.validateTrigger.changeOrFocusout;
};
ValidateOnChangeOrFocusoutBindingBehavior.inject = [aureliaTaskQueue.TaskQueue];
ValidateOnChangeOrFocusoutBindingBehavior = __decorate([
aureliaBinding.bindingBehavior('validateOnChangeOrFocusout')
], ValidateOnChangeOrFocusoutBindingBehavior);
return ValidateOnChangeOrFocusoutBindingBehavior;
}(ValidateBindingBehaviorBase));

/**
Expand Down Expand Up @@ -1865,7 +1931,7 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
config.apply(frameworkConfig.container);
// globalize the behaviors.
if (frameworkConfig.globalResources) {
frameworkConfig.globalResources(ValidateBindingBehavior, ValidateManuallyBindingBehavior, ValidateOnBlurBindingBehavior, ValidateOnChangeBindingBehavior, ValidateOnChangeOrBlurBindingBehavior, ValidationErrorsCustomAttribute, ValidationRendererCustomAttribute);
frameworkConfig.globalResources(ValidateBindingBehavior, ValidateManuallyBindingBehavior, ValidateOnBlurBindingBehavior, ValidateOnFocusoutBindingBehavior, ValidateOnChangeBindingBehavior, ValidateOnChangeOrBlurBindingBehavior, ValidateOnChangeOrFocusoutBindingBehavior, ValidationErrorsCustomAttribute, ValidationRendererCustomAttribute);
}
}

Expand All @@ -1880,6 +1946,8 @@ define('aurelia-validation', ['exports', 'aurelia-binding', 'aurelia-templating'
exports.ValidateOnBlurBindingBehavior = ValidateOnBlurBindingBehavior;
exports.ValidateOnChangeBindingBehavior = ValidateOnChangeBindingBehavior;
exports.ValidateOnChangeOrBlurBindingBehavior = ValidateOnChangeOrBlurBindingBehavior;
exports.ValidateOnFocusoutBindingBehavior = ValidateOnFocusoutBindingBehavior;
exports.ValidateOnChangeOrFocusoutBindingBehavior = ValidateOnChangeOrFocusoutBindingBehavior;
exports.ValidateEvent = ValidateEvent;
exports.ValidateResult = ValidateResult;
exports.ValidationController = ValidationController;
Expand Down
20 changes: 19 additions & 1 deletion dist/aurelia-validation.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,17 @@ export declare enum validateTrigger {
* Validate the binding when the binding's target element fires a DOM "blur" event and
* when it updates the model due to a change in the view.
*/
changeOrBlur = 3
changeOrBlur = 3,
/**
* Validate the binding when the binding's target element fires a DOM "focusout" event.
* Unlike "blur", this event bubbles.
*/
focusout = 4,
/**
* Validate the binding when the binding's target element fires a DOM "focusout" event or
* when it updates the model due to a change in the view.
*/
changeOrFocusout = 6
}
export declare type ValidatorCtor = new (...args: any[]) => Validator;
/**
Expand Down Expand Up @@ -426,6 +436,14 @@ export declare class ValidateOnChangeOrBlurBindingBehavior extends ValidateBindi
static inject: (typeof TaskQueue)[];
getValidateTrigger(): validateTrigger;
}
export declare class ValidateOnFocusoutBindingBehavior extends ValidateBindingBehaviorBase {
static inject: (typeof TaskQueue)[];
getValidateTrigger(): validateTrigger;
}
export declare class ValidateOnChangeOrFocusoutBindingBehavior extends ValidateBindingBehaviorBase {
static inject: (typeof TaskQueue)[];
getValidateTrigger(): validateTrigger;
}
/**
* Creates ValidationController instances.
*/
Expand Down
92 changes: 80 additions & 12 deletions dist/commonjs/aurelia-validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,16 @@ var StandardValidator = /** @class */ (function (_super) {
* when it updates the model due to a change in the view.
*/
validateTrigger[validateTrigger["changeOrBlur"] = 3] = "changeOrBlur";
/**
* Validate the binding when the binding's target element fires a DOM "focusout" event.
* Unlike "blur", this event bubbles.
*/
validateTrigger[validateTrigger["focusout"] = 4] = "focusout";
/**
* Validate the binding when the binding's target element fires a DOM "focusout" event or
* when it updates the model due to a change in the view.
*/
validateTrigger[validateTrigger["changeOrFocusout"] = 6] = "changeOrFocusout";
})(exports.validateTrigger || (exports.validateTrigger = {}));

/**
Expand Down Expand Up @@ -1050,6 +1060,7 @@ var ValidationController = /** @class */ (function () {
return ValidationController;
}());

// tslint:disable:no-bitwise
/**
* Binding behavior. Indicates the bound property should be validated.
*/
Expand All @@ -1076,23 +1087,44 @@ var ValidateBindingBehaviorBase = /** @class */ (function () {
controller.registerBinding(binding, target, rules);
binding.validationController = controller;
var trigger = this.getValidateTrigger(controller);
// tslint:disable-next-line:no-bitwise
if (trigger & exports.validateTrigger.change) {
var event = (trigger & exports.validateTrigger.blur) === exports.validateTrigger.blur ? 'blur'
: (trigger & exports.validateTrigger.focusout) === exports.validateTrigger.focusout ? 'focusout'
: null;
var hasChangeTrigger = (trigger & exports.validateTrigger.change) === exports.validateTrigger.change;
binding.isDirty = !hasChangeTrigger;
binding.validatedOnce = hasChangeTrigger && event === null;
if (hasChangeTrigger) {
binding.vbbUpdateSource = binding.updateSource;
// tslint:disable-next-line:only-arrow-functions
// tslint:disable-next-line:space-before-function-paren
binding.updateSource = function (value) {
this.vbbUpdateSource(value);
this.validationController.validateBinding(this);
this.isDirty = true;
if (this.validatedOnce) {
this.validationController.validateBinding(this);
}
};
}
// tslint:disable-next-line:no-bitwise
if (trigger & exports.validateTrigger.blur) {
binding.validateBlurHandler = function () {
_this.taskQueue.queueMicroTask(function () { return controller.validateBinding(binding); });
if (event !== null) {
binding.blurOrFocusoutValidationHandler = function () {
_this.taskQueue.queueMicroTask(function () {
if (binding.isDirty) {
controller.validateBinding(binding);
binding.validatedOnce = true;
}
});
};
binding.validationTriggerEvent = event;
binding.validateTarget = target;
target.addEventListener('blur', binding.validateBlurHandler);
target.addEventListener(event, binding.blurOrFocusoutValidationHandler);
if (hasChangeTrigger) {
var propertyName_1 = getPropertyInfo(binding.sourceExpression, binding.source).propertyName;
binding.validationSubscription = controller.subscribe(function (event) {
if (!binding.validatedOnce && event.type === 'validate') {
binding.validatedOnce = event.errors.findIndex(function (e) { return e.propertyName === propertyName_1; }) > -1;
}
});
}
}
if (trigger !== exports.validateTrigger.manual) {
binding.standardUpdateTarget = binding.updateTarget;
Expand All @@ -1114,13 +1146,19 @@ var ValidateBindingBehaviorBase = /** @class */ (function () {
binding.updateTarget = binding.standardUpdateTarget;
binding.standardUpdateTarget = null;
}
if (binding.validateBlurHandler) {
binding.validateTarget.removeEventListener('blur', binding.validateBlurHandler);
binding.validateBlurHandler = null;
if (binding.blurOrFocusoutValidationHandler) {
binding.validateTarget.removeEventListener(binding.validationTriggerEvent, binding.blurOrFocusoutValidationHandler);
binding.blurOrFocusoutValidationHandler = null;
binding.validateTarget = null;
}
if (binding.validationSubscription) {
binding.validationSubscription.dispose();
binding.validationSubscription = null;
}
binding.validationController.unregisterBinding(binding);
binding.validationController = null;
binding.isDirty = null;
binding.validatedOnce = null;
};
return ValidateBindingBehaviorBase;
}());
Expand Down Expand Up @@ -1218,6 +1256,34 @@ var ValidateOnChangeOrBlurBindingBehavior = /** @class */ (function (_super) {
aureliaBinding.bindingBehavior('validateOnChangeOrBlur')
], ValidateOnChangeOrBlurBindingBehavior);
return ValidateOnChangeOrBlurBindingBehavior;
}(ValidateBindingBehaviorBase));
var ValidateOnFocusoutBindingBehavior = /** @class */ (function (_super) {
__extends(ValidateOnFocusoutBindingBehavior, _super);
function ValidateOnFocusoutBindingBehavior() {
return _super !== null && _super.apply(this, arguments) || this;
}
ValidateOnFocusoutBindingBehavior.prototype.getValidateTrigger = function () {
return exports.validateTrigger.focusout;
};
ValidateOnFocusoutBindingBehavior.inject = [aureliaTaskQueue.TaskQueue];
ValidateOnFocusoutBindingBehavior = __decorate([
aureliaBinding.bindingBehavior('validateOnFocusout')
], ValidateOnFocusoutBindingBehavior);
return ValidateOnFocusoutBindingBehavior;
}(ValidateBindingBehaviorBase));
var ValidateOnChangeOrFocusoutBindingBehavior = /** @class */ (function (_super) {
__extends(ValidateOnChangeOrFocusoutBindingBehavior, _super);
function ValidateOnChangeOrFocusoutBindingBehavior() {
return _super !== null && _super.apply(this, arguments) || this;
}
ValidateOnChangeOrFocusoutBindingBehavior.prototype.getValidateTrigger = function () {
return exports.validateTrigger.changeOrFocusout;
};
ValidateOnChangeOrFocusoutBindingBehavior.inject = [aureliaTaskQueue.TaskQueue];
ValidateOnChangeOrFocusoutBindingBehavior = __decorate([
aureliaBinding.bindingBehavior('validateOnChangeOrFocusout')
], ValidateOnChangeOrFocusoutBindingBehavior);
return ValidateOnChangeOrFocusoutBindingBehavior;
}(ValidateBindingBehaviorBase));

/**
Expand Down Expand Up @@ -1874,7 +1940,7 @@ frameworkConfig, callback) {
config.apply(frameworkConfig.container);
// globalize the behaviors.
if (frameworkConfig.globalResources) {
frameworkConfig.globalResources(ValidateBindingBehavior, ValidateManuallyBindingBehavior, ValidateOnBlurBindingBehavior, ValidateOnChangeBindingBehavior, ValidateOnChangeOrBlurBindingBehavior, ValidationErrorsCustomAttribute, ValidationRendererCustomAttribute);
frameworkConfig.globalResources(ValidateBindingBehavior, ValidateManuallyBindingBehavior, ValidateOnBlurBindingBehavior, ValidateOnFocusoutBindingBehavior, ValidateOnChangeBindingBehavior, ValidateOnChangeOrBlurBindingBehavior, ValidateOnChangeOrFocusoutBindingBehavior, ValidationErrorsCustomAttribute, ValidationRendererCustomAttribute);
}
}

Expand All @@ -1889,6 +1955,8 @@ exports.ValidateManuallyBindingBehavior = ValidateManuallyBindingBehavior;
exports.ValidateOnBlurBindingBehavior = ValidateOnBlurBindingBehavior;
exports.ValidateOnChangeBindingBehavior = ValidateOnChangeBindingBehavior;
exports.ValidateOnChangeOrBlurBindingBehavior = ValidateOnChangeOrBlurBindingBehavior;
exports.ValidateOnFocusoutBindingBehavior = ValidateOnFocusoutBindingBehavior;
exports.ValidateOnChangeOrFocusoutBindingBehavior = ValidateOnChangeOrFocusoutBindingBehavior;
exports.ValidateEvent = ValidateEvent;
exports.ValidateResult = ValidateResult;
exports.ValidationController = ValidationController;
Expand Down
Loading

0 comments on commit 62f0579

Please sign in to comment.