Skip to content

Commit

Permalink
Add an option watchExternalChanges to react to external changes
Browse files Browse the repository at this point in the history
By default, an AutoNumeric element only format the value set with the `.set()` function.
If you want the element to watch and format value set by third party script using the `aNElement.node().value = 42` notation, then you need to set the `watchExternalChanges` option to `true`.

Signed-off-by: Alexandre Bonneau <alexandre.bonneau@linuxfr.eu>
  • Loading branch information
AlexandreBonneau committed Dec 30, 2017
1 parent e9acca7 commit 824323e
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 10 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,10 @@
## Changelog for autoNumeric

### 4.1.0-beta.19
+ Add an option `watchExternalChanges` to react to external changes
By default, an AutoNumeric element only format the value set with the `.set()` function.
If you want the element to watch and format value set by third party script using the `aNElement.node().value = 42` notation, then you need to set the `watchExternalChanges` option to `true`.

### 4.1.0-beta.18
+ Fix issue #524 Allow changing the `bubble` and `cancelable` attributes of events sent by AutoNumeric
+ Add two new options `eventBubbles` and `eventIsCancelable` that defaults to `true` to manage the event attributes.
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -363,6 +363,7 @@ You can check what are the predefined choices for each option as well as a more
| `unformatOnHover` | Defines if the element value should be unformatted when the user hover his mouse over it while holding the `Alt` key | `true` |
| `unformatOnSubmit` | Removes formatting on submit event | `false` |
| `valuesToStrings` | Provide a way for automatically and transparently replacing the formatted value with a pre-defined string, when the raw value is equal to a specific value.<br>For instance when using `{ 0: '-' }`, the hyphen `'-'` is displayed when the `rawValue` is equal to `0`. Multiple 'replacements' can be defined. | `null` |
| `watchExternalChanges` | Defines if the AutoNumeric element should watch (and format) external changes made without using `.set()`. This is set to `false` by default to prevent infinite loops when used with third party frameworks that relies on the `'autoNumeric:rawValueModified'` events being sent. | `false` |
| `wheelOn` | Used in conjonction with the `modifyValueOnWheel` option, defines when the wheel event will increment or decrement the element value, either when the element is focused, or hovered | `'focus'` |
| `wheelStep` | Used in conjonction with the `modifyValueOnWheel` option, this allow to either define a *fixed* step (ie. `1000`), or a *progressive* one that is calculated based on the size of the current value | `'progressive'` |

Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "autonumeric",
"version": "4.1.0-beta.18",
"version": "4.1.0-beta.19",
"description": "autoNumeric is a standalone Javascript library that provides live *as-you-type* formatting for international numbers and currencies. It supports most international numeric formats and currencies including those used in Europe, Asia, and North and South America.",
"main": "dist/autoNumeric.js",
"readmeFilename": "README.md",
Expand Down
19 changes: 15 additions & 4 deletions src/AutoNumeric.js
@@ -1,7 +1,7 @@
/**
* AutoNumeric.js
*
* @version 4.1.0-beta.18
* @version 4.1.0-beta.19
* @date 2017-10-30 UTC 23:45
*
* @authors Bob Knothe, Alexandre Bonneau
Expand Down Expand Up @@ -848,6 +848,11 @@ export default class AutoNumeric {

return this;
},
watchExternalChanges : watchExternalChanges => { //FIXME test this
this.update({ watchExternalChanges });

return this;
},
wheelOn : wheelOn => {
this.settings.wheelOn = wheelOn; //FIXME test this

Expand All @@ -867,7 +872,7 @@ export default class AutoNumeric {
* @returns {string}
*/
static version() {
return '4.1.0-beta.18';
return '4.1.0-beta.19';
}

/**
Expand Down Expand Up @@ -1413,6 +1418,7 @@ export default class AutoNumeric {
*/
_addWatcher() {
// `getterSetter` can be undefined when a non-input element is used
//TODO We need to either add an option here to prevent reacting to external changes, or find a way to allow third party scripts to change the data without AutoNumeric emitting the formatted event back. Indeed, this breaks some existing Vue.js components that rely and updating the values externally
if (!AutoNumericHelper.isUndefined(this.getterSetter)) {
const { set: setter, get: getter } = this.getterSetter;
Object.defineProperty(this.domElement, this.attributeToWatch, {
Expand All @@ -1421,7 +1427,7 @@ export default class AutoNumeric {
set : val => {
setter.call(this.domElement, val);
// Only `set()` the value if the modification comes from an external source
if (!this.internalModification) {
if (this.settings.watchExternalChanges && !this.internalModification) {
this.set(val);
}
},
Expand All @@ -1437,7 +1443,7 @@ export default class AutoNumeric {
set : val => {
this.valueWatched = val;
// Only `set()` the value if the modification comes from an external source
if (!this.internalModification) {
if (this.settings.watchExternalChanges && !this.internalModification) {
this.set(val);
}
},
Expand Down Expand Up @@ -3959,6 +3965,10 @@ export default class AutoNumeric {
AutoNumericHelper.throwError(`The increment/decrement on mouse wheel option 'modifyValueOnWheel' is invalid ; it should be either 'false' or 'true', [${options.modifyValueOnWheel}] given.`);
}

if (!AutoNumericHelper.isTrueOrFalseString(options.watchExternalChanges) && !AutoNumericHelper.isBoolean(options.watchExternalChanges)) {
AutoNumericHelper.throwError(`The option 'watchExternalChanges' is invalid ; it should be either 'false' or 'true', [${options.watchExternalChanges}] given.`);
}

if (!AutoNumericHelper.isInArray(options.wheelOn, [
AutoNumeric.options.wheelOn.focus,
AutoNumeric.options.wheelOn.hover,
Expand Down Expand Up @@ -7664,6 +7674,7 @@ To solve that, you'd need to either set \`decimalPlacesRawValue\` to \`null\`, o
unformatOnHover : true,
unformatOnSubmit : true,
valuesToStrings : true,
watchExternalChanges : true,
wheelOn : true,
wheelStep : true,

Expand Down
1 change: 1 addition & 0 deletions src/AutoNumericDefaultSettings.js
Expand Up @@ -92,6 +92,7 @@ Object.defineProperty(AutoNumeric, 'defaultSettings', {
unformatOnHover : AutoNumeric.options.unformatOnHover.unformat,
unformatOnSubmit : AutoNumeric.options.unformatOnSubmit.keepCurrentValue,
valuesToStrings : AutoNumeric.options.valuesToStrings.none,
watchExternalChanges : AutoNumeric.options.watchExternalChanges.doNotWatch,
wheelOn : AutoNumeric.options.wheelOn.focus,
wheelStep : AutoNumeric.options.wheelStep.progressive,
};
Expand Down
9 changes: 9 additions & 0 deletions src/AutoNumericOptions.js
Expand Up @@ -788,6 +788,15 @@ Object.defineProperty(AutoNumeric, 'options', {
},
},

/* Defines if the AutoNumeric element should watch external changes made without using `.set()`, but by using the basic `aNElement.node().value = 42` notation.
* If set to `watch`, then AutoNumeric will format the new value using `.set()` internally.
* Otherwise it will neither format it, nor save it in the history.
*/
watchExternalChanges: {
watch : true,
doNotWatch: false,
},

/* Defines when the wheel event will increment or decrement the element value.
* When set to `'focus'`, the AutoNumeric-managed element needs to be focused for the wheel event to change the value.
* When set to `'hover'`, using the wheel event while the mouse is hovering the element is sufficient (no focus needed).
Expand Down
98 changes: 93 additions & 5 deletions test/unit/autoNumeric.spec.js
Expand Up @@ -129,6 +129,7 @@ describe('The autoNumeric object', () => {
unformatOnHover : true,
unformatOnSubmit : false,
valuesToStrings : null,
watchExternalChanges : false,
wheelOn : 'focus',
wheelStep : 'progressive',
};
Expand Down Expand Up @@ -286,6 +287,7 @@ describe('The autoNumeric object', () => {
expect(defaultSettings.unformatOnHover ).toEqual(aNInputSettings.unformatOnHover );
expect(defaultSettings.unformatOnSubmit ).toEqual(aNInputSettings.unformatOnSubmit );
expect(defaultSettings.valuesToStrings ).toEqual(aNInputSettings.valuesToStrings );
expect(defaultSettings.watchExternalChanges ).toEqual(aNInputSettings.watchExternalChanges );
expect(defaultSettings.wheelOn ).toEqual(aNInputSettings.wheelOn );
expect(defaultSettings.wheelStep ).toEqual(aNInputSettings.wheelStep );
});
Expand Down Expand Up @@ -3078,6 +3080,7 @@ describe('autoNumeric options and `options.*` methods', () => {
serializeSpaces
showWarnings
unformatOnHover
watchExternalChanges
wheelOn
wheelStep
*/
Expand Down Expand Up @@ -3925,7 +3928,7 @@ xdescribe('Managing external changes', () => { //XXX This test is deactivated si
document.body.appendChild(inputElement);

// Test the external modification
let aNInput = new AutoNumeric(inputElement, autoNumericOptionsEuro);
let aNInput = new AutoNumeric(inputElement, [autoNumericOptionsEuro, { watchExternalChanges: AutoNumeric.options.watchExternalChanges.watch }]);
aNInput.set(6789.02);
expect(aNInput.getNumericString()).toEqual('6789.02');
expect(aNInput.getFormatted()).toEqual('6.789,02 €');
Expand All @@ -3944,7 +3947,7 @@ xdescribe('Managing external changes', () => { //XXX This test is deactivated si
expect(inputElement.value).toEqual('42');

// Add back the AutoNumeric object and check that there are no errors shown
aNInput = new AutoNumeric(inputElement, autoNumericOptionsEuro);
aNInput = new AutoNumeric(inputElement, [autoNumericOptionsEuro, { watchExternalChanges: AutoNumeric.options.watchExternalChanges.watch }]);
expect(aNInput.getNumericString()).toEqual('42');
expect(aNInput.getFormatted()).toEqual('42,00 €');
aNInput.set(116789.02);
Expand All @@ -3956,13 +3959,50 @@ xdescribe('Managing external changes', () => { //XXX This test is deactivated si
document.body.removeChild(inputElement);
});

xit(`should watch for external change to the input 'textContent' attribute`, () => {
it(`should not watch for external change to the input 'value' attribute`, () => {
// Initialization
const inputElement = document.createElement('input');
document.body.appendChild(inputElement);

// Test the external modification
let aNInput = new AutoNumeric(inputElement, [autoNumericOptionsEuro, { watchExternalChanges: AutoNumeric.options.watchExternalChanges.doNotWatch }]);
aNInput.set(6789.02);
expect(aNInput.getNumericString()).toEqual('6789.02');
expect(aNInput.getFormatted()).toEqual('6.789,02 €');

aNInput.node().value = '1234567.2345';
expect(aNInput.getNumericString()).toEqual('6789.02');
expect(aNInput.getFormatted()).toEqual('1234567.2345');

// Remove the AutoNumeric object
aNInput.remove();

// Test that the default getter and setters are ok
const testingGetter = inputElement.value;
expect(testingGetter).toEqual('1234567.2345');
inputElement.value = '42';
expect(inputElement.value).toEqual('42');

// Add back the AutoNumeric object and check that there are no errors shown
aNInput = new AutoNumeric(inputElement, [autoNumericOptionsEuro, { watchExternalChanges: AutoNumeric.options.watchExternalChanges.doNotWatch }]);
expect(aNInput.getNumericString()).toEqual('42');
expect(aNInput.getFormatted()).toEqual('42,00 €');
aNInput.set(116789.02);
expect(aNInput.getNumericString()).toEqual('116789.02');
expect(aNInput.getFormatted()).toEqual('116.789,02 €');

// Un-initialization
aNInput.remove();
document.body.removeChild(inputElement);
});

it(`should watch for external change to the input 'textContent' attribute`, () => {
// Initialization
const pElement = document.createElement('p');
document.body.appendChild(pElement);

// Test the external modification
let aNElement = new AutoNumeric(pElement, autoNumericOptionsEuro);
let aNElement = new AutoNumeric(pElement, [autoNumericOptionsEuro, { watchExternalChanges: AutoNumeric.options.watchExternalChanges.watch }]);
aNElement.set(6789.02);
expect(aNElement.getNumericString()).toEqual('6789.02');
expect(aNElement.getFormatted()).toEqual('6.789,02 €');
Expand All @@ -3981,7 +4021,44 @@ xdescribe('Managing external changes', () => { //XXX This test is deactivated si
expect(pElement.textContent).toEqual('42');

// Add back the AutoNumeric object and check that there are no errors shown
aNElement = new AutoNumeric(pElement, autoNumericOptionsEuro);
aNElement = new AutoNumeric(pElement, [autoNumericOptionsEuro, { watchExternalChanges: AutoNumeric.options.watchExternalChanges.watch }]);
expect(aNElement.getNumericString()).toEqual('42');
expect(aNElement.getFormatted()).toEqual('42,00 €');
aNElement.set(116789.02);
expect(aNElement.getNumericString()).toEqual('116789.02');
expect(aNElement.getFormatted()).toEqual('116.789,02 €');

// Un-initialization
aNElement.remove();
document.body.removeChild(pElement);
});

it(`should not watch for external change to the input 'textContent' attribute`, () => {
// Initialization
const pElement = document.createElement('p');
document.body.appendChild(pElement);

// Test the external modification
let aNElement = new AutoNumeric(pElement, [autoNumericOptionsEuro, { watchExternalChanges: AutoNumeric.options.watchExternalChanges.doNotWatch }]);
aNElement.set(6789.02);
expect(aNElement.getNumericString()).toEqual('6789.02');
expect(aNElement.getFormatted()).toEqual('6.789,02 €');

aNElement.node().textContent = '1234567.2345'; //FIXME Fails since `this.getterSetter` is undefined when trying to getOwnPropertyDescriptor the `textContent` attribute
expect(aNElement.getNumericString()).toEqual('6789.02');
expect(aNElement.getFormatted()).toEqual('1234567.2345');

// Remove the AutoNumeric object
aNElement.remove();

// Test that the default getter and setters are ok
const testingGetter = pElement.textContent;
expect(testingGetter).toEqual('1234567.2345');
pElement.textContent = '42';
expect(pElement.textContent).toEqual('42');

// Add back the AutoNumeric object and check that there are no errors shown
aNElement = new AutoNumeric(pElement, [autoNumericOptionsEuro, { watchExternalChanges: AutoNumeric.options.watchExternalChanges.doNotWatch }]);
expect(aNElement.getNumericString()).toEqual('42');
expect(aNElement.getFormatted()).toEqual('42,00 €');
aNElement.set(116789.02);
Expand Down Expand Up @@ -7336,6 +7413,11 @@ describe('Static autoNumeric functions', () => {
expect(() => AutoNumeric.validate({ valuesToStrings: { 0: '-' } })).not.toThrow();
expect(() => AutoNumeric.validate({ valuesToStrings: { 0: 'zero' } })).not.toThrow();
expect(() => AutoNumeric.validate({ valuesToStrings: { '-225.34': 'foobar' } })).not.toThrow();

expect(() => AutoNumeric.validate({ watchExternalChanges: true })).not.toThrow();
expect(() => AutoNumeric.validate({ watchExternalChanges: false })).not.toThrow();
expect(() => AutoNumeric.validate({ watchExternalChanges: 'true' })).not.toThrow();
expect(() => AutoNumeric.validate({ watchExternalChanges: 'false' })).not.toThrow();
});

it('should validate, with warnings', () => {
Expand Down Expand Up @@ -7758,6 +7840,12 @@ describe('Static autoNumeric functions', () => {
expect(() => AutoNumeric.validate({ valuesToStrings: [] })).toThrow();
expect(() => AutoNumeric.validate({ valuesToStrings: 42 })).toThrow();
expect(() => AutoNumeric.validate({ valuesToStrings: 'foobar' })).toThrow();

expect(() => AutoNumeric.validate({ watchExternalChanges: 0 })).toThrow();
expect(() => AutoNumeric.validate({ watchExternalChanges: 1 })).toThrow();
expect(() => AutoNumeric.validate({ watchExternalChanges: '0' })).toThrow();
expect(() => AutoNumeric.validate({ watchExternalChanges: '1' })).toThrow();
expect(() => AutoNumeric.validate({ watchExternalChanges: 'foobar' })).toThrow();
});

it('should not allow the `negativeSignCharacter` and `positiveSignCharacter` to be equal', () => {
Expand Down

0 comments on commit 824323e

Please sign in to comment.