@angular/forms#ControlValueAccessor TypeScript Examples

The following examples show how to use @angular/forms#ControlValueAccessor. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: value-accessor.directive.ts    From elements with MIT License 6 votes vote down vote up
export class ValueAccessorDirective implements ControlValueAccessor {
  protected lastValue: any;
  constructor(protected el: ElementRef) {}

  writeValue(value: any): void {
    this.el.nativeElement.value = this.lastValue = value == null ? '' : value;
  }

  handleChangeEvent(value: any): void {
    if (value !== this.lastValue) {
      this.lastValue = value;
      this.onChange(value);
      this.writeValue(value);
    }
  }

  handleBlurEvent(): void {
    this.onTouched();
  }

  registerOnChange(fn: (value: any) => void): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  private onChange: (value: any) => void = () => {
    /**/
  };
  private onTouched: () => void = () => {
    /**/
  };
}
Example #2
Source File: mood-radio.component.ts    From onchat-web with Apache License 2.0 6 votes vote down vote up
@Component({
  selector: 'app-mood-radio',
  templateUrl: './mood-radio.component.html',
  styleUrls: ['./mood-radio.component.scss'],
  providers: [{
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => MoodRadioComponent),
    multi: true
  }]
})
export class MoodRadioComponent implements ControlValueAccessor {
  private _value: Mood;

  set value(value: Mood) {
    this._value = value;
    this.onValueChange(value);
  }

  get value(): Mood {
    return this._value;
  }

  readonly mood: typeof Mood = Mood;

  constructor() { }

  private onValueChange(value: Mood) { }

  writeValue(value: Mood): void {
    this._value = value;
  }

  registerOnChange(fn: SafeAny): void {
    this.onValueChange = fn;
  }

  registerOnTouched(fn: SafeAny): void { }
}
Example #3
Source File: abstract-value-accessor.directive.ts    From s-libs with MIT License 6 votes vote down vote up
@Directive()
export abstract class AbstractValueAccessorDirective<T extends HTMLElement>
  implements ControlValueAccessor
{
  onChangeFn!: (value: any) => void;
  onTouchedFn = noop;

  private elementRef: ElementRef;

  constructor(injector: Injector) {
    this.elementRef = injector.get(ElementRef);
  }

  abstract writeValue(value: any): void;

  registerOnChange(fn: (value: any) => void): void {
    this.onChangeFn = fn;
  }

  registerOnTouched(fn: () => void): void {
    this.onTouchedFn = fn;
  }

  protected get element(): T {
    return this.elementRef.nativeElement;
  }
}
Example #4
Source File: code-input.component.ts    From radiopanel with GNU General Public License v3.0 5 votes vote down vote up
@Component({
	selector: 'app-code-input',
	templateUrl: './code-input.component.html',
	providers: [
		{
			provide: [],
			useExisting: forwardRef(() => CodeInputComponent),
			multi: true,
		},
	],
})
export class CodeInputComponent implements OnInit, OnDestroy, ControlValueAccessor {
	@Input() label?: string;
	@Input() placeholder = '';

	private componentDestroyed$: Subject<boolean> = new Subject<boolean>();

	public control: FormControl = new FormControl('');
	public editorOptions = { theme: 'vs-dark', language: 'css' };
	public updateValue = (_: any) => {};

	constructor(public ngControl: NgControl) {
		ngControl.valueAccessor = this;
	}

	private propagateChange(value: any): void {
		if (this.updateValue) {
			return this.updateValue(value);
		}

		if (this.control) {
			this.control.setValue(value);
		}
	}

	public ngOnInit() {
		this.control.valueChanges.pipe(
			takeUntil(this.componentDestroyed$),
		).subscribe((value) => {
			this.propagateChange(value);
		});
	}

	public ngOnDestroy() {
		this.componentDestroyed$.next(true);
		this.componentDestroyed$.complete();
	}

	public writeValue(value: any) {
		setTimeout(() => this.control.setValue(value));
	}

	public registerOnChange(fn: any) {
		this.updateValue = fn;
	}

	public registerOnTouched() {}
}
Example #5
Source File: custom-inputs-fields-form.component.ts    From fyle-mobile-app with MIT License 5 votes vote down vote up
@Component({
  selector: 'app-custom-inputs-fields-form',
  templateUrl: './custom-inputs-fields-form.component.html',
  styleUrls: ['./custom-inputs-fields-form.component.scss'],
  providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: CustomInputsFieldsFormComponent, multi: true }],
})
export class CustomInputsFieldsFormComponent implements OnInit, ControlValueAccessor, OnDestroy, OnChanges {
  @Input() customInputs: CustomInputs[];

  @Input() combinedCustomProperties: CombinedOptions;

  @Input() disableFormElements: boolean;

  onChangeSub: Subscription;

  customFieldsForm: FormGroup;

  customFields: CustomInputs[];

  constructor(private formBuilder: FormBuilder, private injector: Injector) {}

  ngOnInit() {
    this.customFieldsForm = this.formBuilder.group({
      fields: new FormArray([]),
    });
  }

  generateCustomForm() {
    const customFieldsFormArray = this.customFieldsForm?.controls?.fields as FormArray;
    customFieldsFormArray.clear();
    for (const customField of this.customInputs) {
      customFieldsFormArray.push(
        this.formBuilder.group({
          name: [customField.name],
          value: [customField.value],
        })
      );
    }
    customFieldsFormArray.updateValueAndValidity();
    this.customFields = this.customInputs.map((customField, i) => ({
      ...customField,
      control: customFieldsFormArray.at(i),
    }));
  }

  onTouched = () => {};

  ngOnDestroy(): void {
    this.onChangeSub.unsubscribe();
  }

  ngOnChanges() {
    if (this.customFieldsForm?.controls) {
      this.generateCustomForm();
    }
  }

  writeValue(value: any) {
    if (value) {
      this.customFieldsForm.controls.fields.patchValue(value);
    }
  }

  registerOnChange(onChange): void {
    this.onChangeSub = this.customFieldsForm.valueChanges.subscribe(onChange);
  }

  registerOnTouched(onTouched): void {
    this.onTouched = onTouched;
  }
}
Example #6
Source File: boolean-input.component.ts    From radiopanel with GNU General Public License v3.0 5 votes vote down vote up
@Component({
	selector: 'app-boolean-input',
	templateUrl: './boolean-input.component.html',
	providers: [
		{
			provide: [],
			useExisting: forwardRef(() => BooleanInputComponent),
			multi: true,
		},
	],
})
export class BooleanInputComponent implements OnInit, OnDestroy, ControlValueAccessor {
	@Input() label?: string;
	@Input() mode: InputMode = InputMode.EDIT;

	private componentDestroyed$: Subject<boolean> = new Subject<boolean>();

	public inputMode = InputMode;
	public control: FormControl = new FormControl(false);
	public updateValue = (_: any) => {};

	constructor(public ngControl: NgControl) {
		ngControl.valueAccessor = this;
	}

	private propagateChange(value: any): void {
		if (this.updateValue) {
			return this.updateValue(value);
		}

		if (this.control) {
			this.control.setValue(value);
		}
	}

	public ngOnInit() {
		this.control.valueChanges.pipe(
			takeUntil(this.componentDestroyed$),
		).subscribe((value) => {
			this.propagateChange(value);
		});
	}

	public ngOnDestroy() {
		this.componentDestroyed$.next(true);
		this.componentDestroyed$.complete();
	}

	public writeValue(value: any) {
		setTimeout(() => this.control.setValue(value));
	}

	public registerOnChange(fn: any) {
		this.updateValue = fn;
	}

	public registerOnTouched() {}
}
Example #7
Source File: control-value-accessor-connector.ts    From forms-typed with MIT License 5 votes vote down vote up
export class ControlValueAccessorConnector<T, C extends Controls<T> = Controls<T>>
  implements OnInit, OnDestroy, ControlValueAccessor {
  protected subs = new Subscription();
  protected touchIsChildInitiated: boolean;

  public form: TypedFormGroup<T, C>;

  constructor(@Optional() @Self() private directive: NgControl, form: TypedFormGroup<T, C>) {
    if (directive) {
      directive.valueAccessor = this;
    }
    this.form = form;
  }

  ngOnInit(): void {
    if (this.directive && this.directive.control) {
      forEachControlIn(this.form)
        .markAsTouchedSimultaneouslyWith(this.directive.control, () => this.touchIsChildInitiated)
        .addValidatorsTo(this.directive.control);
    }

    const values = this.form.valueChanges.subscribe(v => this.onChange(v));
    const statuses = this.form.statusChanges.subscribe(s => {
      if (this.form.touched) {
        this.onTouch();
      }
    });

    this.subs.add(values);
    this.subs.add(statuses);
  }

  ngOnDestroy(): void {
    this.subs.unsubscribe();
  }
  protected onChange = (_: T) => { };
  protected onTouch = () => { };
  writeValue(obj: any): void {
    this.form.patchValue(obj || {});
  }
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = () => {
      this.touchIsChildInitiated = true;
      fn();
      this.touchIsChildInitiated = false;
    };
  }
  setDisabledState(disable: boolean) {
    disable ? this.form.disable() : this.form.enable();
    forEachControlIn(this.form).call(disable ? 'disable' : 'enable');
  }
}
Example #8
Source File: permission-input.component.ts    From radiopanel with GNU General Public License v3.0 5 votes vote down vote up
@Component({
	selector: 'app-permission-input',
	templateUrl: './permission-input.component.html',
	providers: [
		{
			provide: [],
			useExisting: forwardRef(() => PermissionInputComponent),
			multi: true,
		},
	],
})
export class PermissionInputComponent implements OnInit, OnDestroy, ControlValueAccessor {
	@Input() label?: string;
	@Input() name?: string;
	@Input() value?: string;

	private componentDestroyed$: Subject<boolean> = new Subject<boolean>();

	public uuid = uuid.v4();
	public control: FormControl = new FormControl('');
	public updateValue = (_: any) => {};

	constructor(public ngControl: NgControl) {
		ngControl.valueAccessor = this;
	}

	private propagateChange(value: any): void {
		if (this.updateValue) {
			return this.updateValue(value);
		}

		if (this.control) {
			this.control.setValue(value);
		}
	}

	public ngOnInit() {
		this.control.valueChanges.pipe(
			takeUntil(this.componentDestroyed$),
		).subscribe((value) => {
			this.propagateChange(value);
		});
	}

	public ngOnDestroy() {
		this.componentDestroyed$.next(true);
		this.componentDestroyed$.complete();
	}

	public writeValue(value: any) {
		setTimeout(() => this.control.setValue(value));
	}

	public registerOnChange(fn: any) {
		this.updateValue = fn;
	}

	public registerOnTouched() {}
}
Example #9
Source File: task-manage-form.component.ts    From ng-ant-admin with MIT License 5 votes vote down vote up
@Component({
  selector: 'app-task-manage-form',
  templateUrl: './task-manage-form.component.html',
  styleUrls: ['./task-manage-form.component.less'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [EXE_COUNTER_VALUE_ACCESSOR]
})
export class TaskManageFormComponent implements OnInit, ControlValueAccessor {
  validateForm!: FormGroup;
  onChange: (value: string) => void = () => null;
  onTouched: () => void = () => null;

  constructor(private fb: FormBuilder) {
  }

  initForm(): void {
    this.validateForm = this.fb.group({
      taskName: [null, [Validators.required]],
      taskDesc: [null, [Validators.required]],
      executor: [null, [Validators.required]],
      responsible: [null, [Validators.required]],
      effectiveTime: [null, [Validators.required]],
      taskType: [null, [Validators.required]],
    });
  }

  checkForm(): boolean {
    // 用下面方式让formArray每一项置脏(如果有formArray的话,这里只是做个示例)
    /* ((this.validateForm.get('fontImgArray') as FormArray).controls).forEach(item => {
       fnCheckForm(item as FormGroup);
     })*/
    return !fnCheckForm(this.validateForm);
  }

  ngOnInit(): void {
    this.initForm();

    this.validateForm.valueChanges.pipe(debounceTime(500),
      distinctUntilChanged(),).subscribe(res => {
      this.onChange(res);
    })
  }

  registerOnChange(fn: NzSafeAny): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: NzSafeAny): void {
  }

  setDisabledState(isDisabled: boolean): void {
  }

  writeValue(obj: TaskManageObj): void {
    if (obj) {
      this.validateForm.patchValue(obj);
    }
  }

}
Example #10
Source File: account-select.component.ts    From digital-bank-ui with Mozilla Public License 2.0 5 votes vote down vote up
@Component({
  providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AccountSelectComponent), multi: true }],
  selector: 'ngx-account-select',
  templateUrl: './account-select.component.html',
})
export class AccountSelectComponent implements ControlValueAccessor, OnInit {
  formControl: FormControl;

  @Input() title: string;

  @Input() required: boolean;

  @Input() type: AccountType;

  accounts: Observable<Account[]>;

  _onTouchedCallback: () => void = noop;

  private _onChangeCallback: (_: any) => void = noop;

  constructor(private accountingService: AccountingService) {}

  ngOnInit(): void {
    this.formControl = new FormControl('');

    this.accounts = this.formControl.valueChanges.pipe(
      distinctUntilChanged(),
      debounceTime(500),
      tap(name => this.changeValue(name)),
      filter(name => name),
      switchMap(name => this.onSearch(name)),
    );
  }

  changeValue(value: string): void {
    this._onChangeCallback(value);
  }

  writeValue(value: any): void {
    this.formControl.setValue(value);
  }

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

  registerOnTouched(fn: any): void {
    this._onTouchedCallback = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    if (isDisabled) {
      this.formControl.disable();
    } else {
      this.formControl.enable();
    }
  }

  onSearch(searchTerm?: string): Observable<Account[]> {
    const fetchRequest: FetchRequest = {
      page: {
        pageIndex: 0,
        size: 5,
      },
      searchTerm: searchTerm,
    };

    return this.accountingService.fetchAccounts(fetchRequest, this.type).pipe(map((accountPage: AccountPage) => accountPage.accounts));
  }

}
Example #11
Source File: textarea.component.ts    From xBull-Wallet with GNU Affero General Public License v3.0 5 votes vote down vote up
@Component({
  selector: 'app-textarea',
  templateUrl: './textarea.component.html',
  styleUrls: ['./textarea.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => TextareaComponent),
      multi: true
    }
  ]
})
export class TextareaComponent implements OnInit, ControlValueAccessor {
  @Input() disabled = false;
  @Input() title = 'Textarea';
  @Input() placeholder = 'Write here...';

  value = '';

  onChange: (quantity: any) => void = (quantity) => {};

  onTouched: () => void = () => {};

  constructor() { }

  ngOnInit(): void {
  }

  onInput(value: any): void {
    this.writeValue(value);
    this.onTouched();
    this.onChange(this.value);
  }

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

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  writeValue(value: any): void {
    if (!!value) {
      this.value = value;
    } else {
      this.value = '';
    }
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

}
Example #12
Source File: checkbox.component.ts    From taiga-front-next with GNU Affero General Public License v3.0 5 votes vote down vote up
@Component({
  selector: 'tg-checkbox',
  templateUrl: './checkbox.component.html',
  styleUrls: ['./checkbox.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: TgCheckboxComponent,
      multi: true,
    },
  ],
})
export class TgCheckboxComponent implements ControlValueAccessor {

  constructor(
    private renderer: Renderer2
  ) {}

  @ViewChild('checkbox') public checkbox: ElementRef;
  @Input() labelPosition: LabelPosition = 'after';
  @Input() ariaLabel?: string | null = null;
  @Input() id = `tg-checkbox-${nextId++}`;

  // tslint:disable-next-line: variable-name
  onChange = (_isChecked: boolean) => {};
  onTouched = () => {};

  public writeValue(isChecked: boolean): void {
    this.onChange(isChecked);
  }

  public registerOnChange(fn: (isDisabled: boolean) => void): void {
    this.onChange = fn;
  }

  public registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  public setDisabledState(isDisabled: boolean): void {
    this.renderer.setProperty(this.checkbox.nativeElement, 'disabled', isDisabled);
  }

  public check = (event: any): void => {
    const isChecked: boolean = event.target.checked;
    this.writeValue(isChecked);
  }
}
Example #13
Source File: rating.component.ts    From Angular-Cookbook with MIT License 5 votes vote down vote up
@Component({
  selector: 'app-rating',
  templateUrl: './rating.component.html',
  styleUrls: ['./rating.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => RatingComponent),
      multi: true,
    },
  ],
})
export class RatingComponent implements OnInit, ControlValueAccessor {
  value = 2;
  hoveredRating = 2;
  isMouseOver = false;
  @Input() disabled = false;

  constructor() {}

  onChange: any = () => {};
  onTouched: any = () => {};

  ngOnInit(): void {}
  onRatingMouseEnter(rating: number) {
    if (this.disabled) return;
    this.hoveredRating = rating;
    this.isMouseOver = true;
  }
  onRatingMouseLeave() {
    this.hoveredRating = null;
    this.isMouseOver = false;
  }

  selectRating(rating: number) {
    if (this.disabled) return;
    this.value = rating;
    this.onChange(rating);
  }

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

  registerOnTouched(fn: any) {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  writeValue(value: number) {
    this.value = value;
  }
}
Example #14
Source File: password-input.component.ts    From ReCapProject-Frontend with MIT License 5 votes vote down vote up
@Component({
  selector: 'app-password-input',
  templateUrl: './password-input.component.html',
  styleUrls: ['./password-input.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => PasswordInputComponent),
      multi: true,
    },
  ],
  host: {
    '(change)': 'onChange($event.target.value)',
    '(input)': 'onChange($event.target.value)',
    '(blur)': 'onTouched()',
  },
})
export class PasswordInputComponent implements OnInit, ControlValueAccessor {
  @Input() id: string = 'password-input';
  @Input() label: string = 'Enter your password';
  @Input() invalidFeedback: string = 'Please enter your password.';
  passwordHidden: boolean = true;
  value: string = '';
  onChange: any = () => {};
  onTouched: any = () => {};
  disabled = false;

  @Input() errors: any;
  @Input() touched: any;
  @Input() dirty: any;

  constructor() {}

  ngOnInit(): void {}

  writeValue(value: string): void {
    this.value = value;
  }

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

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  togglePasswordHidden() {
    this.passwordHidden = !this.passwordHidden;
  }

  isPasswordHidden(): string {
    return this.passwordHidden ? 'password' : 'text';
  }

  isPasswordHiddenIcon(): string {
    return this.passwordHidden ? 'fa-eye-slash' : 'fa-eye text-primary';
  }
}
Example #15
Source File: ledger-select.component.ts    From digital-bank-ui with Mozilla Public License 2.0 5 votes vote down vote up
@Component({
  providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => LedgerSelectComponent), multi: true }],
  selector: 'ngx-ledger-select',
  templateUrl: './ledger-select.component.html',
})
export class LedgerSelectComponent implements ControlValueAccessor, OnInit {
  _onTouchedCallback: () => void = noop;

  private _onChangeCallback: (_: any) => void = noop;

  formControl: FormControl;

  @Input() title: string;

  @Input() required: boolean;

  @Input() type: AccountType;

  ledgers: Observable<Ledger[]>;

  constructor(private accountingService: AccountingService) {}

  ngOnInit(): void {
    this.formControl = new FormControl('');

    this.ledgers = this.formControl.valueChanges.pipe(
      distinctUntilChanged(),
      debounceTime(500),
      tap(name => this.changeValue(name)),
      filter(name => name),
      switchMap(name => this.onSearch(name)),
    );
  }

  changeValue(value: string): void {
    this._onChangeCallback(value);
  }

  writeValue(value: any): void {
    this.formControl.setValue(value);
  }

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

  registerOnTouched(fn: any): void {
    this._onTouchedCallback = fn;
  }

  onSearch(searchTerm?: string): Observable<Ledger[]> {
    const fetchRequest: FetchRequest = {
      page: {
        pageIndex: 0,
        size: 5,
      },
      searchTerm,
    };

    return this.accountingService.fetchLedgers(true, fetchRequest, this.type).pipe(map((ledgerPage: LedgerPage) => ledgerPage.ledgers));
  }
}
Example #16
Source File: nas-model.directive.ts    From s-libs with MIT License 5 votes vote down vote up
#valueAccessor: ControlValueAccessor;
Example #17
Source File: time-input.component.ts    From radiopanel with GNU General Public License v3.0 5 votes vote down vote up
@Component({
	selector: 'app-time-input',
	templateUrl: './time-input.component.html',
	providers: [
		{
			provide: [],
			useExisting: forwardRef(() => TimeInputComponent),
			multi: true,
		},

		// Custom date stuff
		{ provide: DateTimeAdapter, useClass: MomentDateTimeAdapter, deps: [OWL_DATE_TIME_LOCALE] },
		{ provide: OWL_DATE_TIME_FORMATS, useValue: MY_CUSTOM_FORMATS }
	],
})
export class TimeInputComponent implements OnInit, OnDestroy, ControlValueAccessor {
	@Input() label?: string;
	@Input() placeholder = '';
	@Input() type = 'text';
	@Input() disabled = false;
	@Input() icon?: string;
	@Input() max?: string;
	@Input() mode: InputMode = InputMode.EDIT;

	private componentDestroyed$: Subject<boolean> = new Subject<boolean>();

	public inputMode = InputMode;
	public control: FormControl = new FormControl('');
	public updateValue = (_: any) => {};

	constructor(public ngControl: NgControl) {
		ngControl.valueAccessor = this;
	}

	private propagateChange(value: any): void {
		if (this.updateValue) {
			return this.updateValue(value);
		}

		if (this.control) {
			this.control.setValue(value);
		}
	}

	public ngOnInit() {
		if (this.disabled) {
			this.control.disable();
		}

		this.control.valueChanges.pipe(
			takeUntil(this.componentDestroyed$),
		).subscribe((value) => {
			this.propagateChange(value);
		});
	}

	public ngOnDestroy() {
		this.componentDestroyed$.next(true);
		this.componentDestroyed$.complete();
	}

	public writeValue(value: any) {
		setTimeout(() => this.control.setValue(value));
	}

	public registerOnChange(fn: any) {
		this.updateValue = fn;
	}

	public registerOnTouched() {}
}
Example #18
Source File: fy-select-vehicle.component.ts    From fyle-mobile-app with MIT License 5 votes vote down vote up
@Component({
  selector: 'app-fy-select-vehicle',
  templateUrl: './fy-select-vehicle.component.html',
  styleUrls: ['./fy-select-vehicle.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FySelectVehicleComponent),
      multi: true,
    },
  ],
})
export class FySelectVehicleComponent implements OnInit, ControlValueAccessor {
  @Input() mandatory = false;

  @Input() label = 'Type';

  @Input() isAmountDisabled = false;

  @Input() mileageConfig;

  private ngControl: NgControl;

  private innerValue;

  get valid() {
    if (this.ngControl.touched) {
      return this.ngControl.valid;
    } else {
      return true;
    }
  }

  private onTouchedCallback: () => void = noop;

  private onChangeCallback: (_: any) => void = noop;

  constructor(private injector: Injector) {}

  ngOnInit() {
    this.ngControl = this.injector.get(NgControl);
  }

  get value(): any {
    return this.innerValue;
  }

  set value(v: any) {
    if (v !== this.innerValue) {
      this.innerValue = v;

      this.onChangeCallback(v);
    }
  }

  onBlur() {
    this.onTouchedCallback();
  }

  writeValue(value: any): void {
    if (value !== this.innerValue) {
      this.innerValue = value;
    }
  }

  selectType(value: string) {
    this.value = value;
  }

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

  registerOnTouched(fn: any) {
    this.onTouchedCallback = fn;
  }
}
Example #19
Source File: textarea-input.component.ts    From radiopanel with GNU General Public License v3.0 5 votes vote down vote up
@Component({
	selector: 'app-textarea-input',
	templateUrl: './textarea-input.component.html',
	providers: [
		{
			provide: [],
			useExisting: forwardRef(() => TextareaInputComponent),
			multi: true,
		},
	],
})
export class TextareaInputComponent implements OnInit, OnDestroy, ControlValueAccessor {
	@Input() label?: string;
	@Input() placeholder = '';
	@Input() mode: InputMode = InputMode.EDIT;

	private componentDestroyed$: Subject<boolean> = new Subject<boolean>();

	public inputMode = InputMode;
	public control: FormControl = new FormControl('');
	public updateValue = (_: any) => {};

	constructor(public ngControl: NgControl) {
		ngControl.valueAccessor = this;
	}

	private propagateChange(value: any): void {
		if (this.updateValue) {
			return this.updateValue(value);
		}

		if (this.control) {
			this.control.setValue(value);
		}
	}

	public ngOnInit() {
		this.control.valueChanges.pipe(
			takeUntil(this.componentDestroyed$),
		).subscribe((value) => {
			this.propagateChange(value);
		});
	}

	public ngOnDestroy() {
		this.componentDestroyed$.next(true);
		this.componentDestroyed$.complete();
	}

	public writeValue(value: any) {
		setTimeout(() => this.control.setValue(value));
	}

	public registerOnChange(fn: any) {
		this.updateValue = fn;
	}

	public registerOnTouched() {}
}
Example #20
Source File: np-textarea.component.ts    From np-ui-lib with MIT License 4 votes vote down vote up
@Component({
  selector: "np-textarea",
  templateUrl: "./np-textarea.component.html",
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.Default,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => NpTextareaComponent),
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => NpTextareaComponent),
      multi: true,
    },
  ],
})
export class NpTextareaComponent implements ControlValueAccessor, Validator {
  private static controlCount = 1;

  @Input() rows: number;
  @Input() cols: number;
  @Input() resize: boolean = true;
  @Input() minLength: number;
  @Input() maxLength: number;
  @Input() placeholder: string = "";
  @Input() readOnly: boolean;
  @Input() autoFocus: boolean;
  @Input() tabIndex: number;
  @Input() styleClass: string;
  @Input() inputId: string = `np-textarea_${NpTextareaComponent.controlCount++}`;

  @Output() onChange: EventEmitter<any> = new EventEmitter();
  @Output() onFocus: EventEmitter<any> = new EventEmitter();
  @Output() onBlur: EventEmitter<any> = new EventEmitter();

  @ViewChild("control") inputViewChild: ElementRef;

  innerValue: string;
  isDisabled: boolean = false;
  focused: boolean = false;

  private onChangeCallback: (_: any) => void = () => { };
  private onTouchedCallback: () => void = () => { };

  get value(): string {
    return this.innerValue ? this.innerValue : null;
  }

  set value(v: string) {
    if (v !== this.innerValue) {
      this.innerValue = v;
      this.onChangeCallback(v);
      this.onChange.emit(v);
    }
  }

  writeValue(v: string): void {
    if (v !== this.innerValue) {
      this.innerValue = v;
    }
  }

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

  registerOnTouched(fn: any): void {
    this.onTouchedCallback = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
  }

  validate(): any {
    if (
      this.minLength !== undefined &&
      this.value &&
      this.value.length < this.minLength
    ) {
      return {
        minLength: {
          valid: false,
        },
      };
    }
    if (
      this.maxLength !== undefined &&
      this.value &&
      this.value.length > this.maxLength
    ) {
      return {
        maxLength: {
          valid: false,
        },
      };
    }
  }

  focus(): void {
    this.inputViewChild.nativeElement.focus();
  }

  _onInputChange(event: any): void {
    this.value = event.target.value;
  }

  _onBlur($event: any): void {
    this.focused = false;
    this.onTouchedCallback();
    this.onBlur.emit($event);
  }

  _onFocus($event: any): void {
    this.focused = true;
    this.onFocus.emit($event);
  }
}
Example #21
Source File: data-input.component.ts    From radiopanel with GNU General Public License v3.0 4 votes vote down vote up
@Component({
	selector: 'app-data-input',
	templateUrl: './data-input.component.html',
	providers: [
		{
			provide: [],
			useExisting: forwardRef(() => DataInputComponent),
			multi: true,
		},
	],
})
export class DataInputComponent implements OnInit, OnDestroy, ControlValueAccessor, OnChanges {
	@Input() label?: string;
	@Input() placeholder = '';
	@Input() multiple = false;
	@Input() options = [];
	@Input() itemLabel = ['name'];
	@Input() itemValue = ['uuid'];
	@Input() endpoint: string;

	private componentDestroyed$: Subject<boolean> = new Subject<boolean>();

	public data: any;
	public control: FormControl = new FormControl('');
	public updateValue = (_: any) => { };

	constructor(
		public ngControl: NgControl,
		private dynamicInputService: DynamicInputService
	) {
		ngControl.valueAccessor = this;
	}

	public ngOnInit() {
		this.doSearch();
		this.control.valueChanges.pipe(
			takeUntil(this.componentDestroyed$),
		).subscribe((value) => {
			if (!this.multiple) {
				return this.propagateChange(pathOr(value, ['value'])(value));
			}

			this.propagateChange((value || []).map((roleItem) => pathOr(roleItem, ['value'])(roleItem)));
		});
	}

	private propagateChange(value: any): void {
		if (this.updateValue) {
			return this.updateValue(value);
		}

		if (this.control) {
			this.control.setValue(value);
		}
	}

	public ngOnChanges(changes): void {
		this.doSearch();
	}

	public handleSearch(value) {
		// TODO: add debounce
		this.doSearch(value.term);
	}

	public doSearch(searchString = '') {
		this.dynamicInputService.fetchData(this.endpoint, { name: searchString, pagesize: 500 })
			.pipe(
				first(),
				map((item) => {
					return item.map((item) => ({
						value: path(this.itemValue)(item),
						label: path(this.itemLabel)(item)
					}));
				})
			)
			.subscribe(data => this.data = data);
	}

	public ngOnDestroy() {
		this.componentDestroyed$.next(true);
		this.componentDestroyed$.complete();
	}

	public writeValue(value: any) {
		if (!value) {
			return;
		}

		this.dynamicInputService.fetchData(this.endpoint)
			.pipe(first())
			.subscribe(data => {
				if (!this.multiple) {
					return setTimeout(() => this.control.setValue({
						value,
						label: path(this.itemLabel)(data.find((contentItem) => path(this.itemValue)(contentItem) === value))
					}));
				}

				const mappedValue = value.map((item) => ({
					value: item,
					label: path(this.itemLabel)(data.find((contentItem) => path(this.itemValue)(contentItem) === item))
				}));
				this.control.setValue(mappedValue);
			});
	}

	public registerOnChange(fn: any) {
		this.updateValue = fn;
	}

	public registerOnTouched() {}
}
Example #22
Source File: np-time-picker.component.ts    From np-ui-lib with MIT License 4 votes vote down vote up
@Component({
  selector: "np-time-picker",
  templateUrl: "np-time-picker.component.html",
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.Default,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => NpTimePickerComponent),
      multi: true,
    },
  ],
})
export class NpTimePickerComponent
  implements ControlValueAccessor, AfterViewInit, AfterContentInit {
  private static controlCount = 1;

  @Input() defaultOpen: boolean = false;
  @Input() is24Hours: boolean = false;
  @Input() showNowButton: boolean = false;
  @Input() hideSeconds: boolean = false;
  @Input() placeholder: string = "";
  @Input() readOnly: boolean;
  @Input() autoFocus: boolean;
  @Input() tabIndex: number;
  @Input() styleClass: string;
  @Input() inputId: string = `np-time-picker_${NpTimePickerComponent.controlCount++}`;

  @Output() onChange: EventEmitter<any> = new EventEmitter();
  @Output() onFocus: EventEmitter<any> = new EventEmitter();
  @Output() onBlur: EventEmitter<any> = new EventEmitter();

  @ViewChild("templatePortalContent") templatePortalContent: TemplateRef<any>;
  @ViewChild("control") inputViewChild: ElementRef;

  hours: number[] = [];
  minutes: number[] = [];
  seconds: number[] = [];
  isOpen: boolean = false;
  selectedHour: number;
  selectedMinute: number;
  selectedSecond: number;
  selectedAMPM: string = "AM";
  pattern: any;
  innerValue: string;
  isDisabled: boolean = false;
  focused: boolean = false;
  private templatePortal: TemplatePortal<any>;
  private overlayRef: OverlayRef;
  private onChangeCallback: (_: any) => void = () => { };
  private onTouchedCallback: () => void = () => { };

  constructor(
    public overlay: Overlay,
    private viewContainerRef: ViewContainerRef,
    private overlayPositionBuilder: OverlayPositionBuilder,
    private elementRef: ElementRef
  ) { }

  ngAfterViewInit(): void {
    const positionStrategy = this.overlayPositionBuilder
      .flexibleConnectedTo(this.elementRef)
      .withPositions(TopBottomOverlayPositions);
    this.overlayRef = this.overlay.create({
      positionStrategy,
      hasBackdrop: true,
      backdropClass: "np-time-picker-backdrop",
      scrollStrategy: this.overlay.scrollStrategies.reposition(),
      panelClass: this.styleClass,
    });
    this.templatePortal = new TemplatePortal(
      this.templatePortalContent,
      this.viewContainerRef
    );
    this.overlayRef.backdropClick().subscribe(() => this._close());
  }

  get value(): string {
    return this.innerValue ? this.innerValue : null;
  }

  set value(v: string) {
    if (v !== this.innerValue) {
      this.innerValue = v;
      this.onChangeCallback(v);
      this.onChange.emit(v);
    }
  }

  writeValue(v: string): void {
    if (v !== this.innerValue) {
      this.innerValue = v;
      this._extractValues();
    }
  }

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

  registerOnTouched(fn: any): void {
    this.onTouchedCallback = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
  }

  ngAfterContentInit(): void {
    this.hours = [];
    const hoursList = [];
    this.pattern = new RegExp(this._getRegEx());
    if (this.is24Hours) {
      for (let i = 0; i < 24; i++) {
        hoursList.push(i);
      }
    } else {
      for (let i = 0; i < 12; i++) {
        hoursList.push(i);
      }
    }
    this.hours = hoursList;
    const minuteAndSeconds = [];
    for (let i = 0; i < 60; i++) {
      minuteAndSeconds.push(i);
    }
    this.minutes = minuteAndSeconds;
    this.seconds = minuteAndSeconds;
  }

  get24hrsTimeFormat(): string {
    if (this.is24Hours) {
      return this.value;
    }
    return this.timeConvert12to24(this.value);
  }

  get12hrsTimeFormat(): string {
    if (this.is24Hours) {
      return this.timeConvert24to12(this.value);
    }
    return this.value;
  }

  focus(): void {
    this.inputViewChild.nativeElement.focus();
  }

  _minusHour(): void {
    this.selectedHour =
      this.selectedHour === null || this.selectedHour === 0
        ? this.is24Hours
          ? 23
          : 11
        : this.selectedHour - 1;
    this._setValue();
  }

  _minusMinute(): void {
    this.selectedMinute =
      this.selectedMinute === null || this.selectedMinute === 0
        ? 59
        : this.selectedMinute - 1;
    if (this.selectedMinute === 59) {
      this.selectedHour =
        this.selectedHour === null || this.selectedHour === 0
          ? this.is24Hours
            ? 23
            : 11
          : this.selectedHour - 1;
    }
    this._setValue();
  }

  _minusSecond(): void {
    this.selectedSecond =
      this.selectedSecond === null || this.selectedSecond === 0
        ? 59
        : this.selectedSecond - 1;
    if (this.selectedSecond === 59) {
      this.selectedMinute =
        this.selectedMinute === null || this.selectedMinute === 0
          ? 59
          : this.selectedMinute - 1;
      if (this.selectedMinute === 59) {
        this.selectedHour =
          this.selectedHour === null || this.selectedHour === 0
            ? this.is24Hours
              ? 23
              : 11
            : this.selectedHour - 1;
      }
    }
    this._setValue();
  }

  _addHour(): void {
    if (this.selectedHour > (this.is24Hours ? 23 : 11)) {
      this.selectedHour = this.is24Hours ? 23 : 11;
    }
    this.selectedHour =
      this.selectedHour === null ||
        this.selectedHour === (this.is24Hours ? 23 : 11)
        ? 0
        : this.selectedHour + 1;
    this._setValue();
  }

  _addMinute(): void {
    if (this.selectedMinute > 59) {
      this.selectedMinute = 59;
    }
    this.selectedMinute =
      this.selectedMinute === null || this.selectedMinute === 59
        ? 0
        : this.selectedMinute + 1;
    if (this.selectedMinute === 0) {
      this.selectedHour =
        this.selectedHour === null ||
          this.selectedHour === (this.is24Hours ? 23 : 11)
          ? 0
          : this.selectedHour + 1;
    }
    this._setValue();
  }

  _addSecond(): void {
    if (this.selectedSecond > 59) {
      this.selectedSecond = 59;
    }
    this.selectedSecond =
      this.selectedSecond === null || this.selectedSecond === 59
        ? 0
        : this.selectedSecond + 1;
    if (this.selectedSecond === 0) {
      this.selectedMinute =
        this.selectedMinute === null || this.selectedMinute === 59
          ? 0
          : this.selectedMinute + 1;
      if (this.selectedMinute === 0) {
        this.selectedHour =
          this.selectedHour === null ||
            this.selectedHour === (this.is24Hours ? 23 : 11)
            ? 0
            : this.selectedHour + 1;
      }
    }
    this._setValue();
  }

  _changeTime($event: any, arg: string): void {
    if (arg === "hour") {
      this.selectedHour = Number($event.target.value);
    } else if (arg === "minute") {
      this.selectedMinute = Number($event.target.value);
    } else if (arg === "second") {
      this.selectedSecond = Number($event.target.value);
    } else if (arg === "AMPM") {
      this.selectedAMPM = $event.target.value;
    }
    this._setValue();
  }

  _setValue(): void {
    if (this.is24Hours) {
      this.value = `${this._getHours()}:${this._getMinutes()}${this._getSeconds()}`;
    } else {
      this.value = `${this._getHours()}:${this._getMinutes()}${this._getSeconds()} ${this.selectedAMPM
        }`;
    }
  }

  _getHours(): string {
    return this.selectedHour
      ? this.selectedHour.toString().padStart(2, "0")
      : "00";
  }

  _getMinutes(): string {
    return this.selectedMinute
      ? this.selectedMinute.toString().padStart(2, "0")
      : "00";
  }

  _getSeconds(): string {
    return this.hideSeconds
      ? ""
      : ":" +
      (this.selectedSecond
        ? this.selectedSecond.toString().padStart(2, "0")
        : "00");
  }

  private timeConvert12to24(time: string): string {
    const PM: boolean = time.match("PM") ? true : false;
    const timeArray: string[] = time.split(":");
    const min: string = timeArray[1];
    let hour: string;
    let sec: string;
    if (PM) {
      hour = (12 + parseInt(timeArray[0], 10)).toString();
      sec = timeArray[2].replace("PM", "");
    } else {
      hour = timeArray[0];
      sec = timeArray[2].replace("AM", "");
    }
    return `${hour}:${min}:${sec}`;
  }

  private timeConvert24to12(time: string): string {
    const values = time.split(":");
    const hour24 = Number(values[0]);
    const hour12 = hour24 % 12 || 12;
    const ampm = hour24 < 12 || hour24 === 24 ? "AM" : "PM";
    return `${hour12
      .toString()
      .padStart(2, "0")}:${values[1]
        .toString()
        .padStart(2, "0")}:${values[2].toString().padStart(2, "0")} ${ampm}`;
  }

  _toggleTimePicker(): void {
    if (this.isOpen) {
      this._close();
    } else {
      this._open();
    }
  }

  _open(): void {
    if (this.defaultOpen === true || this.isDisabled || this.readOnly) {
      return;
    }
    this.isOpen = true;
    if (!this.overlayRef.hasAttached()) {
      this.overlayRef.attach(this.templatePortal);
    }
  }

  _close(): void {
    if (this.defaultOpen) {
      return;
    }
    this.isOpen = false;
    this.overlayRef.detach();
    this.inputViewChild.nativeElement.focus();
  }

  _extractValues(): void {
    if (
      this.value === undefined ||
      this.value === null ||
      !this.pattern.test(this.value)
    ) {
      this._clearSelectedValue();
      return;
    }
    if (this.is24Hours === true) {
      const result24 = this.timeConvert12to24(this.value);
      const timeArray24 = result24.split(":");
      this.selectedHour = Number(timeArray24[0]);
      this.selectedMinute = Number(timeArray24[1]);
      if (!this.hideSeconds) {
        this.selectedSecond = Number(timeArray24[2]);
      }
    } else {
      const result = this.value.split(" ");
      this.selectedAMPM = result[1].toLowerCase() === "am" ? "AM" : "PM";
      const timeArray = result[0].split(":");
      this.selectedHour = Number(timeArray[0]);
      this.selectedMinute = Number(timeArray[1]);
      if (!this.hideSeconds) {
        this.selectedSecond = Number(timeArray[2]);
      }
    }

    let isChanged = false;
    if (this.selectedHour > (this.is24Hours ? 23 : 11)) {
      isChanged = true;
      this.selectedHour = this.is24Hours ? 23 : 11;
    }
    if (this.selectedMinute > 59) {
      isChanged = true;
      this.selectedMinute = 59;
    }
    if (this.selectedSecond > 59) {
      isChanged = true;
      this.selectedSecond = 59;
    }
    if (isChanged) {
      this._setValue();
    }
  }

  _selectNowTime(): void {
    const today = new Date();
    let nowTime = `${today
      .getHours()
      .toString()
      .padStart(2, "0")}:${today.getMinutes().toString().padStart(2, "0")}${this.hideSeconds
        ? ""
        : ":" + today.getSeconds().toString().padStart(2, "0")
      }`;
    if (!this.is24Hours) {
      nowTime = this.timeConvert24to12(nowTime);
    }
    this.value = nowTime;
    this._extractValues();
    this._close();
  }

  _clear(): void {
    if (this.isDisabled || this.readOnly) {
      return;
    }
    this.value = null;
    this._clearSelectedValue();
    this._close();
  }

  _clearSelectedValue(): void {
    this.selectedHour = null;
    this.selectedMinute = null;
    this.selectedSecond = null;
  }

  _changeAMPM(): void {
    this.selectedAMPM = this.selectedAMPM === "AM" ? "PM" : "AM";
    this._setValue();
  }

  _onKeydown(event: KeyboardEvent): void {
    if (event.key === "Tab" || event.key === "Escape") {
      this._close();
    }
    if (event.key === "ArrowDown") {
      this._open();
      event.preventDefault();
    }
  }

  _onInputChange($event: any): void {
    let time = $event.target.value.trim();
    time = time.toUpperCase();
    const isValid = this.pattern.test(time);
    if (!isValid) {
      $event.target.value = "";
    }
    this.value = isValid ? time : null;
    this._extractValues();
  }

  _onBlur($event: any): void {
    this.focused = false;
    this.onTouchedCallback();
    this.onBlur.emit($event);
  }

  _onFocus($event: any): void {
    this.focused = true;
    this.onFocus.emit($event);
  }

  _getRegEx(): string {
    if (this.is24Hours) {
      if (this.hideSeconds) {
        return "^(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[0-5][0-9])$";
      } else {
        return "^(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[0-5][0-9]):(0[0-9]|[0-5][0-9])$";
      }
    } else {
      if (this.hideSeconds) {
        return "^(0[0-9]|1[0-1]):(0[0-9]|[0-5][0-9]) ?(AM|PM)$";
      } else {
        return "^(0[0-9]|1[0-1]):(0[0-9]|[0-5][0-9]):(0[0-9]|[0-5][0-9]) ?(AM|PM)$";
      }
    }
  }
}
Example #23
Source File: ngx-mat-timepicker.directive.ts    From ngx-mat-timepicker with MIT License 4 votes vote down vote up
@Directive({
    selector: "[ngxMatTimepicker]",
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: NgxMatTimepickerDirective,
            multi: true
        }
    ],
    // tslint:disable-next-line:no-host-metadata-property
    host: {
        "[disabled]": "disabled",
        "(blur)": "onTouched()",
    },
})
export class NgxMatTimepickerDirective implements ControlValueAccessor, OnDestroy, OnChanges {

    get element(): HTMLElement {
        return this._elementRef && this._elementRef.nativeElement;
    }

    get format(): number {
        return this._format;
    }

    @Input()
    set format(value: number) {
        this._format = +value === 24 ? 24 : 12;
        const isDynamicallyChanged = value && (this._previousFormat && this._previousFormat !== this._format);

        if (isDynamicallyChanged) {
            this.value = this._value;
            this._timepicker.updateTime(this._value);
        }
        this._previousFormat = this._format;
    }

    get max(): string | DateTime {
        return this._max;
    }

    @Input()
    set max(value: string | DateTime) {
        if (typeof value === "string") {
            this._max = NgxMatTimepickerAdapter.parseTime(value, {locale: this._locale, format: this.format});

            return;
        }
        this._max = value;
    }

    get min(): string | DateTime {
        return this._min;
    }

    @Input()
    set min(value: string | DateTime) {
        if (typeof value === "string") {
            this._min = NgxMatTimepickerAdapter.parseTime(value, {locale: this._locale, format: this.format});

            return;
        }
        this._min = value;
    }

    @Input("ngxMatTimepicker")
    set timepicker(picker: NgxMatTimepickerComponent) {
        this._registerTimepicker(picker);
    }

    get value(): string {
        if (!this._value) {
            return "";
        }

        return NgxMatTimepickerAdapter.toLocaleTimeString(this._value, {format: this.format, locale: this._locale});
    }

    @Input()
    set value(value: string) {
        if (!value) {
            this._value = "";
            this._updateInputValue();

            return;
        }
        const time = NgxMatTimepickerAdapter.formatTime(value, {locale: this._locale, format: this.format});
        const isAvailable = NgxMatTimepickerAdapter.isTimeAvailable(
            time,
            this._min as DateTime,
            this._max as DateTime,
            "minutes",
            this._timepicker.minutesGap,
            this._format
        );

        if (isAvailable) {
            this._value = time;
            this._updateInputValue();

            return;
        }
        console.warn("Selected time doesn't match min or max value");
    }

    private set _defaultTime(time: string) {
        this._timepicker.defaultTime = NgxMatTimepickerAdapter.formatTime(time, {
            locale: this._locale,
            format: this.format
        });
    }

    // TODO: IMPROVE DETECTING (INJECT) MAT-FORM-FIELD IF PRESENT
    @HostBinding("attr.cdkOverlayOrigin") cdkOverlayOrigin: CdkOverlayOrigin =
        new CdkOverlayOrigin(this._matFormField ? this._matFormField.getConnectedOverlayOrigin() : this._elementRef);
    @Input() disableClick: boolean;
    @Input() disabled: boolean;

    private _format = 12;
    private _max: string | DateTime;
    private _min: string | DateTime;
    private _previousFormat: number;
    private _subsCtrl$: Subject<void> = new Subject<void>();
    private _timepicker: NgxMatTimepickerComponent;
    private _value: string = "";

    constructor(private _elementRef: ElementRef,
                @Optional() @Inject(MatFormField) private _matFormField: MatFormField,
                @Inject(NGX_MAT_TIMEPICKER_LOCALE) private _locale: string) {
    }

    ngOnChanges(changes: SimpleChanges) {
        if (changes.value && changes.value.currentValue) {
            this._defaultTime = changes.value.currentValue;
        }
    }

    ngOnDestroy() {
        this._unregisterTimepicker();
        this._subsCtrl$.next();
        this._subsCtrl$.complete();
    }

    @HostListener("click", ["$event"])
    onClick(event) {
        if (!this.disableClick) {
            this._timepicker.open();
            event.stopPropagation();
        }
    }

    onTouched = () => {
    }

    registerOnChange(fn: (value: any) => void): void {
        this._onChange = fn;
    }

    registerOnTouched(fn: () => void): void {
        this.onTouched = fn;
    }

    setDisabledState(isDisabled: boolean): void {
        this.disabled = isDisabled;
    }

    @HostListener("change", ["$event"])
    updateValue(value: string) {
        this.value = value;
        this._onChange(value);
    }

    writeValue(value: string): void {
        this.value = value;
        if (value) {
            this._defaultTime = value;
        }
    }

    private _onChange: (value: any) => void = () => {
    }

    private _registerTimepicker(picker: NgxMatTimepickerComponent): void {
        if (picker) {
            this._timepicker = picker;
            this._timepicker.registerInput(this);
            this._timepicker.timeSet
                .pipe(takeUntil(this._subsCtrl$))
                .subscribe((time: string) => {
                    this.value = time;
                    this._onChange(this.value);
                    this.onTouched();
                    this._defaultTime = this._value;
                });
        }
        else {
            throw new Error("NgxMatTimepickerComponent is not defined." +
                " Please make sure you passed the timepicker to ngxMatTimepicker directive");
        }
    }

    private _unregisterTimepicker(): void {
        if (this._timepicker) {
            this._timepicker.unregisterInput();
        }
    }

    private _updateInputValue(): void {
        this._elementRef.nativeElement.value = this.value;
    }

}
Example #24
Source File: dynamic-file-input.component.ts    From radiopanel with GNU General Public License v3.0 4 votes vote down vote up
@Component({
	selector: 'app-dynamic-file-input',
	templateUrl: './dynamic-file-input.component.html',
	providers: [
		{
			provide: [],
			useExisting: forwardRef(() => DynamicFileInputComponent),
			multi: true,
		},
	],
})
export class DynamicFileInputComponent implements OnInit, OnDestroy, ControlValueAccessor {
	@Input() label?: string;
	@Input() placeholder = '';
	@Input() type = 'file';
	@Input() key: string = uuid.v4();
	@Input() disabled = false;

	private componentDestroyed$: Subject<boolean> = new Subject<boolean>();

	public id = uuid.v4();
	public circumference = 20 * 2 * Math.PI;
	public isUploading = false;
	public uploadProgress = 0;
	public control: FormControl = new FormControl('');
	public updateValue = (_: any) => {};

	constructor(
		public ngControl: NgControl,
		private http: HttpClient,
	 ) {
		ngControl.valueAccessor = this;
	}

	private propagateChange(value: any): void {
		if (this.updateValue) {
			return this.updateValue(value);
		}

		if (this.control) {
			this.control.setValue(value);
		}
	}

	public clearInput(e: Event): void {
		e.preventDefault();
		this.updateValue(null);
		this.control.setValue(null);
	}

	public shouldShowImage(): boolean {
		return (getType(this.control.value) || 'image/png').startsWith('image');
	}

	public ngOnInit() {
		if (this.disabled) {
			this.control.disable();
		}

		this.control.valueChanges.pipe(
			takeUntil(this.componentDestroyed$),
		).subscribe((value) => {
			this.propagateChange(value);
		});
	}

	onFileChange(fileEvent) {
		if (fileEvent.target.files.length > 0) {
			const file = fileEvent.target.files[0];

			const formData: FormData = new FormData();
			formData.append('file', file, file.name);

			this.http.post('/api/v1/storage/upload', formData, {
				reportProgress: true,
				observe: 'events',
				params: {
					key: this.key,
				},
				headers: {
					'X-FileName': file.name,
				}
			})
				.pipe(takeUntil(this.componentDestroyed$))
				.subscribe((event: HttpEvent<any>) => {
					switch (event.type) {
						case HttpEventType.Sent:
							this.isUploading = true;
							break;
						case HttpEventType.UploadProgress:
							this.uploadProgress = Math.round(event.loaded / event.total * 100);
							break;
						case HttpEventType.Response:
							this.propagateChange(event.body.url);
							this.control.setValue(event.body.url);
							this.isUploading = false;
							this.uploadProgress = 0;
					  }
				});
		}
	}

	public getDashOffset() {
		if (this.isUploading && this.uploadProgress === 100) {
			return this.circumference - 25 / 100 * this.circumference;
		}

		return this.circumference - this.uploadProgress / 100 * this.circumference;
	}

	public ngOnDestroy() {
		this.componentDestroyed$.next(true);
		this.componentDestroyed$.complete();
	}

	public writeValue(value: any) {
		setTimeout(() => this.control.setValue(value));
	}

	public registerOnChange(fn: any) {
		this.updateValue = fn;
	}

	public registerOnTouched() {}
}
Example #25
Source File: date-picker.ts    From sba-angular with MIT License 4 votes vote down vote up
@Component({
    selector: "sq-date-picker",
    template: `
        <div class="sq-date-picker form-row">
            <div class="col">
                <input type="text" #input class="form-control" autocomplete="off" bsDatepicker triggers="click" #picker="bsDatepicker" [bsConfig]="bsConfig()" [ngModel]="value" (ngModelChange)="updateValue($event)" [placeholder]="dateFormat" />
            </div>
        </div>
    `,
    providers: [DATE_PICKER_VALUE_ACCESSOR]
})
export class BsDatePicker implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor {

    private readonly SystemFormat: string = 'YYYY-MM-DD';

    @Input() options: DatePickerOptions;
    value: Date;
    private onChangeCallback: (_: any) => void = () => {};
    private localeChange: Subscription;
    @ViewChild("picker", {static: false}) picker: BsDatepickerDirective;
    @ViewChild('input', {static: false}) input: ElementRef;

    constructor(
        public intlService: IntlService) {
    }

    ngOnInit() {
        if (!this.options) {
            this.options = {};
        }
    }

    public get dateFormat(): string {
        return this.options.system ? this.SystemFormat : moment.localeData().longDateFormat('L');
    }

    setLocale() {
        if (!!this.picker && this.picker.isOpen) {
            this.picker.hide();
            this.picker.show();
        }
    }

    ngAfterViewInit() {
        this.setLocale();
        this.localeChange = Utils.subscribe(this.intlService.events,
            (value) => {
                this.setLocale();
            });
    }

    ngOnDestroy() {
        if (this.localeChange) {
            this.localeChange.unsubscribe();
        }
    }

    bsConfig(): BsDatepickerConfig {
        return <any>{
            minDate: this.options.minDate,
            maxDate: this.options.maxDate,
            containerClass:'theme-default',
            showWeekNumbers: false,
            dateInputFormat: this.options.system ? this.SystemFormat : moment.localeData().longDateFormat('L')
        };
    }

    updateValue(value: Date) {
        this.value = value;
        this.zeroTimes(this.value);
        this.onChangeCallback(this.value);
        this.focus();
    }

    private zeroTimes(value: Date): void {
        if (Utils.isDate(value)) { // includes null checking
            value.setHours(0, 0, 0, 0);
        }
    }

    public focus(): void {
        if (this.input) {
            this.input.nativeElement.focus();
        }
    }

    //#region ControlValueAccessor
    writeValue(value: Date): void {
        this.value = value;
    }

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

    registerOnTouched(fn: any): void {
    }
    //#endregion
}
Example #26
Source File: search.component.ts    From loopback4-microservice-catalog with MIT License 4 votes vote down vote up
@Component({
  selector: 'sourceloop-search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: SearchComponent,
      multi: true,
    },
  ],
})
export class SearchComponent<T extends IReturnType>
  implements OnInit, OnDestroy, ControlValueAccessor
{
  searchBoxInput = '';
  suggestionsDisplay = false;
  categoryDisplay = false;
  searching = false;
  suggestions: T[] = [];
  recentSearches: ISearchQuery[] = [];
  category: string = ALL_LABEL;
  searchRequest$ = new Subject<{input: string; event: Event}>();

  private _config!: Configuration<T>;
  public get config(): Configuration<T> {
    return this._config;
  }
  @Input()
  public set config(value: Configuration<T>) {
    this._config = value;

    if (value && value.models) {
      value.models.unshift({
        name: ALL_LABEL,
        displayName: ALL_LABEL,
      });
    } else if (value && !value.models) {
      value.models = [
        {
          name: ALL_LABEL,
          displayName: ALL_LABEL,
        },
      ];
    } else {
      // do nothing
    }
  }

  @Input() titleTemplate?: TemplateRef<any>;
  @Input() subtitleTemplate?: TemplateRef<any>;
  // emitted when user clicks one of the suggested results (including recent search sugestions)
  @Output() clicked = new EventEmitter<ItemClickedEvent<T>>();
  @Output() searched = new EventEmitter<RecentSearchEvent>();
  /* emitted when user makes search request (including recent search requests & requests made on change in category from dropdown)
  In case of recent search Array of recent Search request result is emitted */

  onChange!: (value: string | undefined) => void;
  onTouched!: () => void;
  disabled = false;

  @ViewChild('searchInput') public searchInputElement!: ElementRef;

  constructor(
    @Inject(SEARCH_SERVICE_TOKEN)
    private readonly searchService: ISearchService<T>,
    // tslint:disable-next-line:ban-types
    @Inject(PLATFORM_ID) private readonly platformId: Object,
    private readonly cdr: ChangeDetectorRef,
  ) {}

  ngOnInit(): void {
    this.searchRequest$
      .pipe(
        tap(v => (this.suggestions = [])),
        debounceTime(DEBOUNCE_TIME),
      )
      .subscribe((value: TypeEvent) => {
        this.searched.emit({
          event: value.event,
          keyword: value.input,
          category: this.category,
        });
        this.getSuggestions(value);
        this.cdr.markForCheck();
      });
  }

  // ControlValueAccessor Implementation
  writeValue(value: string): void {
    this.searchBoxInput = value;
  }
  // When the value in the UI is changed, this method will invoke a callback function
  registerOnChange(fn: (value: string | undefined) => void): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  getSuggestions(eventValue: TypeEvent) {
    eventValue.input = eventValue.input.trim();
    if (!eventValue.input.length) {
      return;
    }
    const order = this.config.order ?? DEFAULT_ORDER;
    let orderString = '';
    order.forEach(preference => (orderString = `${orderString}${preference} `));

    let saveInRecents = this.config.saveInRecents ?? DEFAULT_SAVE_IN_RECENTS;
    if (this.config.saveInRecents && this.config.saveInRecentsOnlyOnEnter) {
      if (
        !eventValue.event ||
        (eventValue.event instanceof KeyboardEvent &&
          eventValue.event.key === 'Enter')
      ) {
        saveInRecents = true; // save in recents only on enter or change in category
      } else {
        // do not save in recent search on typing
        saveInRecents = false;
      }
    }
    /* need to put default value here and not in contructor
    because sonar was giving code smell with definite assertion as all these parameters are optional */
    const requestParameters: ISearchQuery = {
      match: eventValue.input,
      sources: this._categoryToSourceName(this.category),
      limit: this.config.limit ?? DEFAULT_LIMIT,
      limitByType: this.config.limitByType ?? DEFAULT_LIMIT_TYPE,
      order: orderString,
      offset: this.config.offset ?? DEFAULT_OFFSET,
    };

    this.searching = true;
    this.cdr.markForCheck();
    this.searchService
      .searchApiRequest(requestParameters, saveInRecents)
      .subscribe(
        (value: T[]) => {
          this.suggestions = value;
          this.searching = false;
          this.cdr.markForCheck();
        },
        (_error: Error) => {
          this.suggestions = [];
          this.searching = false;
          this.cdr.markForCheck();
        },
      );
  }
  getRecentSearches() {
    if (
      !this.config.hideRecentSearch &&
      this.searchService.recentSearchApiRequest
    ) {
      this.searchService.recentSearchApiRequest().subscribe(
        (value: ISearchQuery[]) => {
          this.recentSearches = value;
          this.cdr.markForCheck();
        },
        (_error: Error) => {
          this.recentSearches = [];
          this.cdr.markForCheck();
        },
      );
    }
  }

  // event can be KeyBoardEvent or Event of type 'change' fired on change in value of drop down for category
  hitSearchApi(event?: Event) {
    // this will happen only in case user searches something and then erases it, we need to update recent search
    if (!this.searchBoxInput) {
      this.suggestions = [];
      this.getRecentSearches();
      return;
    }

    // no debounce time needed in case of searchOnlyOnEnter
    if (this.config.searchOnlyOnEnter) {
      if (!event || (event instanceof KeyboardEvent && event.key === 'Enter')) {
        this.getSuggestions({input: this.searchBoxInput, event});
      }
      return;
    }

    // no debounce time needed in case of change in category
    if (!event) {
      this.getSuggestions({input: this.searchBoxInput, event});
      return;
    }

    this.searchRequest$.next({
      input: this.searchBoxInput,
      event,
    });
  }

  populateValue(suggestion: T, event: MouseEvent) {
    const value = suggestion[
      this.config.displayPropertyName
    ] as unknown as string; // converted to string to assign value to searchBoxInput
    this.searchBoxInput = value;
    this.suggestionsDisplay = false;
    // ngModelChange doesn't detect change in value when populated from outside, hence calling manually
    this.onChange(this.searchBoxInput);
    // need to do this to show more search options for selected suggestion - just in case user reopens search input
    this.getSuggestions({input: this.searchBoxInput, event});
    this.clicked.emit({item: suggestion, event});
  }
  populateValueRecentSearch(recentSearch: ISearchQuery, event: MouseEvent) {
    event.stopPropagation();
    event.preventDefault();
    const value = recentSearch['match'];
    this.searchBoxInput = value;
    this.suggestionsDisplay = false;
    this.onChange(this.searchBoxInput);
    // need to do this to show more search options for selected suggestion - just in case user reopens search input
    this.getSuggestions({input: this.searchBoxInput, event});
    this.focusInput();
    this.showSuggestions();
  }

  fetchModelImageUrlFromSuggestion(suggestion: T) {
    const modelName = suggestion[
      'source' as unknown as keyof T
    ] as unknown as string;
    let url: string | undefined;
    this.config.models.forEach(model => {
      if (model.name === modelName && model.imageUrl) {
        url = model.imageUrl;
      }
    });
    return url;
  }

  boldString(str: T[keyof T] | string, substr: string) {
    const strRegExp = new RegExp(`(${substr})`, 'gi');
    const stringToMakeBold: string = str as unknown as string;
    return stringToMakeBold.replace(strRegExp, `<b>$1</b>`);
  }

  hideSuggestions() {
    this.suggestionsDisplay = false;
    this.onTouched();
  }

  showSuggestions() {
    this.suggestionsDisplay = true;
    this.getRecentSearches();
  }

  focusInput() {
    if (isPlatformBrowser(this.platformId)) {
      this.searchInputElement.nativeElement.focus();
    }
  }

  setCategory(category: string) {
    this.category = category;
    this.categoryDisplay = false;
    if (this.searchBoxInput) {
      this.hitSearchApi();
      this.focusInput();
      this.showSuggestions();
    }
  }

  showCategory() {
    this.categoryDisplay = !this.categoryDisplay;
  }

  hideCategory() {
    this.categoryDisplay = false;
  }

  resetInput() {
    this.searchBoxInput = '';
    this.suggestions = [];
    this.suggestionsDisplay = true;
    this.focusInput();
    // ngModelChange doesn't detect change in value when populated from outside, hence calling manually
    this.onChange(this.searchBoxInput);
    this.getRecentSearches();
  }
  ngOnDestroy() {
    this.searchRequest$.unsubscribe();
  }

  _categoryToSourceName(category: string) {
    if (category === ALL_LABEL) {
      return [];
    } else {
      return [category];
    }
  }
  getModelFromModelName(name: string) {
    return this.config.models.find(item => item.name === name) as IModel;
  }
  getModelsWithSuggestions() {
    const modelsWithSuggestions: {model: IModel; items: T[]}[] = [];
    const sources: string[] = [];
    this.suggestions.forEach(suggestion => {
      if (sources.indexOf(suggestion['source']) >= 0) {
        modelsWithSuggestions.every(modelWithSuggestions => {
          if (modelWithSuggestions.model.name === suggestion['source']) {
            modelWithSuggestions.items.push(suggestion);
            return false;
          }
          return true;
        });
      } else {
        const model = this.getModelFromModelName(suggestion['source']);
        modelsWithSuggestions.push({model, items: [suggestion]});
        sources.push(suggestion['source']);
      }
    });
    return modelsWithSuggestions;
  }
}
Example #27
Source File: ngx-mat-timepicker-field.component.ts    From ngx-mat-timepicker with MIT License 4 votes vote down vote up
@Component({
    selector: "ngx-mat-timepicker-field",
    templateUrl: "./ngx-mat-timepicker-field.component.html",
    styleUrls: ["./ngx-mat-timepicker-field.component.scss"],
    providers: [
        NgxMatTimepickerService,
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: NgxMatTimepickerFieldComponent,
            multi: true
        }
    ],
    changeDetection: ChangeDetectionStrategy.OnPush,
    encapsulation: ViewEncapsulation.None
})
export class NgxMatTimepickerFieldComponent implements OnInit, OnDestroy, ControlValueAccessor {

    get color(): ThemePalette {
        return this._color;
    }

    @Input()
    set color(newValue: ThemePalette) {
        this._color = newValue;
    }

    get defaultTime(): string {
        return this._defaultTime;
    }

    @Input()
    set defaultTime(val: string) {
        this._defaultTime = val;
        this._isDefaultTime = !!val;
    }

    get floatLabel(): FloatLabelType {
        return this._floatLabel;
    }

    @Input()
    set floatLabel(newValue: FloatLabelType) {
        this._floatLabel = newValue;
    }

    get format(): number {
        return this._format;
    }

    @Input()
    set format(value: number) {
        this._format = value === 24 ? 24 : 12;
        this.minHour = this._format === 12 ? 1 : 0;
        this.maxHour = this._format === 12 ? 12 : 23;
        this.hoursList = NgxMatTimepickerUtils.getHours(this._format);
        const isDynamicallyChanged = value && (this._previousFormat && this._previousFormat !== this._format);

        if (isDynamicallyChanged) {
            this._updateTime(this.timepickerTime);
        }
        this._previousFormat = this._format;
    }

    get max(): string | DateTime {
        return this._max;
    }

    @Input()
    set max(value: string | DateTime) {
        if (typeof value === "string") {
            this._max = NgxMatTimepickerAdapter.parseTime(value, {locale: this._locale, format: this.format});

            return;
        }
        this._max = value;
    }

    get min(): string | DateTime {
        return this._min;
    }

    @Input()
    set min(value: string | DateTime) {
        if (typeof value === "string") {
            this._min = NgxMatTimepickerAdapter.parseTime(value, {locale: this._locale, format: this.format});

            return;
        }
        this._min = value;
    }

    @Input()
    cancelBtnTmpl: TemplateRef<Node>;

    @Input()
    clockTheme: NgxMatTimepickerTheme;

    @Input()
    confirmBtnTmpl: TemplateRef<Node>;

    @Input()
    controlOnly: boolean;

    @Input() disabled: boolean;
    hour$: Observable<NgxMatTimepickerClockFace>;

    hoursList: NgxMatTimepickerClockFace[];
    isChangePeriodDisabled: boolean;
    isTimeRangeSet: boolean;
    maxHour = 12;
    minHour = 1;
    minute$: Observable<NgxMatTimepickerClockFace>;
    minutesList: NgxMatTimepickerClockFace[];
    period: NgxMatTimepickerPeriods = NgxMatTimepickerPeriods.AM;
    periods: NgxMatTimepickerPeriods[] = [
        NgxMatTimepickerPeriods.AM,
        NgxMatTimepickerPeriods.PM
    ];

    @Output() timeChanged = new EventEmitter<string>();
    timepickerTime: string;

    timeUnit = NgxMatTimepickerUnits;
    @Input() toggleIcon: TemplateRef<HTMLObjectElement>;

    private _color: ThemePalette = "primary";
    private _defaultTime: string;
    private _floatLabel: FloatLabelType = "never";
    private _format = 12;
    private _isDefaultTime: boolean;
    private _isFirstTimeChange: boolean = true;
    private _max: string | DateTime;
    private _min: string | DateTime;
    private _previousFormat: number;
    private _selectedHour: number;
    private _subsCtrl$: Subject<void> = new Subject<void>();

    constructor(private _timepickerService: NgxMatTimepickerService,
                @Inject(NGX_MAT_TIMEPICKER_LOCALE) private _locale: string) {
    }

    changeHour(hour: number): void {
        this._timepickerService.hour = this.hoursList.find(h => h.time === hour);
        this._changeTime();
    }

    changeMinute(minute: number): void {
        this._timepickerService.minute = this.minutesList.find(m => m.time === minute);
        this._changeTime();
    }

    changePeriod(event: MatSelectChange): void {
        this._timepickerService.period = event.value as NgxMatTimepickerPeriods;
        this._changeTime();
    }

    ngOnDestroy(): void {
        this._subsCtrl$.next();
        this._subsCtrl$.complete();
    }

    ngOnInit() {
        this._initTime(this.defaultTime);

        this.hoursList = NgxMatTimepickerUtils.getHours(this._format);
        this.minutesList = NgxMatTimepickerUtils.getMinutes();
        this.isTimeRangeSet = !!(this.min || this.max);

        this.hour$ = this._timepickerService.selectedHour.pipe(
            tap((clockTime: NgxMatTimepickerClockFace) => this._selectedHour = clockTime.time),
            map(this._changeDefaultTimeValue.bind(this)),
            tap(() => this.isTimeRangeSet && this._updateAvailableMinutes())
        ) as Observable<NgxMatTimepickerClockFace>;
        this.minute$ = this._timepickerService.selectedMinute.pipe(
            map(this._changeDefaultTimeValue.bind(this)),
            tap(() => this._isFirstTimeChange = false)
        ) as Observable<NgxMatTimepickerClockFace>;

        if (this.format === 12) {
            this._timepickerService.selectedPeriod.pipe(
                distinctUntilChanged<NgxMatTimepickerPeriods>(),
                tap((period: NgxMatTimepickerPeriods) => this.period = period),
                tap(period => this.isChangePeriodDisabled = this._isPeriodDisabled(period)),
                takeUntil(this._subsCtrl$)
            ).subscribe(() => this.isTimeRangeSet && this._updateAvailableTime());
        }

    }

    onTimeSet(time: string): void {
        this._updateTime(time);
        this._emitLocalTimeChange(time);
    }

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

    registerOnTouched(_fn_: any): void {
    }

    setDisabledState(isDisabled: boolean): void {
        this.disabled = isDisabled;
    }

    writeValue(val: string): void {
        if (val) {
            this._initTime(val);
        }
        else {
            this._resetTime();
        }
    }

    private _changeDefaultTimeValue(clockFaceTime: NgxMatTimepickerClockFace): NgxMatTimepickerClockFace {
        if (!this._isDefaultTime && this._isFirstTimeChange) {
            return {...clockFaceTime, time: null};
        }

        return clockFaceTime;
    }

    private _changeTime(): void {
        const time = this._timepickerService.getFullTime(this.format);
        this.timepickerTime = time;

        this._emitLocalTimeChange(time);
    }

    private _emitLocalTimeChange(time: string): void {
        const localTime = NgxMatTimepickerAdapter.toLocaleTimeString(time, {format: this.format, locale: this._locale});

        this._onChange(localTime);
        this.timeChanged.emit(localTime);
    }

    private _initTime(time): void {
        const isDefaultTimeAvailable = NgxMatTimepickerAdapter
            .isTimeAvailable(time, this.min as DateTime, this.max as DateTime, "minutes", null, this.format);
        if (!isDefaultTimeAvailable) {
            if (this.min) {
                this._updateTime(NgxMatTimepickerAdapter.fromDateTimeToString(this.min as DateTime, this.format));

                return;
            }
            if (this.max) {
                this._updateTime(NgxMatTimepickerAdapter.fromDateTimeToString(this.max as DateTime, this.format));

                return;
            }
        }
        this._updateTime(time);
    }

    private _isPeriodDisabled(period): boolean {
        return NgxMatTimepickerUtils.disableHours(NgxMatTimepickerUtils.getHours(12), {
            min: this.min as DateTime,
            max: this.max as DateTime,
            format: 12,
            period: period === NgxMatTimepickerPeriods.AM ? NgxMatTimepickerPeriods.PM : NgxMatTimepickerPeriods.AM
        }).every(time => time.disabled);
    }

    private _onChange: (value: string) => void = () => {
    };

    private _resetTime(): void {
        this._timepickerService.hour = {angle: 0, time: null};
        this._timepickerService.minute = {angle: 0, time: null};
    }

    private _updateAvailableHours(): void {
        this.hoursList = NgxMatTimepickerUtils.disableHours(this.hoursList, {
            min: this.min as DateTime,
            max: this.max as DateTime,
            format: this.format,
            period: this.period
        });
    }

    private _updateAvailableMinutes(): void {
        this.minutesList = NgxMatTimepickerUtils.disableMinutes(this.minutesList, this._selectedHour, {
            min: this.min as DateTime,
            max: this.max as DateTime,
            format: this.format,
            period: this.period
        });
    }

    private _updateAvailableTime(): void {
        this._updateAvailableHours();
        if (this._selectedHour) {
            this._updateAvailableMinutes();
        }
    }

    private _updateTime(time: string): void {
        if (time) {
            const formattedTime = NgxMatTimepickerAdapter.formatTime(time, {locale: this._locale, format: this.format});
            this._timepickerService.setDefaultTimeIfAvailable(formattedTime, this.min as DateTime, this.max as DateTime, this.format);
            this.timepickerTime = formattedTime;
        }
    }

}
Example #28
Source File: dxc-text-input.component.ts    From halstack-angular with Apache License 2.0 4 votes vote down vote up
@Component({
  selector: "dxc-text-input",
  templateUrl: "./dxc-text-input.component.html",
  providers: [
    DxcTextInputService,
    DxcTextInputHelper,
    CssUtils,
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => DxcTextInputComponent),
      multi: true,
    },
  ],
})
export class DxcTextInputComponent
  implements OnInit, OnChanges, OnDestroy, ControlValueAccessor
{
  @HostBinding("class") className;
  @HostBinding("class.hasError") hasError = false;

  /**
   * Text to be placed above the input. This label will be used as the aria-label attribute of the list of suggestions.
   */
  @Input() label: string;
  /**
   * Name attribute of the input element.
   */
  @Input() name: string = "";
  /**
   * Value of the input. If undefined, the component will be uncontrolled and the value will be managed internally by the component.
   */
  @Input() value: string;
  /**
   * Initial value of the input, only when it is uncontrolled.
   */
  @Input() defaultValue: string;
  /**
   * Helper text to be placed above the input.
   */
  @Input() helperText: string;
  /**
   * Text to be put as placeholder of the input.
   */
  @Input() placeholder: string = "";
  /**
   * HTML autocomplete attribute. Lets the user specify if any permission the user agent has to provide automated assistance in filling out the input value.
   * Its value must be one of all the possible values of the HTML autocomplete attribute: 'on', 'off', 'email', 'username', 'new-password', ...
   */
  @Input() autocomplete: string = "off";

  hasAction = () => this.onActionClick.observers.length;
  /**
   * If true, the component will be disabled.
   */
  @Input()
  get disabled(): boolean {
    return this._disabled;
  }
  set disabled(value: boolean) {
    this._disabled = coerceBooleanProperty(value);
  }
  private _disabled = false;
  /**
   * If true, the input will be optional, showing '(Optional)'
   * next to the label. Otherwise, the field will be considered required and an error will be
   * passed as a parameter to the OnBlur and onChange events when it has
   * not been filled.
   */
  @Input()
  get optional(): boolean {
    return this._optional;
  }
  set optional(value: boolean) {
    this._optional = coerceBooleanProperty(value);
  }
  private _optional = false;
  /**
   * If true, the input will have an action to clear the entered value.
   */
  @Input() clearable: boolean = false;
  /**
   * If it is a defined value and also a truthy string, the component will
   * change its appearance, showing the error below the input component. If the
   * defined value is an empty string, it will reserve a space below the
   * component for a future error, but it would not change its look. In case of
   * being undefined or null, both the appearance and the space for the error
   * message would not be modified.
   */
  @Input() error: string = undefined;
  /**
   * Regular expression that defines the valid format allowed by the input.
   * This will be checked when the input loses the focus. If the value entered
   * does not match the pattern, the onBlur event will emit with the value
   * entered and the error informing that the value does not match the pattern
   * as parameters. If the pattern is accomplished, the error parameter will be
   * undefined.
   */
  @Input() pattern: string;
  /**
   * Specifies the minimun length allowed by the input. This will be checked
   * both when the input element loses the focus and while typing within it. If
   * the string entered does not comply the minimum length, the onBlur and
   * onChange events will emit with the current value and an internal error
   * informing that the value length does not comply the specified range. If a
   * valid length is reached, the error parameter of both events will be undefined.
   */
  @Input() minLength: number;
  /**
   * Specifies the maximum length allowed by the input. This will be checked
   * both when the input element loses the focus and while typing within it. If
   * the string entered does not comply the maximum length, the onBlur and
   * onChange events will emit with the current value and an internal error
   * informing that the value length does not comply the specified range. If a
   * valid length is reached, the error parameter of both events will be undefined.
   */
  @Input() maxLength: number;
  /**
   * Size of the margin to be applied to the component ('xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'xxlarge').
   * You can pass an object with 'top', 'bottom', 'left' and 'right' properties in order to specify different margin sizes.
   */
  @Input() margin: Space | Spacing;
  /**
   * These are the options to be displayed as suggestions. It can be either an array or a function:
   *    - Array:    Array of options that will be filtered by the component.
   *    - Function: This function will be called when the user changes the value, we will send as a parameter the new value;
   *                apart from that this function should return one promise on which we should make 'then' to get the suggestions filtered.
   */
  @Input() suggestions: Array<string> | any;

  /**
   * Value of the tabindex attribute.
   */
  @Input() tabIndexValue: number = 0;
  /**
   * Size of the component ('small' | 'medium' | 'large' | 'fillParent').
   */
  @Input() size: "small" | "medium" | "large" | "fillParent" = "medium";

  id: string;
  autoSuggestId: string;

  private controlled: boolean;

  defaultInputs = new BehaviorSubject<TextInputProperties>({
    placeholder: "",
    error: undefined,
    clearable: false,
    optional: false,
    disabled: false,
    helperText: "",
    value: undefined,
    name: "",
    label: "",
    margin: null,
    suggestions: [],
    tabIndexValue: 0,
    size: "medium",
  });
  /**
   * This event will emit when the user types within the input element
   * of the component. An object including the current value and the error (if
   * the value entered is not valid) will be passed to this function. An
   * example of this object is: { value: value, error: error }. If there is no
   * error, error will not be defined.
   */
  @Output()
  onChange = new EventEmitter<EmittedValue>();
  /**
   * This event will emit when the input element loses the focus. An object
   * including the input value and the error (if the value entered is not
   * valid) will be passed to this function. An example of this object is: {
   * value: value, error: error }. If there is no error, error will not be
   * defined.
   */
  @Output()
  onBlur = new EventEmitter<EmittedValue>();
  /**
   * This event will emit when the action is clicked.
   */
  @Output()
  onActionClick = new EventEmitter<string>();

  @ViewChild("inputRef", { static: true }) inputRef: ElementRef;
  @ViewChild("autoSuggestOptions", { static: false }) optionsRef: ElementRef;
  @ViewChild("stepButtonMinus", { static: false }) stepButtonMinus: ElementRef;
  @ViewChild("stepButtonPlus", { static: false }) stepButtonPlus: ElementRef;
  @ViewChild("actionButton", { static: false }) actionButton: ElementRef;

  autosuggestVisible: boolean = false;

  focusedOption: number;

  activedOption: number;

  filteredOptions: Array<string>;

  darkBackground: boolean = false;

  isDirty: boolean = false;

  options: Array<string> = [];

  loading = new BehaviorSubject<boolean>(false);

  fetchingError = new BehaviorSubject<boolean>(false);

  autosuggestType: string;

  validationError: string = "";

  isInputNumber: boolean = false;

  constructor(
    private cdRef: ChangeDetectorRef,
    private service: DxcTextInputService,
    private helper: DxcTextInputHelper,
    @Optional() public bgProviderService?: BackgroundProviderService
  ) {
    this.bgProviderService.$changeColor.subscribe((value) => {
      setTimeout(() => {
        if (value === "dark") {
          this.darkBackground = true;
        } else if (value === "light") {
          this.darkBackground = false;
        }
        this.className = `${this.helper.getDynamicStyle({
          ...this.defaultInputs.getValue(),
          darkBackground: this.darkBackground,
          validationError: this.validationError,
        })}`;
      }, 0);
    });
  }

  onTouch = () => {};

  writeValue(value: any): void {
    if (value) {
      this.value = value || "";
    } else {
      this.value = "";
    }
  }
  registerOnChange(fn: any): void {
    this.handleOnChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  ngOnDestroy(): void {}

  ngOnInit(): void {
    this.id = this.id || uuidv4();
    this.autoSuggestId = this.id + "-listBox";
    this.className = `${this.helper.getDynamicStyle({
      ...this.defaultInputs.getValue(),
      darkBackground: this.darkBackground,
      validationError: this.validationError,
    })}`;
    if (this.value === undefined) {
      this.defaultValue ? (this.value = this.defaultValue) : (this.value = "");
      this.controlled = false;
    } else {
      this.controlled = true;
    }
    this.service.visualFocused.subscribe((value) => {
      this.focusedOption = value;
      if (
        this.optionsRef &&
        this.optionsRef.nativeElement.children[this.focusedOption]
      ) {
        this.optionsRef.nativeElement.children[
          this.focusedOption
        ].scrollIntoView({
          behavior: "smooth",
          block: "nearest",
          inline: "nearest",
        });
      }
    });
    this.service.activeOption.subscribe((value) => {
      this.activedOption = value;
    });
    this.service.filteredOptions.subscribe((filteredArray) => {
      this.filteredOptions = filteredArray;
      if (
        this.suggestions &&
        (this.suggestions.length || this.autosuggestType === "async") &&
        this.filteredOptions?.length > 0 &&
        this.isDirty
      ) {
        this.autosuggestVisible = true;
      } else {
        this.autosuggestVisible = false;
      }
      this.cdRef.detectChanges();
    });
    if (this.suggestions && typeof this.suggestions === "function") {
      this.getAsyncSuggestions();
      this.autosuggestType = "async";
    } else if (this.suggestions && Array.isArray(this.suggestions)) {
      this.options = this.suggestions;
      this.autosuggestType = "array";
    }
  }

  ngAfterViewInit(): void {
    if (this.inputRef) {
      if (this.suggestions && this.suggestions.length) {
        this.optionsRef.nativeElement.ariaLabel = this.label;
      }
      this.inputRef.nativeElement.ariaDisabled = this.disabled;
    }
  }

  ngOnChanges(changes: SimpleChanges): void {
    this.hasError = this.error && !this.disabled ? true : false;
    const inputs = Object.keys(changes).reduce((result, item) => {
      result[item] = changes[item].currentValue;
      return result;
    }, {});
    this.defaultInputs.next({ ...this.defaultInputs.getValue(), ...inputs });
    this.className = `${this.helper.getDynamicStyle({
      ...this.defaultInputs.getValue(),
      darkBackground: this.darkBackground,
      validationError: this.validationError,
    })}`;
  }

  handleOnChange(event) {
    if (this.value !== event && this.isDirty) {
      this.onChange.emit({ value: event, error: this.validateValue(event) });
    }
    if (!this.controlled) {
      this.value = event;
      if (this.autosuggestType === "async") {
        this.getAsyncSuggestions();
      }
    } else {
      setTimeout(() => {
        if (this.inputRef.nativeElement.value !== this.value) {
          this.inputRef.nativeElement.value = this.value;
          this.cdRef.detectChanges();
        }
        if (this.autosuggestType === "async") {
          this.getAsyncSuggestions();
        }
      }, 0);
    }
    this.cdRef.detectChanges();
    this.service.setSelectedIndex(-1);
  }

  handleDefaultClearAction() {
    if (!this.isDirty) {
      this.isDirty = true;
    }
    this.handleOnChange("");
    this.inputRef.nativeElement.focus();
    if (this.suggestions) {
      this.autosuggestVisible = false;
      this.cdRef.detectChanges();
    }
  }

  handleActionOnClick(event) {
    this.handleOnBlur();
    this.onActionClick.emit(this.value);
  }

  handleOnBlur() {
    this.onBlur.emit({
      value: this.value,
      error: this.handleValidationError(),
    });
  }

  handleOnFocus() {
    if (!this.isDirty) {
      this.isDirty = true;
    }
    if (
      this.suggestions &&
      (this.suggestions.length || this.autosuggestType === "async") &&
      this.filteredOptions?.length > 0
    ) {
      this.autosuggestVisible = true;
      this.cdRef.detectChanges();
    } else {
      this.autosuggestVisible = false;
      this.cdRef.detectChanges();
    }
  }

  focusInput() {
    this.inputRef.nativeElement.focus();
  }

  handleOnClick() {
    if (
      this.suggestions &&
      (this.suggestions.length || this.autosuggestType === "async") &&
      this.filteredOptions?.length > 0
    ) {
      this.autosuggestVisible = true;
      this.cdRef.detectChanges();
    } else {
      this.autosuggestVisible = false;
      this.cdRef.detectChanges();
    }
  }

  handleOnClose() {
    if (this.autosuggestVisible) {
      this.service.visualFocused.next(-1);
      this.autosuggestVisible = false;
      this.cdRef.detectChanges();
    }
  }

  handleOnClickOption(event) {
    this.onChange.emit({ value: event, error: this.validateValue(event) });
    this.value = event;
    this.handleOnClose();
    this.service.activeOption.next(-1);
  }

  handleEnterKey() {
    if (this.focusedOption >= 0) {
      this.handleOnChange(this.filteredOptions[this.focusedOption]);
    }
    this.handleOnClose();
  }

  handleMouseDown(event, index) {
    this.service.activeOption.next(index);
    event.preventDefault();
  }

  handleOnLeave() {
    this.service.activeOption.next(-1);
  }

  handleOnKeyDown(event) {
    switch (event.key) {
      case "ArrowDown":
        event.preventDefault();
        this.service.onArrowDown();
        this.handleOnClick();
        break;
      case "ArrowUp":
        event.preventDefault();
        this.service.onArrowUp();
        this.handleOnFocus();
        break;
      case "Enter":
        this.handleEnterKey();
        break;
      case "Escape":
        if (this.suggestions && this.suggestions.length) {
          event.preventDefault();
          this.handleDefaultClearAction();
          this.handleOnClose();
        }
        break;
    }
  }

  private patternMatch(pattern, value) {
    const patternToMatch = new RegExp(pattern);
    return patternToMatch.test(value);
  }

  private validateValue(value) {
    if (this.isRequired(value))
      return `This field is required. Please, enter a value.`;
    if (this.isLengthIncorrect(value))
      return `Min length ${this.minLength}, Max length ${this.maxLength}`;
    if (value && !this.patternMatch(this.pattern, value))
      return `Please use a valid pattern`;
    return undefined;
  }

  private handleValidationError() {
    const validationError = this.validateValue(this.value);
    this.validationError = validationError;
    return validationError;
  }

  private isRequired = (value) => value === "" && !this.optional;

  private isLengthIncorrect = (value) =>
    (value !== "" &&
      this.minLength &&
      value &&
      value.length < +this.minLength) ||
    (this.maxLength && value && value.length > +this.maxLength);

  getAsyncSuggestions() {
    this.loading.next(true);
    this.fetchingError.next(false);
    this.suggestions(this.value).subscribe(
      (suggestionsOptionsList) => {
        this.options = suggestionsOptionsList;
        this.autosuggestVisible = true;
        this.cdRef.markForCheck();
        this.loading.next(false);
      },
      (err) => {
        this.fetchingError.next(true);
        this.autosuggestVisible = false;
        this.loading.next(false);
        this.cdRef.markForCheck();
      }
    );
  }
}
Example #29
Source File: fy-number.component.ts    From fyle-mobile-app with MIT License 4 votes vote down vote up
@Component({
  selector: 'app-fy-number',
  templateUrl: './fy-number.component.html',
  styleUrls: ['./fy-number.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => FyNumberComponent),
      multi: true,
    },
  ],
})
export class FyNumberComponent implements ControlValueAccessor, OnInit, OnDestroy {
  @Input() placeholder: string;

  @Input() disabled: boolean;

  @Input() min: number;

  isDisabled = false;

  fc: FormControl;

  isIos = false;

  private innerValue;

  private onTouchedCallback: () => void = noop;

  private onChangeCallback: (_: any) => void = noop;

  constructor(private platform: Platform) {}

  get value(): any {
    return this.innerValue;
  }

  set value(v: any) {
    if (v !== this.innerValue) {
      this.innerValue = v;
      this.onChangeCallback(v);
    }
  }

  writeValue(value: any): void {
    if (value !== this.innerValue) {
      this.innerValue = value && parseFloat(value);
      this.fc.setValue(value && parseFloat(value));
    }
  }

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

  registerOnTouched(fn: any) {
    this.onTouchedCallback = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.isDisabled = isDisabled;
  }

  onBlur() {
    this.onTouchedCallback();
  }

  ngOnDestroy(): void {}

  ngOnInit() {
    this.isIos = this.platform.is('ios');
    this.fc = new FormControl();
    this.fc.valueChanges.subscribe((value) => {
      if (typeof value === 'string') {
        this.value = value && parseFloat(value);
      } else if (typeof value === 'number') {
        this.value = value;
      } else {
        this.value = null;
      }
    });
  }
}