cv-dialog-sdk
Version:
Catavolt Dialog Javascript API
358 lines (342 loc) • 14.5 kB
text/typescript
import moment from 'moment';
import numeral from 'numeral';
import { CatavoltApi } from '../dialog/CatavoltApi';
import { CvLocale } from '../util/CvLocale';
import { CodeRef } from './CodeRef';
import { GpsReadingProperty } from './GpsReadingProperty';
import { MapLocationProperty } from './MapLocationProperty';
import { ObjectRef } from './ObjectRef';
import { Property } from './Property';
import { PropertyDef } from './PropertyDef';
// Chose the locales to load based on this list:
// https://stackoverflow.com/questions/9711066/most-common-locales-for-worldwide-compatibility
// Best effort for now. Need to dynamically load these from Globalize???
import 'moment/locale/de';
import 'moment/locale/en-ca';
import 'moment/locale/en-gb';
import 'moment/locale/es';
import 'moment/locale/fr';
import 'moment/locale/it';
import 'moment/locale/ja';
import 'moment/locale/pt';
import 'moment/locale/pt-br';
import 'moment/locale/ru';
import 'moment/locale/zh-cn';
// we must use these to prevent giving moment non-existent locales that cause it hurl in RN
// @see https://github.com/moment/moment/issues/3624#issuecomment-288713419
const supportedLocales = ['en', 'de', 'en-ca', 'en-gb', 'es', 'fr', 'it', 'ja', 'pt', 'pt-br', 'ru', 'zh-cn'];
import { DateTimeValue } from '../util/DateTimeValue';
import { DateValue } from '../util/DateValue';
import { TimeValue } from '../util/TimeValue';
/**
* Helper for transforming values to and from formats suitable for reading and writing to the server
* (i.e. object to string and string to object)
*/
class PrivatePropFormats {
public static decimalFormat: string[] = [
'0,0',
'0,0.0',
'0,0.00',
'0,0.000',
'0,0.0000',
'0,0.00000',
'0,0.000000',
'0,0.0000000',
'0,0.00000000',
'0,0.000000000',
'0,0.0000000000'
];
public static decimalFormatGeneric: string = '0,0.[0000000000000000000000000]';
public static moneyFormat: string[] = [
'$0,0',
'$0,0.0',
'$0,0.00',
'$0,0.000',
'$0,0.0000',
'$0,0.00000',
'$0,0.000000',
'$0,0.0000000',
'$0,0.00000000',
'$0,0.000000000',
'$0,0.0000000000'
];
public static moneyFormatGeneric: string = '$0,0.00[0000000000000000000000000]';
public static percentFormat: string[] = [
'0,0%',
'0,0%',
'0,0%',
'0,0.0%',
'0,0.00%',
'0,0.000%',
'0,0.0000%',
'0,0.00000%',
'0,0.000000%',
'0,0.0000000%',
'0,0.00000000%'
];
public static percentFormatGeneric: string = '0,0.[0000000000000000000000000]%';
public static wholeFormat: string = '0,0';
}
export class PropertyFormatter {
private static _singleton: PropertyFormatter;
// Default format for money at varying decimal lengths.
// For numeral format options, see: http://numeraljs.com/
public decimalFormat: string[];
public decimalFormatGeneric: string;
public moneyFormat: string[];
public moneyFormatGeneric: string;
public percentFormat: string[];
public percentFormatGeneric: string;
public wholeFormat: string;
public static singleton(catavoltApi: CatavoltApi): PropertyFormatter {
if (!PropertyFormatter._singleton) {
PropertyFormatter._singleton = new PropertyFormatter(catavoltApi);
}
return PropertyFormatter._singleton;
}
private constructor(private _catavoltApi: CatavoltApi) {
if (PropertyFormatter._singleton) {
throw new Error('Singleton instance already created');
}
this.resetFormats();
PropertyFormatter._singleton = this;
}
/**
* Get a string representation of this property suitable for 'reading'
* @param prop
* @param propDef
* @returns {string}
*/
public formatForRead(prop: Property, propDef: PropertyDef): string {
if (prop === null || prop === undefined) {
return '';
} else {
return this.formatValueForRead(prop.value, propDef);
}
}
public formatValueForRead(value: any, propDef: PropertyDef) {
const locale: CvLocale = this._catavoltApi.locale;
const locales: string[] = [];
locales.push(this.getSupportedLocale(locale.langCountryString));
if (value === null || value === undefined) {
return '';
} else if ((propDef && propDef.isCodeRefType) || value instanceof CodeRef) {
return (value as CodeRef).description;
} else if ((propDef && propDef.isObjRefType) || value instanceof ObjectRef) {
return (value as ObjectRef).description;
} else if ((propDef && propDef.isDateTimeType) || value instanceof DateTimeValue) {
const dateValue = (value instanceof DateTimeValue) ? value.dateObj : value;
return moment(dateValue)
.locale(locales)
.format('lll');
} else if ((propDef && propDef.isDateType) || value instanceof Date || value instanceof DateValue) {
const dateValue = (value instanceof DateValue) ? value.dateObj : value;
return moment(dateValue)
.locale(locales)
.format('ll');
} else if ((propDef && propDef.isTimeType) || value instanceof TimeValue) {
return moment(value as TimeValue)
.locale(locales)
.format('LT');
} else if (propDef && propDef.isPasswordType) {
return (value as string).replace(/./g, '*');
} else if ((propDef && propDef.isListType) || Array.isArray(value)) {
return value.reduce((prev, current) => {
return (prev ? prev + ', ' : '') + this.formatValueForRead(current, null);
}, '');
} else {
return this.toString(value, propDef);
}
}
/**
* Get a string representation of this property suitable for 'writing'
* @param prop
* @param propDef
* @returns {string}
*/
public formatForWrite(prop: Property, propDef: PropertyDef): string {
if (prop === null || prop === undefined || prop.value === null || prop.value === undefined) {
return null;
} else if ((propDef && propDef.isCodeRefType) || prop.value instanceof CodeRef) {
return (prop.value as CodeRef).description;
} else if ((propDef && propDef.isObjRefType) || prop.value instanceof ObjectRef) {
return (prop.value as ObjectRef).description;
} else {
return this.toStringWrite(prop.value, propDef);
}
}
/**
* Attempt to construct (or preserve) the appropriate data type given primitive (or already constructed) value.
* Note this must be done here and not at 'write' time because it requires the knowledge of the PropDef
* @param value
* @param propDef
* @returns {}
*/
public parse(value: any, propDef: PropertyDef): any {
let propValue: any = value;
if (propDef.isDecimalType) {
const newVal = typeof value === 'string' ? value.replace(',', '') : value;
propValue = Number(newVal);
} else if (propDef.isLongType) {
const newVal = typeof value === 'string' ? value.replace(',', '') : value;
propValue = Number(newVal);
} else if (propDef.isBooleanType) {
if (typeof value === 'string') {
propValue = value !== 'false' && value !== 'no' && value !== '0';
} else {
propValue = !!value;
}
} else if (propDef.isDateType) {
// this could be a DateValue, a Date, or a string
if (value instanceof DateValue) {
propValue = value;
} else if (typeof value === 'object') {
propValue = new DateValue(value);
} else {
// parse as local time
propValue = new DateValue(moment(value).toDate());
}
} else if (propDef.isDateTimeType) {
// this could be a DateTimeValue, a Date, or a string
if (value instanceof DateTimeValue) {
propValue = value;
} else if (typeof value === 'object') {
propValue = new DateTimeValue(value);
} else {
// parse as local time
propValue = new DateTimeValue(moment(value).toDate());
}
} else if (propDef.isTimeType) {
if (value instanceof TimeValue) {
propValue = value;
} else if (typeof value === 'object') {
propValue = TimeValue.fromDateValue(value);
} else {
propValue = TimeValue.fromString(value);
}
} else if (propDef.isCodeRefType) {
if(typeof value === 'string') {
propValue = CodeRef.fromString(value);
}
} else if (propDef.isObjRefType) {
if(typeof value === 'string') {
propValue = ObjectRef.fromString(value);
}
}
return propValue;
}
public resetFormats(): void {
this.decimalFormat = PrivatePropFormats.decimalFormat.slice(0);
this.decimalFormatGeneric = PrivatePropFormats.decimalFormatGeneric;
this.moneyFormat = PrivatePropFormats.moneyFormat.slice(0);
this.moneyFormatGeneric = PrivatePropFormats.moneyFormatGeneric;
this.percentFormat = PrivatePropFormats.percentFormat.slice(0);
this.percentFormatGeneric = PrivatePropFormats.percentFormatGeneric;
this.wholeFormat = PrivatePropFormats.wholeFormat;
}
public toString(o: any, propDef: PropertyDef): string {
return this.toStringRead(o, propDef);
}
/**
* Render this value as a string
*
* @param o
* @param {PropertyDef} propDef
* @returns {string}
*/
public toStringRead(o: any, propDef: PropertyDef): string {
if (typeof o === 'number') {
if (propDef && !propDef.isUnformattedNumericType) {
if (propDef.isMoneyType) {
let f =
propDef.scale < this.moneyFormat.length
? this.moneyFormat[propDef.scale]
: this.moneyFormatGeneric;
// If there is a currency symbol, remove it noting it's position pre/post
// Necessary because numeral will replace $ with the symbol based on the locale of the browser.
// This may be desired down the road, but for now, the server provides the symbol to use.
const atStart: boolean = f.length > 0 && f[0] === '$';
const atEnd: boolean = f.length > 0 && f[f.length - 1] === '$';
if (this._catavoltApi.currencySymbol) {
f = f.replace('$', ''); // Format this as a number, and slam in Extender currency symbol
let formatted = numeral(o).format(f);
if (atStart) {
formatted = this._catavoltApi.currencySymbol + formatted;
}
if (atEnd) {
formatted = formatted + this._catavoltApi.currencySymbol;
}
return formatted;
} else {
return numeral(o).format(f); // Should substitute browsers locale currency symbol
}
} else if (propDef.isPercentType) {
const f =
propDef.scale < this.percentFormat.length
? this.percentFormat[propDef.scale]
: this.percentFormatGeneric;
return numeral(o).format(f); // numeral accomplishs * 100, relevant if we use some other symbol
} else if (propDef.isIntType || propDef.isLongType) {
return numeral(o).format(this.wholeFormat);
} else if (propDef.isDecimalType || propDef.isDoubleType) {
const f =
propDef.scale < this.decimalFormat.length
? this.decimalFormat[propDef.scale]
: this.decimalFormatGeneric;
return numeral(o).format(f);
}
} else {
return String(o);
}
} else if (typeof o === 'object') {
if (o instanceof Date) {
return o.toISOString();
} else if (o instanceof DateValue) {
return (o as DateValue).dateObj.toISOString();
} else if (o instanceof DateTimeValue) {
return (o as DateTimeValue).dateObj.toISOString();
} else if (o instanceof TimeValue) {
return o.toString();
} else if (o instanceof CodeRef) {
return o.toString();
} else if (o instanceof ObjectRef) {
return o.toString();
} else if (o instanceof GpsReadingProperty) {
return o.toString();
} else if (o instanceof MapLocationProperty) {
return o.toString();
} else {
return String(o);
}
} else {
return String(o);
}
}
public toStringWrite(o: any, propDef: PropertyDef): string {
if (typeof o === 'number' && propDef) {
if (propDef.isMoneyType) {
return o.toFixed(2);
} else if (propDef.isIntType || propDef.isLongType) {
return o.toFixed(0);
} else if (propDef.isDecimalType || propDef.isDoubleType) {
return o.toFixed(Math.max(2, (o.toString().split('.')[1] || []).length));
}
} else {
return this.toStringRead(o, propDef);
}
}
private getSupportedLocale(locale): string {
if (supportedLocales.indexOf(locale)) {
return locale;
} else {
const sepIndex = locale.indexOf('-');
if (sepIndex > -1) {
const lang = locale.substring(0, sepIndex);
if (supportedLocales.indexOf(lang)) {
return lang;
}
}
return this._catavoltApi.DEFAULT_LOCALE.language;
}
}
}