I want to group my refs in to an object and access them by object notation. With that in mind, I now have this component:
new Vue({
el: '#app',
template: `
<div>
<input ref="input.name" type="text"/>
<input ref="input.email" type="email"/>
<input ref="input.password" type="password"/>
</div>
`,
created(){
console.log(this.$refs["input.name"]); // This works fine
console.log(this.$refs.input.name); // throws "TypeError: Cannot read property 'name' of undefined"
}
})
But it only shows this:
{
input.name: input,
input.email: input,
input.password: input,
}
What I want is something like this:
{
input: {
name: input,
email: input,
password: input,
},
}
Check fiddle here
Update:
I'm well aware of v-model but that's not what I need, I'll be doing something else with DOM.
new Vue({
el: '#app',
data() {
return {
inputs: {}
}
},
template: `
<div>
<input ref="input.name" type="text" value="123"/>
<input ref="input.email" type="email"/>
<input ref="input.password" type="password"/>
<button type="button" #click="clickMe">Click ME</button>
</div>
`,
mounted() {
this.setRefs();
console.log("this.inputs", this.inputs);
},
methods: {
setRefs() {
const INPUTS = {};
for (key in this.$refs) {
if (key.indexOf(".")) {
const KEYS_LIST = key.split(".");
INPUTS[KEYS_LIST[0]] = INPUTS[KEYS_LIST[0]] || {};
INPUTS[KEYS_LIST[0]][KEYS_LIST[1]] = this.$refs[key];
}
}
this.inputs = INPUTS;
},
clickMe() {
console.log("this.inputs.input.name.value", this.inputs.input.name.value);
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
This cannot work: refs only support strings as keys (see the docs), so there is not way of using objects here.
Also, a string with "dot notation" does not magically transform into an object with a nested property -- it's just a string. You could work around this by manually parsing the ref strings into an object structure, as has been demonstrated by #Ilia Brykin's answer. But I honestly think that you are misunderstanding the purpose of the whole ref system if you really want to do that.
P.S.: If you drop the "dort notation" part, you could use keys like input_name and access them via this.$refs.input_name.
EDIT
Another idea: you can get all input refs by their common prefix.
Object.keys(this.$refs).filter(ref => ref.startsWith('input.')
This gives you an array of ref strings of input elements.
Related
I have a list of checkboxes:
<ul>
<li v-for="system in payment_systems">
<input type="checkbox" :id="'ps-' + system.id" v-bind:value="system" v-model="checked_payment_systems">
<label :for="'ps-' + system.id">{{ system.translated.name }}</label>
</li>
</ul>
And I need to store checked items to Vuex so I use computed properties like this:
computed: {
checked_payment_systems: {
get() {
return this.$store.state.program.payment_systems;
},
set(payment_systems) {
console.log(payment_systems)
}
}
},
The problem is that in setter I get only true/false instead of object or array of objects.
the computed property you defined v-models with an input value. the set property will be called on with the input value.
in your example, you are binding the same get-set to all of your checkboxes. it should be done differently.
if i where you, i would remove the v-model and manually declare a function to happen onchange and a value, and pass them the a key, yo get the specific value in my object.
i made for you an example: https://jsfiddle.net/efrat19/p87ag0w3/1/
const store = new Vuex.Store({
state: {
program:{
payment_systems:{'paypal':false,'tranzila':false},
}
},
mutations:{
setPayment(state,{system,value}){
state.program.payment_systems[system]=value;
}
},
actions:{
setPayment({commit},{system,value}){
commit('setPayment',{system,value})
}
}
})
const app = new Vue({
store,
el: '#app',
data() {
},
computed: {
checked_payment_systems(){
return system=>
this.$store.state.program.payment_systems[system]
}
},
methods:{
setValue(system,value){
this.$store.dispatch('setPayment',{system,value})
}
}});
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="
https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.js"></script>
<div id="app">
<li v-for="(value,system) in $store.state.program.payment_systems">
<input type="checkbox" :id="'ps-' + system.id" :checked="checked_payment_systems(system)" #change="setValue(system,$event.target.checked)">
<label :for="'ps-' + system.id" >{{system}}</label>
</li>
<br>
values in the store:
<br>
<br>
{{$store.state.program.payment_systems}}
</div>
I have a button, for load files or add some text
After load it pushed in data() prop
How i can validate this prop, if them not have input
Im found only one solution - make watch for data props. and set validate in
Maybe exist more beautiful way?
I try validator.verify() - but it dont send error in main errorBag from validateAll
This is example
<div id="app">
<testc></testc>
</div>
<script type="text/x-template" id="test">
<div>
<input type="text" v-validate="'required'" name="test_vee">
{{errors.first('test_vee')}}
<hr>
<button #click="addRow">Add</button>
<input type="text" v-model="inputValue" name="test_input"/>
<hr>
{{rows}}
<hr>
{{errors.first('rows')}}
<button #click="validateForm">Validate</button>
</div>
</script>
and script
Vue.component('testc', {
template: '#test',
data() {
return {
inputValue: '',
rows: []
}
},
watch: {
rows: {
handler: function(newVal, oldVal) {
this.$validator.errors.remove('rows');
if (this.rows.length < 2) {
this.$validator.errors.add({
id: 'rows',
field: 'rows',
msg: 'Need 2 rows!',
});
}
}
}
},
methods: {
addRow: function() {
this.rows.push(this.inputValue);
this.inputValue = '';
},
validateForm: function(){
this.$validator.validateAll();
}
}
});
Vue.use(VeeValidate);
new Vue({
el: '#app'
})
https://codepen.io/gelid/pen/YBajER
First input in example: default validate - its ok
Second input: for add items - dont need validate or has self validate (not empty for example)
In data of component i have prop rows - it is need validate before ajax request to backend for save data
I'm trying to get two input values using one mounted function for the date picker. But not getting how do I do that?
Here is the code:
HTML:
<div id="app">
<form v-on:submit.prevent="saveFleetJob">
<input type="text" class="fleet-date" v-model="item.from">
<input type="text" class="fleet-date" v-model="item.to">
<button class="btn btn-primary">Click</button>
</form>
</div>
Vue code:
new Vue({
el: "#app",
data: {
item: {
from:'',
to:''
},
},
mounted() {
$(".fleet-date").datepicker().on(
"changeDate", () => {
this.item.from = $('.fleet-date').val()
}
);
},
methods: {
saveFleetJob() {
alert(JSON.stringify(this.item));
},
}
})
js fiddle demo link here
Any suggestions please?
As Sphinx pointed there is some context mess, but it's not the only issue.
See this updated fiddle: https://jsfiddle.net/pb91fk6o/
You have to add
elem.dispatchEvent(new Event('input'))
to make this work.
So mounted should look like:
mounted() {
$(".fleet-date")
.datepicker()
.on("changeDate", (e) => {
e.target.dispatchEvent(new Event('input'))
})
},
See this issue for more details: https://github.com/vuejs/vue/issues/2804
Can the v-model syntax be used from inside a Vue component template?
The following works as expected when included directly in an .html
<input type="text" v-model="selected_service_shortname">
Putting the following stuff into a component template does not work.
var service_details = {
template: `
...
<input type="text" v-model="selected_service_shortname">
...
`
};
vm = new Vue({
el: "#app",
components: {
'service-details': service_details
},
Results in vue.min.js:6 ReferenceError: selected_service_shortname is not defined
Changing the template syntax to
<input type="text" v-model="this.$parent.selected_service_shortname">
Seems to halfway work -- changes applied externally to selected_service_shortname appear in the input box as expected. But making changes to the input box directly results in Uncaught TypeError: Cannot convert undefined or null to object
Is what I'm trying to do a supported use case? If so, are there working examples somewhere?
You can implement support for v-model in your component. This is covered in the documentation here.
Here is an example.
var service_details = {
props: ["value"],
template: `
<input type="text" v-model="internalValue">
`,
computed: {
internalValue: {
get() {
return this.value
},
set(v) {
this.$emit("input", v)
}
}
}
};
Basically, v-model, by default, is simply sugar for passing a value property and listening for the input event. So all you need to do is add a value property to your component, and emit an input event. This can also be customized as described in the documentation.
console.clear()
var service_details = {
props: ["value"],
template: `
<input type="text" v-model="internalValue">
`,
computed: {
internalValue: {
get() {
return this.value
},
set(v) {
this.$emit("input", v)
}
}
}
};
new Vue({
el: "#app",
data: {
selected_service_shortname: "some service name"
},
components: {
'service-details': service_details
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.min.js"></script>
<div id="app">
<service-details v-model="selected_service_shortname"></service-details>
<hr>
Selected Service Shortname: {{selected_service_shortname}}
</div>
Used in the parent like this:
<service-details v-model="selected_service_shortname"></service-details>
I'am trying to create a component that have 'just' an text input. String typed in this input will be used to filter a list. My problem is that I cannot handle how to share this filter string between my component and the main app that contains the list to filter.
I tried several things and most of the time I get the error :
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value
So I looked Vuex but I thinks it cannot help in this case because I can have several filter component used in he same page for different list, and I don't want them to be synchronized ^^
Here is what I have:
The filter component
<script type="x/template" id="filterTpl">
<div>
<span class="filter-wrapper">
<input type="search" class="input input-filter" v-model.trim="filter" />
</span>
</div>
</script>
<script>
Vue.component('list-filter', {
props: {
filter: String
}
template: '#filterTpl'
});
</script>
And my main app:
<div id="contacts">
<list-filter :filter="filter"></list-filter>
<ul class="contacts-list managed-list flex">
<li class="contact" v-for="contactGroup in filteredData">
[...]
</li>
</ul>
</div>
<script>
var contactsV = new Vue({
el: '#contacts',
data: {
filter: "",
studyContactsGroups: []
},
computed: {
filteredData: function(){
// Using this.filter to filter the studyContactsGroups data
[...]
return filteredContacts;
}
}
});
</script>
Thanks for any help or tips :)
You can synchronize child value and parent prop either via explicit prop-event connection or more concise v-bind with sync modifier:
new Vue({
el: '#app',
data: {
rawData: ['John', 'Jane', 'Jim', 'Eddy', 'Maggy', 'Trump', 'Che'],
filter: ''
},
components: {
'my-input' : {
// bind prop 'query' to value and
// #input update parent prop 'filter' via event used with '.sync'
template: `<input :value="query" #input="updateFilter">`,
props: ['query'],
methods: {
updateFilter: function(e) {
this.$emit('update:query', e.target.value) // this is described in documentation
}
}
}
},
computed: {
filteredData: function() {
// simple filter function
return this.rawData.filter(el => el.toLowerCase()
.match(this.filter.toLowerCase()))
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<my-input :query.sync="filter"></my-input>
<hr>
<ul>
<li v-for="line in filteredData">{{ line }}</li>
</ul>
</div>