UNPKG

comindware.core.ui

Version:

Comindware Core UI provides the basic components like editors, lists, dropdowns, popups that we so desperately need while creating Marionette-based single-page applications.

309 lines (263 loc) 11.5 kB
import { keyCode } from 'utils'; import template from './templates/numberEditor.hbs'; import BaseEditorView from './base/BaseEditorView'; import formRepository from '../formRepository'; import iconWrapRemove from './iconsWraps/iconWrapRemove.html'; import iconWrapNumber from './iconsWraps/iconWrapNumber.html'; import { maskInput, createNumberMask } from 'lib'; //docs: https://github.com/text-mask/text-mask/tree/master/addons const changeMode = { keydown: 'keydown', blur: 'blur' }; const defaultAllowFroat = false; const defaultOptions = { max: undefined, min: undefined, step: undefined, changeMode: changeMode.blur, format: undefined, showTitle: true, class: undefined, allowFloat: defaultAllowFroat, requireDecimal: false, allowNegative: true, allowLeadingZeroes: true, intlOptions: { style: 'decimal', useGrouping: true, /* The following properties fall into two groups: minimumIntegerDigits, minimumFractionDigits, and maximumFractionDigits in one group, minimumSignificantDigits and maximumSignificantDigits in the other. If at least one property from the second group is defined, then the first group is ignored. https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat */ minimumFractionDigits: 0, maximumFractionDigits: defaultAllowFroat ? 3 : 0, maximumSignificantDigits: undefined, //default from Intl is 21 minimumSignificantDigits: undefined //default from Intl is 1 } }; /** * @name NumberEditorView * @memberof module:core.form.editors * @class Редактор числовых значений. Поддерживаемый тип данных: <code>Number</code>. * @extends module:core.form.editors.base.BaseEditorView * @param {Object} options Options object. All the properties of {@link module:core.form.editors.base.BaseEditorView BaseEditorView} class are also supported. * @param {Boolean} [options.allowFloat=false] Если <code>true</code>, ко вводу допускаются значения с плавающей точкой. * @param {String} [options.changeMode='blur'] Определяет момент обновления значения редактора:<ul> * <li><code>'keydown'</code> - при нажатии клавиши.</li> * <li><code>'blur'</code> - при потери фокуса.</li></ul> * @param {Number} [options.max=null] Максимальное возможное значение. Если <code>null</code>, не ограничено. * @param {Number} [options.min=null] Минимальное возможное значение. Если <code>null</code>, не ограничено. * * !!!Deprecated @param {String} [options.format=null] A [NumeralJS](http://numeraljs.com/) format string (e.g. '$0,0.00' etc.). * @param {Object} [options.intlOptions=null] options for new Intl.NumberFormat([locales[, options]]) https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat * * @param {Boolean} {options.showTitle=true} Whether to show title attribute. * */ export default formRepository.editors.Number = BaseEditorView.extend({ template: Handlebars.compile(template), focusElement: '.js-input', className: 'editor editor_number', initialize(options) { this.__applyOptions(options, defaultOptions); this.format = this.options.format || this.options.intlOptions || this.options.allowFloat; this.decimalSymbol = Core.services.LocalizationService.decimalSymbol; if (this.format) { this.intl = new Intl.NumberFormat(Core.services.LocalizationService.langCode, this.options.intlOptions); this.thousandsSeparator = Core.services.LocalizationService.thousandsSeparatorSymbol; if (this.options.intlOptions && this.options.intlOptions.useGrouping === false) { this.thousandsSeparator = ''; } this.numberMask = createNumberMask({ prefix: '', suffix: '', includeThousandsSeparator: this.options.intlOptions.useGrouping, thousandsSeparatorSymbol: this.thousandsSeparator, allowDecimal: this.options.allowFloat, decimalSymbol: this.decimalSymbol, decimalLimit: this.options.intlOptions.maximumFractionDigits, integerLimit: this.options.intlOptions.maximumSignificantDigits || null, requireDecimal: this.options.requireDecimal, allowNegative: this.options.allowNegative, allowLeadingZeroes: this.options.allowLeadingZeroes }); } this.isChangeModeKeydown = this.options.changeMode === changeMode.keydown; }, ui: { input: '.js-input', clearButton: '.js-clear-button' }, events() { const events = { 'click @ui.clearButton': '__onClearClickHandler', 'dblclick @ui.clearButton': '__onClearDblclick', 'keyup @ui.input': '__keyup', 'change @ui.input': '__onChange', 'blur @ui.input': '__onBlur' }; if (!this.options.hideClearButton) { events.mouseenter = '__onMouseenter'; } return events; }, onRender() { this.__setInputOptions(); }, onAttach() { if (this.format) { this.maskedInputController = maskInput({ inputElement: this.ui.input[0], mask: this.numberMask }); } this.__value(this.value, false, false, true); }, onDestroy() { this.maskedInputController && this.maskedInputController.destroy(); }, __keyup(event) { let value = event.target.value; const max = event.target.max; const min = event.target.min; if (this.__isTypeInFractionPart(value) && this.__isTypeInTheEnd(value) && value.slice(-1) === '0') { return; } const parsed = this.__parse(value); if ((parsed === 0 || parsed === null) && value[0] === '-') { return; } value = Number(min) < 0 ? this.__checkMaxMinValue(value, max, min) : this.__checkMaxMinValue(value, max); this.__value(value, true, this.isChangeModeKeydown || event.keyCode === keyCode.ENTER, false); }, __isTypeInFractionPart(value) { const posCaret = this.ui.input[0].selectionStart; const posDecimalSymbol = value.indexOf(this.decimalSymbol); return posDecimalSymbol !== -1 && posCaret > posDecimalSymbol; }, __isTypeInTheEnd(value) { const posCaret = this.ui.input[0].selectionStart; return posCaret === value.length; }, __onChange() { const input = this.ui.input; const max = input[0].getAttribute('max'); const min = input[0].getAttribute('min'); const value = this.__checkMaxMinValue(input.val(), max, min); this.__value(value, false, true, false); }, __onBlur() { if (Core.services.MobileService.isIE) { this.__onChange(); } }, __checkMaxMinValue(value, max, min) { let val = this.__parseToNumber(value); if (max) { val = val > Number(max) ? max : val; } if (min) { val = val < Number(min) ? min : val; } return this.__parseToString(val); }, setValue(value) { this.__value(value, false, false, false); }, isEmptyValue() { return !_.isNumber(this.getValue()); }, __setActive(el, isActive) { el.classList.toggle('ui-state-active', isActive); }, __setEnabled(enabled) { BaseEditorView.prototype.__setEnabled.call(this, enabled); this.ui.input.prop?.('disabled', !enabled); }, __setReadonly(readonly) { BaseEditorView.prototype.__setReadonly.call(this, readonly); if (this.getEnabled()) { this.ui.input.prop?.('readonly', readonly); this.ui.input.prop?.('tabindex', readonly ? -1 : 0); } }, __onClearClick() { if (this.__isDoubleClicked) { this.__isDoubleClicked = false; return; } this.__value(null, false, this.isChangeModeKeydown, false); this.focus(); return false; }, __value(newValue, suppressRender, triggerChange, force) { let value = newValue; if ((value === this.value && !force) || (typeof value === 'string' && [this.decimalSymbol, '_', '-'].includes(value.slice(-1)))) { // '_' - placeholder from mask return; } let parsed; if (value !== '' && value !== null) { parsed = this.__parse(value); if (parsed !== null) { if (!this.options.allowFloat) { value = Math.floor(parsed); } else { value = parsed; } } else { value = null; } } else if (value === '') { value = null; } this.value = value; this.__updateEmpty(); if (triggerChange) { this.__triggerChange(); } if (!this.isRendered()) { return; } if (this.options.showTitle) { this.$editorEl.prop('title', value); } if (!this.format || this.value === null) { this.ui.input.val(value); } else { this.maskedInputController && this.maskedInputController.textMaskInputElement.update(this.intl.format(value)); } }, __parse(value) { let val = value; if (typeof val === 'string' && val !== '') { val = this.__parseToNumber(val); if (val === Number.POSITIVE_INFINITY) { val = Number.MAX_VALUE; } else if (val === Number.NEGATIVE_INFINITY) { val = Number.MIN_VALUE; } } return val === '' || isNaN(val) ? null : val; }, __setInputOptions() { this.options.min !== undefined && this.ui.input[0].setAttribute('min', this.options.min); this.options.max !== undefined && this.ui.input[0].setAttribute('max', this.options.max); this.options.step !== undefined && this.ui.input[0].setAttribute('step', this.options.step); }, __parseToNumber(string) { let newValue = string.replace(new RegExp(`\\${this.decimalSymbol}`, 'g'), '.'); if (this.thousandsSeparator) { newValue = newValue.replace(new RegExp(`\\${this.thousandsSeparator}`, 'g'), ''); } newValue = newValue.replace(/[^\d\.-]*/g, ''); return parseFloat(newValue); }, __parseToString(number) { return String(number).replace(new RegExp('\\.', 'g'), this.decimalSymbol); }, __onMouseenter() { this.$editorEl.off('mouseenter'); if (!this.options.hideClearButton) { this.renderIcons(iconWrapNumber, iconWrapRemove); } } });