ng-elastic icon indicating copy to clipboard operation
ng-elastic copied to clipboard

large textarea input that extends below the input keyboard and also can not scroll the page below that large text area and keep it there.

Open TheHibbs opened this issue 8 years ago • 17 comments

While entering in text on Android so that the textarea extends below the keyboard, the text is hidden under the keyboard. You can scroll the page to see where you are typing, but then it will continue below the keyboard again. Often, the textarea will scroll to the top while typing. When finished enetring in text and you click outside of the text area, the page scrolls to the top and I can not access anything under that textarea. I can scroll the page down, but as i release my finger, it scrolls back to the top.

TheHibbs avatar Apr 11 '17 17:04 TheHibbs

~Would you be able to test out the patch #13 to see if this solves your issue?~

EDIT: this won't work, please see below instead.

fiznool avatar Apr 11 '17 21:04 fiznool

I've released a new version of this module under a new name, ng-elastic. I believe this should fix this issue.

Please see these instructions for information on how to migrate to the new version. If you could test this out and let me know if the issue is resolve, it would be much appreciated.

Thanks!

fiznool avatar Apr 12 '17 08:04 fiznool

This patch has fixed many issues that I was having. The textarea does not scroll to the top while typing anymore and I can now scroll to the bottom of the page. However, as the text area expands enough to reach the android keyboard. the bottom couple of text rows are still under the keyboard. When deleting text, the textarea does not shrink back, but stays expanded. Good job so far though. Thanks for the good work.

TheHibbs avatar Apr 12 '17 17:04 TheHibbs

@TheHibbs thanks for testing this, I'll see what I can do to get this working on Android.

fiznool avatar Apr 13 '17 08:04 fiznool

@fiznool i can confirm the problem on Android and IOS

i am using ionic and tried latest release of this repo i mean ng-elastic and i can confirm some issues

on IOS: when textarea grow up to be under the keyboard it will be really under the keyboard :) i mean it's not making small scroll down to can see what are u typing but actually this practice from IOS it's self they are not scrolling automatically u should scroll ur self but the problem with this directive we can't scroll our self because if u tried to scroll down ur self it will back to top after any character u type so the last line will disappear again

on Android: it's small better than ios so u can see what are u typing but not all the time :) if the textarea grow up more and more as my trial about 15 lines , the problem will appear and it's almost same of IOS i mean the last line disappear after every character u type

thanks for the good work and i hope u can fix this issues as soon as possible

alaa-alshamy avatar May 15 '17 13:05 alaa-alshamy

@fiznool after diving into this i figured the jumping happening because that we make the height of the textarea auto to can calc the height value we should set , but that making the page jump so the last line of the textarea will be under the keyboard

so i decided to make a clone from the textarea and make it absolute so it's not effect the scroll area and calc the height we should set from it so the main textarea will not effect

so my final directive code looks like this:

	import { ElementRef, HostListener, Directive, AfterViewInit } from '@angular/core';

@Directive({
  selector: '[my-fz-elastic]'
})

export class MyElasticDirective implements AfterViewInit {
  private textareaEl: HTMLTextAreaElement;
  private clonedTextareaEl: HTMLTextAreaElement;

  constructor(
    private element: ElementRef,
  ) {}

  ngAfterViewInit() {
    if (this.isTextarea(this.element.nativeElement)) {
      this.setupTextarea(this.element.nativeElement);
      return;
    }

    const children: HTMLElement[] = Array.from(this.element.nativeElement.children) as HTMLElement[];
    const textareaEl = children.find(el => this.isTextarea(el));
    if (textareaEl) {
      this.setupTextarea(textareaEl as HTMLTextAreaElement);
      return;
    }

    throw new Error('The `fz-elastic` attribute directive must be used on a `textarea` or an element that contains a `textarea`.');
  }

  @HostListener('input')
  onInput(): void {
    // This is run whenever the user changes the input.
    this.adjust();
  }

  private isTextarea(el: HTMLElement) {
    return el.tagName === 'TEXTAREA';
  }

  private setupTextarea(textareaEl: HTMLTextAreaElement) {
    this.textareaEl = textareaEl;

    // Set some necessary styles
    const style = this.textareaEl.style;
    style.overflow = 'hidden';
    style.resize = 'none';

    //clone the input to calc the height
    this.clonedTextareaEl = <HTMLTextAreaElement>this.textareaEl.cloneNode(false);
    this.clonedTextareaEl.tabIndex = -1;
    this.clonedTextareaEl.style.height = 'auto';
    this.clonedTextareaEl.style.opacity = '0';
    this.clonedTextareaEl.style.position = 'absolute';
    this.clonedTextareaEl.style.top = '0';
    this.clonedTextareaEl.style.zIndex = '-100';

    this.textareaEl.parentElement.style.position = 'relative';
    this.textareaEl.parentNode.appendChild(this.clonedTextareaEl);

    // Ensure we adjust the textarea if
    // content is already present
    this.adjust();
  }

  private adjust(): void {
    if (!this.textareaEl || !this.clonedTextareaEl) {
      return;
    }

    this.clonedTextareaEl.value = this.textareaEl.value;

    this.textareaEl.style.height = this.clonedTextareaEl.scrollHeight + "px";
  }
}

and i actually removed blocks that i didn't see it's necessary in my case

NOTE: i tested this with ionic only and it's working perfect

finally i hope this can help some one :)

alaa-alshamy avatar May 17 '17 13:05 alaa-alshamy

@alaa-alshamy this is fantastic work. Thanks so much!

I will try to look at this soon. It's the last thing on my TODO list before v1 final is released.

fiznool avatar May 17 '17 13:05 fiznool

@fiznool i have small update to fix bug in ios webview to force that webview to make render

the change is after this line in the adjust function:

this.clonedTextareaEl.value = this.textareaEl.value;

just add this line :

this.clonedTextareaEl.style.zIndex = (this.clonedTextareaEl.style.zIndex == '-100' ? '-101' : '-100');// this just to force the ios webview to make render

alaa-alshamy avatar May 20 '17 10:05 alaa-alshamy

@fiznool i made some changes on the directive also :) because i faced some issues with making the cloned textarea absolute in WKWebView in IOS, so i decided to not using absolute and make the cloned textarea's height = 0

so my final directive code looks like this:

import { ElementRef, HostListener, Directive, AfterViewInit } from '@angular/core';

@Directive({
  selector: '[my-fz-elastic]'
})

export class MyElasticDirective implements AfterViewInit {
  private textareaEl: HTMLTextAreaElement;
  private defaultTextareaElHeight: number;
  private clonedTextareaEl: HTMLTextAreaElement;

  constructor(
    private element: ElementRef,
  ) {}

  ngAfterViewInit() {
    if (this.isTextarea(this.element.nativeElement)) {
      this.setupTextarea(this.element.nativeElement);
      return;
    }

    const children: HTMLElement[] = Array.from(this.element.nativeElement.children) as HTMLElement[];
    const textareaEl = children.find(el => this.isTextarea(el));
    if (textareaEl) {
      this.setupTextarea(textareaEl as HTMLTextAreaElement);
      return;
    }

    throw new Error('The `fz-elastic` attribute directive must be used on a `textarea` or an element that contains a `textarea`.');
  }

  @HostListener('input')
  onInput(): void {
    // This is run whenever the user changes the input.
    this.adjust();
  }

  private isTextarea(el: HTMLElement) {
    return el.tagName === 'TEXTAREA';
  }

  private setupTextarea(textareaEl: HTMLTextAreaElement) {
    this.textareaEl = textareaEl;
    this.defaultTextareaElHeight = this.textareaEl.clientHeight;

    // Set some necessary styles
    const style = this.textareaEl.style;
    style.overflow = 'hidden';
    style.resize = 'none';

    //clone the input to calc the height
    this.clonedTextareaEl = <HTMLTextAreaElement>this.textareaEl.cloneNode(false);
    this.clonedTextareaEl.tabIndex = -1;
    this.clonedTextareaEl.disabled = true;
    this.clonedTextareaEl.style.height = '0';
    // remove margin top and bottom because it does not matter in calculating the height of textarea and they make useless space
    // we just need margin right and left
    this.clonedTextareaEl.style.marginTop = '0';
    this.clonedTextareaEl.style.marginBottom = '0';

    this.textareaEl.parentNode.appendChild(this.clonedTextareaEl);

    // Ensure we adjust the textarea if
    // content is already present
    this.adjust();
  }

  private adjust(): void {
    if (!this.textareaEl || !this.clonedTextareaEl) {
      return;
    }

    this.clonedTextareaEl.value = this.textareaEl.value;

      this.textareaEl.style.height = Math.max(this.clonedTextareaEl.scrollHeight, this.defaultTextareaElHeight) + "px";
  }
}

alaa-alshamy avatar May 24 '17 06:05 alaa-alshamy

also facing the same issue and would love to see this get into ng-elastic!

@fiznool @alaa-alshamy if it helps i can turn this into a pull request..?

shanesmith avatar Aug 21 '17 14:08 shanesmith

Sorry for the radio silence on this. I've started merging in the changes from @alaa-alshamy but the code snippet above was taken from an older version of the library, so it's taking a little longer to incorporate it in.

I will try to get this incorporated soon, but in the meantime @shanesmith if you want to submit a PR then please go ahead!

fiznool avatar Aug 23 '17 08:08 fiznool

I pushed the original edits above to a new branch (fix-gh15). It needs work as the cloned textarea can be seen below the original one (see gif below). There is also some jumpy movement on the text when a newline is entered (can't see this on the gif as the frame rate was too slow to pick it up).

If anyone would like to take this on further please let me know, otherwise I should be able to take another look in the next week or two.

aug-23-2017 09-40-45

fiznool avatar Aug 23 '17 08:08 fiznool

Thanks for getting back to me.

The cloned area showing at the bottom could perhaps be a min-height styling, or maybe some padding if it has box-sizing: content-box rather than border-box.

The jumpy movement seems to be caused by the setTimeout() here. I've tried removing it and the texterea seems to behave as expected after some quick testing, do you know why it was added?

I'll likely have time to submit a PR soon when we fix these remaining issues. =)

shanesmith avatar Aug 23 '17 14:08 shanesmith

@fiznool for that cloned textarea can be seen below the original one, it's because there's padding top and bottom and border top and bottom, but we can't remove those as we did with margins because if we did the calculation will fail, so the safest way to fix this in my opinion that make the cloned text area opacity: 0, this will make useless space below the original text area but i think it's not a big trouble, And the other solution is using absolute position but it's making some issues in ios WKWebView as i said before

for the jumpy movement, i agree with @shanesmith, so it's caused by the timeout

i made plnkr has the latest changes u can check it here: https://plnkr.co/edit/BIHIlnjOHPHT9dFMk3Zk?p=preview

alaa-alshamy avatar Aug 23 '17 18:08 alaa-alshamy

Is it possible that the position absolute solution is failing due to a negative z-index? Or because it is a sibling of the real textarea?

The angular1 library uses a cloned textarea which is added as a child to the body, and positioned off the page completely. Maybe this is an approach to try?

https://github.com/monospaced/angular-elastic/blob/4c2c75e413e668b4117bf2b3741272b2ab9e24c5/elastic.js#L52

On Wed, 23 Aug 2017 at 19:10, alaa-alshamy [email protected] wrote:

@fiznool https://github.com/fiznool for that cloned textarea can be seen below the original one, it's because there's padding top and bottom and border top and bottom, but we can't remove those as we did with margins because if we did the calculation will fail, so the safest way to fix this in my opinion that make the cloned text area opacity: 0, this will make useless space below the original text area but i think it's not a big trouble, And the other solution is using absolute position but it's making some issues in ios WKWebView as i said before

for the jumpy movement, i agree with @shanesmith https://github.com/shanesmith, so it's caused by the timeout

i made plnkr has the latest changes u can check it here: https://plnkr.co/edit/BIHIlnjOHPHT9dFMk3Zk?p=preview

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/fiznool/ng-elastic/issues/15#issuecomment-324417412, or mute the thread https://github.com/notifications/unsubscribe-auth/AAyGf4ZfqcKZZfnCgLIi-XbV0ZsTfYZwks5sbGsIgaJpZM4M6aWe .

fiznool avatar Aug 23 '17 18:08 fiznool

Position absolute was also the approach I first had in mind.... @alaa-alshamy what issues were you seeing on WKWebView?

shanesmith avatar Aug 23 '17 18:08 shanesmith

@fiznool @shanesmith

actually i forgot the issues that i faced there :) it was long time, but i will try to remember

alaa-alshamy avatar Aug 23 '17 18:08 alaa-alshamy