NativeScript / nativescript-angular

Integrating NativeScript with Angular
http://docs.nativescript.org/angular/tutorial/ng-chapter-0
Apache License 2.0
1.21k stars 241 forks source link

Make a Currency Input with TextField #1526

Closed domzinhuu closed 6 years ago

domzinhuu commented 6 years ago

Hi,

I need create a currency input mask for a textfield that change the dot and comma when the user input some value exemple:

initial value 0,00;

input 1 field 0,01;

input 2 field 0,12;

input 3 field 1,23;

input 4 field 1.234;

input 5 field 1.234,05;

I try with ControlValueAccessor and textChange but no success when format the first value with comma or dot the cursor jump to start of the textField.

tsonevn commented 6 years ago

Hi @domzinhuu. In your case, you can use some nativescript-masked-text-field or nativescript-input-mask plugins. The plugins allow you to set up, how the text in the component should be displayed.

dav1app commented 5 years ago

Sorry for respawning this topic. But I think that the question resembles a much bigger problem in data formats. Not only on NativeScript but on the web. The reason I know it is that most of the apps that I've developed are related to financial industries.

There is no default way to handle money input. This also explains why the plugins shown above are insufficient to create the desired behavior. Here are some of the problems:

  1. Currency inputs are normally handled in RTL (right-to-left), starting with the 0,00 or 0.00 (depending on your location). If you press any digit (Eg:6) it should display 0.06 and move the cursor to the end of the string. If another key is pressed (Eg:3), the input should display 0.63, and so on. There is no support for RTL in TextField for NativeScript. The only support for this is a UI plugin that reverses a GridLayout.

  2. Currency inputs usually use masking. As @tsonevn mentioned before, there are some options for plugins. None of those (and none of the ones that I am aware of) can be used to :

    • set a pattern (like [*0].[00]) to include N numbers before the dot. You have to specify how many characters you need to use.
    • set default values that are going to be replaced during typing. So we are unable to input 6 inside a 0.00 and get a 6.00.
  3. There is a problem with using float points as currency representation: rounding. The correct way of handling it is using a BigInt variable for the smallest currency with DP = 2 and RM = 1 (according to the law for most countries).

My point here is: this is hard. Believe me, I spent almost a month searching for good a solution and didn't find one. I ended up using currency.js on most of my projects.

I hope this changes in the future, but until that, I really don't think that NativeScript developers are going to do something about it. W3C is struggling with this. JavaScript is trying hard to do something about it (with things like toStringLocale()).

Good luck to anyone finding this post in the future.

faelperetta commented 5 years ago

Hi @domzinhuu, did you find a solution for your problem? I'm trying to do the same thing and the suggested plugins nativescript-masked-text-field or nativescript-input-mask has a different behavior that we expect.

domzinhuu commented 5 years ago

Hi @faelperetta , i create a custom mask component for this and this line of code save-me:

 if (this.page.android) {
        this.fieldValue.android.setSelection(this.fieldValue.android.length());
  }

This make the cursor to always be at the end of the written text.

the custom component complete:

component.ts

import { forwardRef, OnInit, Component, Input, Output, EventEmitter } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { TextField } from 'tns-core-modules/ui/text-field';
import { Page } from 'tns-core-modules/ui/page/page';

@Component({
  moduleId: module.id,
  templateUrl: './nativescript-currency-mask.component.html',
  selector: 'TextFieldCurrency,[TextFieldCurrency]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CurrencyMaskPtComponent),
      multi: true
    }
  ]
})
export class CurrencyMaskPtComponent implements OnInit, ControlValueAccessor {
  @Input()
  hint: string;
  @Input()
  className: string = 'input-field';
  @Input()
  returnKeyType: string;
  @Input()
  isEnabled = true;

  @Input()
  colSpan: string;

  @Input()
  row: string;
  @Input()
  col: string;

  @Output()
  returnPress = new EventEmitter();

  inputValue: string;
  private fieldValue: TextField;
  private propagateChange = (_: any) => {};

  constructor(private page: Page) {}

  ngOnInit() {
    this.fieldValue = <TextField>this.page.getViewById('fieldValue');
  }

  writeValue(obj: any): void {}

  registerOnChange(fn: any): void {
    this.propagateChange = fn;
  }

  registerOnTouched(fn: any): void {}
  setDisabledState?(isDisabled: boolean): void {}

  changeValue(event: any) {
    if (event.object.text) {
      let value: string = event.object.text;

      value = value.replace(/\D/g, '');

      if (value.length === 3) {
        value = value.replace(/(\d{1})(\d{2})/, '$1,$2');
      } else if (value.length === 4) {
        value = value.replace(/(\d{2})(\d{2})/, '$1,$2');
      } else if (value.length === 5) {
        value = value.replace(/(\d{3})(\d{2})/, '$1,$2');
      } else if (value.length === 6) {
        value = value.replace(/(\d{1})(\d{3})(\d{2})/, '$1.$2,$3');
      } else if (value.length === 7) {
        value = value.replace(/(\d{2})(\d{3})(\d{2})/, '$1.$2,$3');
      } else if (value.length === 8) {
        value = value.replace(/(\d{3})(\d{3})(\d{2})/, '$1.$2,$3');
      } else if (value.length === 9) {
        value = value.replace(/(\d{1})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3,$4');
      } else if (value.length === 10) {
        value = value.replace(/(\d{2})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3,$4');
      } else if (value.length === 11) {
        value = value.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3,$4');
      } else if (value.length === 12) {
        value = value.replace(/(\d{1})(\d{3})(\d{3})(\d{3})(\d{2})/, '$1.$2.$3.$4,$5');
      } else {
        value = value.replace(/(\d{10,})(\d{2})/, '$1.$2');
      }

      this.inputValue = value;
      if (this.page.android) {
        this.fieldValue.android.setSelection(this.fieldValue.android.length());
      }

      const resetedValue = this.prepareToPropagate(value);
      this.propagateChange(resetedValue);
    } else {
     /*  this.propagateChange(''); */
    }
  }

  focusOut(event: any) {
    this.returnPress.emit(event);
  }

  private prepareToPropagate(value: string): string {
    if (value) {
      value = value.replace(/\D/g, '');
      const integerPart = value.slice(0, value.length - 2);
      const decimalPart = value.slice(value.length - 2);
      return parseFloat(integerPart + '.' + decimalPart).toFixed(2);
    }

    return undefined;
  }
}

component.html


<TextField
  [hint]="hint"
  [row]="row"
  [class]="className"
  [col]="col"
  [colSpan]="colSpan"
  [isEnabled]="isEnabled"
  (returnPress)="focusOut($event)"
  id="fieldValue"
  [text]="inputValue"
  keyboardType="number"
  [returnType]="returnKeyType"
  (textChange)="changeValue($event)"
></TextField>

and.. on code i use:

<TextFieldCurrency
          name="amountField"
          [className]="'input-transaction'"
          [(ngModel)]="transactionValues.amount"
          [returnKeyType]="'next'"
          (returnPress)="open('destinationAccount')"
          hint="0,00"
        ></TextFieldCurrency>
immocalcul commented 5 years ago

Good morning all. I read your comments. And I try in a calculation application to display the amount in this way: $2 000 000.

with the help of two programmer we tried the pipes on android. we have come to successful but on ios when registering a $5 digits he will be writing $55 due to the dollars.

another solution that I thought and seen in application on other mobile application its : have a self adjustable textfield in width with a sign of dollards on the outside of it. And create spaces between the numbers in the textfield.

can you explain to me the best way to proceed? your solution works on android but not on ios. Is there any new solution for ios or android?

faelperetta commented 5 years ago

Hi @domzinhuu, thanks for share your solution. I ended up creating my own solution using a set method for the model where I format the value. I using the Intl.NumberFormat to format the value for me according to the current language of the user.

Natym-git commented 5 years ago

Sorry for respawning this topic. But I think that the question resembles a much bigger problem in data formats. Not only on NativeScript but on the web. The reason I know it is that most of the apps that I've developed are related to financial industries.

There is no default way to handle money input. This also explains why the plugins shown above are insufficient to create the desired behavior. Here are some of the problems:

  1. Currency inputs are normally handled in RTL (right-to-left), starting with the 0,00 or 0.00 (depending on your location). If you press any digit (Eg:6) it should display 0.06 and move the cursor to the end of the string. If another key is pressed (Eg:3), the input should display 0.63, and so on. There is no support for RTL in TextField for NativeScript. The only support for this is a UI plugin that reverses a GridLayout.
  2. Currency inputs usually use masking. As @tsonevn mentioned before, there are some options for plugins. None of those (and none of the ones that I am aware of) can be used to :
  • set a pattern (like [*0].[00]) to include N numbers before the dot. You have to specify how many characters you need to use.
  • set default values that are going to be replaced during typing. So we are unable to input 6 inside a 0.00 and get a 6.00.
  1. There is a problem with using float points as currency representation: rounding. The correct way of handling it is using a BigInt variable for the smallest currency with DP = 2 and RM = 1 (according to the law for most countries).

My point here is: this is hard. Believe me, I spent almost a month searching for good a solution and didn't find one. I ended up using currency.js on most of my projects.

I hope this changes in the future, but until that, I really don't think that NativeScript developers are going to do something about it. W3C is struggling with this. JavaScript is trying hard to do something about it (with things like toStringLocale()).

Good luck to anyone finding this post in the future.

Hi @davimello28 ! Could you please provide an example of the use of currency.js in Nativescript?

Thank you!

MateusSpadari commented 4 years ago

Hello @faelperetta i'm struggling with this, can you share you solution ?

edusperoni commented 4 years ago

https://play.nativescript.org/?template=play-ng&id=b2D1DH&v=2

Check currency-input.ts. You can change numberToCurrency and currencyToNumber to fit your needs.

I've added a helper function (toEnd) that will move the cursor to the end of the string.