4

Basically, I'm creating a form component that is contained inside a v-dialog. The form component will have different child components that are rendered based on select input. So I have to set width of v-dialog to "unset", so that the width of the dialog will stretch to match its content.

The transition works when I toggle the value of width, eg: either 450px or 300px. The problem is that I don't know beforehand the width of the form contains in the dialog, so I definitely need to use dynamic width.

So far, I can not find anyways to achieve transition when using dynamic width. I was trying to get the width of the form component using refs, but setting width to unset, prevent the transition. By the way, the transition I'm talking about is the transition of the width, when using fixed width, it shows nice transition but not for dynamic width

<div id="app">
  <v-app id="inspire">
    <div class="text-center">
      <v-dialog v-model="dialog" width="unset">
        <template v-slot:activator="{ on }">
          <v-btn color="red lighten-2" dark v-on="on">
            Click Me
          </v-btn>
        </template>
        <v-card>
          <v-select v-model="selectedForm" :items="items">
          </v-select>
          <div v-if="selectedForm==='form-a'" class='form-a'>FormA</div>
          <div v-if="selectedForm==='form-b'" class='form-b'>FormB</div>
          <v-card-actions>
            <v-spacer></v-spacer>
            <v-btn color="primary" text @click="dialog = false">
              I accept
            </v-btn>
          </v-card-actions>
        </v-card>
      </v-dialog>
    </div>
  </v-app>
</div> 

new Vue({
  el: "#app",
  vuetify: new Vuetify(),
  data() {
    return {
      selectedForm: "form-a",
      items: ["form-a", "form-b"],
      dialog: false
    };
  }
});

codepen for using fixed width: https://codepen.io/duongthienlee/pen/MWaBLXm
codepen for using dynamic width: https://codepen.io/duongthienlee/pen/GRpBzmL

Noted that in the example i made in codepen, I defined width already, but the real case is that I don't know beforehand the width of form-a and form-b component. form-a and form-b width will be inherited by its parent div which is v-dialog, so that's why I set the width of v-dialog to be unset. An example of what I mean "dynamic width": form-a has a select input. When user chooses an item, there will be a request to server to get input labels. So form-a will render multiple input fields based on the response body from server. The response body will contain label and default values information. So that makes the width of form-a becomes dynamic.

Winchester
  • 460
  • 6
  • 19

4 Answers4

2

I think something like this can work for you.

Change v-dialog like so:

<v-dialog v-model="dialog" :width="forms.find(x => x.name===selectedForm).width">

Modify data() to return a forms prop:

data() {
    return {
      selectedForm: "form-a",
      items: ["form-a", "form-b"],
      dialog: false,
      forms: [
        {
          name: 'form-a',
          width: 200
        },
        {
          name: 'form-b',
          width: 1000
        }
      ]
    };
  }
Cato Minor
  • 2,992
  • 3
  • 29
  • 42
  • Hmm, this way still uses fixed width meaning that we provide value of the width beforehand. My situation may be a little bit different from what I describe in the questions. The form input fields are queried from the server, so the labels of inputs are different for each form. So I can only get the width of the form after the form is rendered – Winchester May 17 '20 at 20:31
  • OK, I think I better understand the problem, but I don't quite know the solution. I might try using `$refs` and `clientWidth`, similar to this situation: https://stackoverflow.com/questions/44948714/get-element-height-with-vuejs/44949482 – Cato Minor May 17 '20 at 21:14
  • The problem when I using refs to get clientWidth is that I still need to specify the width of the modal to be "unset", so that the content of the modal(the form) will have its width. But then, since the width was set to be unset, then transition won't work even I get the width after that. – Winchester May 18 '20 at 08:01
1

What you want to do is get the size of the rendered form, and then apply it to the dialog. This is a common theme when attempting to animate content with dynamic dimensions. One way to do this is by:

  1. Set the form's visibility as hidden
  2. Wait for it to render
  3. Get the form's width and set it to the dialog
  4. Unset the form's visibility

The tricky/hacky part is that you have to properly await DOM (setTimeout) and Vue ($nextTick) recalculations. I didn't have to await for Vue's $nextTick in this example, but you probably will if you're rendering nested form components:

<div class="form-container">
  <div :style="formStyle('form-a')" class='form-a' ref="form-a">FormA</div>
  <div :style="formStyle('form-b')" class='form-b' ref="form-b">FormB</div>
</div>
computed:{
  formStyle(){
    return form => ({
      visibility: this.selectedForm == form ? 'inherit' : 'hidden',
      position: this.selectedForm == form ? 'inherit' : 'absolute'
    })
  }
},
methods: {
  async onSelectChange(form){
    // async request
    await new Promise(resolve => setTimeout(resolve, 1000))
    this.selectedForm = form
    this.recalculate()
  },
  async recalculate(){
    // wait for DOM to recalculate
    await new Promise(resolve => setTimeout(resolve))
    const formEl = this.$refs[this.selectedForm]
    this.dialogWidth = formEl.clientWidth
    this.dialogHeight = formEl.clientHeight
  },
  ...
}

Here's the working code to give you an idea: https://codepen.io/cuzox/pen/yLYwoQo

cuzox
  • 794
  • 7
  • 15
0

If I understand you correctly, then this can be done using css. You can try replace all the fix width in the form with

  width: fit-content;

For example in you codepen:

.form-a {
  width: fit-content;
  height: 350px;
  background: blue;
}
.form-b {
  width: fit-content;
  height: 500px;
  background: red;
}
Jake Lam
  • 3,254
  • 3
  • 25
  • 44
  • This is not exactly what I'm looking for as I believe the problem is on v-dialog component not on form component. Setting width of v-dialog component to be unset will make the form component to have its max width. I may need to rewrite my question a little bit. – Winchester May 22 '20 at 09:28
0

The v-dialog renders into a div with class v-dialog: v-dialog width set to 300 or 450

v-dialog width set to unset

It seems the animation only works when the the width is of known value, so it cannot be just "unset". The solution would be to get the width of the child element, and set the width of the v-dialog accordingly with a variable.

See VueJS get Width of Div on how to get the width of the child element.

Let me know if it works, I find this is very interesting.

Stefani
  • 142
  • 1
  • 2
  • 10
  • If i omit setting the width to be "unset", v-dialog will render the dialog component to 100% width subtract the margin. The problem is that I don't know the width of the form component as it depends on the user selection. So the only way to make v-dialog component to not render to 100% is to set the width="unset". – Winchester May 22 '20 at 13:59
  • could you elaborate how the form width changes depending on user selection? can this width not be captured into a dynamic variable that sets the v-dialog width? – Stefani May 22 '20 at 14:25
  • Let me take an example: form-a has a select input. When user chooses an item, there will be a request to server to get input labels. So form-a will render multiple input fields based on the response body from server. The response body will contain label and default values information. So that makes the width of form-a becomes dynamic – Winchester May 22 '20 at 14:33