UNPKG

autonumeric

Version:

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 Am

1,048 lines (919 loc) 478 kB
/** * AutoNumeric.js * * @version 4.10.8 * @date 2025-03-03 UTC 08:10 * * @authors 2016-2025 Alexandre Bonneau <alexandre.bonneau@linuxfr.eu> * 2009-2016 Bob Knothe <bob.knothe@gmail.com> * @contributors Sokolov Yura and others, cf. AUTHORS * @copyright Alexandre Bonneau & Robert J. Knothe * @since 2009-08-09 * * @summary AutoNumeric is a standalone Javascript library * that provides live *as-you-type* formatting for * international numbers and currencies. * * @link http://autonumeric.org * @docs https://docs.autonumeric.org * * Note : Some functions are borrowed from big.js * @see https://github.com/MikeMcl/big.js/ * * Please report any bugs to https://github.com/autoNumeric/autoNumeric * * @license Released under the MIT License * @link http://www.opensource.org/licenses/mit-license.php * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sub license, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ //TODO Prevent having to enter relative path in the js files (i.e. using `./AutoNumericHelper` instead of just `AutoNumericHelper`) (cf. http://moduscreate.com/es6-es2015-import-no-relative-path-webpack/) import AutoNumericHelper from './AutoNumericHelper'; import AutoNumericEnum from './AutoNumericEnum'; import Evaluator from './maths/Evaluator'; import Parser from './maths/Parser'; /** * Class declaration for the AutoNumeric object. * * An AutoNumeric element is an object wrapper that keeps a reference to the DOM element it manages (usually an <input> one), and provides autoNumeric-specific variables and functions. */ export default class AutoNumeric { static options; // Those static declarations are only used by the IDE, to prevent error messages not finding those fields declarations static events; static defaultSettings; static predefinedOptions; /** * Return the autoNumeric version number (for debugging purpose) * * @returns {string} */ static version() { return '4.10.8'; } /** * Initialize the AutoNumeric object onto the given DOM element, and attach the settings and related event listeners to it. * The options passed as a parameter is an object that contains the settings (i.e. {digitGroupSeparator: ".", decimalCharacter: ",", currencySymbol: '€ '}) * * @example * anElement = new AutoNumeric(domElement); // With the default options * anElement = new AutoNumeric(domElement, { options }); // With one option object * anElement = new AutoNumeric(domElement, 'euroPos'); // With a named pre-defined string * anElement = new AutoNumeric(domElement, [{ options1 }, 'euroPos', { options2 }]); // With multiple option objects (the latest option overwriting the previous ones) * anElement = new AutoNumeric(domElement, null, { options }); // With one option object, and a failed initial value * anElement = new AutoNumeric(domElement).french(); // With one pre-defined language object * anElement = new AutoNumeric(domElement).french({ options });// With one pre-defined language object and additional options that will override the defaults * * // ...or init and set the value in one call : * anElement = new AutoNumeric(domElement, 12345.789); // With the default options, and an initial value * anElement = new AutoNumeric(domElement, 12345.789, { options }); * anElement = new AutoNumeric(domElement, '12345.789', { options }); * anElement = new AutoNumeric(domElement, 12345.789, 'euroPos'); * anElement = new AutoNumeric(domElement, 12345.789, [{ options1 }, 'euroPos', { options2 }]); * anElement = new AutoNumeric(domElement, 12345.789).french({ options }); * anElement = new AutoNumeric(domElement, 12345.789, { options }).french({ options }); // Not really helpful, but possible * * // The AutoNumeric constructor class can also accept a string as a css selector. Under the hood this use `QuerySelector` and limit itself to only the first element it finds. * anElement = new AutoNumeric('.myCssClass > input'); * anElement = new AutoNumeric('.myCssClass > input', { options }); * anElement = new AutoNumeric('.myCssClass > input', 'euroPos'); * anElement = new AutoNumeric('.myCssClass > input', [{ options1 }, 'euroPos', { options2 }]); * anElement = new AutoNumeric('.myCssClass > input', 12345.789); * anElement = new AutoNumeric('.myCssClass > input', 12345.789, { options }); * anElement = new AutoNumeric('.myCssClass > input', 12345.789, 'euroPos'); * anElement = new AutoNumeric('.myCssClass > input', 12345.789, [{ options1 }, 'euroPos', { options2 }]); * anElement = new AutoNumeric('.myCssClass > input', null, { options }); // With a failed initial value * anElement = new AutoNumeric('.myCssClass > input', 12345.789).french({ options }); * * @param {object|Array|number|string} arg1 * @param {object|Array|number|string|null} arg2 * @param {object|Array|number|string|null} arg3 * @throws */ constructor(arg1 = null, arg2 = null, arg3 = null) { // -------------------------------------------------------- // -------------- Initialization // Initialize the arguments const { domElement, initialValue, userOptions } = AutoNumeric._setArgumentsValues(arg1, arg2, arg3); // Initialize the element this.domElement = domElement; // Generate the settings this.defaultRawValue = ''; // The default raw value to set when initializing an AutoNumeric object this._setSettings(userOptions, false); //TODO If `styleRules` is not null, add by default a class 'autoNumeric' that adds transition to color, background-color, border-color properties // Check if the DOM element is supported this._checkElement(); // Store the additional attributes inside the AutoNumeric object // Note: This variable is needed and not a duplicate of `initialValueOnFirstKeydown` nor `valueOnFocus` since it serves a different purpose and has a different lifecycle this.savedCancellableValue = null; // Initialize the undo/redo variables this.historyTable = []; // Keep track of *all* valid states of the element value this.historyTableIndex = -1; // Pointer to the current undo/redo state. This will be set to '0' during initialization since it first adds itself. this.onGoingRedo = false; // Variable that keeps track if a 'redo' is ongoing (in order to prevent an 'undo' to be launch when releasing the shift key before the ctrl key after a 'redo' shortcut) // Initialize the parent form element, if any this.parentForm = this._getParentForm(); // Set the initial value if it exists and if the `formatOnPageLoad` option will allow it if (!this.runOnce && this.settings.formatOnPageLoad) { // Format the element value if needed this._formatDefaultValueOnPageLoad(initialValue); } else { // Otherwise set the `rawValue` and the element value, but do not format the latter yet let valueToSet; if (AutoNumericHelper.isNull(initialValue)) { switch (this.settings.emptyInputBehavior) { case AutoNumeric.options.emptyInputBehavior.min: valueToSet = this.settings.minimumValue; break; case AutoNumeric.options.emptyInputBehavior.max: valueToSet = this.settings.maximumValue; break; case AutoNumeric.options.emptyInputBehavior.zero: valueToSet = '0'; break; case AutoNumeric.options.emptyInputBehavior.focus: case AutoNumeric.options.emptyInputBehavior.press: case AutoNumeric.options.emptyInputBehavior.always: valueToSet = ''; break; // It's possible to set the `null` value as the initial value case AutoNumeric.options.emptyInputBehavior.null: valueToSet = null; break; // When `emptyInputBehavior` is a number or a string representing a number default : valueToSet = this.settings.emptyInputBehavior; } } else { valueToSet = initialValue; } this._setElementAndRawValue(valueToSet); } this.runOnce = true; // Add the events listeners only on input or editable elements this.hasEventListeners = false; if (this.isInputElement || this.isContentEditable) { if (!this.settings.noEventListeners) { //XXX Here we make sure the global list is created after creating the event listeners, to only create the event listeners on `document` once this._createEventListeners(); } this._setWritePermissions(true); } // Save the initial values (html attribute + element.value) for the pristine test this._saveInitialValues(initialValue); // Set up the data for the persistent storage solution (i.e. sessionStorage) this.storageNamePrefix = 'AUTO_'; // The prefix for the raw value storage name variable can be modified here this._setPersistentStorageName(); // -------------------------------------------------------- // -------------- Tracking this.validState = true; // Keep track if the element is in the valid state this.isFocused = false; // Keep track if the element is currently focused this.isWheelEvent = false; // Keep track if a mouse wheel event is currently ongoing this.isDropEvent = false; // Keep track if a drop event is currently ongoing this.isEditing = false; // Keep track if the user is currently editing the element this.rawValueOnFocus = void(0); // Keep track of the rawValue (needed to define if a change event must be sent on blur or enter key) // Watch any external changes to the element value/textContent/nodeValue and `set()` the new value so that it gets formatted/saved in the history this.internalModification = false; // This is temporarily set to `true` only when the AutoNumeric object does update the element value this.attributeToWatch = this._getAttributeToWatch(); this.getterSetter = Object.getOwnPropertyDescriptor(this.domElement.__proto__, this.attributeToWatch); this._addWatcher(); if (this.settings.createLocalList) { // Keep track of every AutoNumeric elements that this object initialized this._createLocalList(); } // Keep track of all AutoNumeric elements in the current web page this.constructor._addToGlobalList(this); // -------------------------------------------------------- // -------------- Methods // Create the global functions this.global = { /** * Set the same given element value for each element in the local AutoNumeric element list, and format those elements immediately * * @param {number|string} newValue The value must be a number or a numeric string * @param {object} options A settings object that will override the current settings. Note: the update is done only if the `newValue` is defined. */ set: (newValue, options = null) => { this.autoNumericLocalList.forEach(aNObject => { aNObject.set(newValue, options); }); }, /** * Set the value given value directly as the DOM element value, without formatting it beforehand. * This sets the same unformatted value for each element in the local AutoNumeric element list. * * @param {number|string} value * @param {object} options */ setUnformatted: (value, options = null) => { this.autoNumericLocalList.forEach(aNObject => { aNObject.setUnformatted(value, options); }); }, /** * This is an alias of the `getNumericString()` function, and should not be used anymore. * * @param {function|null} callback If a callback is passed, then the result is passed to it as its first argument, and the AutoNumeric object has its second * @returns {Array<string>} * @deprecated */ get: (callback = null) => { const result = []; this.autoNumericLocalList.forEach(aNObject => { result.push(aNObject.get()); }); this._executeCallback(result, callback); return result; }, /** * Return an array of the unformatted values (as a string) of each AutoNumeric element of the local AutoNumeric element list * * @param {function|null} callback If a callback is passed, then the result is passed to it as its first argument, and the AutoNumeric object has its second * @returns {Array<string>} */ getNumericString: (callback = null) => { const result = []; this.autoNumericLocalList.forEach(aNObject => { result.push(aNObject.getNumericString()); }); this._executeCallback(result, callback); return result; }, /** * Return an array of the current formatted values (as a string) of each AutoNumeric element of the local AutoNumeric element list * * @param {function|null} callback If a callback is passed, then the result is passed to it as its first argument, and the AutoNumeric object has its second * @returns {Array<string>} */ getFormatted: (callback = null) => { const result = []; this.autoNumericLocalList.forEach(aNObject => { result.push(aNObject.getFormatted()); }); this._executeCallback(result, callback); return result; }, /** * Return an array of the element unformatted values (as a real Javascript number), for each element of the local AutoNumeric element list * * @param {function|null} callback If a callback is passed, then the result is passed to it as its first argument, and the AutoNumeric object has its second * @returns {Array<number>} */ getNumber: (callback = null) => { const result = []; this.autoNumericLocalList.forEach(aNObject => { result.push(aNObject.getNumber()); }); this._executeCallback(result, callback); return result; }, /** * Returns the unformatted values (following the `outputFormat` setting) of each element of the local AutoNumeric element list into an array * * @param {function|null} callback If a callback is passed, then the result is passed to it as its first argument, and the AutoNumeric object has its second * @returns {Array<string>} */ getLocalized: (callback = null) => { const result = []; this.autoNumericLocalList.forEach(aNObject => { result.push(aNObject.getLocalized()); }); this._executeCallback(result, callback); return result; }, /** * Force each element of the local AutoNumeric element list to reformat its value */ reformat: () => { this.autoNumericLocalList.forEach(aNObject => { aNObject.reformat(); }); }, /** * Remove the formatting and keep only the raw unformatted value (as a numericString) in each element of the local AutoNumeric element list */ unformat: () => { this.autoNumericLocalList.forEach(aNObject => { aNObject.unformat(); }); }, /** * Remove the formatting and keep only the localized unformatted value in the element, with the option to override the default outputFormat if needed * * @param {null|string} forcedOutputFormat If set to something different from `null`, then this is used as an overriding outputFormat option */ unformatLocalized: (forcedOutputFormat = null) => { this.autoNumericLocalList.forEach(aNObject => { aNObject.unformatLocalized(forcedOutputFormat); }); }, /** * Updates the AutoNumeric settings, and immediately format the elements accordingly, for each element of the local AutoNumeric element list * * @param {object} newOptions This can be either one or more option objects */ update: (...newOptions) => { this.autoNumericLocalList.forEach(aNObject => { aNObject.update(...newOptions); }); }, /** * Return `true` if *all* the autoNumeric-managed elements are pristine, if their raw value hasn't changed. * By default, this returns `true` if the raw unformatted value is still the same even if the formatted one has changed (due to a configuration update for instance). * * @param {boolean} checkOnlyRawValue If set to `true`, the pristine value is done on the raw unformatted value, not the formatted one. If set to `false`, this also checks that the formatted value hasn't changed. * @returns {boolean} */ isPristine: (checkOnlyRawValue = true) => { let isPristine = true; this.autoNumericLocalList.forEach(aNObject => { if (isPristine && !aNObject.isPristine(checkOnlyRawValue)) { isPristine = false; } }); return isPristine; }, /** * Execute the `clear()` method on each AutoNumeric object in the local AutoNumeric element list * * @param {boolean} forceClearAll */ clear: (forceClearAll = false) => { this.autoNumericLocalList.forEach(aNObject => { aNObject.clear(forceClearAll); }); }, /** * Execute the `remove()` method on each AutoNumeric object in the local AutoNumeric element list */ remove: () => { this.autoNumericLocalList.forEach(aNObject => { aNObject.remove(); }); }, /** * Execute the `wipe()` method on each AutoNumeric object in the local AutoNumeric element list */ wipe: () => { this.autoNumericLocalList.forEach(aNObject => { aNObject.wipe(); }); }, /** * Execute the `nuke()` method on each AutoNumeric object in the local AutoNumeric element list */ nuke: () => { this.autoNumericLocalList.forEach(aNObject => { aNObject.nuke(); }); }, /** * Return `true` if the given AutoNumeric object (or DOM element) is in the local AutoNumeric element list * * @param {HTMLElement|HTMLInputElement|AutoNumeric} domElementOrAutoNumericObject * @returns {*} */ has: domElementOrAutoNumericObject => { let result; if (domElementOrAutoNumericObject instanceof AutoNumeric) { result = this.autoNumericLocalList.has(domElementOrAutoNumericObject.node()); } else { result = this.autoNumericLocalList.has(domElementOrAutoNumericObject); } return result; }, /** * Add an existing AutoNumeric object (or DOM element) to the local AutoNumeric element list, using the DOM element as the key. * This manages the case where `addObject` is used on an AutoNumeric object that already has multiple elements in its local list. * * @param {HTMLElement|HTMLInputElement|AutoNumeric} domElementOrAutoNumericObject */ addObject: domElementOrAutoNumericObject => { // Start with the same data, whatever the user passed as arguments let domElement; let otherAutoNumericObject; if (domElementOrAutoNumericObject instanceof AutoNumeric) { domElement = domElementOrAutoNumericObject.node(); otherAutoNumericObject = domElementOrAutoNumericObject; } else { domElement = domElementOrAutoNumericObject; otherAutoNumericObject = AutoNumeric.getAutoNumericElement(domElement); } // Check if the current autoNumeric object has a local list if (!this._hasLocalList()) { this._createLocalList(); } // Check if the other autoNumeric object has a local list... let otherANLocalList = otherAutoNumericObject._getLocalList(); if (otherANLocalList.size === 0) { // Special case if the other AutoNumeric object has an empty local list, then populate itself to it otherAutoNumericObject._createLocalList(); otherANLocalList = otherAutoNumericObject._getLocalList(); // Update the other local list } let mergedLocalLists; if (otherANLocalList instanceof Map) { // ...If it does, merge the local lists together mergedLocalLists = AutoNumericHelper.mergeMaps(this._getLocalList(), otherANLocalList); } else { // ...If not, just set the current local list onto the other AutoNumeric object // We need to specify the AutoNumeric object, otherwise the `_addToLocalList` function would not correctly add the AutoNumeric object since we would not have a reference to it, but a reference to the current AutoNumeric object on which is called this method. this._addToLocalList(domElement, otherAutoNumericObject); mergedLocalLists = this._getLocalList(); } // Update the resulting list, on all the objects of that local list (so that we can indifferently use `init()` on any object belonging to that list) mergedLocalLists.forEach(aNObject => { aNObject._setLocalList(mergedLocalLists); }); }, /** * Remove the given AutoNumeric object (or DOM element) from the local AutoNumeric element list, using the DOM element as the key. * If this function attempts to remove the current AutoNumeric object from the local list, a warning is shown, but the deletion is still done. * * Special cases : * - If the current object removes itself, then it's removed from the shared local list, then a new empty local list is used/created * - If another object remove this object, then a local list with only this object is used/created * * @param {HTMLElement|HTMLInputElement|AutoNumeric} domElementOrAutoNumericObject * @param {boolean} keepCurrentANObject If set to `false`, then the function will also remove the current AutoNumeric object if asked, otherwise it will ignore it and print a warning message */ removeObject: (domElementOrAutoNumericObject, keepCurrentANObject = false) => { // Start with the same data, whatever the user passed as arguments let domElement; let otherAutoNumericObject; if (domElementOrAutoNumericObject instanceof AutoNumeric) { domElement = domElementOrAutoNumericObject.node(); otherAutoNumericObject = domElementOrAutoNumericObject; } else { domElement = domElementOrAutoNumericObject; otherAutoNumericObject = AutoNumeric.getAutoNumericElement(domElement); } // Remove the other object from the local list const initialCompleteLocalList = this.autoNumericLocalList; this.autoNumericLocalList.delete(domElement); // Update the local list for all objects in it initialCompleteLocalList.forEach(aNObject => { aNObject._setLocalList(this.autoNumericLocalList); }); if (!keepCurrentANObject && domElement === this.node()) { // This object is removed by itself // Empty the object local list otherAutoNumericObject._setLocalList(new Map); } else { // This object is removed by another object // Set the local list for the removed object, with only this object in it otherAutoNumericObject._createLocalList(); } }, /** * Remove all elements from the shared list, effectively emptying it. * This is the equivalent of calling `detach()` on each of its elements. * * @param {boolean} keepEachANObjectInItsOwnList If set to `true`, then instead of completely emptying the local list of each AutoNumeric objects, each one of those keeps itself in its own local list */ empty: (keepEachANObjectInItsOwnList = false) => { const initialCompleteLocalList = this.autoNumericLocalList; // Update the local list for all objects in it initialCompleteLocalList.forEach(aNObject => { if (keepEachANObjectInItsOwnList) { aNObject._createLocalList(); } else { aNObject._setLocalList(new Map); } }); }, /** * Return an array containing all the AutoNumeric DOM elements that have been initialized by each other * * @returns {Array<HTMLElement>} */ elements: () => { const result = []; this.autoNumericLocalList.forEach(aNObject => { result.push(aNObject.node()); }); return result; }, /** * Return the `Map` object directly * @returns {Map} */ getList: () => this.autoNumericLocalList, /** * Return the number of element in the local AutoNumeric element list * @returns {number} */ size: () => this.autoNumericLocalList.size, }; // Create the functions that will allow to change each setting one by one /** * For each option, we define if we need to reformat the element content (does changing the options should change the way its value is displayed?). * If yes, then we use the `update()` for force a reformat, otherwise, we just update the `settings` object. */ this.options = { /** * Reset any options set previously, by overwriting them with the default settings * * @returns {AutoNumeric} */ reset : () => { //TODO Add a `settings` parameter so that the user can reset to a specific set of settings. This is different than update since it drops any non-default settings before using those new settings. this.settings = { rawValue : this.defaultRawValue }; // Here we pass the default rawValue in order to prevent showing a warning that we try to set an `undefined` value this.update(AutoNumeric.defaultSettings); return this; }, allowDecimalPadding : allowDecimalPadding => { this.update({ allowDecimalPadding }); return this; }, alwaysAllowDecimalCharacter : alwaysAllowDecimalCharacter => { //FIXME Test this this.update({ alwaysAllowDecimalCharacter }); return this; }, caretPositionOnFocus : caretPositionOnFocus => { //FIXME test this this.settings.caretPositionOnFocus = caretPositionOnFocus; return this; }, createLocalList : createLocalList => { this.settings.createLocalList = createLocalList; // Delete the local list when this is set to `false`, create it if this is set to `true` and there is not pre-existing list if (this.settings.createLocalList) { if (!this._hasLocalList()) { this._createLocalList(); } } else { this._deleteLocalList(); } return this; }, currencySymbol : currencySymbol => { this.update({ currencySymbol }); return this; }, currencySymbolPlacement : currencySymbolPlacement => { this.update({ currencySymbolPlacement }); return this; }, decimalCharacter : decimalCharacter => { this.update({ decimalCharacter }); return this; }, decimalCharacterAlternative : decimalCharacterAlternative => { this.settings.decimalCharacterAlternative = decimalCharacterAlternative; return this; }, /** * Update the decimal places globally, which means this override any previously set number of decimal shown on focus, on blur, or in the raw value. * * @param {int} decimalPlaces * @returns {AutoNumeric} */ decimalPlaces : decimalPlaces => { AutoNumericHelper.warning('Using `options.decimalPlaces()` instead of calling the specific `options.decimalPlacesRawValue()`, `options.decimalPlacesShownOnFocus()` and `options.decimalPlacesShownOnBlur()` methods will reset those options.\nPlease call the specific methods if you do not want to reset those.', this.settings.showWarnings); this.update({ decimalPlaces }); return this; }, decimalPlacesRawValue : decimalPlacesRawValue => { //FIXME test this this.update({ decimalPlacesRawValue }); return this; }, decimalPlacesShownOnBlur : decimalPlacesShownOnBlur => { this.update({ decimalPlacesShownOnBlur }); return this; }, decimalPlacesShownOnFocus : decimalPlacesShownOnFocus => { this.update({ decimalPlacesShownOnFocus }); return this; }, defaultValueOverride : defaultValueOverride => { this.update({ defaultValueOverride }); return this; }, digitalGroupSpacing : digitalGroupSpacing => { this.update({ digitalGroupSpacing }); return this; }, digitGroupSeparator : digitGroupSeparator => { this.update({ digitGroupSeparator }); return this; }, divisorWhenUnfocused : divisorWhenUnfocused => { this.update({ divisorWhenUnfocused }); return this; }, emptyInputBehavior : emptyInputBehavior => { if (this.rawValue === null && emptyInputBehavior !== AutoNumeric.options.emptyInputBehavior.null) { // Special case : if the current `rawValue` is `null` and the `emptyInputBehavior` is changed to something else than `'null'`, then it makes that `rawValue` invalid. // Here we can either prevent the option update and throw an error, or still accept the option update and update the value from `null` to `''`. // We cannot keep `rawValue` to `null` since if `emptyInputBehavior` is not set to `null`, lots of function assume `rawValue` is a string. AutoNumericHelper.warning(`You are trying to modify the \`emptyInputBehavior\` option to something different than \`'null'\` (${emptyInputBehavior}), but the element raw value is currently set to \`null\`. This would result in an invalid \`rawValue\`. In order to fix that, the element value has been changed to the empty string \`''\`.`, this.settings.showWarnings); this.rawValue = ''; } this.update({ emptyInputBehavior }); return this; }, eventBubbles : eventBubbles => { this.settings.eventBubbles = eventBubbles; return this; }, eventIsCancelable : eventIsCancelable => { this.settings.eventIsCancelable = eventIsCancelable; return this; }, failOnUnknownOption : failOnUnknownOption => { this.settings.failOnUnknownOption = failOnUnknownOption; //TODO test this with unit tests return this; }, formatOnPageLoad : formatOnPageLoad => { this.settings.formatOnPageLoad = formatOnPageLoad; //TODO test this with unit tests return this; }, formulaMode : formulaMode => { this.settings.formulaMode = formulaMode; //TODO test this with unit tests return this; }, historySize : historySize => { this.settings.historySize = historySize; return this; }, invalidClass : invalidClass => { this.settings.invalidClass = invalidClass; //TODO test this with unit tests return this; }, isCancellable : isCancellable => { this.settings.isCancellable = isCancellable; //TODO test this with unit tests return this; }, leadingZero : leadingZero => { this.update({ leadingZero }); return this; }, maximumValue : maximumValue => { this.update({ maximumValue }); return this; }, minimumValue : minimumValue => { this.update({ minimumValue }); return this; }, modifyValueOnUpDownArrow : modifyValueOnUpDownArrow => { this.settings.modifyValueOnUpDownArrow = modifyValueOnUpDownArrow; //TODO test this with unit tests return this; }, modifyValueOnWheel : modifyValueOnWheel => { this.settings.modifyValueOnWheel = modifyValueOnWheel; //TODO test this with unit tests return this; }, negativeBracketsTypeOnBlur : negativeBracketsTypeOnBlur => { this.update({ negativeBracketsTypeOnBlur }); return this; }, negativePositiveSignPlacement: negativePositiveSignPlacement => { this.update({ negativePositiveSignPlacement }); return this; }, negativeSignCharacter : negativeSignCharacter => { this.update({ negativeSignCharacter }); return this; }, negativePositiveSignBehavior : negativePositiveSignBehavior => { this.settings.negativePositiveSignBehavior = negativePositiveSignBehavior; //TODO test this with unit tests return this; }, noEventListeners : noEventListeners => { //TODO test this with unit tests if (noEventListeners === AutoNumeric.options.noEventListeners.noEvents && this.settings.noEventListeners === AutoNumeric.options.noEventListeners.addEvents) { // Remove the events once this._removeEventListeners(); } this.update({ noEventListeners }); return this; }, onInvalidPaste : onInvalidPaste => { this.settings.onInvalidPaste = onInvalidPaste; //TODO test this with unit tests return this; }, outputFormat : outputFormat => { this.settings.outputFormat = outputFormat; return this; }, overrideMinMaxLimits : overrideMinMaxLimits => { this.update({ overrideMinMaxLimits }); return this; }, positiveSignCharacter : positiveSignCharacter => { this.update({ positiveSignCharacter }); return this; }, rawValueDivisor : rawValueDivisor => { this.update({ rawValueDivisor }); return this; }, readOnly : readOnly => { // When changing the readOnly attribute, the raw and formatted values do not change, so no need to call the costly 'update()` method this.settings.readOnly = readOnly; this._setWritePermissions(); return this; }, roundingMethod : roundingMethod => { this.update({ roundingMethod }); return this; }, saveValueToSessionStorage : saveValueToSessionStorage => { this.update({ saveValueToSessionStorage }); return this; }, symbolWhenUnfocused : symbolWhenUnfocused => { this.update({ symbolWhenUnfocused }); return this; }, selectNumberOnly : selectNumberOnly => { this.settings.selectNumberOnly = selectNumberOnly; //TODO test this with unit tests return this; }, selectOnFocus : selectOnFocus => { this.settings.selectOnFocus = selectOnFocus; //TODO test this with unit tests return this; }, serializeSpaces : serializeSpaces => { this.settings.serializeSpaces = serializeSpaces; //TODO test this with unit tests return this; }, showOnlyNumbersOnFocus : showOnlyNumbersOnFocus => { this.update({ showOnlyNumbersOnFocus }); return this; }, showPositiveSign : showPositiveSign => { this.update({ showPositiveSign }); return this; }, showWarnings : showWarnings => { this.settings.showWarnings = showWarnings; //TODO test this with unit tests return this; }, styleRules : styleRules => { this.update({ styleRules }); return this; }, suffixText : suffixText => { this.update({ suffixText }); return this; }, unformatOnHover : unformatOnHover => { this.settings.unformatOnHover = unformatOnHover; //TODO test this with unit tests return this; }, unformatOnSubmit : unformatOnSubmit => { this.settings.unformatOnSubmit = unformatOnSubmit; //TODO test this with unit tests return this; }, upDownStep : upDownStep => { this.settings.upDownStep = upDownStep; //TODO test this with unit tests return this; }, valuesToStrings : valuesToStrings => { this.update({ valuesToStrings }); return this; }, watchExternalChanges : watchExternalChanges => { //TODO test this with unit tests this.update({ watchExternalChanges }); return this; }, wheelOn : wheelOn => { this.settings.wheelOn = wheelOn; //TODO test this with unit tests return this; }, wheelStep : wheelStep => { this.settings.wheelStep = wheelStep; //TODO test this with unit tests return this; }, }; // Once the autoNumeric element has been initialized, broadcast that message with additional info. // Note: When using `AutoNumeric.multiple()`, one event is sent *per* element initialized this._triggerEvent(AutoNumeric.events.initialized, this.domElement, { newValue : AutoNumericHelper.getElementValue(this.domElement), newRawValue: this.rawValue, error : null, aNElement : this, }); } /** * Take the parameters given to the AutoNumeric object, and output the three variables that are needed to finish initializing it : * - domElement : The target DOM element * - initialValue : The initial value, or `null` if none is given * - userOptions : The option object * * @param {object|Array|number|string} arg1 * @param {object|Array|number|string|null} arg2 * @param {object|Array|number|string|null} arg3 * @returns {{domElement: *, initialValue: *, userOptions: *}} * @throws * @private */ static _setArgumentsValues(arg1, arg2, arg3) { // Basic check on the argument count if (AutoNumericHelper.isNull(arg1)) { AutoNumericHelper.throwError('At least one valid parameter is needed in order to initialize an AutoNumeric object'); } // Prepare the arguments in order to create the AutoNumeric object with the right values // Test the argument types const isArg1Element = AutoNumericHelper.isElement(arg1); const isArg1String = AutoNumericHelper.isString(arg1); const isArg2Object = AutoNumericHelper.isObject(arg2); const isArg2Array = Array.isArray(arg2) && arg2.length > 0; const isArg2Number = AutoNumericHelper.isNumberOrArabic(arg2) || arg2 === ''; const isArg2PreDefinedOptionName = this._isPreDefinedOptionValid(arg2); const isArg2Null = AutoNumericHelper.isNull(arg2); const isArg2EmptyString = AutoNumericHelper.isEmptyString(arg2); const isArg3Object = AutoNumericHelper.isObject(arg3); const isArg3Array = Array.isArray(arg3) && arg3.length > 0; const isArg3Null = AutoNumericHelper.isNull(arg3); const isArg3PreDefinedOptionName = this._isPreDefinedOptionValid(arg3); // Given the parameters passed, sort the data and return a stable state before the initialization let domElement; let userOptions; let initialValue; //TODO Simplify those tests --> if (isArg1Element && isArg2Null && isArg3Null) { // new AutoNumeric(domElement); // With the default options domElement = arg1; initialValue = null; userOptions = null; } else if (isArg1Element && isArg2Number && isArg3Null) { // new AutoNumeric(domElement, 12345.789); // With the default options, and an initial value // new AutoNumeric(domElement, '12345.789'); domElement = arg1; initialValue = arg2; userOptions = null; } else if (isArg1Element && isArg2Object && isArg3Null) { // new AutoNumeric(domElement, { options }); // With one option object domElement = arg1; initialValue = null; userOptions = arg2; } else if (isArg1Element && isArg2PreDefinedOptionName && isArg3Null) { // new AutoNumeric(domElement, 'euroPos'); // With one pre-defined option name domElement = arg1; initialValue = null; userOptions = this._getOptionObject(arg2); } else if (isArg1Element && isArg2Array && isArg3Null) { // new AutoNumeric(domElement, [{ options1 }, { options2 }]); // With multiple option objects (the latest option overwriting the previous ones) domElement = arg1; initialValue = null; userOptions = this.mergeOptions(arg2); } else if (isArg1Element && (isArg2Null || isArg2EmptyString) && isArg3Object) { // new AutoNumeric(domElement, null, { options }); // With one option object domElement = arg1; initialValue = null; userOptions = arg3; } else if (isArg1Element && (isArg2Null || isArg2EmptyString) && isArg3Array) { // new AutoNumeric(domElement, null, [{ options1 }, { options2 }]); // With multiple option objects domElement = arg1; initialValue = null; userOptions = this.mergeOptions(arg3); } else if (isArg1String && isArg2Null && isArg3Null) { // new AutoNumeric('.myCssClass > input'); domElement = document.querySelector(arg1); initialValue = null; userOptions = null; } else if (isArg1String && isArg2Object && isArg3Null) { // new AutoNumeric('.myCssClass > input', { options }); domElement = document.querySelector(arg1); initialValue = null; userOptions = arg2; } else if (isArg1String && isArg2PreDefinedOptionName && isArg3Null) { // new AutoNumeric('.myCssClass > input', 'euroPos'); domElement = document.querySelector(arg1); initialValue = null; userOptions = this._getOptionObject(arg2); } else if (isArg1String && isArg2Array && isArg3Null) { // new AutoNumeric('.myCssClass > input', [{ options1 }, { options2 }]); // With multiple option objects domElement = document.querySelector(arg1); initialValue = null; userOptions = this.mergeOptions(arg2); } else if (isArg1String && (isArg2Null || isArg2EmptyString) && isArg3Object) { // new AutoNumeric('.myCssClass > input', null, { options }); domElement = document.querySelector(arg1); initialValue = null; userOptions = arg3; } else if (isArg1String && (isArg2Null || isArg2EmptyString) && isArg3Array) { // new AutoNumeric('.myCssClass > input', null, [{ options1 }, { options2 }]); // With multiple option objects domElement = document.querySelector(arg1); initialValue = null; userOptions = this.mergeOptions(arg3); } else if (isArg1String && isArg2Number && isArg3Null) { // new AutoNumeric('.myCssClass > input', 12