I recently tackled the issue of mask, and didn’t like aurelia-mask
.
I had a lot of trouble with it, mainly because of the underlying library that aurelia-mask
uses. (ui-mask)
but I also didn’t like the syntax for creating a mask, and binding to the value.
<input masked="value.bind: myvalue; mask: (999) 999-9999; placeholder: *" />
so I moved to imask.js witch is a more stable library with more consistent options.
you can easily use IMask directly without wrapping anything. to have control over all it’s features.
I decided to wrap it in a CustomAttribute, to facilitate the use of the library for simple scenarios (I have a large project with ~200 forms - so I figured it will save a lot of time for my team)
I considered publishing it to npm as an official imask.js aurelia wrapper.
using my CustomAttribute will apply imask.js on to the given <input>
while preserving the regular value.bind
like you would expect.
so two-way databinding work as always, setting “wrong” values from the VM will behave exactly like if you try to insert them manually - and will be “cleaned”.
also: if you bind an input to some value with mask, and then bind another input to the same value - the masking mechanism will prevent you from entering wrong values into the second input as well - because they are all two-way bounded - as expected.
I didn’t see that kind of functionality in any other mask framework.
I also tried to simplify the 95% of cases as default cases, while still having the ability to pass all imask.js options.
USAGE:
app.html
<template>
<label>
<span>masked with simple string - will be used as 'Pattern': </span>
<input mask="000.000.000.000" value.bind="myIp" placeholder="IP" />
</label>
<label>
<span>masked with options: </span>
<input mask.bind="options" value.bind="myValue" />
<input value.bind="myValue" placeholder="no mask - but bounded to same myValue" />
</label>
</template>
app.ts
import IMask from "imask/esm/imask";
export class App
{
public myValue = "a123";
public options: IMask.AnyMaskedOptions = {
mask: /^[abc]?\d{0,3}$/,
};
}
mask.ts (customAttribute - made global in the project)
notice that I have imported only the base IMASK factory to reduce bundle size - if you need extra masks (like Number) - you will need to adjust your imports accordingly.
import { autoinject, bindable, bindingMode, BindingEngine, TaskQueue, Disposable } from "aurelia-framework";
import IMask from "imask/esm/imask";
@autoinject
export class MaskCustomAttribute {
@bindable({ defaultBindingMode: bindingMode.oneTime, primaryProperty: true }) private options: string | IMask.AnyMaskedOptions;
private element: HTMLInputElement;
private maskInstance: IMask.InputMask<IMask.AnyMaskedOptions>;
private valueSyncHandler: Disposable;
constructor(element: Element, private bindingEngine: BindingEngine, private taskQueue: TaskQueue) {
if (!(element instanceof HTMLInputElement)) {
throw new Error(`masked CustomAttribute can ony be used on <input> you used it on <${element.tagName}>`);
}
this.element = element;
}
private attached() {
const options = (typeof this.options === "string") ?
{
mask: this.options
} : this.options;
this.maskInstance = IMask(this.element, options);
this.valueSyncHandler = this.bindingEngine.propertyObserver(this.element, "value")
.subscribe((newValue: string) => {
this.taskQueue.queueTask(() => {
if (this.maskInstance.value !== newValue) {
this.maskInstance.value = newValue;
this.element.dispatchEvent(new Event('change'));
}
});
});
this.element.dispatchEvent(new Event('change'));
}
private detached() {
this.valueSyncHandler.dispose();
this.maskInstance.destroy();
}
}
as for you specific question regarding IP - https://github.com/uNmAnNeR/imaskjs/issues/401