Vue: Diretiva de modelo v dinâmico

Criado em 16 jul. 2015  ·  42Comentários  ·  Fonte: vuejs/vue

Atualmente, a diretiva v-model não oferece suporte a associações do tipo bigode para expressões de associação, mas esse recurso seria extremamente útil para criar implementações do tipo construtor de formulários.

Comentários muito úteis

você já pode fazer isso com v-model="form[field.name]" .

Todos 42 comentários

Você pode dar um exemplo de para que seria útil?

Digamos, imagine que estamos construindo um clone do phpmyadmin, que recebe dados da instrução DESCRIBE TABLE e constrói um formulário de editor de linha a partir desses dados. As expressões de ligação serão inerentemente dinâmicas neste caso, pois só saberemos os nomes dos campos após executar SQL DESCRIBE TABLE.

1, estou procurando por isso também

1, espero ver isso

Ainda não entendo totalmente o que isso permite que a sintaxe atual não consegue alcançar. Talvez alguns exemplos de código?

Algum pseudo-código relacionado ao clone phpmyadmin descrito acima:

    <script>
    modue.exports = {
        data: function(){
            //returns table structure pulled from the backend somehow
            return {fields: [
                {name: "id", type: "integer"},
                {name: "name", type: "varchar"},
                {name: "gender", type: "varchar"}
            ], 
            // this was initialised based on the structure above, does not matter how.
            form: {id: null, name: null, gender: null}); 
        },
       methods: {
          getBindingExpr: function(field){ /* blah-blah */ }
       }

    }
    </script>
    <template>
       <div v-repeat="field: fields">
          <!-- Here we need to display an editor bound to the field -->
           <input type="text" v-model="form.{{field.name}}">
        <!-- Or, we can call a function that calculates the binding expression --
          <input type="text" v-model="{{getBindingExpr(field)}}">
      </div>
    </template>

você já pode fazer isso com v-model="form[field.name]" .

nós podemos ? Uau !

evan você pode colocar um violino js mostrando um exemplo todo-ish

@ yyx990803 , isso é ótimo, mas foi apenas um exemplo mostrando apenas um exemplo de uso dinâmico. A lógica pode ser mais complexa em algum tipo de aplicativo de negócios.

Só para ficar claro, sou contra a ideia de permitir interpolações dentro de expressões diretivas. No momento, bigodes significa que se espera que o resultado avaliado seja uma string e usado como uma string (para ser inserido no DOM ou fazer uma pesquisa de ID). Avaliar bigodes em expressões que podem ser avaliadas torna-o duas camadas de abstração e pode acabar tornando seus modelos muito confusos.

acho que seria muito valioso adicionar a capacidade de interpolar a string antes de avaliar a expressão

o método data[pathString] funciona bem para objetos com 1 nível aninhado, mas para 2 ou mais não encontrei uma maneira de vincular dinamicamente.

talvez adicione um modificador para a ligação para que seja mais claro do que bigodes

Exemplo

let myData = {}
let varPath = 'myData.path.to["my"].obj'
let modelPath = 'myData.path.to["my"].model'
<component-name :myparam.interpolate='varPath'></component-name>
<input v-model.interpolate='modelPath'>

ou talvez uma função getter / setter que pode ser passada.

isenção de responsabilidade: eu não li a especificação 2.0, então você pode ter abordado isso lá.

@bhoriuchi por que nenhuma propriedade computada?

computed: {
  varPath: function() {
    return this.myData.path.to['my'].obj;
  },
},
<component-name :myparam="varPath"></component-name>

E por v-model você pode usar a propriedade computada com setter.

@simplesmiler Eu não tentei propriedades computadas em uma ligação bidirecional, vou tentar. Obrigado pela dica.

Atualizar

@simplesmiler - então o problema que estou enfrentando ao usar uma propriedade computada é que não tenho como passar argumentos para a propriedade computada. this dentro do getter ou mesmo value em get(value) ambos apontam para o componente.

algumas informações sobre meu caso de uso.

Estou criando um construtor de formulários que usa um objeto json para construir os formulários. o objeto de configuração é mais ou menos uma matriz bidimensional de objetos (linhas / formulários). cada objeto de configuração do formulário tem um campo de modelo que contém a string do caminho para o campo que deve ser definido. a fim de usar uma propriedade computada para isso, eu precisaria ser capaz de determinar a partir do componente usando a vinculação de componente qual índice de linha / formulário a fim de procurar o caminho do modelo do objeto de configuração

atualmente, tenho isso funcionando usando uma matriz bidimensional pré-inicializada chamada formData qual vinculo cada modelo de formulário com v-model="formData[rowIndex][formIndex]" e observo as alterações nesse objeto e atualizo o objeto de dados pai, mas eu não gosto dessa abordagem porque exige que eu pré-inicialize uma matriz para adição de campo dinâmico.

eu preciso de 2 níveis de aninhamento porque estou usando este componente construtor de formulários em outro componente que precisa definir um objeto que se parece com algo como

data: {
  templates: {
    operatingSystems: {
      <someuuid1>: [ <osid1>, <osid2> ],
      <someuuid2>: [ <osid5>, <osid10>, <osid22> ]
   }
  }
}

onde minha string de caminho ficaria

templates.operatingSystems[<dynamic uuid>]

Atualização 2

mudei de usar uma matriz multidimensional para um objeto simples com nomes de chave

"<rowIndex>_<formIndex>"

e usou uma observação profunda para manter os dados em sincronia com o pai. Ainda acho que uma ligação interplated seria benéfica.

+1

Para mim, v-model="$data[field.name]" faz o truque!

@victorwpbastos isso não funciona para definir objetos profundamente aninhados, pois usará apenas o field.name como a chave

por exemplo, se você tiver os seguintes dados e string de campo

$data = {
  'animal': {
    'dog': {
      'husky': 1
    }
  }
}
field.name = 'animal.dog.husky'

e você usa

v-model="$data[field.name]"

e inserir o valor 2 no formulário, os dados ficariam assim

$data = {
  'animal': {
    'dog': {
      'husky': 1
    }
  },
 'animal.dog.husky': 2
}

a razão pela qual o bind interpolado é útil é quando você está construindo entradas aninhadas dinâmicas onde você não pode "codificar" o caminho pai (por exemplo, 'animal.dog') na diretiva

Eu revisitei isso e encontrei uma solução mais simples. Você pode criar um objeto personalizado e adicionar getters / setters a ele na criação usando a string de caminho do modelo. Aqui está um exemplo simples

lista de entrada

<template lang="jade">
  div
    div(v-for="form in config.forms")
      input(v-model="formData[form.model]")
</template>

<script type="text/babel">
  import Vue from 'vue'
  import _ from 'lodash'

  export default {
    props: ['value', 'config'],
    computed: {},
    methods: {
      vueSet (obj, path, val) {
        let value = obj
        let fields = _.isArray(path) ? path : _.toPath(path)
        for (let f in fields) {
          let idx = Number(f)
          let p = fields[idx]
          if (idx === fields.length - 1) Vue.set(value, p, val)
          else if (!value[p]) Vue.set(value, p, _.isNumber(p) ? [] : {})
          value = value[p]
        }
      }
    },
    data () {
      return {
        formData: {}
      }
    },
    created () {
      _.forEach(this.config.forms, (form) => {
        Object.defineProperty(this.formData, form.model, {
          get: () => _.get(this.value, form.model),
          set: (v) => this.vueSet(this.value, form.model, v)
        })
      })
    }
  }
</script>

em uso

<template lang="jade">
  div
    input-list(v-model="formData", :config='formConfig')
</template>

<script type="text/babel">
  import InputList from './InputList'
  export default {
    components: {
      InputList
    },
    data () {
      return {
        formData: {
          name: 'Jon',
          loc: {
            id: 1
          }
        },
        formConfig: {
          forms: [
            { type: 'input', model: 'loc.id' },
            { type: 'input', model: 'loc["name"]' }
          ]
        }
      }
    }
  }
</script>

Se usarmos desta forma, podemos definir o observador para cada um dos dados reativos criados dinamicamente?

@ luqmanrom Não estou familiarizado com o funcionamento interno do observador vue, mas acredito que qualquer coisa criada com vue.set pode ser observada, então você pode adicionar algum código para assistir adereços dinâmicos e emitir eventos nas mudanças ou você pode ver o objeto alvo. Alguém pode ter uma sugestão melhor

Eu escrevi um kit de ferramentas para isso. também permite que você modifique vuex usando v-model

https://github.com/bhoriuchi/vue-deepset

Isso deve funcionar:

Diretriz

Vue.directive('deep-model', {
    bind(el, binding, vnode) {
        el.addEventListener('input', e => {
            new Function('obj', 'v', `obj.${binding.value} = v`)(vnode.context.$data, e.target.value);
        });
    },
    unbind(el) {
        el.removeEventListener('input');
    },
    inserted(el, binding, vnode) {
        el.value = new Function('obj', `return obj.${binding.value}`)(vnode.context.$data);
    },
    update(el, binding, vnode) {
        el.value = new Function('obj', `return obj.${binding.value}`)(vnode.context.$data);
    }
});

Uso (componente)

const component = Vue.extend({
    template: `<input v-deep-model="'one.two.three'">`,
    data() {
        return {
            one: { two: { three: 'foo' } }
        };
    }
});

Aqui está a referência essencial .

Olá, qualquer pessoa aqui. Estou usando o VUE.js com o Laravel. Tenho campos de formulário personalizado dinâmico provenientes do banco de dados. Eu segui @ yyx990803 . v-model = "formulário ['nome']". O campo funciona. Mas o problema é que não consigo obter os valores dos campos no laravel Controller. Alguém aqui. Estou usando a classe @tylerOtwell Form.js.
sua ajuda será muito apreciada.
Obrigado

Este não é um fórum de ajuda. Temos um dedicado para responder a perguntas em https://forum.vuejs.org

Realmente tive dificuldade em tentar invocar uma função para descobrir o v-model . Aqui está um exemplo.

Estou tentando construir um seletor de intervalo de datas semelhante a este.
image

Aqui, as predefinições vêm de uma matriz semelhante a esta ..

presets = [
  {
    label: 'Today',
    range: [moment().format('YYYY-MM-DD'), moment().format('YYYY-MM-DD')]
  },
]

Agora, também tenho duas datas para esses campos de entrada em meu data de componente. startDate & endDate .

O que eu realmente quero fazer é comparar a data que o usuário selecionou com as datas passadas em minha configuração predefinida e definir o v-model como true ou false mas não consigo Porque...

  • v-model não aceita condições, portanto não posso fazer preset.range[0] === startDate && preset.range[1] === endDate .
  • v-model não permite que v-for alias seja passado para uma função. Então eu não posso fazer algo como
<li v-model="isActive(index)" v-for="(preset, index) in presets">
...
</li>

Permitir pelo menos instruções condicionais pode resolver esse problema facilmente.

Além disso, posso estar fazendo algo fundamentalmente errado, então, por favor, indique se eu poderia fazer isso de alguma maneira diferente.

Atualmente, resolvi o problema explorando o fato de que as propriedades computadas são chamadas de função.

Roteiro

computed: {
  isActive() {
      return this.presets.map(
        preset =>
          preset.range[0] === this.startDate && preset.range[1] === this.endDate
      );
    }
}

Modelo

<li v-model="isActive[index]" v-for="(preset, index) in presets">
...
</li>

Mas realmente parece um hack para mim. Não tenho certeza. Por favor sugira.

Alguém sabe se isso também funciona em combinação com o Vuex, conforme explicado aqui? https://vuex.vuejs.org/guide/forms.html

Eu quero ter um campo de entrada que seja um pouco dinâmico.

<input v-model="dataHandler" :scope="foo" type="checkbox" />

Como posso acessar o "escopo" do elemento dom dentro do código a seguir?

computed: {
  message: {
    get () {
      //
    },
    set (value) {
      //
    }
  }
}

@vielhuber tentou usar ref ?

<input ref="myInput" v-model="dataHandler" :scope="foo" type="checkbox" />
this.$refs.myInput.getAttribute('scope') // => 'foo'

Olá, tenho uma pergunta do Vue relacionada a este tópico - "diretiva do modelo v dinâmico":

Quando estou implementando um componente Vue, como posso controlar dinamicamente o modificador v-model - .lazy , etc ??
por exemplo:

<el-input v-model[field.lazy ? '.lazy' : '']="model[field.key]">

Isso funciona para mim.

<input v-model="$data[field].key" type="text">

@fritx Para alterar "dinamicamente" o modificador, usei o diretor v-if assim.

<input v-if="field.lazy" v-model.lazy="model[field.key]">
<input v-else v-model="model[field.key]">

Isso pode ser complicado se você quiser uma grande variedade de combinações múltiplas de modificadores.

Acho que uma opção poderia ser criar um componente reutilizável que contenha todas as instruções if e passar para ele o componente de entrada que você deseja renderizar e a matriz de modificadores que determina qual entrada com os modificadores desejados é renderizada. Usar a instrução if como acima foi bom o suficiente para mim.

Não consegui encontrar a maneira de acessar dinamicamente a propriedade computada na diretiva v-model.
Não tenho como acessar minhas propriedades computadas, pois você pode acessar as propriedades de dados com
v-model = "$ data [algo]"

Meu código é mais ou menos assim:

computed: { get () { // }, set (value) { // } } }

Preciso de uma maneira de acessar a propriedade computada com string, que não consegui encontrar.
Este é um exemplo, mas soluções diferentes também funcionam.
<input v-model="$computed[someDynamicString]">
ou apenas
<input v-model="[someDynamicString]">

A coisa mais próxima que encontrei é "_computedWatchers [someDynamicString] .value", mas que não funciona com setters e getters, talvez funcionasse se fosse apenas um valor calculado.

v-model="dialogTemp.tmBasFuelEntities[dialogTemp.tmBasFuelEntities.findIndex(t=>t.paramCode==item.paramCode)].paramValue"

Este é meu diálogoTemp:

dialogTemp: {
  tmBasFuelEntities: [
    {
      paramCode: '',
      paramValue: ''
    },
    {
      paramCode: '',
      paramValue: ''
    },
    {
      paramCode: '',
      paramValue: ''
    },
  ]
}

@fritx Para alterar "dinamicamente" o modificador, usei o diretor v-if assim.

<input v-if="field.lazy" v-model.lazy="model[field.key]">
<input v-else v-model="model[field.key]">

Isso pode ser complicado se você quiser uma grande variedade de combinações múltiplas de modificadores.

Acho que uma opção poderia ser criar um componente reutilizável que contenha todas as instruções if e passar para ele o componente de entrada que você deseja renderizar e a matriz de modificadores que determina qual entrada com os modificadores desejados é renderizada. Usar a instrução if como acima foi bom o suficiente para mim.

É legal, mas eu tive que passar muitos adereços para aquele que é tão prolixo, alguma ideia? @danhanson

<template v-else-if="itemCom">
        <component v-if="getFieldType(field) === 'number'"
          :is="itemCom"
          :model="model"
          :field="field"
          :schema="schema"
          v-model.number="model[field.key]"
          v-loading="field.loading"
          v-bind="getFieldAttrs(field)"
          v-on="field.listen"
          @form-emit="handleFormEmit"
        ></component>
        <component v-else
          :is="itemCom"
          :model="model"
          :field="field"
          :schema="schema"
          v-model="model[field.key]"
          v-loading="field.loading"
          v-bind="getFieldAttrs(field)"
          v-on="field.listen"
          @form-emit="handleFormEmit"
        ></component>

@fritx Você pode alterar v-model para :value / @input e analisá-lo manualmente.

</template>
        <component v-if="getFieldType(field) === 'number'"
          :is="itemCom"
          :model="model"
          :field="field"
          :schema="schema"
          :value="parseField(field, model[field.key])"
          @input="model[field.key] = parseField(field, $event.target.value)"
          v-loading="field.loading"
          v-bind="getFieldAttrs(field)"
          v-on="field.listen"
          @form-emit="handleFormEmit"
        ></component>
<template>
<script>

export default {
    ...
    methods: {
        parseField (field, val) {
            if (this.getFieldType(field) === 'number') {
                return Number(val);
            }
            return val;
        }
    }
};
</script>

@danhanson parece ótimo, cara

@danhanson temo que deveria ser:

:value="getFieldValue(field, model[field.key])"
@input="model[field.key] = getFieldValue(field, $event)"
@change="model[field.key] = getFieldValue(field, $event)"

Não tenho certeza, vou tentar. Obrigado!

@ninojovic

Encontrei uma solução aqui: https://forum.vuejs.org/t/accessing-computed-properties-from-template-dynamically/4798/9

<input v-model="_self[someDynamicString]">
funciona para mim

Algo assim

<el-input
  v-if="!nestedField.widget"
  v-model="form[nestedField.id]"
  placeholder=""
  v-bind="nestedField.rest"
>
[
  {
    label: '收房价格',
    id: 'housePrice',
    type: Number,
    widget: 'div',
    fieldSet: [
      {
        label: '',
        id: 'housePrice',
        type: Number,
        defaultValue: 0,
        rest: {
          style: 'width:5em;'
        },
      },
      {
        label: '',
        id: 'priceUnit',
        type: String,
        widget: 'select',
        defaultValue: '元/月',
        options: [
          { label: '元/月', value: '元/月' },
          { label: '元/年', value: '元/年' },
          { label: ' 元/天·m2', value: '元/天·m2' },
        ],
        rest: {
          style: 'width:6em;'
        },
      },
    ],
  },
]

Quando o tipo de campo é Number , quero usar v-model.number , que é muito mais conveniente. @fritx

Desmontei o modelo V para caber nele.

<el-input
  v-if="!nestedField.widget"
  :value="form[nestedField.id]"
  @input="v => { form[nestedField.id] = isNumber(nestedField.type) ? Number(v) : v }"
  placeholder=""
  v-bind="nestedField.rest"
>
  <template v-if="nestedField.suffixText" slot="append">{{nestedField.suffixText}}</template>
</el-input>

Tenho clone HMTL usando (alguma parte para entrada de formulário) que estou inserindo usando jquery. (não diga por que estou usando jquery). agora meu elemento está sendo inserido por jquery. portanto, é possível vincular o modelo v.

$('.area').append('formPart')
in form i have some inputs like
<div class="form-group">
<input type="text" name="area2" /> 
<input type="text" name="area3" />
</div>

Então, como posso vincular o modelo-v nas áreas 2 e 3.

@ninojovic

Encontrei uma solução aqui: https://forum.vuejs.org/t/accessing-computed-properties-from-template-dynamically/4798/9

<input v-model="_self[someDynamicString]">
funciona para mim

Funciona para mim também, mas a variável "_self" é reservada para as propriedades internas do Vue (consulte # 2098).

Em outras palavras, essa implementação pode quebrar no futuro.

Eu prefiro assim:

<template>
  <input v-model="mySelf[someDynamicString]">
</template>

<script>
export default {
  data() {
    return {
      mySelf: this
    }
  }
}
</script>

Para obter mais detalhes, consulte: https://stackoverflow.com/questions/52104176/use-of-self-attribute-from-vue-vm-is-reliable

Esta página foi útil?
0 / 5 - 0 avaliações