Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Numeric directive for Vue (v-decimal and v-integer, with an "unsigned" modifier). Useful to improve existing components which you can't modify.
//+ Jonas Raoni Soares Silva
//@ http://raoni.org
export default class NumericDirective {
constructor(input, binding) {
Object.assign(this, {
input,
binding
});
input.addEventListener('keydown', this);
input.addEventListener('change', this);
}
static install(Vue) {
Vue.directive('decimal', this.directive);
Vue.directive('integer', this.directive);
}
static directive = {
bind(el, binding) {
el = el instanceof HTMLInputElement ? el : el.querySelector("input");
if (el) {
return new NumericDirective(el, binding);
}
}
}
handleEvent(event) {
this[event.type](event);
}
keydown(event) {
const { target, key, keyCode, ctrlKey, metaKey } = event;
if (!(
// Is numeric
(key >= '0' && key <= '9') ||
// Is special symbol allowed (. and -)
(
((key === '.' && this.binding.name === 'decimal') || (key === '-' && !this.binding.modifiers.unsigned)) &&
!~target.value.indexOf(key)
) ||
// Is system key
[
'Delete', 'Backspace', 'Tab', 'Esc', 'Escape', 'Enter',
'Home', 'End', 'PageUp', 'PageDown', 'Del', 'Delete',
'Left', 'ArrowLeft', 'Right', 'ArrowRight', 'Insert',
'Up', 'ArrowUp', 'Down', 'ArrowDown'
].includes(key) ||
// Is ctrl + a, c, x, v
((ctrlKey || metaKey) && [65, 67, 86, 88].includes(keyCode))
)) {
event.preventDefault();
}
}
change({ target }) {
const isDecimal = this.binding.name === 'decimal';
let value = target.value;
if (!value) {
return;
}
// Is it a negative number and is it allowed?
const isNegative = /^\s*-/.test(value) && !this.binding.modifiers.unsigned;
// Remove invalid digits (if it's a decimal, then allows "," and "." to stay)
value = value.replace(isDecimal ? /[^\d,.]/g : /\D/g, '');
if (isDecimal) {
// Naive adjustment for decimal values, breaks the number by ",." and considers the last group as the decimal part
const pieces = value.split(/[,.]/);
// Removes useless zeroes on the right
const decimal = pieces.pop().replace(/0+$/, '');
if (pieces.length) {
value = `${pieces.join('') || (decimal ? '0' : '')}${decimal ? `.${decimal}` : ''}`;
}
}
// Removes useless zeroes on the left
value = value.replace(/^(?:0(?!\b))+/, '');
if (value && isNegative) {
value = `-${value}`;
}
// Raise a fake event to signal others that we've updated the field
if (target.value !== value) {
target.value = value;
const event = document.createEvent('UIEvent');
event.initEvent('input', true, false, window, 0);
target.dispatchEvent(event);
}
}
}
@fgutr

This comment has been minimized.

Copy link

@fgutr fgutr commented Aug 28, 2020

Hi, how can i use ? do you have any example? thank you !

@jonasraoni

This comment has been minimized.

Copy link
Owner Author

@jonasraoni jonasraoni commented Aug 28, 2020

Hey @fgutr! I've left an example here, you just need to install the directive and add the "v-integer" or "v-decimal" to the element (there's an extra "unsigned" modifier, as you can see in the example)...

https://codesandbox.io/s/elated-flower-h4e5x

I've created this mainly to add "constraints" to components from another package that I had no control...

@fgutr

This comment has been minimized.

Copy link

@fgutr fgutr commented Aug 28, 2020

Thank you ! @jonasraoni I will check it.

@rogeriotaques

This comment has been minimized.

Copy link

@rogeriotaques rogeriotaques commented Apr 30, 2021

Hey, @jonasraoni!

Thanks for the good work! 🙏

May I suggest an improvement to make it work smoothly also on Macs?

Line 30, replace with:

const { target, key, keyCode, ctrlKey, metaKey } = event;

And replace line 44 with:

 ((ctrlKey || metaKey) && [65, 67, 86, 88].includes(keyCode))

That should be enough for enabling CTRL+A, C , X , V on Macs (which uses the CMD instead of CTRL for these actions). 🤘

@jonasraoni

This comment has been minimized.

Copy link
Owner Author

@jonasraoni jonasraoni commented May 4, 2021

Thanks @rogeriotaques! I'm unable to test on Apple devices, but it works on IE11 💩

  • I think that filtering special keys smells bad haha (the issue you found confirms that), so I've created an improved version, using another technique, I just left this one here to avoid throwing code in trash 🤗
  • Enforcing formatting is also something that I don't like... But I didn't try to solve this issue yet, as it wasn't needed for my use case.
@rogeriotaques

This comment has been minimized.

Copy link

@rogeriotaques rogeriotaques commented May 7, 2021

Thanks for confirming and accepting the suggestion, @jonasraoni! 🙇‍♂️ Perhaps you can add the link to your improved version here, as a comment, so people who find this can also check the enhanced version as well. 🚀

@christopherchristensen

This comment has been minimized.

Copy link

@christopherchristensen christopherchristensen commented Jun 19, 2021

Hi @jonasraoni, thanks for the good example! 🙏

By adding the following line at the beginning of the bind hook, you could enable the directive conditionally:

bind (el, binding) {
    if (binding.hasOwnProperty('value') && ! binding.value) {
        return;
    }
    el = el instanceof HTMLInputElement ? el : el.querySelector("input");
    if (el) {
        return new NumericDirective(el, binding);
    }
}

That way you could do both v-integer or v-integer="false".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment