Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(datepicker): double tap required to select a date with the datepicker on ios #2810

Closed
Frank84 opened this issue Oct 6, 2017 · 24 comments · Fixed by #5821
Closed

fix(datepicker): double tap required to select a date with the datepicker on ios #2810

Frank84 opened this issue Oct 6, 2017 · 24 comments · Fixed by #5821

Comments

@Frank84
Copy link

Frank84 commented Oct 6, 2017

Is there a config option we can pass with the datepicker so it automatically closes the popup when the user touch a date on ios? At the moment it triggers the isHover state, adding the hover css on the date. An additional touch is then require to select the date and close the modal.

Versions:
ngx-bootstrap: 1.9.3
Angular: 4.3.3
Bootstrap: 3.3.7

@valorkin
Copy link
Member

Thanks for an issue, it's interesting.

@praveen33in
Copy link

Issue reported by Frank84 is happening to me as well.
Could we expect fix for the same any time soon?

@valorkin
Copy link
Member

valorkin commented Nov 2, 2017

First touch on iOS treated as hover, second as a click. So I have not much options: remove hover effect and replace with mouse enter/leave and manipulate selection class from js. Which, of course, will affect performance

@praveen33in
Copy link

Will there be an update with the mentioned change or ignore the issue to not affect performance?

@YevheniiaMazur YevheniiaMazur changed the title Double tap required to select a date with the datepicker on ios fix(datepicker): double tap required to select a date with the datepicker on ios Jan 17, 2018
@ktsangop
Copy link

A dirty hack is to "remove" the hover event listener when the device is "recognized" to be an iPhone/iPad etc (FYI i did this for the RatingModule but it should work for datepicker also):

@ViewChild('ngxRating') rating: any;

ngAfterViewInit() {
    if ( (!!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)) && 'ontouchstart' in window && this.rating ) {
      this.rating.enter = () => {};
    }
  }

Where ngxRating is a template reference to the rating component :
<rating #ngxRating ... ></rating>

@danitt
Copy link

danitt commented Apr 11, 2018

if it helps anyone, you can also capture and extend the hover listener, to manually update the value or close the picker - hacky but works until a formalised solution is implemented

// component.html
<input type="text"
    class="form-control"
    #datePicker="bsDatepicker"
    (onShown)="onShowPicker($event)"
    bsDatepicker>
// component.ts
...
onShowPicker(container) {
    const dayHoverHandler = container.dayHoverHandler;
    const hoverWrapper = function($event) {
        const { cell, isHovered } = $event;
        // do whatever with hovered cell/event
        return dayHoverHandler($event);
    };
    container.dayHoverHandler = hoverWrapper;
}

@iaguilarmartin
Copy link

Thanks to the suggestions from @danitt and @ktsangop I managed to solve that issue on iOS devices. Here is the solution. It works for BsDatePicker and BsDateRangePicker:

// component.html
<input
      bsDatepicker
      #datepicker="bsDatepicker"
      type="text"
      class="form-control"
      (onShown)="onShowPicker($event)"
>

// component.ts
@ViewChild('datepicker')
private _picker: BsDatepickerDirective;

onShowPicker(event) {
    const dayHoverHandler = event.dayHoverHandler;
    const hoverWrapper = (hoverEvent) => {
        const { cell, isHovered } = hoverEvent;

        if ((isHovered &&
          !!navigator.platform &&
          /iPad|iPhone|iPod/.test(navigator.platform)) &&
          'ontouchstart' in window
        ) {
            (this._picker as any)._datepickerRef.instance.daySelectHandler(cell);
        }

        return dayHoverHandler(hoverEvent);
    };
    event.dayHoverHandler = hoverWrapper;
}

@ricardofilho6
Copy link

ricardofilho6 commented Feb 1, 2019

i'm having the same issue. The first click shows the form input error (required), and the second click on the datepicker fills the input correctly. I tried the suggestions above, but none of them worked. I'm using angular 7.

component.html

<input class="form-control" placeholder="Data início"
  name="validation_start_at"
  formControlName="validation_start_at"
  bsDatepicker
  (onShown)="onShowPicker($event)"
  [bsConfig]="{ dateInputFormat: 'DD/MM/YYYY', containerClass: 'theme-red' }"
  [ngClass]="displayFieldCssError('validation_start_at')"
  required>
<div class="help-block c-red-500" *ngIf="form.validation_start_at.invalid && (form.validation_start_at.dirty || form.validation_start_at.touched)">
  <div *ngIf="form.validation_start_at.errors.required"><i class="fa fa-close"></i> Campo obrigatório</div>
</div>

component.ts

  displayFieldCssError(field: string) {
    return {
      'is-invalid': !this.postData.get(field).valid && this.postData.get(field).touched
    };
  }

  onShowPicker(container) {
    const dayHoverHandler = container.dayHoverHandler;
    const hoverWrapper = function($event) {
      const { cell, isHovered } = $event;
      // do whatever with hovered cell/event
      return dayHoverHandler($event);
    };
    container.dayHoverHandler = hoverWrapper;
  }

@ketsune
Copy link

ketsune commented Feb 26, 2019

i'm having the same issue. The first click shows the form input error (required), and the second click on the datepicker fills the input correctly. I tried the suggestions above, but none of them worked. I'm using angular 7.

component.html

<input class="form-control" placeholder="Data início"
  name="validation_start_at"
  formControlName="validation_start_at"
  bsDatepicker
  (onShown)="onShowPicker($event)"
  [bsConfig]="{ dateInputFormat: 'DD/MM/YYYY', containerClass: 'theme-red' }"
  [ngClass]="displayFieldCssError('validation_start_at')"
  required>
<div class="help-block c-red-500" *ngIf="form.validation_start_at.invalid && (form.validation_start_at.dirty || form.validation_start_at.touched)">
  <div *ngIf="form.validation_start_at.errors.required"><i class="fa fa-close"></i> Campo obrigatório</div>
</div>

component.ts

  displayFieldCssError(field: string) {
    return {
      'is-invalid': !this.postData.get(field).valid && this.postData.get(field).touched
    };
  }

  onShowPicker(container) {
    const dayHoverHandler = container.dayHoverHandler;
    const hoverWrapper = function($event) {
      const { cell, isHovered } = $event;
      // do whatever with hovered cell/event
      return dayHoverHandler($event);
    };
    container.dayHoverHandler = hoverWrapper;
  }

I have a same issue here, Angular 7 with Reactive Form input which is bsDatePicker
1st click => it's warn me that this input is required field (Note that: formControl doesn't has any value yet.)

2nd click => Everything is going well, get the value, no required field waring.

@marie-dk
Copy link

I'm also facing this with Angular 7.2.6. Strangely it was enough for me to just add the hover handler. Then the double-click issue on day selection disappeared in Safari 12 / iOS 12.1.

onOpenCalendar(container) {
 //Note: I also have selecthandlers for both year, month and day
  container.dayHoverHandler = (event: any): void => {
    console.log(event);
  }
}

@aniketdeshbhratar
Copy link

Thanks to the suggestions from @danitt and @ktsangop I managed to solve that issue on iOS devices. Here is the solution. It works for BsDatePicker and BsDateRangePicker:

// component.html
<input
      bsDatepicker
      #datepicker="bsDatepicker"
      type="text"
      class="form-control"
      (onShown)="onShowPicker($event)"
>

// component.ts
@ViewChild('datepicker')
private _picker: BsDatepickerDirective;

onShowPicker(event) {
    const dayHoverHandler = event.dayHoverHandler;
    const hoverWrapper = (hoverEvent) => {
        const { cell, isHovered } = hoverEvent;

        if ((isHovered &&
          !!navigator.platform &&
          /iPad|iPhone|iPod/.test(navigator.platform)) &&
          'ontouchstart' in window
        ) {
            (this._picker as any)._datepickerRef.instance.daySelectHandler(cell);
        }

        return dayHoverHandler(hoverEvent);
    };
    event.dayHoverHandler = hoverWrapper;
}

Hi @iaguilarmartin ,

I tried your code which is working properly but in console i am getting below error.
"undefined is not an object (evaluating '_this._picker._datepickerRef')"
Do you have any solution for this?

@sucotronic
Copy link

sucotronic commented Sep 25, 2019

Due to iOS 13, the navigator.platform for iPad has changed, and also, they're firing two events, so the code above has to be:

if ((isHovered && cell.isHovered &&
      Modernizr.touchEvents && vendor == 'Apple'
) {

@pavantripsolver
Copy link

Due to iOS 13, the navigator.platform for iPad has changed, and also, they're firing two events, so the code above has to be:

if ((isHovered && cell.isHovered &&
      Modernizr.touchEvents && vendor == 'Apple'
) {

But Modernizr and vendor are displaying as an error. how to import these two in a component?

@sucotronic
Copy link

@pavantripsolver I've downloaded Modernizer to the same folder as the .ts file (with only the touchevents feature). I also use Bowser to detect if it is an iOS device(just install it with npm).

import * as Bowser from 'bowser';
import './modernizr.js';
...
export class XXXX {
    bw = null;
    constructor() {
        this.bw = Bowser.getParser(window.navigator.userAgent);
    }
    public iosLessThan13() {
        if (this.bw.parsedResult.os.name == 'iOS' && parseFloat(this.bw.parsedResult.os.version) < 13) {
            return true;
        } else {
            return false;
        }
    }
    public iosMoreThan12() {
        if (this.bw.parsedResult.os.name == 'macOS' ||  // iPad OS ¬¬
            (this.bw.parsedResult.os.name == 'iOS' && parseFloat(this.bw.parsedResult.os.version) >= 13)) {
            return true;
        } else {
            return false;
        }
    }

    public isSafariTouch() {
        if (window['Modernizr']['touchevents'] && this.bw.parsedResult.platform.vendor == 'Apple') {
            return true;
        } else {
            return false;
        }
    }

onShowPicker(event: any) {
        const dayHoverHandler = event.dayHoverHandler;
        const hoverWrapper = (hoverEvent: any) => {
            const { cell, isHovered } = hoverEvent;

            if (isHovered && this.isSafariTouch() &&
                ((this.iosMoreThan12() && cell.isHovered) || this.iosLessThan13())
            ) {
                try {
                    (this._picker as any)._datepickerRef.instance.daySelectHandler(cell);
                } catch (e) {}
            }

            return dayHoverHandler(hoverEvent);
        };

        event.dayHoverHandler = hoverWrapper;
    }

@Suhas-Nadarge
Copy link

I'm getting same issue in angular 6, but not all the time. I am able reproduce it very few time. but I want to resolve it. Any solutions will be appreciated.

@lizlux
Copy link

lizlux commented Feb 18, 2020

I'm having the same issue in angular 8. Will also try a workaround, though it would be great if this were fixed!

@Suhas-Nadarge
Copy link

Issue was due to .touched which I was using in HTML for validation. This worked for me :)

I added validateDate() which will dynamically add .touched validation which we are using for required field.

date.component.html

<input type="text" formControlName="" (focusout)="validateDate()"
(click)="validateDate()" class="form-control datepicker"
bsDatepicker [bsConfig]="{ containerClass: 'theme-default', dateInputFormat: 'MM/DD/YYYY'}"/>

<ng-container *ngFor="let error of config.validations">
<p *ngIf="group.get(config.name).hasError(error.name) && (group.get(config.name).dirty || addValidation)">{{ error.message }}


date.component.ts

validateDate(): void {
this.addValidation = 'group.get(config.name).touched';
}

@pravinkumart
Copy link

I had the same issue but in web application. Device: Laptop. OS - Windows 10. Browser - Firefox, chrome

@t00
Copy link

t00 commented Jul 2, 2020

For those who cannot wait for the fix in 5.7.0 please find this reusable directive:


import { NavigatorUtils } from './../utils/navigator-utils';
import { Subscription } from 'rxjs';
import { Directive, OnDestroy, Optional } from '@angular/core';
import { BsDatepickerDirective } from 'ngx-bootstrap/datepicker';

@Directive({ selector: '[iosFixes]' })
export class IosFixesDirective implements OnDestroy {

  private subscription: Subscription;

  constructor(@Optional() private bsDatepicker: BsDatepickerDirective) {
    const isIos = navigator.platform && (/iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1));
    if (isIos && bsDatepicker) {
      this.subscription = bsDatepicker.onShown.subscribe(event => {
        const dayHoverHandler = event.dayHoverHandler;
        const hoverWrapper = (hoverEvent) => {
          const { cell, isHovered } = hoverEvent;

          if (isHovered && (bsDatepicker as any)._datepickerRef) {
            (bsDatepicker as any)._datepickerRef.instance.daySelectHandler(cell);
          }

          return dayHoverHandler ? dayHoverHandler(hoverEvent) : null;
        };
        event.dayHoverHandler = hoverWrapper;
      });
    }
  }

  ngOnDestroy(): void {
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }
}

@Amolip
Copy link

Amolip commented Sep 2, 2020

this is not working for me for angular 8 and ngx-bootstrap "ngx-bootstrap": "5.1.2",

@valorkin
Copy link
Member

valorkin commented Sep 2, 2020

Try latest v5, or v6

@daniloff200
Copy link
Contributor

Yeah, in latest 6.1.0 it should work correctly
This fix was released in 6.0.0, but, had some other issues with hovering on macOS, and in 6.1.0 it was also fixed

cc @Amolip

@Amolip
Copy link

Amolip commented Sep 3, 2020 via email

@lewisvail3
Copy link

Try latest v5, or v6

Looks like this was fixed in version 5.7.0 but that version was never published to npm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.