IPv4 address masking in Aurelia

I’m trying to mask and IPv4 address input for users so it can be more friendly to type the IPv4 address. Tried to use aurelia-mask but I think it does not support IPv4 addresses mask. Does anyone knows a good library for doing that or how can it be achieved using only Aurelia features.

Using Aurelia CLI v.1.3.1

2 Likes

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

4 Likes
2 Likes

Thank you @avrahamcool for such a good explanation with examples. The only thing that I did not understand is the part with “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.” I’m new to Aurelia framework and definitely don’t have experience working with custom attributes and how they work. From your example, I got that you MaskCustomAttribute works only for strings as you are masking and regex there but how to adapt that for numbers, or range I don’t understand yet.

1 Like

as per the documentation - https://imask.js.org/guide.html#advanced
I didn’t import all of Imask functionality. but only what I needed…

1 Like

When I had to do this, I wasted way too much time with mask libraries and couldn’t get it exactly how I wanted it.

Perhaps it’s possible with these libraries, but I specifically wanted the functionality I’ve found when entering IP addresses in desktop apps where hitting tab, . or the third digit moves to the next group.

In the end I decided to just roll my own with 4 separate text inputs with their borders hidden and a virtual focus area around them so it appears like a standard single text input.

1 Like

Inputmask allows tabbing through mask parts

3 Likes

sounds like Inputmask is a very good solution.
I skipped it in my research because at first glance it looks like it depends on JQ.

2 Likes

Use this imaskjs

1 Like

I made a masked input for a date myself, because I did not know these libraries exist.
What I really miss for date input is that you cannot use the arrow keys to increase or decrease a day, month or year. I think that an IP address input could have the same function.

For example inputmask seems to focus each digit instead of a complete day, month or year. So this is not possible. imaskjs does not do that either.

In my input you can use the left and right arrow keys to switch between day, month and year and the up and down arrow keys to increase/decrease. So how the date input in Chrome works.

1 Like