Closed loranger closed 8 years ago
In Vue 2.0, the template is like a function: it gets called every time something changes. With this in mind, having an inline value
is basically saying the input's value is static and should never change - which doesn't make sense when you are using v-model
with it.
This does makes your use case a bit more complicated, but here's how I'd do it - render your form state in your server HTML output and initialize your component with that data: https://jsfiddle.net/vzns7us7/
I'm surprised it's now so hard to instanciate from a simple html element, but I can understand the reasons. Anyway, thank you for your support, and thank you so much for Vue.js !
I don't think this is an elegant approach - Consider a situation as below:
We got a regular form A with two fields:
When the user for the first time visits this form, I want to show some defaults. So, I set them up in my data as follows:
{
postName: "Hello world",
permalink: "sample-permalink"
}
(You could argue this could be set as a placeholder attribute, but this isn't always the case) Now, after the user updates, these values are persisted in the db and each time the user visits the same form, these values will be in the value attribute. HOWEVER, vue will still render from the JSON above. This is a very common scenario in popular MVC frameworks such as rails.
The only other way (being new to Vue) would be to manually set the attributes by accessing the elements:
vm.postName: $("#blah").val() || "Hello world",
But, that defeats the purpose if I manually need to preset the values, isn't it?. Kindly correct me if I'm wrong.
Thanks @dsignr, is not most elegant solution, but that help me a lot
Another simple solution would be to use ref
and data
attributes to reference the element and then access it once the template has been rendered.
Template
<input type="text" ref="name" data-value="John">
Javascript
data() {
return {
name: ''
}
},
mounted() {
this.name = this.$refs.name.dataset.value; // See note below
}
Note: check for dataset
compatibility, otherwise use jQuery or any library to extract data-
.
Hope that helps π
I got around it by doing
document.querySelectorAll('.textfield').forEach((el) => { new Vue({ el, data: function () { return { value, } } })
That said, there's other things in the markup, like error messages, I also want to bind to the model and I'd have to do similar for all of them. At that point the markup becomes a little strange.
My aim, maybe like the original poster, is to try find a framework that combines the template syntax and 2 way binding of Vue with the progressive enhancement and SSR ideals that allow sites to be built that can fall back to no JS.
Actually makes me a little sad that Vue js claims to allow progressive enhancement and then states it wont pickup initial model values from markup thus contradicting that statement.
I solved this way (you need to know the input field ID in advance)
data() {
return {
defaultValue: null, // the SSR rendered value
initialValue: null, // the value just before mounting
focusedBeforeMount: false,
};
},
beforeMount() {
const el = document.getElementById('[component_id]');
this.defaultValue = el.defaultValue;
this.initialValue = el.value;
this.focusedBeforeMount = document.activeElement === el;
},
mounted() {
if (this.initialValue) {
// this.initialValue contains the state to be saved
}
if (this.focusedBeforeMount) {
this.$refs.input.focus(); // you must set a reference to the input field
}
},
This code addresses one more problem: if you SSR the page, the input field could be edited manually from the user before the component is been mounted (think about a slow mobile connection thwat could delay the JS execution). Thus the input state could differ from the value set by the server. Thank you all π
This is my approach to this issue and I think it's pretty neat depending on your usage.
Parent Component
<app-form :defaults="{ name: 'Debbie' }"></app-form>
AppForm Component
created () {
this.formData = Object.assign({}, this.formData, this.default)
}
As for
having an inline value is basically saying the input's value is static and should never change - which doesn't make sense when you are using v-model with it.
It makes sense to me, because that's how native html works.
The value
attribute set in an input element <input value="test">
is meant to be changed.
Question is which should take priority when both the inline value and the v-model value are set.
I'm aware the issue is closed, but wanted to share my approach in case it helps anyone, or in case I've done a big no no π¨
It should dynamically add any input fields to the data.
const element = '#test';
new Vue({
el: element,
data() {
// grabs the component
const $el = document.querySelector(element);
// grabs all fields with v-model within the component
// in an iterable array
const $fields = [...$el.querySelectorAll('input[v-model]')];
const ssrValues = {};
// add the v-model attribute as the key for the ssrValue
// and the value of the field as the key's value
$fields.forEach(field => {
const name = field.getAttribute('v-model');
const value = field.value;
ssrValues[name] = value;
});
// define any extra data you would like
const data = {
tacos: ['un taco', 'dos tacos', 'tres!']
};
// fuse the ssr data with our extra data and return
return Object.assign(data, ssrValues);
}
});
I'm aware the issue is closed, but wanted to share my approach in case it helps anyone, or in case I've done a big no no π¨
I've updated a fraction of your code.
data() {
const $el = document.querySelector(evenElement);
const $fields = [...$el.querySelectorAll('*[v-model]')];
const ssrValues = {};
for (let i = 0; i < $fields.length; i++) {
let field = $fields[i];
let name = field.getAttribute('v-model');
let value = field.value;
let type = field.getAttribute('type');
if (!value) {
continue;
}
if (['radio', 'checkbox'].includes(type) && !field.hasAttribute('checked')) {
continue;
}
ssrValues[name] = value;
}
const data = {};
return Object.assign(data, ssrValues);
}
Coming from Symfony, this is giving me a huge headache. I want to keep using Symfony way to render form which is extremely convenient for me while making it work with Vue. Things would be much easier if Vue allows some way to lazily initialize the model (which was possible in Angular 1.x)
The problems are:
I understand that I can pre-process the form, dump a big ass json and pass that to the form component first, but that doesn't seem like an elegant way to do things :(.
Hi ! Here is another workaround, with a custom directive
// Usage <input v-model="my_input" v-init="'Hi!'" />
// The property my_input will be initialized with the value of v-init, here Hi!
Vue.directive('init', {
bind (el, binding, vnode) {
let vModel = vnode.data.directives.find(d => d.rawName == "v-model")
if (vModel) {
vnode.context[vModel.expression] = binding.value
}
}
})
Vue.new({
data: {
input: undefined,
other: "See"
}
}
<input v-model="my_input" v-init="'Hi!'" />
<input v-model="my_input" v-init="'other + ' you...'" />
I'm a beginner with Vue, I know this is not very elegant, but please tell me if I just wrote some crazy things !
You can also automatically fill the v-init attribute before your vue component get initialized :
$('input[v-model], select[v-model]').each(function() {
if (!$(this).val()) return
let value = $(this).attr('type') == "checkbox" ? $(this).prop('checked') : `'${$(this).val()}'`
$(this).attr(`v-init:${$(this).attr('v-model')}`, value)
})
<input v-model="my_input" value="Hi!" />
Hi,
I've try this and it's works :
data: {
inputs : {
lastname : '',
firstname : ''
}
},
beforeMount: function() {
for (const [input_name, input_value] of Object.entries(this.inputs)) {
if (this.$el.querySelector('[name="'+input_name+'"]')) {
this.inputs[input_name] = this.$el.querySelector('[name="'+input_name+'"]').dataset.default;
}
}
}
HTML :
<input type="text" name="lastname" v-model="inputs.lastname" data-default="SMITH" >
<input type="text" name="firstname" v-model="inputs.firstname" data-default="John" >
It's not the most elegant solution but I find this one quite simple.
Vue.js version
2.0.2
Reproduction Link
https://jsfiddle.net/uojhhjr7/ https://jsfiddle.net/k8bfrxwz/
While switching to Vue2, I realize that it's not possible to define a model initial value from the template anymore.
Let's say I have a server-side validated form (with a preview powered by Vue) which brings back the user if the validation detected an error.
I'd like to populate the form with the previously typed values (and get the corresponding preview), but I cannot simply set an inline value anymore because Vue warns me with
inline value attributes will be ignored when using v-model
I took a look at the migration guide and found a confirmation it was not possible anymore, but I can't find any workaround.
Of course, the simple solution would be to modify the initial value of the model within the Vue instantiation, but it's a children component from a vue file, webpacked with other scripts.
I got this solution working, but it seems a little bit overkill to me, regarding the usual elegance of Vue.js.
So here is the aim of this issue : Is there a way to pass an initial value to a v-model from template to Vue as we used to ?