UNPKG

comindware.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.

390 lines (342 loc) 12.4 kB
/** * Developer: Stepan Burguchev * Date: 10/3/2014 * Copyright: 2009-2016 Comindware® * All Rights Reserved * Published under the MIT license */ 'use strict'; import template from './templates/numberEditor.hbs'; import BaseItemEditorView from './base/BaseItemEditorView'; import { numeral, Handlebars } from 'lib'; import { keyCode } from 'utils'; import formRepository from '../formRepository'; const changeMode = { keydown: 'keydown', blur: 'blur' }; const constants = { STEP: 1, PAGE: 10, INCREMENTAL: true }; const defaultOptions = { max: null, min: 0, allowFloat: false, changeMode: changeMode.blur, format: null, showTitle: true }; const allowedKeys = [ keyCode.DELETE, keyCode.BACKSPACE, keyCode.TAB, keyCode.ESCAPE, keyCode.ENTER, keyCode.NUMPAD_ENTER, keyCode.NUMPAD_DECIMAL, keyCode.PERIOD, keyCode.COMMA, keyCode.HOME, keyCode.END, keyCode.RIGHT, keyCode.LEFT, keyCode.UP, keyCode.DOWN, keyCode.E, keyCode.ADD, keyCode.SUBTRACT, keyCode.NUMPAD_ADD, keyCode.NUMPAD_SUBTRACT, keyCode.SLASH ]; const ALLOWED_CHARS = '0123456789+-.,Ee'; /** * @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=0] Минимальное возможное значение. Если <code>null</code>, не ограничено. * @param {String} [options.format=null] A [NumeralJS](http://numeraljs.com/) format string (e.g. '$0,0.00' etc.). * @param {Boolean} {options.showTitle=true} Whether to show title attribute. * */ formRepository.editors.Number = BaseItemEditorView.extend(/** @lends module:core.form.editors.NumberEditorView.prototype */{ initialize(options) { if (options.schema) { _.extend(this.options, defaultOptions, _.pick(options.schema, _.keys(defaultOptions))); } else { _.extend(this.options, defaultOptions, _.pick(options || {}, _.keys(defaultOptions))); } _.bindAll(this, '__stop'); }, template: Handlebars.compile(template), focusElement: '.js-input', className: 'editor editor_number', ui: { input: '.js-input', spinnerUp: '.js-spinner-up', spinnerDown: '.js-spinner-down', spinnerButtons: '.js-spinner-button' }, events: { 'click .js-clear-button': '__clear', 'keydown @ui.input': '__keydown', 'keypress @ui.input': '__keypress', 'keyup @ui.input'(event) { if ([keyCode.UP, keyCode.DOWN, keyCode.PAGE_UP, keyCode.PAGE_DOWN].indexOf(event.keyCode) !== -1) { this.__stop(); } if (this.options.changeMode === changeMode.keydown) { this.__value(this.ui.input.val(), true, true, false); } else { this.__value(this.ui.input.val(), true, false, false); } }, 'change @ui.input'() { this.__value(this.ui.input.val(), false, true, false); }, 'mousewheel @ui.input'(event) { if (!this.getEnabled() || this.getReadonly() || !this.hasFocus) { return; } this.__start(); this.__spin((event.deltaY > 0 ? 1 : -1) * constants.STEP); clearTimeout(this.mousewheelTimer); //noinspection JSCheckFunctionSignatures this.mousewheelTimer = setTimeout(this.__stop, 100); return false; }, 'mousedown @ui.spinnerUp'(event) { event.preventDefault(); this.focus(); this.__setActive(this.ui.spinnerDown, true); this.__start(); this.__repeat(null, 1); }, 'mousedown @ui.spinnerDown'(event) { event.preventDefault(); this.focus(); this.__setActive(this.ui.spinnerUp, true); this.__start(); this.__repeat(null, -1); }, 'mouseup @ui.spinnerButtons': '__stop', 'mouseleave @ui.spinnerButtons': '__stop' }, onRender() { this.__value(this.value, false, false, true); }, __setActive(el, isActive) { if (isActive) { $(el).addClass('ui-state-active'); } else { $(el).removeClass('ui-state-active'); } }, setPermissions(enabled, readonly) { BaseItemEditorView.prototype.setPermissions.call(this, enabled, readonly); if (enabled && !readonly) { this.ui.spinnerUp.show(); this.ui.spinnerDown.show(); } else { this.ui.spinnerUp.hide(); this.ui.spinnerDown.hide(); } }, __setEnabled(enabled) { BaseItemEditorView.prototype.__setEnabled.call(this, enabled); this.ui.input.prop('disabled', !enabled); }, __setReadonly(readonly) { BaseItemEditorView.prototype.__setReadonly.call(this, readonly); if (this.getEnabled()) { this.ui.input.prop('readonly', readonly); this.ui.input.prop('tabindex', readonly ? -1 : 0); } }, __clear() { this.__value(null, false, true, false); this.focus(); return false; }, __repeat(i, steps) { i = i || 500; clearTimeout(this.timer); this.timer = setTimeout(() => { if (!this.isDestroyed) { this.__repeat(40, steps); } }, i); this.__spin(steps * constants.STEP); }, __keydown(event) { this.__start(); const options = this.options; switch (event.keyCode) { case keyCode.UP: this.__repeat(null, 1, event); return false; case keyCode.DOWN: this.__repeat(null, -1, event); return false; case keyCode.PAGE_UP: this.__repeat(null, constants.PAGE, event); return false; case keyCode.PAGE_DOWN: this.__repeat(null, -constants.PAGE, event); return false; } if (event.ctrlKey === true || allowedKeys.indexOf(event.keyCode) !== -1) { return true; } }, __keypress(event) { const code = event.which == null /* check for IE */ ? event.keyCode : event.charCode; return ALLOWED_CHARS.indexOf(String.fromCharCode(code)) !== -1; }, __start() { if (!this.counter) { this.counter = 1; } this.spinning = true; }, __spin(step) { let value = this.getValue() || 0; if (!this.counter) { this.counter = 1; } value = this.__adjustValue(value + step * this.__increment(this.counter)); this.__value(value, false, true, false); this.counter++; }, __stop() { if (this.isDestroyed) { return; } if (!this.spinning) { return; } this.__setActive(this.ui.spinnerButtons, false); clearTimeout(this.timer); clearTimeout(this.mousewheelTimer); this.counter = 0; this.spinning = false; }, __value(value, suppressRender, triggerChange, force) { if (value === this.value && !force) { return; } let parsed, formattedValue = null; if (value !== '' && value !== null) { parsed = this.__parse(value); if (parsed !== null) { value = this.__adjustRange(parsed); if (!this.options.allowFloat) { value = Math.floor(value); } if (this.options.format) { formattedValue = numeral(value).format(this.options.format); value = numeral().unformat(formattedValue); } } else { return; } } else if (value === '') { value = null; } this.value = value; if (this.options.showTitle) { if (formattedValue) { this.$el.prop('title', formattedValue); } else { this.$el.prop('title', value); } } if (!suppressRender) { if (formattedValue) { this.ui.input.val(formattedValue); } else { this.ui.input.val(value); } } if (triggerChange) { this.__triggerChange(); } }, __parse(val) { if (typeof val === 'string' && val !== '') { if (numeral.languageData().delimiters.decimal !== '.') { val = val.replace('.', numeral.languageData().delimiters.decimal); } val = numeral().unformat(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; }, __precision() { let precision = this.__precisionOf(constants.STEP); if (this.options.min !== null) { precision = Math.max(precision, this.__precisionOf(this.options.min)); } return precision; }, __precisionOf(num) { const str = num.toString(); const decimal = str.indexOf('.'); return decimal === -1 ? 0 : str.length - decimal - 1; }, __increment(i) { const incremental = constants.INCREMENTAL; if (incremental) { return $.isFunction(incremental) ? incremental(i) : Math.floor(i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1); } return 1; }, __adjustRange(value) { const options = this.options; if (options.max !== null && value > options.max) { return options.max; } if (options.min !== null && value < options.min) { return options.min; } return value; }, __adjustValue(value) { let base; let aboveMin; const options = this.options; // make sure we're at a valid step // - find out where we are relative to the base (min or 0) base = options.min !== null ? options.min : 0; aboveMin = value - base; // - round to the nearest step aboveMin = Math.round(aboveMin / constants.STEP) * constants.STEP; // - rounding is based on 0, so adjust back to our base value = base + aboveMin; // fix precision from bad JS floating point math value = parseFloat(value.toFixed(this.__precision())); return value; }, setValue(value) { this.__value(value, false, false, false); }, isEmptyValue() { return !_.isNumber(this.getValue()); } }); export default formRepository.editors.Number;