/*
* @Author: 疯狂秀才(Lucas Huang)
* @Date: 2019-08-05 10:26:50
* @LastEditors: 疯狂秀才(Lucas Huang)
* @LastEditTime: 2020-11-16 11:43:34
* @QQ: 1055818239
* @Version: v0.0.1
*/
import {
Component, Input, Output, EventEmitter, forwardRef, OnInit, ViewChild, ElementRef, Injector, AfterViewInit,
Renderer2, OnDestroy, OnChanges, SimpleChanges, HostBinding, ChangeDetectorRef, HostListener
} from '@angular/core';
import { ControlValueAccessor, FormControl, NgControl, NgModel, NG_VALUE_ACCESSOR } from '@angular/forms';
import { BigNumber } from 'bignumber.js';
import { LocaleService } from '@farris/ui-locale';
import { CommonUtils } from '@farris/ui-common';
export interface NumberFormatter {
/** 前置符号 */
prefix?: string;
/** 后缀 */
suffix?: string;
/** 小数点 */
decimalSeparator?: string;
/** 千分位符号 */
groupSeparator?: string;
/** 千分位分组 */
groupSize?: number;
}
@Component({
selector: 'farris-number-spinner',
template: `
`,
styleUrls: ['./number.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NumberSpinnerComponent),
multi: true
}
]
})
export class NumberSpinnerComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges, OnDestroy {
@HostBinding('class.f-cmp-number-spinner') hostClass = true;
@Input() disabled = false;
@Input() readonly = false;
@Input() editable = true;
// formatter 和 parser 必须同时存在
@Input() formatter: (val: number) => string;
@Input() parser: (val: string | number) => number;
// 空白提示文本
@Input() placeholder = '';
// up or down 步长
@Input() step = 1;
// 最大值
@Input() max: any;
// 最小值
@Input() min: any;
/** 启用大数支持 */
@Input() bigNumber = false;
// 是否显示加减按钮
@Input() showButton = true;
// 是否使用千分值
@Input() useThousands = true;
// 文本方向
@Input() textAlign = 'left';
// 自动补全小数
@Input() autoDecimal = true;
// 允许为空
@Input() canNull = false;
// 精度
@Input() precision = 0;
// 前缀
@Input() prefix = '';
// 后缀
@Input() suffix = '';
/** 小数点符号 */
@Input() decimalSeparator = '.';
/** 千分位符号 */
@Input() groupSeparator = ',';
/** 使用千分位时,每组显示的字符数 */
@Input() groupSize = 3;
private _value = '';
@Input()
get value() {
return this._value;
}
set value(val: any) {
this._value = val;
}
/** 显示0值 */
@Input() showZero = true;
@Output() valueChange = new EventEmitter();
@Output() blur = new EventEmitter();
@Output() focus = new EventEmitter();
@ViewChild('input') input: ElementRef;
isFocus = false;
popValue = '';
isActiveTip = false;
formatOptions: NumberFormatter = {};
private _realValue = null;
cdRef: ChangeDetectorRef;
commonUtils: CommonUtils;
private _ngControl: NgControl;
private showtipTimer = null;
_updateOn = 'change';
localeService: LocaleService = null;
onTouchedCallback: () => void = () => { };
onChangeCallback: (_: any) => void = () => { };
constructor(public el: ElementRef, public render: Renderer2, public injector: Injector) {
this.cdRef = this.injector.get(ChangeDetectorRef, null);
this.localeService = this.injector.get(LocaleService, null);
this.commonUtils = this.injector.get(CommonUtils, new CommonUtils());
}
ngOnInit(): void {
this.formatOptions = this.buildFormatOptions();
this._ngControl = this.injector.get(NgControl, null);
if (this.localeService) {
const defaultPlaceHolder = this.localeService.getValue('numberSpinner.placeholder') || '请输入数字';
if (!this.placeholder) {
this.placeholder = defaultPlaceHolder;
}
}
}
ngAfterViewInit() {
if (this._ngControl && this._ngControl.control) {
this._updateOn = this._ngControl.control.updateOn;
}
this.listenInputPasteEvent();
}
ngOnChanges(changes: SimpleChanges) {
if (changes.value && !changes.value.isFirstChange()) {
this._realValue = this.getRealValue(changes.value.currentValue);
this.value = this.format(this._realValue);
}
if (changes.showZero && !changes.showZero.isFirstChange()) {
this.value = this.format(this._realValue);
}
if (changes.precision && !changes.precision.isFirstChange()) {
this.onOptionsChanged();
}
if (changes.useThousands && !changes.useThousands.isFirstChange()) {
this.onOptionsChanged();
}
if (changes.prefix && !changes.prefix.isFirstChange()) {
this.onOptionsChanged();
}
if (changes.suffix && !changes.suffix.isFirstChange()) {
this.onOptionsChanged();
}
}
private onOptionsChanged() {
this.formatOptions = this.buildFormatOptions();
this.value = this.format(this._realValue);
}
ngOnDestroy() {}
// 支持粘贴带格式的数据
listenInputPasteEvent() {
this.input.nativeElement.addEventListener('paste', event => {
event.preventDefault();
const clipboardData = event.clipboardData || window['clipboardData'];
const pasteValue = clipboardData.getData('text');
const val = this.cleanNumString(pasteValue);
if (this.isEmpty(val)) {
return;
}
const target = event.target;
const start = (target as any).selectionStart;
const end = (target as any).selectionEnd;
if (this.isEmpty(val)) {
target.value = '';
} else {
target.value = target.value.slice(0, start) + val + target.value.slice(end);
}
if (this._updateOn === 'change') {
this._realValue = this.getRealValue(target.value);
this.value = this._realValue;
this._modelChanged(this._realValue);
}
});
}
onBlur($event, type: string = '') {
if (this.readonly || this.disabled) {
return;
}
if (this._updateOn === 'blur') {
const val = this.cleanNumString($event.value);
this._realValue = this.getRealValue(val);
}
this.value = this.format(this._realValue);
this.input.nativeElement.value = this.value;
this.isFocus = false;
this.onModelChange(this._realValue, 'blur');
this.blur.emit({ event: $event, formatted: this.value, value: this._realValue, instance: this });
}
onClick($event) {
$event.stopPropagation();
}
onFocus($event) {
if (this.readonly || this.disabled) {
this.isFocus = false;
return;
}
this.value = this.isEmpty(this._realValue) ? '' : ((!this.showZero && this._realValue == '0') ? '' : this._realValue );
this.isFocus = true;
this.focus.emit({ event: $event, formatted: this.value, value: this._realValue, instance: this });
}
onMouseEnter($event) {
if (this.value) {
this.popValue = this.value;
this.isActiveTip = this.isShowPopover();
}
}
onKeyDown(e: KeyboardEvent) {
if (e.key === 'ArrowDown') {
e.preventDefault();
this.down(e);
e.stopPropagation();
}
if (e.key === 'ArrowUp') {
e.preventDefault();
this.up(e);
e.stopPropagation();
}
}
private _modelChanged(realVal) {
this._realValue = realVal;
this.onChangeCallback(realVal);
this.onTouchedCallback();
this.valueChange.emit(realVal);
}
onModelChange(realVal: any, updateOn = 'change') {
// this._realValue = this.getRealValue(val);
let _realValue = realVal;
if (updateOn === 'change') {
_realValue = this.getRealValue(realVal);
}
this.isActiveTip = false;
this.popValue = this.format(_realValue);
if (this._updateOn === updateOn) {
this._modelChanged(_realValue);
}
}
up(e: Event, type = null) {
this.compute('up');
e.stopPropagation();
}
down(e: Event, type = null) {
this.compute('down');
e.stopPropagation();
}
compute(tye = 'up') {
if (this.isDisableOfBtn(tye)) {
let _resultValue;
const realBigNum = new BigNumber(this._realValue || 0);
if (tye === 'up') {
_resultValue = realBigNum.plus(Number(this.step));
} else {
_resultValue = realBigNum.minus(Number(this.step));
}
const s = _resultValue.toFixed();
if (!this.isFocus) {
this.value = this.format(s);
} else {
this.value = s;
}
this.input.nativeElement.value = this.value;
// this.onModelChange(this._getRealValue(_resultValue), 'blur');
this._modelChanged(this.getRealValue(_resultValue));
}
}
isDisableOfBtn(type: string, value?: any) {
if (value === undefined) {
value = this._realValue;
}
value = new BigNumber(value);
if (type === 'up' && !(new BigNumber(this.max)).isNaN() && value.gte(this.max)) {
return false;
}
if (type === 'down' && !(new BigNumber(this.min)).isNaN() && value.lte(this.min)) {
return false;
}
return true;
}
isShowPopover() {
const width = this.input.nativeElement.clientWidth;
const { paddingLeft, paddingRight } = window.getComputedStyle(this.input.nativeElement);
const _width = width - (Number(paddingLeft.replace('px', '')) || 0) - (Number(paddingRight.replace('px', '')) || 0);
const txtWidth = this.commonUtils.getTextWidth(this.value, window.getComputedStyle(this.input.nativeElement).font);
if (_width && _width < txtWidth) {
return true;
}
return false;
}
_getPrecision() {
return Number(this.precision || 0);
}
toFixed(n: BigNumber | number) {
let _n = n;
if (!BigNumber.isBigNumber(n)) {
_n = new BigNumber(n);
}
if (this.precision !== null && this.precision !== undefined) {
return n.toFixed(this._getPrecision());
}
return n.toFixed();
}
_getRealValue(_n: BigNumber) {
const n = this.toFixed(_n);
return this.bigNumber ? n : Number(n);
}
getRealValue(val: any) {
if (this.parser) {
if (!isNaN(Number(val))) {
return val;
} else {
return this.parser(val);
}
}
let _n = this.validInterval(new BigNumber(val));
if (_n.isNaN()) {
if (this.canNull) {
return null;
} else {
const minBigNum = new BigNumber('' + this.min);
const maxBigNum = new BigNumber('' + this.max);
if (!minBigNum.isNaN()) {
_n = minBigNum;
} else if (!maxBigNum.isNaN()) {
_n = maxBigNum;
} else {
return 0;
}
}
// if (this.canNull || minBigNum.isNaN()) {
// return null;
// } else {
// _n = minBigNum;
// }
}
return this._getRealValue(_n);
}
private buildFormatOptions() {
return {
prefix: this.prefix,
suffix: this.suffix,
decimalSeparator: this.decimalSeparator,
groupSeparator: this.useThousands ? this.groupSeparator : '',
groupSize: this.groupSize
};
}
isEmpty(val: any) {
return isNaN(val) || val === null || val === undefined || val === '';
}
validInterval(bn: BigNumber) {
let _bnVal = bn;
if (!this.isEmpty(this.max)) {
const _maxBigNum = new BigNumber('' + this.max);
if (bn.gt(_maxBigNum) ) {
_bnVal = _maxBigNum;
const _realValue = this._getRealValue(_maxBigNum);
this._modelChanged(_realValue);
}
}
if (!this.isEmpty(this.min)) {
const _minBigNum = new BigNumber('' + this.min);
if (bn.lt(_minBigNum)) {
_bnVal = _minBigNum;
const _realValue = this._getRealValue(_minBigNum);
this._modelChanged(_realValue);
}
}
return _bnVal;
}
format(val: any) {
val = this.cleanNumString(val);
const bigVal = new BigNumber(val);
const _bgNum = this.validInterval(bigVal);
if (_bgNum.valueOf() == '0' && !this.showZero) {
return '';
}
if (this.canNull && bigVal.isNaN()) {
return '';
} else {
if (_bgNum.isNaN()) {
return '';
}
}
if (this.formatter) {
return this.formatter(_bgNum.toNumber());
} else {
if (!Object.keys(this.formatOptions).length) {
this.formatOptions = this.buildFormatOptions();
}
return this._toFormat(_bgNum, this.formatOptions);
}
}
_toFormat(_bgNum: BigNumber, fmt: NumberFormatter) {
if (this.precision !== null && this.precision !== undefined) {
return _bgNum.toFormat(this._getPrecision(), fmt);
} else {
return _bgNum.toFormat(fmt);
}
}
cleanNumString(val: any) {
val = (val === null || val === undefined || val === '') ? '' : String(val);
val = val.replace(new RegExp(this.prefix, 'g'), '')
.replace(new RegExp(this.suffix, 'g'), '').replace(/\,/g, '');
if (this.groupSeparator && this.groupSeparator !== ',') {
val = val.replace(new RegExp(`\\${this.groupSeparator}`, 'g'), '');
}
if (this.decimalSeparator && this.decimalSeparator !== '.') {
val = val.replace(new RegExp(`\\${this.decimalSeparator}`, 'g'), '.');
}
return val;
}
updateValue(val) {
val = this.cleanNumString(val);
this._realValue = this.getRealValue(val);
this.value = this.format(this._realValue);
this.el.nativeElement.value = this.value;
}
private updateControlValue() {
let _val = this.cleanNumString(this.value);
if (this.parser) {
_val = this.parser(this.value);
}
if (this.isEmpty(this._realValue) && this.isEmpty(_val)) {
return;
}
const rv = new BigNumber(this._realValue);
const cv = new BigNumber(_val); // display value
if (rv.isNaN() && cv.isNaN()) {
return;
}
// if (!rv.eq(cv)) {
// }
if (this._ngControl) {
const formgroup = this._ngControl['formDirective'];
if (formgroup) {
let ctrl = formgroup.control.get(this._ngControl.name);
if (!ctrl && formgroup.control.controls) {
ctrl = formgroup.control.controls[this._ngControl.name];
}
if (ctrl) {
if (ctrl.value !== this._realValue) {
ctrl.setValue(this._realValue);
}
}
}
}
}
writeValue(val: any): void {
this._realValue = val;
this.value = this.format(this._realValue);
this.updateControlValue();
}
registerOnChange(fn: any): void {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any): void {
this.onTouchedCallback = fn;
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled;
}
}