dustbuster
dustbuster

Reputation: 82182

Set default 'value' of custom Component v-model for v-text-field

Basically, I am using a custom component someone else wrote, and I want to set a default value for this v-text-field and i have tried EVERYTHING to override the editedItem.color v-model. Nothing I am capable of coming up with will work!

I am a laravel php dev, and I could really use some help from my stack friends here. I am at a new job, and i do not wish to fail.

<div v-if="formState === 'create'">
  <v-text-field
    v-model="editedItem.color"
    :default="'#FF0000'"
    :value="'#FF0000'"
    :disabled="true"
    label="Color*"
   />
</div>

As far as data, when i go the custom component, the data is as follows:

  data: () => ({
    formState: 'create',
    loading: false,
    items: [],
    editedItem: {},
    selectedItems: [],
  }),

This seems like something that should be really easy. Just set a default value, and send that to the API. But with the v-model, it will not accept a v-bind:value or v-bind:default

This is a Vuetify component, and I am a newb Vue dev.

So in Summary, nothing will work without the v-model="editedItem.color", and yet, nothing will work if i do not set that default value.

The issue being that color picker returns an array, and we need it NOT to return an array.

So either I need to set the default value for 'create' mode to the #FF0000 hex value, or i need to parse out the returned value from the v-color-picker and JUST use the hex value and not return the array. So basically it all boils down to intercepting the editedItem.color for either solution.

This is my complete page/tags/index.vue page implementing the custom component.

Thanks SOF Fam!


<template>
  <work-custom-table
    v-model="headers"
    :routes="routes"
    :title="title"
    settings-key="crud.table"
    sort-by="name"
    allow-merge
  >
    <template #item.preview="{ item }">
      <v-chip :color="item.color">{{ item.name }}</v-chip>
    </template>
    <template #form="{editedItem, formState}">
      <v-row>
        <v-col>
          <v-text-field
            v-model="editedItem.name"
            :disabled="formState === 'view'"
            :rules="[$rules.required]"
            label="Name*"
            hint="*Required"
          />
        </v-col>
      </v-row>

      <v-row>
        <v-col>
          <v-text-field
            v-model="editedItem.description"
            :disabled="formState === 'view'"
            :rules="[$rules.required]"
            label="Description"
          />
        </v-col>
      </v-row>

      <v-row>
        <v-col>
          <div v-if="formState === 'create'">
            <v-text-field
              v-model="editedItem.color"
              :disabled="true"
              label="Color*"
            />
          </div>
          <div v-else>
            <v-color-picker
              id="tag-color"
              v-model="editedItem.color"
              :default="'#FF0000'"
              :disabled="formState === 'view'"
              class="elevation-0"
              label="Color*"
              hint="*Required"
              mode="hexa"
              hide-canvas
            />
          </div>
        </v-col>
      </v-row>
    </template>
  </work-custom-table>
</template>

Any help would be great!

Upvotes: 1

Views: 12465

Answers (2)

tao
tao

Reputation: 90188

<v-text-field> is just a wrapper around an html <input>.

And in Vue, on <input>s, v-model and value are mutually exclusive. value is only read when you don't have two-way data binding (using v-model).

Which means all you need to do is provide the default value in the editedItem itself, before passing it to the <v-text-input> (and remove default & value). Example:

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
    formState: 'create',
    editedItem: {
      color: '#FF0000'
    }
  })
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>

<div id="app">
  <div v-if="formState === 'create'">
    <v-text-field v-model="editedItem.color" :disabled="true" label="Color*" />
  </div>
</div>

Obviously, if editedItem is coming from another component or from an external API, you'll have to intercept it and populate the default value of color on it if color does not have a truthy value. A generic example:

methods: {
  getEditedItemFromApi() {
    this.$http.get('some/api/url')
      .then(r => this.editedItem = ({ 
        ...r.data,
        // assumming the API returns the editingItem object
        color: r.data.color || '#FF0000'
      }));
 }
}

(we're destructuring the response data and adding color property to all its existing properties, with a value of its own color property if it's anything truthy or the default value if it's falsey.

The gist of it is: you have to populate the default value on the actual property bound to v-model, prior to passing it to the <input>.


Here's the relevant part of Vue documentation on v-model.


And, to cover all cases, here's how you'd use a computed with set and get, allowing you map default values:

Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
  el: '#app',
  data: () => ({
    items: [
      { id: 'one', color: 'black' },
      { id: 'two' }
    ],
    selectedItem: null
  }),
  computed: {
    editingItem: {
      get() {
        return { ...this.selectedItem, color: this.selectedItem?.color || '#FF0000' };
      },
      set(item) {
        this.selectedItem = item;
      }
    }
  }
})
.flexer { 
  display: flex;
  align-items: center;
}
.flexer span {
  width: 1em;
  height: 1em;
  border: 3px solid;
  border-radius: 50%;
  margin-left: .5rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <select v-model="selectedItem">
    <option :value="null">Select item</option>
    <option v-for="item in items" :value="item">{{item.id}}</option>
  </select>
  <div class="flexer" v-if="selectedItem">
    <input v-model="editingItem.color" :disabled="true" />
    <span :style="{ borderColor: editingItem.color }" /> 
   </div>
   <pre v-html="{ selectedItem, editingItem }" />
</div>

This would also cover the case when it's coming as prop, from a parent.

Upvotes: 1

dustbuster
dustbuster

Reputation: 82182

My solution has little to do with VUE, but hopefully it can help someone, I hit that a personal state where you start looking for other solutions.

The problem is that on 'create' the color-picker was returning an array, where on edit, it was returning the hex value, so my (maybe a little hacky) solution was to set a default value for the color value initially, and then set color on edit. But instead of manipulating vue vars with getters and setters, I went into my Laravel API FormRequest instance, and you can prepare your data before validation using prepareForValidation() method.

I did this:

protected function prepareForValidation(){
    if(gettype($this->color) == 'array'){
        $this->merge(['color' => $this->color['hex']]);
    }
}

I was able to check for an array, and if array, parse out the value. Sadly, I was not able to get the get and set to work for me.

Thanks!

Upvotes: 0

Related Questions