Vue: Directiva dinámica del modelo v

Creado en 16 jul. 2015  ·  42Comentarios  ·  Fuente: vuejs/vue

Actualmente, la directiva v-model no admite enlaces de tipo bigote para expresiones de enlace, pero esta característica sería extremadamente útil para crear implementaciones de tipo constructor de formularios.

Comentario más útil

ya puedes hacerlo con v-model="form[field.name]" .

Todos 42 comentarios

¿Puede dar un ejemplo de para qué sería útil?

Digamos, imaginemos que estamos construyendo un clon de phpmyadmin, que recibe datos de la declaración DESCRIBE TABLE y construye el formulario del editor de filas a partir de esos datos. Las expresiones de enlace serán inherentemente dinámicas en este caso, ya que solo conoceremos los nombres de los campos después de ejecutar SQL DESCRIBE TABLE.

+1, yo también estoy buscando esto

+1, espero ver esto

Todavía no entiendo completamente qué permite esto que la sintaxis actual no puede lograr. ¿Quizás algunas muestras de código?

Algún pseudocódigo relacionado con el clon de phpmyadmin descrito anteriormente:

    <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>

ya puedes hacerlo con v-model="form[field.name]" .

podemos ? Guau !

evan, ¿puedes poner un violín js que muestre un ejemplo de todo-ish?

@ yyx990803 , eso es genial, pero fue solo un ejemplo que muestra solo un ejemplo de uso dinámico. La lógica puede ser más compleja en algún tipo de aplicación empresarial.

Para que quede claro, estoy en contra de la idea de permitir interpolaciones dentro de expresiones directivas. En este momento, bigotes significa que se espera que el resultado evaluado sea una cadena y se use como una cadena (para insertarse en el DOM o realizar una búsqueda de identificación). Evaluar los bigotes en expresiones que luego se pueden evaluar lo convierte en dos capas de abstracción y puede terminar haciendo que sus plantillas sean muy confusas.

Creo que sería muy valioso agregar la capacidad de interpolar la cadena antes de evaluar la expresión.

el método data[pathString] funciona bien para objetos con 1 nivel anidado, pero para 2 o más no he encontrado una manera de enlazar dinámicamente.

tal vez agregue un modificador a la encuadernación para que sea más claro que los bigotes

Ejemplo

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'>

o tal vez una función getter / setter que se pueda pasar.

descargo de responsabilidad: no he leído la especificación 2.0, por lo que es posible que haya abordado esto allí.

@bhoriuchi ¿

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

Y por v-model puede usar la propiedad calculada con setter.

@simplesmiler no he probado propiedades calculadas en un enlace bidireccional, lo intentaré. gracias por el consejo.

Actualizar

@simplesmiler : el problema con el que me estoy this dentro del captador o incluso value en get(value) ambos apuntan al componente.

algunos antecedentes sobre mi caso de uso.

Estoy creando un generador de formularios que usa un objeto json para construir los formularios. el objeto de configuración es más o menos una matriz bidimensional de objetos (filas / formularios). cada objeto de configuración de formulario tiene un campo modelo que tiene la cadena de ruta al campo que debe establecerse. para usar una propiedad calculada para esto, necesitaría poder determinar a partir del componente utilizando el enlace del componente qué índice de fila / formulario para buscar la ruta del modelo desde el objeto de configuración

actualmente tengo esto funcionando usando una matriz bidimensional preinicializada llamada formData que vinculo cada modelo de formulario con v-model="formData[rowIndex][formIndex]" y observo cambios en ese objeto y actualizo el objeto de datos principal, pero yo No me gusta este enfoque porque requiere que inicialice previamente una matriz para la adición de campos dinámicos.

Necesito 2 niveles de anidación porque estoy usando este componente de creación de formularios en otro componente que necesita establecer un objeto que se parezca a

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

donde se vería mi cadena de ruta

templates.operatingSystems[<dynamic uuid>]

Actualización 2

cambié de usar una matriz multidimensional a un objeto simple con nombres clave

"<rowIndex>_<formIndex>"

y usó una vigilancia profunda para mantener los datos sincronizados con los de los padres. Sigo pensando que sería beneficioso un enlace entrelazado.

+1

¡Para mí, v-model="$data[field.name]" hace el truco!

@victorwpbastos esto no funciona para configurar objetos profundamente anidados, ya que solo usará field.name como clave

por ejemplo, si tiene la siguiente cadena de datos y campo

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

y tu usas

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

e ingrese el valor de 2 en el formulario, los datos terminarían luciendo así

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

la razón por la que el enlace interpolado es útil es cuando está construyendo entradas dinámicas anidadas donde no puede "codificar" la ruta principal (por ejemplo, 'animal.dog') en la directiva

Revisé esto y encontré una solución más simple. Puede crear un objeto personalizado y agregarle captadores / definidores al crearlo utilizando la cadena de ruta del modelo. Aquí hay un ejemplo simple.

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>

en 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>

Si se usa de esta manera, ¿de alguna manera podemos configurar el observador para cada uno de los datos reactivos creados dinámicamente?

@luqmanrom No estoy familiarizado con el funcionamiento interno del observador de vue, pero creo que cualquier cosa creada con vue.set se puede ver, por lo que podría agregar un código para ver accesorios dinámicos y emitir pares en los cambios o puede filtrar ver el objeto de destino. Alguien más podría tener una sugerencia mejor

Escribí un juego de herramientas para esto. también te permite mutar vuex usando v-model

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

Esto debería funcionar:

Directiva

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' } }
        };
    }
});

Aquí está la referencia esencial .

Hola a todos. Estoy usando VUE.js con Laravel. Tengo campos de formularios personalizados dinámicos que provienen de la base de datos. Seguí a @ yyx990803 . v-model = "formulario ['nombre']". El campo funciona. Pero el problema es que no puedo obtener los valores de campo en el controlador laravel. Alguien aquí. Estoy usando @tylerOtwell Form.js Class.
Su ayuda será muy apreciada.
Gracias

Este no es un foro de ayuda. Tenemos uno dedicado para responder preguntas en https://forum.vuejs.org

Realmente tuve problemas para intentar invocar una función para averiguar el v-model . He aquí un ejemplo.

Estoy tratando de crear un selector de rango de fechas que se vea así.
image

Aquí, los ajustes preestablecidos provienen de una matriz que se ve así ...

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

Ahora, también tengo dos fechas para esos campos de entrada en mi data de componente. startDate & endDate .

Lo que realmente quiero hacer es comparar la fecha que el usuario ha seleccionado con las fechas pasadas en mi configuración preestablecida y establecer el v-model en true o false pero no puedo porque...

  • v-model no acepta condiciones, así que no puedo hacer preset.range[0] === startDate && preset.range[1] === endDate .
  • v-model no permite que se pase v-for alias a una función. Entonces no puedo hacer algo como
<li v-model="isActive(index)" v-for="(preset, index) in presets">
...
</li>

Permitir tener declaraciones condicionales al menos puede resolver este problema fácilmente.

Además, es posible que esté haciendo algo fundamentalmente incorrecto, así que indique si podría lograrlo de alguna manera diferente.

Actualmente, he resuelto el problema aprovechando el hecho de que las propiedades calculadas son llamadas a funciones.

Texto

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

Plantilla

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

Pero realmente me parece un truco. No estoy seguro. Por favor recomiende.

¿Alguien sabe si esto también funciona en combinación con Vuex como se explica aquí? https://vuex.vuejs.org/guide/forms.html

Quiero tener un campo de entrada que sea un poco dinámico.

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

¿Cómo puedo acceder al "alcance" del elemento dom dentro del siguiente código?

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

@vielhuber intenta usar ref ?

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

Hola, tengo una pregunta de Vue relacionada con este tema: "directiva dinámica del modelo v":

Cuando estoy implementando un componente de Vue, ¿cómo puedo controlar dinámicamente el modificador del modelo v - .lazy , etc.?
por ejemplo:

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

Esto funciona para mi.

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

@fritx Para cambiar "dinámicamente" el modificador, utilicé el director v-if así.

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

Sin embargo, esto puede resultar engorroso si desea una gran variedad de múltiples combinaciones de modificadores.

Supongo que una opción podría ser crear un componente reutilizable que contenga todas las declaraciones if y pasarle el componente de entrada que desea representar y la matriz de modificadores que determina qué entrada con los modificadores deseados se representa. Sin embargo, usar la declaración if como la anterior fue lo suficientemente bueno para mí.

No pude encontrar la manera de acceder dinámicamente a la propiedad calculada en la directiva del modelo v.
No hay forma de que acceda a mis propiedades calculadas, ya que puede acceder a las propiedades de datos con
v-model = "$ datos [algo]"

Mi código es algo como esto:

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

Necesito la forma de acceder a la propiedad calculada con una cadena, que no pude encontrar.
Este es un ejemplo, pero también funcionarían diferentes soluciones.
<input v-model="$computed[someDynamicString]">
o solo
<input v-model="[someDynamicString]">

Lo más parecido que he encontrado es "_computedWatchers [someDynamicString] .value" pero eso no funciona con setters y getters, tal vez funcionaría si fuera solo un valor calculado.

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

Este es mi dialogTemp:

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

@fritx Para cambiar "dinámicamente" el modificador, utilicé el director v-if así.

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

Sin embargo, esto puede resultar engorroso si desea una gran variedad de múltiples combinaciones de modificadores.

Supongo que una opción podría ser crear un componente reutilizable que contenga todas las declaraciones if y pasarle el componente de entrada que desea representar y la matriz de modificadores que determina qué entrada con los modificadores deseados se representa. Sin embargo, usar la declaración if como la anterior fue lo suficientemente bueno para mí.

Es genial, pero tuve que pasarle muchos accesorios al que es tan detallado, ¿alguna idea? @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 Puede cambiar v-model a :value / @input y analizarlo 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 se ve genial, hombre

@danhanson me temo que debería ser:

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

No estoy seguro, lo intentaré. ¡Gracias!

@ninojovic

Encontré una solución aquí: https://forum.vuejs.org/t/accessing-computed-properties-from-template-dynamically/4798/9

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

Algo como esto

<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;'
        },
      },
    ],
  },
]

Cuando el tipo de campo es Number , quiero usar v-model.number , que es mucho más conveniente. @fritx

Desmonto el modelo en V para ajustarlo.

<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>

He clonado HMTL usando (alguna parte para la entrada de formulario) que estoy insertando usando jquery. (no digas por qué estoy usando jquery). ahora mi elemento está siendo insertado por jquery. por lo que es posible vincular v-model.

$('.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>

Entonces, ¿cómo puedo vincular el modelo v en el área 2 y 3?

@ninojovic

Encontré una solución aquí: https://forum.vuejs.org/t/accessing-computed-properties-from-template-dynamically/4798/9

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

También funciona para mí, pero la variable "_self" está reservada para las propiedades internas de Vue (ver # 2098).

En otras palabras, esta implementación puede romperse en el futuro.

Prefiero esta forma:

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

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

Para obtener más detalles, consulte: https://stackoverflow.com/questions/52104176/use-of-self-attribute-from-vue-vm-is-reliable

¿Fue útil esta página
0 / 5 - 0 calificaciones