2

I have a Vue page which loads an json array from an api and displays the content in a list of multiple s by using v-for.

If you focus on one of the textarea's or change the text a function automatically resize's the textarea to fit the content.

<div v-for="post in posts">
<textarea v-model="post.body" rows="1" @focus="resizeTextarea" @keyup="resizeTextarea"></textarea>
</div>

resizeTextarea(e) {
  let area = e.target;
  area.style.overflow = 'hidden';
  area.style.height = area.scrollHeight + 'px';
}

With my limited Vue knowledge, I can't find a solution to automatically resize all textarea's after loading the data from the API. There is no @load on a textarea.

I was trying to reach the same goal with using a watcher on the data but it feels like a long workaround.

Anyone a descent solution? Thank you!

https://jsfiddle.net/oehoe83/c1b8frup/19/

2 Answers 2

9

One solution would be to create a component for your textarea element and then resize it in the mounted() hook. Here's an example using single-file components:

// CustomTextarea.vue
<template>
  <textarea
    v-model="value"
    ref="textarea"
    rows="1"
    @focus="resize"
    @keyup="resize"
  >
  </textarea>
</template>

<script>
  export default {
    props: {
      value: {
        type: String,
        required: true,
      }
    },
    mounted() {
      this.resize();
    },
    methods: {
      resize() {
        const { textarea } = this.$refs;
        textarea.style.height = textarea.scrollHeight - 4 + 'px';
      }
    }
  }
</script>

Then in your parent:

<template>
  <div v-for="post in posts">
    <CustomTextarea v-model="post.body" />
  </div>
</template>

<script>
  import CustomTextarea from './CustomTextarea.vue';

  export default {
    components: {
      CustomTextarea,
    }
    // etc.
  }
</script>

Note: if you're using Vue 3, replace value with modelValue in the child component.

Alternatively you could use a watch like you suggested, there's nothing wrong with that. Something like this:

watch: {
  posts() {
    // Wait until the template has updated
    this.$nextTick(() => {
      [...document.querySelectorAll('textarea')].forEach(textarea => {
        this.resizeTextarea({ target: textarea });
      });
    });
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

you should use a vuejs ref instead of [...document.querySelectorAll('textarea')] or be more precise with a custom css selector if you dont want to target textarea outside your vuejs component
Thank you! Both are nice options. In my case I only have textareas which I want to resize so it works perfect.
I needed to add 4px rather than minus 4px so that the scroll bar didn't show
2

you can add the ref attribute :

<div id="app">
    <div v-for="post in posts" ref="container">
        <textarea v-model="post.body" rows="1"@focus="resizeTextarea" @keyup="resizeTextarea" ></textarea>
    </div>
</div>

and add the following code at the end of mounted() :

this.$nextTick(()=>{
    this.$refs.container.forEach( ta => {
        ta.firstChild.dispatchEvent(new Event("keyup"));
    });
});

2 Comments

you can test it here jsfiddle.net/vtz1gwh3
This is the best solution. Thank you! However for now I use Hannah her second answer because in the real situation, the textarea is nested in a MDBTextarea a couple of levels below the v-for. I'll have to read about the refs functionality to be able to implement it in my case myself.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.