blob: 0b848196fd9929c468965231878dd27b7729e460 [file] [log] [blame]
/**
* @license
* Copyright 2016-2017 Leif Olsen. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This code is built with Google Material Design Lite,
* which is Licensed under the Apache License, Version 2.0
*/
import {jsonStringToObject} from '../utils/json-utils';
import {
IS_UPGRADED,
} from '../utils/constants';
const JS_FORMAT_FIELD = 'mdlext-js-formatfield';
const FORMAT_FIELD_COMPONENT = 'MaterialExtFormatfield';
/**
* Detect browser locale
* @returns {string} the locale
* @see http://stackoverflow.com/questions/1043339/javascript-for-detecting-browser-language-preference
*/
const browserLanguage = () => {
return navigator.languages
? navigator.languages[0]
: navigator.language || navigator.userLanguage;
};
/**
* The formatfield formats an input field using language sensitive number formatting.
*/
class FormatField {
static timer = null;
element_;
input_;
options_ = {};
intlGroupSeparator_;
intlDecimalSeparator_;
constructor(element) {
this.element_ = element;
this.init();
}
clickHandler = () => {
clearTimeout(FormatField.timer);
};
focusInHandler = () => {
if(!(this.input.readOnly || this.input.disabled)) {
this.input.value = this.unformatInput();
//setTimeout(() => this.input.setSelectionRange(0, this.input.value.length), 20);
FormatField.timer = setTimeout(() => this.input.select(), 200);
}
};
focusOutHandler = () => {
clearTimeout(FormatField.timer);
if(!(this.input.readOnly || this.input.disabled)) {
this.formatValue();
}
};
get element() {
return this.element_;
}
get input() {
return this.input_;
}
get options() {
return this.options_;
}
stripSeparatorsFromValue() {
const doReplace = () => this.input.value
.replace(/\s/g, '')
.replace(new RegExp(this.options.groupSeparator, 'g'), '')
.replace(this.options.decimalSeparator, '.');
//.replace(this.intlGroupSeparator_, ''),
//.replace(this.intlDecimalSeparator_, '.');
return this.input.value ? doReplace() : this.input.value;
}
fixSeparators(value) {
const doReplace = () => value
.replace(new RegExp(this.intlGroupSeparator_, 'g'), this.options.groupSeparator)
.replace(this.intlDecimalSeparator_, this.options.decimalSeparator);
return value ? doReplace() : value;
}
formatValue() {
if(this.input.value) {
const v = new Intl.NumberFormat(this.options.locales, this.options)
.format(this.stripSeparatorsFromValue());
if('NaN' !== v) {
this.input.value = this.fixSeparators(v);
}
}
}
unformat() {
const doReplace = () => this.input.value
.replace(/\s/g, '')
.replace(new RegExp(this.options.groupSeparator, 'g'), '')
.replace(this.options.decimalSeparator, '.');
return this.input.value ? doReplace() : this.input.value;
}
unformatInput() {
const doReplace = () => this.input.value
.replace(/\s/g, '')
.replace(new RegExp(this.options.groupSeparator, 'g'), '');
return this.input.value ? doReplace() : this.input.value;
}
removeListeners() {
this.input.removeEventListener('click', this.clickHandler);
this.input.removeEventListener('focusin', this.focusInHandler);
this.input.removeEventListener('focusout', this.focusOutHandler);
}
init() {
const addListeners = () => {
this.input.addEventListener('click', this.clickHandler);
this.input.addEventListener('focusin', this.focusInHandler);
this.input.addEventListener('focusout', this.focusOutHandler);
};
const addOptions = () => {
const opts = this.element.getAttribute('data-formatfield-options') ||
this.input.getAttribute('data-formatfield-options');
if(opts) {
this.options_ = jsonStringToObject(opts, this.options);
}
};
const addLocale = () => {
if(!this.options.locales) {
this.options.locales = browserLanguage() || 'en-US'; //'nb-NO', //'en-US',
}
};
const addGrouping = () => {
const s = (1234.5).toLocaleString(this.options.locales, {
style: 'decimal',
useGrouping: true,
minimumFractionDigits: 1,
maximumFractionDigits: 1
});
this.intlGroupSeparator_ = s.charAt(1);
this.intlDecimalSeparator_ = s.charAt(s.length-2);
this.options.groupSeparator = this.options.groupSeparator || this.intlGroupSeparator_;
this.options.decimalSeparator = this.options.decimalSeparator || this.intlDecimalSeparator_;
if(this.options.groupSeparator === this.options.decimalSeparator) {
const e = `Error! options.groupSeparator, "${this.options.groupSeparator}" ` +
'and options.decimalSeparator, ' +
`"${this.options.decimalSeparator}" should not be equal`;
throw new Error(e);
}
};
this.input_ = this.element.querySelector('input') || this.element;
addOptions();
addLocale();
addGrouping();
this.formatValue();
addListeners();
}
downgrade() {
this.removeListeners();
}
}
(function() {
'use strict';
/**
* @constructor
* @param {HTMLElement} element The element that will be upgraded.
*/
const MaterialExtFormatfield = function MaterialExtFormatfield(element) {
this.element_ = element;
this.formatField_ = null;
// Initialize instance.
this.init();
};
window['MaterialExtFormatfield'] = MaterialExtFormatfield;
/**
* Initialize component
*/
MaterialExtFormatfield.prototype.init = function() {
if (this.element_) {
this.element_.classList.add(IS_UPGRADED);
this.formatField_ = new FormatField(this.element_);
// Listen to 'mdl-componentdowngraded' event
this.element_.addEventListener('mdl-componentdowngraded', this.mdlDowngrade_.bind(this));
}
};
/**
* Get options object
*
* @public
*
* @returns {Object} the options object
*/
MaterialExtFormatfield.prototype.getOptions = function() {
return this.formatField_.options;
};
MaterialExtFormatfield.prototype['getOptions'] = MaterialExtFormatfield.prototype.getOptions;
/**
* A unformatted value is a string value where the locale specific decimal separator
* is replaced with a '.' separator and group separators are stripped.
* The returned value is suitable for parsing to a JavaScript numerical value.
*
* @example
* input.value = '1 234,5';
* inputElement.MaterialExtFormatfield.getUnformattedValue();
* // Returns '1234.5'
*
* @public
*
* @returns {String} the unformatted value
*/
MaterialExtFormatfield.prototype.getUnformattedValue = function() {
return this.formatField_.unformat();
};
MaterialExtFormatfield.prototype['getUnformattedValue'] = MaterialExtFormatfield.prototype.getUnformattedValue;
/*
* Downgrade component
* E.g remove listeners and clean up resources
*/
MaterialExtFormatfield.prototype.mdlDowngrade_ = function() {
this.formatField_.downgrade();
};
// The component registers itself. It can assume componentHandler is available
// in the global scope.
/* eslint no-undef: 0 */
componentHandler.register({
constructor: MaterialExtFormatfield,
classAsString: FORMAT_FIELD_COMPONENT,
cssClass: JS_FORMAT_FIELD,
widget: true
});
})();